/** ** Send command: Unit return goods. ** ** @param unit pointer to unit. ** @param goal pointer to destination of the goods. (NULL=search best) ** @param flush Flag flush all pending commands. */ void SendCommandReturnGoods(CUnit &unit, CUnit *goal, int flush) { if (!IsNetworkGame()) { CommandLog("return", &unit, flush, -1, -1, goal, NULL, -1); CommandReturnGoods(unit, goal, flush); } else { NetworkSendCommand(MessageCommandReturn, unit, 0, 0, goal, 0, flush); } }
/** ** Execute a command (from network). ** ** @param msgnr Network message type ** @param unum Unit number (slot) that receive the command. ** @param x optional X map position. ** @param y optional y map position. ** @param dstnr optional destination unit. */ void ExecCommand(unsigned char msgnr, UnitRef unum, unsigned short x, unsigned short y, UnitRef dstnr) { CUnit &unit = UnitManager.GetSlotUnit(unum); const Vec2i pos(x, y); const int arg1 = x; const int arg2 = y; // // Check if unit is already killed? // if (unit.Destroyed) { DebugPrint(" destroyed unit skipping %d\n" _C_ UnitNumber(unit)); return; } Assert(unit.Type); const int status = (msgnr & 0x80) >> 7; // Note: destroyed destination unit is handled by the action routines. switch (msgnr & 0x7F) { case MessageSync: return; case MessageQuit: return; case MessageChat: return; case MessageCommandStop: CommandLog("stop", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1); CommandStopUnit(unit); break; case MessageCommandStand: CommandLog("stand-ground", &unit, status, -1, -1, NoUnitP, NULL, -1); CommandStandGround(unit, status); break; case MessageCommandDefend: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("defend", &unit, status, -1, -1, &dest, NULL, -1); CommandDefend(unit, dest, status); } break; } case MessageCommandFollow: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("follow", &unit, status, -1, -1, &dest, NULL, -1); CommandFollow(unit, dest, status); } break; } case MessageCommandMove: //Wyrmgus start // CommandLog("move", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); // CommandMove(unit, pos, status); if (!unit.CanMove()) { //FIXME: find better way to identify whether the unit should move or set a rally point CommandLog("rally-point", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandRallyPoint(unit, pos); } else { CommandLog("move", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandMove(unit, pos, status); } //Wyrmgus end break; //Wyrmgus start case MessageCommandPickUp: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("pick-up", &unit, status, -1, -1, &dest, NULL, -1); CommandPickUp(unit, dest, status); } break; } //Wyrmgus end case MessageCommandRepair: { CUnit *dest = NoUnitP; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("repair", &unit, status, pos.x, pos.y, dest, NULL, -1); CommandRepair(unit, pos, dest, status); break; } case MessageCommandAutoRepair: CommandLog("auto-repair", &unit, status, arg1, arg2, NoUnitP, NULL, 0); CommandAutoRepair(unit, arg1); break; case MessageCommandAttack: { CUnit *dest = NoUnitP; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("attack", &unit, status, pos.x, pos.y, dest, NULL, -1); CommandAttack(unit, pos, dest, status); break; } case MessageCommandGround: CommandLog("attack-ground", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandAttackGround(unit, pos, status); break; //Wyrmgus start case MessageCommandUse: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("use", &unit, status, -1, -1, &dest, NULL, -1); CommandUse(unit, dest, status); } break; } //Wyrmgus end case MessageCommandPatrol: CommandLog("patrol", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandPatrolUnit(unit, pos, status); break; case MessageCommandBoard: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("board", &unit, status, arg1, arg2, &dest, NULL, -1); CommandBoard(unit, dest, status); } break; } case MessageCommandUnload: { CUnit *dest = NULL; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("unload", &unit, status, pos.x, pos.y, dest, NULL, -1); CommandUnload(unit, pos, dest, status); break; } case MessageCommandBuild: CommandLog("build", &unit, status, pos.x, pos.y, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandBuildBuilding(unit, pos, *UnitTypes[dstnr], status); break; case MessageCommandDismiss: CommandLog("dismiss", &unit, FlushCommands, -1, -1, NULL, NULL, -1); CommandDismiss(unit); break; case MessageCommandResourceLoc: CommandLog("resource-loc", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandResourceLoc(unit, pos, status); break; case MessageCommandResource: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("resource", &unit, status, -1, -1, &dest, NULL, -1); CommandResource(unit, dest, status); } break; } case MessageCommandReturn: { CUnit *dest = (dstnr != (unsigned short)0xFFFF) ? &UnitManager.GetSlotUnit(dstnr) : NULL; CommandLog("return", &unit, status, -1, -1, dest, NULL, -1); CommandReturnGoods(unit, dest, status); break; } case MessageCommandTrain: //Wyrmgus start // CommandLog("train", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); // CommandTrainUnit(unit, *UnitTypes[dstnr], status); CommandLog("train", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), arg1); // use X as a way to mark the player CommandTrainUnit(unit, *UnitTypes[dstnr], arg1, status); //Wyrmgus end break; case MessageCommandCancelTrain: // We need (short)x for the last slot -1 if (dstnr != (unsigned short)0xFFFF) { CommandLog("cancel-train", &unit, FlushCommands, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), (short)x); CommandCancelTraining(unit, (short)x, UnitTypes[dstnr]); } else { CommandLog("cancel-train", &unit, FlushCommands, -1, -1, NoUnitP, NULL, (short)x); CommandCancelTraining(unit, (short)x, NULL); } break; case MessageCommandUpgrade: //Wyrmgus start /* CommandLog("upgrade-to", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandUpgradeTo(unit, *UnitTypes[dstnr], status); break; */ if (arg1 == 2) { //use X as a way to mark whether this is an upgrade or a transformation CommandLog("transform-into", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandTransformIntoType(unit, *UnitTypes[dstnr]); } else { CommandLog("upgrade-to", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandUpgradeTo(unit, *UnitTypes[dstnr], status); } break; //Wyrmgus end case MessageCommandCancelUpgrade: CommandLog("cancel-upgrade-to", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1); CommandCancelUpgradeTo(unit); break; case MessageCommandResearch: CommandLog("research", &unit, status, -1, -1, NoUnitP, AllUpgrades[arg1]->Ident.c_str(), -1); CommandResearch(unit, *AllUpgrades[arg1], status); break; case MessageCommandCancelResearch: CommandLog("cancel-research", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1); CommandCancelResearch(unit); break; //Wyrmgus start case MessageCommandQuest: { CommandLog("quest", &unit, 0, 0, 0, NoUnitP, Quests[arg1]->Ident.c_str(), -1); CommandQuest(unit, Quests[arg1]); break; } case MessageCommandBuy: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("buy", &unit, 0, -1, -1, &dest, NULL, arg1); CommandBuy(unit, &dest, arg1); } break; } //Wyrmgus end default: { int id = (msgnr & 0x7f) - MessageCommandSpellCast; if (arg2 != (unsigned short)0xFFFF) { CUnit *dest = NULL; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("spell-cast", &unit, status, pos.x, pos.y, dest, NULL, id); CommandSpellCast(unit, pos, dest, *SpellTypeTable[id], status); } else { CommandLog("auto-spell-cast", &unit, status, arg1, -1, NoUnitP, NULL, id); CommandAutoSpellCast(unit, id, arg1); } break; } } }
/** ** Assign workers to collect resources. ** ** If we have a shortage of a resource, let many workers collecting this. ** If no shortage, split workers to all resources. */ static void AiCollectResources() { CUnit *units_with_resource[MaxCosts][UnitMax]; // Worker with resource CUnit *units_assigned[MaxCosts][UnitMax]; // Worker assigned to resource CUnit *units_unassigned[MaxCosts][UnitMax]; // Unassigned workers int num_units_with_resource[MaxCosts]; int num_units_assigned[MaxCosts]; int num_units_unassigned[MaxCosts]; int c; int src_c; int i; int j; int k; int n; CUnit **units; int percent[MaxCosts]; int percent_total; int priority_resource[MaxCosts]; int priority_needed[MaxCosts]; int wanted[MaxCosts]; int total_harvester; memset(num_units_with_resource, 0, sizeof(num_units_with_resource)); memset(num_units_unassigned, 0, sizeof(num_units_unassigned)); memset(num_units_assigned, 0, sizeof(num_units_assigned)); // // Collect statistics about the current assignment // total_harvester = 0; n = AiPlayer->Player->TotalNumUnits; units = AiPlayer->Player->Units; for (i = 0; i < n; ++i) { CUnit &unit = *units[i]; if (!unit.Type->Harvester) { continue; } c = unit.CurrentResource; // // See if it's assigned already // if (c && unit.OrderCount == 1 && unit.CurrentAction() == UnitActionResource) { units_assigned[c][num_units_assigned[c]++] = &unit; total_harvester++; continue; } // // Ignore busy units. ( building, fighting, ... ) // if (!unit.IsIdle()) { continue; } // // Send workers with resources back home. // if (unit.ResourcesHeld && c) { units_with_resource[c][num_units_with_resource[c]++] = &unit; CommandReturnGoods(unit, 0, FlushCommands); total_harvester++; continue; } // // Look what the unit can do // for (c = 1; c < MaxCosts; ++c) { if (unit.Type->ResInfo[c]) { units_unassigned[c][num_units_unassigned[c]++] = &unit; } } ++total_harvester; } if(!total_harvester) { return; } memset(wanted, 0, sizeof(wanted)); percent_total = 100; for (c = 1; c < MaxCosts; ++c) { percent[c] = AiPlayer->Collect[c]; if ((AiPlayer->NeededMask & (1 << c))) { // Double percent if needed percent_total += percent[c]; percent[c] <<= 1; } } // // Turn percent values into harvester numbers. // for (c = 1; c < MaxCosts; ++c ) { if(percent[c]) { // Wanted needs to be representative. if (total_harvester < 5) { wanted[c] = 1 + (percent[c] * 5) / percent_total; } else { wanted[c] = 1 + (percent[c] * total_harvester) / percent_total; } } } // // Initialise priority & mapping // for (c = 0; c < MaxCosts; ++c) { priority_resource[c] = c; priority_needed[c] = wanted[c] - num_units_assigned[c] - num_units_with_resource[c]; if (c && num_units_assigned[c] > 1) { //first should go workers with lower ResourcesHeld value qsort(units_assigned[c], num_units_assigned[c], sizeof(CUnit*), CmpWorkers); } } CUnit* unit; do { // // sort resources by priority // for (i = 0; i < MaxCosts; ++i) { for (j = i + 1; j < MaxCosts; ++j) { if (priority_needed[j] > priority_needed[i]) { c = priority_needed[j]; priority_needed[j] = priority_needed[i]; priority_needed[i] = c; c = priority_resource[j]; priority_resource[j] = priority_resource[i]; priority_resource[i] = c; } } } unit = NoUnitP; // // Try to complete each ressource in the priority order // for (i = 0; i < MaxCosts; ++i) { c = priority_resource[i]; // // If there is a free worker for c, take it. // if (num_units_unassigned[c]) { // Take the unit. j = 0; while (j < num_units_unassigned[c] && !AiAssignHarvester(*units_unassigned[c][j], c)) { // can't assign to c => remove from units_unassigned ! units_unassigned[c][j] = units_unassigned[c][--num_units_unassigned[c]]; } // unit is assigned if (j < num_units_unassigned[c]) { unit = units_unassigned[c][j]; units_unassigned[c][j] = units_unassigned[c][--num_units_unassigned[c]]; // remove it from other ressources for (j = 0; j < MaxCosts; ++j) { if (j == c || !unit->Type->ResInfo[j]) { continue; } for (k = 0; k < num_units_unassigned[j]; ++k) { if (units_unassigned[j][k] == unit) { units_unassigned[j][k] = units_unassigned[j][--num_units_unassigned[j]]; break; } } } } } // // Else : Take from already assigned worker with lower priority. // if (!unit) { // Take from lower priority only (i+1). for (j = i + 1; j < MaxCosts && !unit; ++j) { // Try to move worker from src_c to c src_c = priority_resource[j]; // Don't complete with lower priority ones... if (wanted[src_c] > wanted[c] || (wanted[src_c] == wanted[c] && num_units_assigned[src_c] <= num_units_assigned[c] + 1)) { continue; } for (k = num_units_assigned[src_c] - 1; k >= 0 && !unit; --k) { unit = units_assigned[src_c][k]; if (unit->SubAction >= 65 /* SUB_STOP_GATHERING */ ) { //worker returning with resource continue; } // unit can't harvest : next one if (!unit->Type->ResInfo[c] || !AiAssignHarvester(*unit, c)) { unit = NoUnitP; continue; } // Remove from src_c units_assigned[src_c][k] = units_assigned[src_c][--num_units_assigned[src_c]]; // j need one more priority_needed[j]++; } } } // // We just moved an unit. Adjust priority & retry // if (unit) { // i got a new unit. priority_needed[i]--; // Add to the assigned units_assigned[c][num_units_assigned[c]++] = unit; // Recompute priority now break; } } } while (unit); // Unassigned units there can't be assigned ( ie : they can't move to ressource ) // IDEA : use transporter here. }
/** ** Assign workers to collect resources. ** ** If we have a shortage of a resource, let many workers collecting this. ** If no shortage, split workers to all resources. */ static void AiCollectResources() { std::vector<CUnit *> units_assigned[MaxCosts]; // Worker assigned to resource std::vector<CUnit *> units_unassigned[MaxCosts]; // Unassigned workers int num_units_with_resource[MaxCosts]; int num_units_assigned[MaxCosts]; int num_units_unassigned[MaxCosts]; int percent[MaxCosts]; int priority_resource[MaxCosts]; int priority_needed[MaxCosts]; int wanted[MaxCosts]; int total_harvester = 0; memset(num_units_with_resource, 0, sizeof(num_units_with_resource)); memset(num_units_unassigned, 0, sizeof(num_units_unassigned)); memset(num_units_assigned, 0, sizeof(num_units_assigned)); // Collect statistics about the current assignment const int n = AiPlayer->Player->GetUnitCount(); for (int i = 0; i < n; ++i) { CUnit &unit = AiPlayer->Player->GetUnit(i); if (!unit.Type->Harvester) { continue; } // See if it's assigned already if (unit.Orders.size() == 1 && unit.CurrentAction() == UnitActionResource) { const COrder_Resource &order = *static_cast<COrder_Resource *>(unit.CurrentOrder()); const int c = order.GetCurrentResource(); units_assigned[c].push_back(&unit); num_units_assigned[c]++; total_harvester++; continue; } // Ignore busy units. ( building, fighting, ... ) if (!unit.IsIdle()) { continue; } // Send workers with resources back home. if (unit.ResourcesHeld) { const int c = unit.CurrentResource; num_units_with_resource[c]++; CommandReturnGoods(unit, 0, FlushCommands); total_harvester++; continue; } // Look what the unit can do for (int c = 1; c < MaxCosts; ++c) { if (unit.Type->ResInfo[c]) { units_unassigned[c].push_back(&unit); num_units_unassigned[c]++; } } ++total_harvester; } if (!total_harvester) { return; } memset(wanted, 0, sizeof(wanted)); int percent_total = 100; for (int c = 1; c < MaxCosts; ++c) { percent[c] = AiPlayer->Collect[c]; if ((AiPlayer->NeededMask & (1 << c))) { // Double percent if needed percent_total += percent[c]; percent[c] <<= 1; } } // Turn percent values into harvester numbers. for (int c = 1; c < MaxCosts; ++c) { if (percent[c]) { // Wanted needs to be representative. if (total_harvester < 5) { wanted[c] = 1 + (percent[c] * 5) / percent_total; } else { wanted[c] = 1 + (percent[c] * total_harvester) / percent_total; } } } // Initialise priority & mapping for (int c = 0; c < MaxCosts; ++c) { priority_resource[c] = c; priority_needed[c] = wanted[c] - num_units_assigned[c] - num_units_with_resource[c]; if (c && num_units_assigned[c] > 1) { //first should go workers with lower ResourcesHeld value std::sort(units_assigned[c].begin(), units_assigned[c].end(), CmpWorkers); } } CUnit *unit; do { // sort resources by priority for (int i = 0; i < MaxCosts; ++i) { for (int j = i + 1; j < MaxCosts; ++j) { if (priority_needed[j] > priority_needed[i]) { std::swap(priority_needed[i], priority_needed[j]); std::swap(priority_resource[i], priority_resource[j]); } } } unit = NULL; // Try to complete each ressource in the priority order for (int i = 0; i < MaxCosts; ++i) { int c = priority_resource[i]; // If there is a free worker for c, take it. if (num_units_unassigned[c]) { // Take the unit. while (0 < num_units_unassigned[c] && !AiAssignHarvester(*units_unassigned[c][0], c)) { // can't assign to c => remove from units_unassigned ! units_unassigned[c][0] = units_unassigned[c][--num_units_unassigned[c]]; units_unassigned[c].pop_back(); } // unit is assigned if (0 < num_units_unassigned[c]) { unit = units_unassigned[c][0]; units_unassigned[c][0] = units_unassigned[c][--num_units_unassigned[c]]; units_unassigned[c].pop_back(); // remove it from other ressources for (int j = 0; j < MaxCosts; ++j) { if (j == c || !unit->Type->ResInfo[j]) { continue; } for (int k = 0; k < num_units_unassigned[j]; ++k) { if (units_unassigned[j][k] == unit) { units_unassigned[j][k] = units_unassigned[j][--num_units_unassigned[j]]; units_unassigned[j].pop_back(); break; } } } } } // Else : Take from already assigned worker with lower priority. if (!unit) { // Take from lower priority only (i+1). for (int j = i + 1; j < MaxCosts && !unit; ++j) { // Try to move worker from src_c to c const int src_c = priority_resource[j]; // Don't complete with lower priority ones... if (wanted[src_c] > wanted[c] || (wanted[src_c] == wanted[c] && num_units_assigned[src_c] <= num_units_assigned[c] + 1)) { continue; } for (int k = num_units_assigned[src_c] - 1; k >= 0 && !unit; --k) { unit = units_assigned[src_c][k]; Assert(unit->CurrentAction() == UnitActionResource); COrder_Resource &order = *static_cast<COrder_Resource *>(unit->CurrentOrder()); if (order.IsGatheringFinished()) { //worker returning with resource continue; } // unit can't harvest : next one if (!unit->Type->ResInfo[c] || !AiAssignHarvester(*unit, c)) { unit = NULL; continue; } // Remove from src_c units_assigned[src_c][k] = units_assigned[src_c][--num_units_assigned[src_c]]; units_assigned[src_c].pop_back(); // j need one more priority_needed[j]++; } } } // We just moved an unit. Adjust priority & retry if (unit) { // i got a new unit. priority_needed[i]--; // Recompute priority now break; } } } while (unit); // Unassigned units there can't be assigned ( ie : they can't move to ressource ) // IDEA : use transporter here. }
static void Finish(COrder_Built &order, CUnit &unit) { const CUnitType &type = *unit.Type; CPlayer &player = *unit.Player; DebugPrint("%d: Building %s(%s) ready.\n" _C_ player.Index _C_ type.Ident.c_str() _C_ type.Name.c_str()); // HACK: the building is ready now player.UnitTypesCount[type.Slot]++; if (unit.Active) { player.UnitTypesAiActiveCount[type.Slot]++; } unit.Constructed = 0; if (unit.Frame < 0) { unit.Frame = -1; } else { unit.Frame = 0; } CUnit *worker = order.GetWorkerPtr(); if (worker != NULL) { if (type.BoolFlag[BUILDERLOST_INDEX].value) { // Bye bye worker. LetUnitDie(*worker); worker = NULL; } else { // Drop out the worker. worker->ClearAction(); DropOutOnSide(*worker, LookingW, &unit); // If we can harvest from the new building, do it. if (worker->Type->ResInfo[type.GivesResource]) { CommandResource(*worker, unit, 0); } // If we can reurn goods to a new depot, do it. if (worker->CurrentResource && worker->ResourcesHeld > 0 && type.CanStore[worker->CurrentResource]) { CommandReturnGoods(*worker, &unit, 0); } } } if (type.GivesResource && type.StartingResources != 0) { // Has StartingResources, Use those unit.ResourcesHeld = type.StartingResources; } player.Notify(NotifyGreen, unit.tilePos, _("New %s done"), type.Name.c_str()); if (&player == ThisPlayer) { if (type.MapSound.Ready.Sound) { PlayUnitSound(unit, VoiceReady); } else if (worker) { PlayUnitSound(*worker, VoiceWorkCompleted); } else { PlayUnitSound(unit, VoiceBuilding); } } if (player.AiEnabled) { /* Worker can be NULL */ AiWorkComplete(worker, unit); } // FIXME: Vladi: this is just a hack to test wall fixing, // FIXME: also not sure if the right place... // FIXME: Johns: hardcoded unit-type wall / more races! if (&type == UnitTypeOrcWall || &type == UnitTypeHumanWall) { Map.SetWall(unit.tilePos, &type == UnitTypeHumanWall); unit.Remove(NULL); UnitLost(unit); UnitClearOrders(unit); unit.Release(); return ; } UpdateForNewUnit(unit, 0); // Set the direction of the building if it supports them if (type.NumDirections > 1 && type.BoolFlag[NORANDOMPLACING_INDEX].value == false) { if (type.BoolFlag[WALL_INDEX].value) { // Special logic for walls CorrectWallDirections(unit); CorrectWallNeighBours(unit); } else { unit.Direction = (MyRand() >> 8) & 0xFF; // random heading } UnitUpdateHeading(unit); } if (IsOnlySelected(unit) || &player == ThisPlayer) { SelectedUnitChanged(); } MapUnmarkUnitSight(unit); unit.CurrentSightRange = unit.Stats->Variables[SIGHTRANGE_INDEX].Max; MapMarkUnitSight(unit); order.Finished = true; }
/** ** Assign workers to collect resources. ** ** If we have a shortage of a resource, let many workers collecting this. ** If no shortage, split workers to all resources. */ static void AiCollectResources() { std::vector<CUnit *> units_assigned[MaxCosts]; // Worker assigned to resource std::vector<CUnit *> units_unassigned[MaxCosts]; // Unassigned workers int num_units_with_resource[MaxCosts]; int num_units_assigned[MaxCosts]; int num_units_unassigned[MaxCosts]; int percent[MaxCosts]; int priority_resource[MaxCosts]; int priority_needed[MaxCosts]; int wanted[MaxCosts]; int total_harvester = 0; memset(num_units_with_resource, 0, sizeof(num_units_with_resource)); memset(num_units_unassigned, 0, sizeof(num_units_unassigned)); memset(num_units_assigned, 0, sizeof(num_units_assigned)); // Collect statistics about the current assignment const int n = AiPlayer->Player->GetUnitCount(); for (int i = 0; i < n; ++i) { CUnit &unit = AiPlayer->Player->GetUnit(i); //Wyrmgus start // if (!unit.Type->BoolFlag[HARVESTER_INDEX].value) { if (!unit.Type->BoolFlag[HARVESTER_INDEX].value || !unit.Active) { //Wyrmgus end continue; } // See if it's assigned already if (unit.Orders.size() == 1 && unit.CurrentAction() == UnitActionResource) { const COrder_Resource &order = *static_cast<COrder_Resource *>(unit.CurrentOrder()); const int c = order.GetCurrentResource(); units_assigned[c].push_back(&unit); num_units_assigned[c]++; total_harvester++; continue; } // Ignore busy units. ( building, fighting, ... ) if (!unit.IsIdle()) { continue; } // Send workers with resources back home. if (unit.ResourcesHeld) { const int c = unit.CurrentResource; num_units_with_resource[c]++; CommandReturnGoods(unit, 0, FlushCommands); total_harvester++; continue; } // Look what the unit can do for (int c = 1; c < MaxCosts; ++c) { if (unit.Type->ResInfo[c]) { units_unassigned[c].push_back(&unit); num_units_unassigned[c]++; } } ++total_harvester; } if (!total_harvester) { return; } memset(wanted, 0, sizeof(wanted)); int percent_total = 100; for (int c = 1; c < MaxCosts; ++c) { percent[c] = AiPlayer->Collect[c]; if ((AiPlayer->NeededMask & (1 << c))) { // Double percent if needed percent_total += percent[c]; percent[c] <<= 1; } } // Turn percent values into harvester numbers. for (int c = 1; c < MaxCosts; ++c) { if (percent[c]) { // Wanted needs to be representative. if (total_harvester < 5) { wanted[c] = 1 + (percent[c] * 5) / percent_total; } else { wanted[c] = 1 + (percent[c] * total_harvester) / percent_total; } } } // Initialise priority & mapping for (int c = 0; c < MaxCosts; ++c) { priority_resource[c] = c; priority_needed[c] = wanted[c] - num_units_assigned[c] - num_units_with_resource[c]; if (c && num_units_assigned[c] > 1) { //first should go workers with lower ResourcesHeld value std::sort(units_assigned[c].begin(), units_assigned[c].end(), CmpWorkers); } } CUnit *unit; do { // sort resources by priority for (int i = 0; i < MaxCosts; ++i) { for (int j = i + 1; j < MaxCosts; ++j) { if (priority_needed[j] > priority_needed[i]) { std::swap(priority_needed[i], priority_needed[j]); std::swap(priority_resource[i], priority_resource[j]); } } } unit = NULL; // Try to complete each resource in the priority order for (int i = 0; i < MaxCosts; ++i) { int c = priority_resource[i]; // If there is a free worker for c, take it. if (num_units_unassigned[c]) { // Take the unit. while (0 < num_units_unassigned[c] && !AiAssignHarvester(*units_unassigned[c][0], c)) { // can't assign to c => remove from units_unassigned ! units_unassigned[c][0] = units_unassigned[c][--num_units_unassigned[c]]; units_unassigned[c].pop_back(); } // unit is assigned if (0 < num_units_unassigned[c]) { unit = units_unassigned[c][0]; units_unassigned[c][0] = units_unassigned[c][--num_units_unassigned[c]]; units_unassigned[c].pop_back(); // remove it from other ressources for (int j = 0; j < MaxCosts; ++j) { if (j == c || !unit->Type->ResInfo[j]) { continue; } for (int k = 0; k < num_units_unassigned[j]; ++k) { if (units_unassigned[j][k] == unit) { units_unassigned[j][k] = units_unassigned[j][--num_units_unassigned[j]]; units_unassigned[j].pop_back(); break; } } } } } // Else : Take from already assigned worker with lower priority. if (!unit) { // Take from lower priority only (i+1). for (int j = i + 1; j < MaxCosts && !unit; ++j) { // Try to move worker from src_c to c const int src_c = priority_resource[j]; //Wyrmgus start //don't reassign workers from one resource to another, that is too expensive performance-wise (this could be re-implemented if the AI is altered to keep track of found resource spots break; //Wyrmgus end //Wyrmgus start // don't reassign if the src_c resource has no workers, or if the new resource has 0 "wanted" if (num_units_assigned[src_c] == 0 || !wanted[c]) { continue; } //Wyrmgus end // Don't complete with lower priority ones... //Wyrmgus start /* if (wanted[src_c] > wanted[c] || (wanted[src_c] == wanted[c] && num_units_assigned[src_c] <= num_units_assigned[c] + 1)) { */ if (wanted[src_c] && ((num_units_assigned[src_c] + 1) * 100 / wanted[src_c]) <= (num_units_assigned[c] * 100 / wanted[c])) { // what matters is the percent of "wanted" fulfilled, not the absolute quantity of needed workers for that resource; add one worker to num_units_assigned[src_c] so that you won't get one worker being shuffled back and forth //Wyrmgus end //Wyrmgus start // continue; if (num_units_assigned[c] != 0) { // if there are no workers for that resource, allow reshuffling regardless of proportion calculation, so that if the game starts with few workers (like two), the AI isn't stuck gathering only the first resource it finds continue; } //Wyrmgus end } //Wyrmgus start if (num_units_assigned[src_c] <= wanted[src_c]) { // don't reassign if the src_c resource already has less workers than desired if (num_units_assigned[c] != 0) { continue; } } //Wyrmgus end //Wyrmgus start // for (int k = num_units_assigned[src_c] - 1; k >= 0 && !unit; --k) { for (int k = num_units_assigned[src_c] - 1; k >= 0; --k) { // unit may be NULL now, continue instead of breaking loop if so //Wyrmgus end unit = units_assigned[src_c][k]; //Wyrmgus start if (!unit) { continue; } //Wyrmgus end Assert(unit->CurrentAction() == UnitActionResource); COrder_Resource &order = *static_cast<COrder_Resource *>(unit->CurrentOrder()); if (order.IsGatheringFinished()) { //worker returning with resource continue; } //Wyrmgus start if (unit->Removed || unit->CurrentAction() == UnitActionBuild) { //if unit is removed or is currently building something, it can't be told to harvest (do this here so that AiAssignHarvester returning false later on is only because of unit type not being able to harvest something) unit = NULL; continue; } //Wyrmgus end // unit can't harvest : next one if (!unit->Type->ResInfo[c] || !AiAssignHarvester(*unit, c)) { unit = NULL; continue; } // Remove from src_c units_assigned[src_c][k] = units_assigned[src_c][--num_units_assigned[src_c]]; units_assigned[src_c].pop_back(); // j need one more priority_needed[j]++; //Wyrmgus start break; //only reassign one worker per time //Wyrmgus end } } } // We just moved an unit. Adjust priority & retry if (unit) { // i got a new unit. priority_needed[i]--; // Recompute priority now break; } } } while (unit); // Unassigned units there can't be assigned ( ie : they can't move to ressource ) // IDEA : use transporter here. }