sots-engine/src/game/ai/tasks.cpp

169 lines
6.5 KiB
C++

#include "game/ai/tasks.h"
#include <algorithm>
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<int>(t);
return i >= 0 && i < kTaskTypeCount;
}
// The shared tail every arm ends with, in call order.
void AppendCommonTail(std::vector<TaskType>& 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<TaskType>& 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<TaskType>& 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<int>(t)].name : ""; }
int TablePriority(TaskType t) { return InRange(t) ? kTable[static_cast<int>(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<RankedTask>& 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<TaskType> CreationOrder(sim::Species species, bool policyNonZero) {
std::vector<TaskType> 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