#include "game/ai/tasks.h" #include namespace sots::ai { namespace { struct Row { const char* name; int priority; }; // Indexed by task type id. Both retired ids keep their priority and carry no name. constexpr Row kTable[kTaskTypeCount] = { /* 0x00 */ {"AITSteamroll", 1250}, /* 0x01 */ {"AITExplore", 600}, /* 0x02 */ {"AITExploreInForce", 550}, /* 0x03 */ {"AITEscortGate", 700}, /* 0x04 */ {"AITEscortGateInvade", 400}, /* 0x05 */ {"AITEscortGateInvadeGoal", 950}, /* 0x06 */ {"AITDeployGateAt", 1400}, /* 0x07 */ {"AITColonize", 900}, /* 0x08 */ {"AITColonizeGoal", 970}, /* 0x09 */ {"AITColonizeAt", 1300}, /* 0x0a */ {"AITInvade", 500}, /* 0x0b */ {"AITInvadeGate", 1000}, /* 0x0c */ {"AITInvadeGoal", 930}, /* 0x0d */ {"", 200}, /* 0x0e */ {"AITDefendColonyIncoming", 1100}, /* 0x0f */ {"", 300}, /* 0x10 */ {"AITDefendGateIncoming", 1200}, /* 0x11 */ {"AITKillEasterEgg", 800}, /* 0x12 */ {"AITInterceptEnemy", 850}, /* 0x13 */ {"AITMining", 350}, /* 0x14 */ {"AITMiningReturn", 375}, /* 0x15 */ {"AITAttackBlockade", 100}, /* 0x16 */ {"AITAdvanceIdleShips", 0}, /* 0x17 */ {"AITStockFreighters", 50}, /* 0x18 */ {"AITRespondAttackSystem", 980}, /* 0x19 */ {"AITRespondDefendSystem", 990}, /* 0x1a */ {"AITNodeBore", 1275}, /* 0x1b */ {"AITBuildStations", 910}, /* 0x1c */ {"AITBuildPoliceShips", 75}, /* 0x1d */ {"AITBuildDeepScanShips", 60}, /* 0x1e */ {"AITRaid", 399}, /* 0x1f */ {"AITRetrieveArtifact", 1}, /* 0x20 */ {"AITReturnArtifact", 2}, }; // The two artifact tasks ignore their table entries entirely and return these instead. Keeping // them here rather than in kTable is deliberate: the table values are real, reachable through // nothing, and a future reader who "fixes" the table would be wrong. constexpr int kRetrieveArtifactPriority = 1260; constexpr int kReturnArtifactPriority = 1261; constexpr bool InRange(TaskType t) { const int i = static_cast(t); return i >= 0 && i < kTaskTypeCount; } // The shared tail every arm ends with, in call order. void AppendCommonTail(std::vector& out, bool policeShips, bool deepScanShips) { out.push_back(TaskType::InterceptEnemy); out.push_back(TaskType::Mining); out.push_back(TaskType::MiningReturn); out.push_back(TaskType::AdvanceIdleShips); out.push_back(TaskType::Raid); out.push_back(TaskType::StockFreighters); out.push_back(TaskType::AttackBlockade); out.push_back(TaskType::BuildStations); if (policeShips) out.push_back(TaskType::BuildPoliceShips); if (deepScanShips) out.push_back(TaskType::BuildDeepScanShips); } // The goal group: one creator that builds four families. void AppendGoalGroup(std::vector& out) { out.push_back(TaskType::ColonizeGoal); out.push_back(TaskType::EscortGateInvadeGoal); out.push_back(TaskType::Invade); out.push_back(TaskType::InvadeGoal); } // The two defensive families, behind the policy gate. DefendGateIncoming is Hiver-only. void AppendDefensive(std::vector& out, bool policyNonZero, sim::Species species) { if (!policyNonZero) return; if (species == sim::Species::Hiver) out.push_back(TaskType::DefendGateIncoming); out.push_back(TaskType::DefendColonyIncoming); } } // namespace const char* TaskTypeName(TaskType t) { return InRange(t) ? kTable[static_cast(t)].name : ""; } int TablePriority(TaskType t) { return InRange(t) ? kTable[static_cast(t)].priority : 0; } int PriorityOf(const RankedTask& t, const TaskPriorityPolicy& policy) { switch (t.type) { case TaskType::RetrieveArtifact: return kRetrieveArtifactPriority; case TaskType::ReturnArtifact: return kReturnArtifactPriority; case TaskType::Invade: return t.priorityFlagBit0 ? policy.flaggedInvade : TablePriority(t.type); case TaskType::EscortGateInvade: return t.priorityFlagBit0 ? policy.flaggedEscortGateInvade : TablePriority(t.type); case TaskType::AttackBlockade: return t.overridePriority ? t.priority : TablePriority(t.type); default: return TablePriority(t.type); } } void Rank(std::vector& tasks, const TaskPriorityPolicy& policy) { // std::stable_sort, not the introsort in game/config/msvc_sort.h: the original sorts a // std::list, and list::sort is a merge sort -- stable by construction, in every library. // The comparison is a strict greater-than on the priority, so equal keys never move. std::stable_sort(tasks.begin(), tasks.end(), [&policy](const RankedTask& a, const RankedTask& b) { return PriorityOf(a, policy) > PriorityOf(b, policy); }); } std::vector CreationOrder(sim::Species species, bool policyNonZero) { std::vector out; if (BuildsNoTasks(species)) return out; if (species == sim::Species::Hiver) { out.push_back(TaskType::Steamroll); out.push_back(TaskType::Colonize); out.push_back(TaskType::ColonizeAt); AppendDefensive(out, policyNonZero, species); out.push_back(TaskType::EscortGateInvade); out.push_back(TaskType::InvadeGate); out.push_back(TaskType::DeployGateAt); out.push_back(TaskType::EscortGate); AppendGoalGroup(out); AppendCommonTail(out, /*policeShips=*/true, /*deepScanShips=*/true); return out; } if (species == sim::Species::Zuul) { out.push_back(TaskType::Steamroll); out.push_back(TaskType::NodeBore); out.push_back(TaskType::Colonize); out.push_back(TaskType::ColonizeAt); AppendDefensive(out, policyNonZero, species); out.push_back(TaskType::KillEasterEgg); out.push_back(TaskType::Invade); out.push_back(TaskType::ExploreInForce); AppendGoalGroup(out); AppendCommonTail(out, /*policeShips=*/false, /*deepScanShips=*/true); return out; } // Human, Tarkas, Liir, Morrigi. out.push_back(TaskType::Steamroll); out.push_back(TaskType::Colonize); out.push_back(TaskType::ColonizeAt); AppendDefensive(out, policyNonZero, species); out.push_back(TaskType::KillEasterEgg); out.push_back(TaskType::Invade); out.push_back(TaskType::Explore); out.push_back(TaskType::ExploreInForce); AppendGoalGroup(out); AppendCommonTail(out, /*policeShips=*/true, /*deepScanShips=*/true); return out; } } // namespace sots::ai