Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+386
View File
@@ -0,0 +1,386 @@
// Budget
// ------
// Tasked with managing the empire's money and making sure we have enough to spend
// on various things, as well as dealing with prioritization and budget allocation.
//
import empire_ai.weasel.WeaselAI;
enum BudgetType {
BT_Military,
BT_Colonization,
BT_Development,
BT_COUNT
};
final class AllocateBudget {
int id = -1;
uint type;
int cost = 0;
int maintenance = 0;
double requestTime = 0;
double priority = 1;
bool allocated = false;
int opCmp(const AllocateBudget@ other) const {
if(priority < other.priority)
return -1;
if(priority > other.priority)
return 1;
if(requestTime < other.requestTime)
return 1;
if(requestTime > other.requestTime)
return -1;
return 0;
}
void save(SaveFile& file) {
file << id;
file << type;
file << cost;
file << maintenance;
file << requestTime;
file << priority;
file << allocated;
}
void load(SaveFile& file) {
file >> id;
file >> type;
file >> cost;
file >> maintenance;
file >> requestTime;
file >> priority;
file >> allocated;
}
};
final class BudgetPart {
uint type;
array<AllocateBudget@> allocations;
//How much we've spent this cycle
int spent = 0;
//How much is remaining to be spent this cycle
int remaining = 0;
//How much maintenance we've gained this cycle
int gainedMaintenance = 0;
//How much maintenance we can still gain this cycle
int remainingMaintenance = 0;
void update(AI& ai, Budget& budget) {
for(uint i = 0, cnt = allocations.length; i < cnt; ++i) {
auto@ alloc = allocations[i];
if(alloc.priority < 1.0) {
if(alloc.cost >= remaining && alloc.maintenance >= remainingMaintenance) {
budget.spend(type, alloc.cost, alloc.maintenance);
alloc.allocated = true;
allocations.removeAt(i);
break;
}
}
else {
if(budget.canSpend(type, alloc.cost, alloc.maintenance)) {
budget.spend(type, alloc.cost, alloc.maintenance);
alloc.allocated = true;
allocations.removeAt(i);
break;
}
}
}
}
void turn(AI& ai, Budget& budget) {
spent = 0;
remaining = 0;
gainedMaintenance = 0;
remainingMaintenance = 0;
}
void save(Budget& budget, SaveFile& file) {
file << spent;
file << remaining;
file << gainedMaintenance;
file << remainingMaintenance;
uint cnt = allocations.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
budget.saveAlloc(file, allocations[i]);
allocations[i].save(file);
}
}
void load(Budget& budget, SaveFile& file) {
file >> spent;
file >> remaining;
file >> gainedMaintenance;
file >> remainingMaintenance;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ alloc = budget.loadAlloc(file);
alloc.load(file);
}
}
};
final class Budget : AIComponent {
array<BudgetPart@> parts;
int NextAllocId = 0;
int InitialBudget = 0;
int InitialUpcoming = 0;
double Progress = 0;
double RemainingTime = 0;
int FreeBudget = 0;
int FreeMaintenance = 0;
bool checkedMilitarySpending = false;
void create() {
parts.length = BT_COUNT;
for(uint i = 0; i < BT_COUNT; ++i) {
@parts[i] = BudgetPart();
parts[i].type = BudgetType(i);
}
}
void save(SaveFile& file) {
file << InitialBudget;
file << InitialUpcoming;
file << Progress;
file << RemainingTime;
file << FreeBudget;
file << FreeMaintenance;
file << NextAllocId;
file << checkedMilitarySpending;
for(uint i = 0, cnt = parts.length; i < cnt; ++i)
parts[i].save(this, file);
}
void load(SaveFile& file) {
file >> InitialBudget;
file >> InitialUpcoming;
file >> Progress;
file >> RemainingTime;
file >> FreeBudget;
file >> FreeMaintenance;
file >> NextAllocId;
file >> checkedMilitarySpending;
for(uint i = 0, cnt = parts.length; i < cnt; ++i)
parts[i].load(this, file);
}
array<AllocateBudget@> loadIds;
AllocateBudget@ loadAlloc(int id) {
if(id == -1)
return null;
for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) {
if(loadIds[i].id == id)
return loadIds[i];
}
AllocateBudget alloc;
alloc.id = id;
loadIds.insertLast(alloc);
return alloc;
}
AllocateBudget@ loadAlloc(SaveFile& file) {
int id = -1;
file >> id;
if(id == -1)
return null;
else
return loadAlloc(id);
}
void saveAlloc(SaveFile& file, AllocateBudget@ alloc) {
int id = -1;
if(alloc !is null)
id = alloc.id;
file << id;
}
void postLoad(AI& ai) {
loadIds.length = 0;
}
void spend(uint type, int money, int maint = 0) {
auto@ part = parts[type];
part.spent += money;
part.gainedMaintenance += maint;
if(part.remaining >= money) {
part.remaining -= money;
}
else if(part.remaining >= 0) {
money -= part.remaining;
FreeBudget -= money;
part.remaining = 0;
}
else {
FreeBudget -= money;
}
if(part.remainingMaintenance >= maint) {
part.remainingMaintenance -= maint;
}
else if(part.remainingMaintenance >= 0) {
maint -= part.remainingMaintenance;
FreeMaintenance -= maint;
part.remainingMaintenance = 0;
}
else {
FreeMaintenance -= money;
}
}
bool canSpend(uint type, int money, int maint = 0) {
int canFree = FreeBudget;
int canFreeMaint = FreeMaintenance;
//Don't allow generic spending until we've checked if we need to spend on military this cycle
if(type == BT_Development && !checkedMilitarySpending && Progress < 0.33)
canFree = 0;
if(type == BT_Colonization)
canFree += 160;
auto@ part = parts[type];
if(money > part.remaining + canFree)
return false;
if(maint != 0 && maint > part.remainingMaintenance + canFreeMaint)
return false;
return true;
}
int spendable(uint type) {
return FreeBudget + parts[type].remaining;
}
int maintainable(uint type) {
return FreeMaintenance + parts[type].remainingMaintenance;
}
void claim(uint type, int money, int maint = 0) {
auto@ part = parts[type];
FreeBudget -= money;
part.remaining += money;
FreeMaintenance -= maint;
part.remainingMaintenance += maint;
}
void turn() {
if(log && gameTime > 10.0) {
ai.print("==============");
ai.print("Unspent:");
ai.print(" Military: "+parts[BT_Military].remaining+" / "+parts[BT_Military].remainingMaintenance);
ai.print(" Colonization: "+parts[BT_Colonization].remaining+" / "+parts[BT_Colonization].remainingMaintenance);
ai.print(" Development: "+parts[BT_Development].remaining+" / "+parts[BT_Development].remainingMaintenance);
ai.print(" FREE: "+FreeBudget+" / "+FreeMaintenance);
ai.print("==============");
ai.print("Total Expenditures:");
ai.print(" Military: "+parts[BT_Military].spent+" / "+parts[BT_Military].gainedMaintenance);
ai.print(" Colonization: "+parts[BT_Colonization].spent+" / "+parts[BT_Colonization].gainedMaintenance);
ai.print(" Development: "+parts[BT_Development].spent+" / "+parts[BT_Development].gainedMaintenance);
ai.print("==============");
}
//Collect some data about this turn
InitialBudget = ai.empire.RemainingBudget;
InitialUpcoming = ai.empire.EstNextBudget;
FreeBudget = InitialBudget;
FreeMaintenance = InitialUpcoming;
checkedMilitarySpending = false;
//Tell the budget parts to perform turns
for(uint i = 0, cnt = parts.length; i < cnt; ++i)
parts[i].turn(ai, this);
}
void remove(AllocateBudget@ alloc) {
if(alloc is null)
return;
if(alloc.allocated) {
FreeBudget += alloc.cost;
FreeMaintenance += alloc.maintenance;
}
parts[alloc.type].allocations.remove(alloc);
}
AllocateBudget@ allocate(uint type, int cost, int maint = 0, double priority = 1.0) {
AllocateBudget alloc;
alloc.id = NextAllocId++;
alloc.type = type;
alloc.cost = cost;
alloc.maintenance = maint;
alloc.priority = priority;
return allocate(alloc);
}
AllocateBudget@ allocate(AllocateBudget@ allocation) {
allocation.requestTime = gameTime;
parts[allocation.type].allocations.insertLast(allocation);
parts[allocation.type].allocations.sortDesc();
return allocation;
}
void applyNow(AllocateBudget@ alloc) {
auto@ part = parts[alloc.type];
spend(alloc.type, alloc.cost, alloc.maintenance);
alloc.allocated = true;
part.allocations.remove(alloc);
}
void grantBonus(int cost, int maint = 0) {
//Spread some bonus budget across all the different parts
FreeBudget += cost;
FreeMaintenance += maint;
}
void tick(double time) {
//Record some simple data
Progress = ai.empire.BudgetTimer / ai.empire.BudgetCycle;
RemainingTime = ai.empire.BudgetCycle - ai.empire.BudgetTimer;
//Update one of the budget parts
for(uint i = 0, cnt = parts.length; i < cnt; ++i) {
auto@ part = parts[i];
part.update(ai, this);
}
}
void focusTick(double time) {
//Detect any extra budget we need to use
int ExpectBudget = FreeBudget;
int ExpectMaint = FreeMaintenance;
for(uint i = 0, cnt = parts.length; i < cnt; ++i) {
ExpectBudget += parts[i].remaining;
ExpectMaint += parts[i].remainingMaintenance;
}
int HaveBudget = ai.empire.RemainingBudget;
int HaveMaint = ai.empire.EstNextBudget;
if(ExpectBudget != HaveBudget || ExpectMaint != HaveMaint)
grantBonus(HaveBudget - ExpectBudget, max(0, HaveMaint - ExpectMaint));
}
};
AIComponent@ createBudget() {
return Budget();
}
File diff suppressed because it is too large Load Diff
+364
View File
@@ -0,0 +1,364 @@
// Consider
// --------
// Helps AI usage hints to consider various things in the empire.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Development;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Colonization;
import empire_ai.weasel.Intelligence;
import buildings;
import ai.consider;
from ai.artifacts import ArtifactConsider;
class Consider : AIComponent, Considerer {
Systems@ systems;
Fleets@ fleets;
Planets@ planets;
Construction@ construction;
Development@ development;
Resources@ resources;
Intelligence@ intelligence;
Colonization@ colonization;
void create() {
@systems = cast<Systems>(ai.systems);
@fleets = cast<Fleets>(ai.fleets);
@planets = cast<Planets>(ai.planets);
@development = cast<Development>(ai.development);
@construction = cast<Construction>(ai.construction);
@resources = cast<Resources>(ai.resources);
@intelligence = cast<Intelligence>(ai.intelligence);
@colonization = cast<Colonization>(ai.colonization);
}
Empire@ get_empire() {
return ai.empire;
}
Object@ secondary;
ArtifactConsider@ artifactConsider;
double bestWeight;
ImportData@ request;
const BuildingType@ bldType;
ConsiderComponent@ comp;
ConsiderFilter@ cfilter;
double get_selectedWeight() {
return bestWeight;
}
Object@ get_currentSupplier() {
return secondary;
}
ArtifactConsider@ get_artifact() {
return artifactConsider;
}
void set_artifact(ArtifactConsider@ cons) {
@artifactConsider = cons;
}
double get_idleTime() {
if(request !is null)
return gameTime - request.idleSince;
return 0.0;
}
double timeSinceMatchingColonize() {
if(request is null)
return INFINITY;
return colonization.timeSinceMatchingColonize(request.spec);
}
const BuildingType@ get_building() {
return bldType;
}
void set_building(const BuildingType@ type) {
@bldType = type;
}
ConsiderComponent@ get_component() {
return comp;
}
void set_component(ConsiderComponent@ comp) {
@this.comp = comp;
}
void set_filter(ConsiderFilter@ filter) {
@this.cfilter = filter;
}
void clear() {
@secondary = null;
@artifactConsider = null;
@request = null;
@comp = null;
@bldType = null;
@cfilter = null;
}
Object@ OwnedSystems(const ConsiderHook& hook, uint limit = uint(-1)) {
Object@ best;
bestWeight = 0.0;
uint offset = randomi(0, systems.owned.length-1);
uint cnt = min(systems.owned.length, limit);
for(uint i = 0; i < cnt; ++i) {
uint index = (i+offset) % systems.owned.length;
Region@ obj = systems.owned[index].obj;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
clear();
return best;
}
Object@ Fleets(const ConsiderHook& hook) {
Object@ best;
bestWeight = 0.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
Object@ fleet = fleets.fleets[i].obj;
if(fleet !is null) {
if(cfilter !is null && !cfilter.filter(fleet))
continue;
double w = hook.consider(this, fleet);
if(w > bestWeight) {
bestWeight = w;
@best = fleet;
}
}
}
clear();
return best;
}
Object@ BorderSystems(const ConsiderHook& hook) {
Object@ best;
bestWeight = 0.0;
for(uint i = 0, cnt = systems.border.length; i < cnt; ++i) {
Region@ obj = systems.border[i].obj;
if(obj.PlanetsMask & ~ai.mask == 0)
continue;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) {
Region@ obj = systems.outsideBorder[i].obj;
if(obj.PlanetsMask & ~ai.mask == 0)
continue;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
clear();
return best;
}
Object@ OtherSystems(const ConsiderHook& hook) {
Object@ best;
bestWeight = 0.0;
for(uint i = 0, cnt = intelligence.intel.length; i < cnt; ++i) {
auto@ intel = intelligence.intel[i];
if(intel is null)
continue;
for(uint i = 0, cnt = intel.theirOwned.length; i < cnt; ++i) {
Region@ obj = intel.theirOwned[i].obj;
if(obj.PlanetsMask & ~ai.mask == 0)
continue;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
}
clear();
return best;
}
Object@ ImportantPlanets(const ConsiderHook& hook) {
Object@ best;
bestWeight = 0.0;
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
Object@ obj = development.focuses[i].obj;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
clear();
return best;
}
Object@ AllPlanets(const ConsiderHook& hook) {
Object@ best;
bestWeight = 0.0;
for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) {
Object@ obj = planets.planets[i].obj;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
clear();
return best;
}
Object@ SomePlanets(const ConsiderHook& hook, uint count, bool alwaysImportant) {
Object@ best;
bestWeight = 0.0;
if(alwaysImportant) {
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
Object@ obj = development.focuses[i].obj;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
}
uint planetCount = planets.planets.length;
uint offset = randomi(0, planetCount-1);
uint cnt = min(count, planetCount);
for(uint i = 0; i < cnt; ++i) {
uint index = (offset+i) % planetCount;
Object@ obj = planets.planets[index].obj;
if(obj !is null) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
clear();
return best;
}
Object@ FactoryPlanets(const ConsiderHook& hook) {
Object@ best;
bestWeight = 0.0;
for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) {
Object@ obj = construction.factories[i].obj;
if(obj !is null && obj.isPlanet) {
if(cfilter !is null && !cfilter.filter(obj))
continue;
double w = hook.consider(this, obj);
if(w > bestWeight) {
bestWeight = w;
@best = obj;
}
}
}
clear();
return best;
}
Object@ MatchingImportRequests(const ConsiderHook& hook, const ResourceType@ type, bool considerExisting) {
Object@ best;
bestWeight = 0.0;
for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) {
ImportData@ req = resources.requested[i];
if(!considerExisting) {
if(req.beingMet || req.claimedFor)
continue;
}
if(req.spec.meets(type, req.obj, req.obj)) {
@secondary = null;
@request = req;
double w = hook.consider(this, req.obj);
if(w > bestWeight) {
if(cfilter !is null && !cfilter.filter(req.obj))
continue;
bestWeight = w;
@best = req.obj;
}
}
}
if(considerExisting) {
for(uint i = 0, cnt = resources.used.length; i < cnt; ++i) {
ExportData@ res = resources.used[i];
ImportData@ req = res.request;
if(req !is null && req.spec.meets(type, req.obj, req.obj)) {
@secondary = res.obj;
@request = req;
double w = hook.consider(this, req.obj);
if(w > bestWeight) {
if(cfilter !is null && !cfilter.filter(req.obj))
continue;
bestWeight = w;
@best = req.obj;
}
}
}
}
clear();
return best;
}
};
AIComponent@ createConsider() {
return Consider();
}
File diff suppressed because it is too large Load Diff
+617
View File
@@ -0,0 +1,617 @@
// Creeping
// --------
// Uses fleets that aren't currently doing anything to eliminate creeps.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Movement;
import empire_ai.weasel.searches;
import saving;
from empire import Creeps;
class CreepingMission : Mission {
Pickup@ pickup;
Object@ protector;
MoveOrder@ move;
void save(Fleets& fleets, SaveFile& file) {
file << pickup;
file << protector;
fleets.movement.saveMoveOrder(file, move);
}
void load(Fleets& fleets, SaveFile& file) {
file >> pickup;
file >> protector;
@move = fleets.movement.loadMoveOrder(file);
}
void start(AI& ai, FleetAI& fleet) override {
vec3d position = pickup.position;
double dist = fleet.radius;
if(protector !is null && protector.valid)
dist = fleet.obj.getEngagementRange();
position += (fleet.obj.position - pickup.position).normalized(dist);
@move = cast<Movement>(ai.movement).move(fleet.obj, position);
}
void tick(AI& ai, FleetAI& fleet, double time) {
if(move !is null) {
if(move.completed) {
if(protector !is null && protector.valid) {
if(!protector.isVisibleTo(ai.empire)) { //Yo nebulas are scary yo
fleet.obj.addMoveOrder(protector.position);
fleet.obj.addAttackOrder(protector, append=true);
}
else {
fleet.obj.addAttackOrder(protector);
}
}
@move = null;
}
else if(move.failed) {
canceled = true;
return;
}
else
return;
}
if(protector is null || !protector.valid) {
if(!fleet.obj.hasOrders) {
if(pickup is null || !pickup.valid) {
if(cast<Creeping>(ai.creeping).log)
ai.print("Finished clearing creep camp", fleet.obj);
completed = true;
}
else {
fleet.obj.addPickupOrder(pickup);
@protector = null;
}
}
}
else {
if((fleet.filled < 0.3 || fleet.supplies < 0.3 || fleet.flagshipHealth < 0.4)
&& protector.getFleetStrength() * ai.behavior.remnantOverkillFactor > fleet.strength) {
//Holy shit what's going on? ABORT! ABORT!
if(cast<Creeping>(ai.creeping).logCritical)
ai.print("ABORTED CREEPING: About to lose fight", fleet.obj);
canceled = true;
cast<Fleets>(ai.fleets).returnToBase(fleet, MP_Critical);
}
}
}
};
class ClearMission : Mission {
Region@ region;
Object@ eliminate;
MoveOrder@ move;
void save(Fleets& fleets, SaveFile& file) {
file << region;
file << eliminate;
fleets.movement.saveMoveOrder(file, move);
}
void load(Fleets& fleets, SaveFile& file) {
file >> region;
file >> eliminate;
@move = fleets.movement.loadMoveOrder(file);
}
void start(AI& ai, FleetAI& fleet) override {
@move = cast<Movement>(ai.movement).move(fleet.obj, region);
}
void tick(AI& ai, FleetAI& fleet, double time) {
if(move !is null) {
if(move.completed) {
@move = null;
}
else if(move.failed) {
canceled = true;
return;
}
else
return;
}
if(eliminate is null) {
@eliminate = cast<Creeping>(ai.creeping).findRemnants(region);
if(eliminate is null) {
completed = true;
return;
}
}
if(eliminate !is null) {
if(!eliminate.valid) {
@eliminate = null;
}
else {
if(!fleet.obj.hasOrders)
fleet.obj.addAttackOrder(eliminate);
if((fleet.filled < 0.3 || fleet.supplies < 0.3 || fleet.flagshipHealth < 0.4)
&& eliminate.getFleetStrength() * ai.behavior.remnantOverkillFactor > fleet.strength) {
//Holy shit what's going on? ABORT! ABORT!
if(cast<Creeping>(ai.creeping).logCritical)
ai.print("ABORTED CREEPING: About to lose fight", fleet.obj);
canceled = true;
cast<Fleets>(ai.fleets).returnToBase(fleet, MP_Critical);
}
}
}
}
};
final class CreepPenalty : Savable {
Object@ obj;
double until;
void save(SaveFile& file) {
file << obj;
file << until;
}
void load(SaveFile& file) {
file >> obj;
file >> until;
}
};
final class ClearSystem {
SystemAI@ sys;
array<Ship@> remnants;
void save(Creeping& creeping, SaveFile& file) {
creeping.systems.saveAI(file, sys);
uint cnt = remnants.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << remnants[i];
}
void load(Creeping& creeping, SaveFile& file) {
@sys = creeping.systems.loadAI(file);
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Ship@ remn;
file >> remn;
if(remn !is null)
remnants.insertLast(remn);
}
}
void record() {
auto@ objs = findEnemies(sys.obj, null, Creeps.mask);
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
Ship@ ship = cast<Ship>(objs[i]);
if(ship !is null)
remnants.insertLast(ship);
}
}
double getStrength() {
double str = 0.0;
for(uint i = 0, cnt = remnants.length; i < cnt; ++i) {
if(remnants[i].valid)
str += sqrt(remnants[i].getFleetStrength());
}
return str * str;
}
};
class Creeping : AIComponent {
Systems@ systems;
Fleets@ fleets;
array<SystemAI@> requested;
array<CreepPenalty@> penalties;
array<CreepingMission@> active;
array<ClearSystem@> quarantined;
void create() {
@systems = cast<Systems>(ai.systems);
@fleets = cast<Fleets>(ai.fleets);
}
void save(SaveFile& file) {
uint cnt = requested.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
systems.saveAI(file, requested[i]);
cnt = penalties.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << penalties[i];
cnt = active.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets.saveMission(file, active[i]);
cnt = quarantined.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
quarantined[i].save(this, file);
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ sys = systems.loadAI(file);
if(sys !is null)
requested.insertLast(sys);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
CreepPenalty pen;
file >> pen;
if(pen.obj !is null)
penalties.insertLast(pen);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ miss = cast<CreepingMission>(fleets.loadMission(file));
if(miss !is null)
active.insertLast(miss);
}
if(file >= SV_0151) {
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
ClearSystem qsys;
qsys.load(this, file);
quarantined.insertLast(qsys);
}
}
}
void requestClear(SystemAI@ system) {
if(system is null)
return;
if(log)
ai.print("Requested creep camp clear", system.obj);
if(requested.find(system) == -1)
requested.insertLast(system);
}
CreepingMission@ creepWithFleet(FleetAI@ fleet, Pickup@ pickup, Object@ protector = null) {
if(protector is null)
@protector = pickup.getProtector();
if(log)
ai.print("Clearing creep camp in "+pickup.region.name, fleet.obj);
CreepingMission mission;
@mission.pickup = pickup;
@mission.protector = protector;
fleets.performMission(fleet, mission);
active.insertLast(mission);
return mission;
}
Pickup@ best;
Object@ bestProtector;
vec3d ourPosition;
double bestWeight;
double ourStrength;
void check(SystemAI@ sys, double weight = 1.0) {
for(uint n = 0, ncnt = sys.pickups.length; n < ncnt; ++n) {
Pickup@ pickup = sys.pickups[n];
Object@ protector = sys.pickupProtectors[n];
if(!pickup.valid)
continue;
double protStrength;
if(protector !is null && protector.valid) {
protStrength = protector.getFleetStrength();
if(protStrength * ai.behavior.remnantOverkillFactor > ourStrength)
continue;
}
else
protStrength = 1.0;
if(isCreeping(pickup))
continue;
double w = weight;
w /= protStrength / 1000.0;
w /= pickup.position.distanceTo(ourPosition);
if(w > bestWeight) {
bestWeight = w;
@best = pickup;
@bestProtector = protector;
}
}
}
void penalize(Object@ obj, double time) {
for(uint i = 0, cnt = penalties.length; i < cnt; ++i) {
if(penalties[i].obj is obj) {
penalties[i].until = max(penalties[i].until, gameTime + time);
return;
}
}
CreepPenalty p;
@p.obj = obj;
p.until = gameTime + time;
penalties.insertLast(p);
}
bool isPenalized(Object@ obj) {
for(uint i = 0, cnt = penalties.length; i < cnt; ++i) {
if(penalties[i].obj is obj)
return true;
}
return false;
}
bool isCreeping(Pickup@ pickup) {
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
if(active[i].pickup is pickup)
return true;
}
return false;
}
CreepingMission@ creepWithFleet(FleetAI@ fleet) {
@best = null;
@bestProtector = null;
bestWeight = 0.0;
ourStrength = fleet.strength;
ourPosition = fleet.obj.position;
//Check requested systems first
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
auto@ sys = requested[i];
if(sys.pickups.length == 0) {
requested.removeAt(i);
--i; --cnt;
continue;
}
if(haveQuarantinedSystem(sys))
continue;
check(sys);
}
if(best !is null)
return creepWithFleet(fleet, best, bestProtector);
if(!ai.behavior.remnantAllowArbitraryClear)
return null;
if(log)
ai.print("Attempted to find creep camp to clear", fleet.obj);
//Check systems in our territory
for(uint i = 0, cnt = systems.owned.length; i < cnt; ++i) {
SystemAI@ sys = systems.owned[i];
if(sys.pickups.length != 0)
check(sys);
}
if(best !is null)
return creepWithFleet(fleet, best, bestProtector);
//Check systems just outside our border
for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) {
SystemAI@ sys = systems.outsideBorder[i];
if(sys.seenPresent & ai.otherMask != 0)
continue;
if(haveQuarantinedSystem(sys))
continue;
if(sys.pickups.length != 0)
check(sys, 1.0 / double(1.0 + sys.hopDistance));
}
if(best !is null)
return creepWithFleet(fleet, best, bestProtector);
penalize(fleet.obj, 90.0);
return null;
}
Object@ findRemnants(Region@ reg) {
for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) {
auto@ qsys = quarantined[i];
if(qsys.sys.obj !is reg)
continue;
for(uint n = 0, ncnt = qsys.remnants.length; n < ncnt; ++n) {
auto@ remn = qsys.remnants[n];
if(remn is null || !remn.valid)
continue;
return remn;
}
}
return null;
}
ClearMission@ sendToClear(FleetAI@ fleet, ClearSystem@ system) {
ClearMission miss;
@miss.region = system.sys.obj;
fleets.performMission(fleet, miss);
if(log)
ai.print("Clear remnant defenders in "+miss.region.name, fleet.obj);
return miss;
}
bool isQuarantined(SystemAI@ sys) {
if(sys.planets.length == 0)
return false;
for(uint i = 0, cnt = sys.planets.length; i < cnt; ++i) {
if(!sys.planets[i].quarantined)
return false;
}
return true;
}
bool isQuarantined(Region@ region) {
for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) {
if(quarantined[i].sys.obj is region)
return true;
}
return false;
}
bool haveQuarantinedSystem(SystemAI@ sys) {
for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) {
if(quarantined[i].sys is sys)
return true;
}
return false;
}
void recordQuarantinedSystem(SystemAI@ sys) {
ClearSystem qsys;
@qsys.sys = sys;
quarantined.insertLast(qsys);
qsys.record();
}
uint ownedCheck = 0;
uint outsideCheck = 0;
void focusTick(double time) {
//Manage creeping check penalties
for(uint i = 0, cnt = penalties.length; i < cnt; ++i) {
if(penalties[i].until < gameTime) {
penalties.removeAt(i);
--i; --cnt;
}
}
//Manage current creeping missions
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
if(active[i].completed || active[i].canceled) {
active.removeAt(i);
--i; --cnt;
}
}
//Find new systems that are quarantined
if(systems.owned.length != 0) {
ownedCheck = (ownedCheck+1) % systems.owned.length;
auto@ sys = systems.owned[ownedCheck];
if(sys.explored && isQuarantined(sys)) {
if(!haveQuarantinedSystem(sys))
recordQuarantinedSystem(sys);
}
}
if(systems.outsideBorder.length != 0) {
outsideCheck = (outsideCheck+1) % systems.outsideBorder.length;
auto@ sys = systems.outsideBorder[outsideCheck];
if(sys.explored && isQuarantined(sys)) {
if(!haveQuarantinedSystem(sys))
recordQuarantinedSystem(sys);
}
}
//Update existing quarantined systems list
for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) {
auto@ qsys = quarantined[i];
if(!isQuarantined(qsys.sys)) {
quarantined.removeAt(i);
--i; --cnt;
continue;
}
for(uint n = 0, ncnt = qsys.remnants.length; n < ncnt; ++n) {
auto@ remn = qsys.remnants[n];
if(remn is null || !remn.valid || remn.region !is qsys.sys.obj) {
qsys.remnants.removeAt(n);
--n; --ncnt;
}
}
}
//See if we should try to clear a quarantined system
bool waitingForGather = false;
if(ai.behavior.remnantAllowArbitraryClear) {
ClearSystem@ best;
double bestStr = INFINITY;
for(uint i = 0, cnt = quarantined.length; i < cnt; ++i) {
double str = quarantined[i].getStrength();
if(quarantined[i].remnants.length == 0)
continue;
if(str < bestStr) {
bestStr = str;
@best = quarantined[i];
}
}
if(best !is null) {
double needStr = bestStr * ai.behavior.remnantOverkillFactor;
if(fleets.getTotalStrength(FC_Combat) > needStr) {
waitingForGather = true;
if(fleets.getTotalStrength(FC_Combat, readyOnly=true) > needStr) {
//Order sufficient fleets to go clear this system
double takeStr = sqrt(needStr);
double haveStr = 0.0;
uint offset = randomi(0, fleets.fleets.length-1);
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
FleetAI@ fleet = fleets.fleets[(i+offset)%cnt];
if(fleet.fleetClass != FC_Combat)
continue;
if(!fleet.readyForAction)
continue;
haveStr += sqrt(fleet.strength);
sendToClear(fleet, best);
if(haveStr > takeStr)
break;
}
}
}
}
}
//Find new fleets to creep with
if(!waitingForGather) {
uint offset = randomi(0, fleets.fleets.length-1);
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
FleetAI@ fleet = fleets.fleets[(i+offset)%cnt];
if(fleet.fleetClass != FC_Combat)
continue;
if(!fleet.readyForAction)
continue;
if(isPenalized(fleet.obj))
continue;
creepWithFleet(fleet);
break;
}
}
}
};
AIComponent@ createCreeping() {
return Creeping();
}
+690
View File
@@ -0,0 +1,690 @@
import empire_ai.weasel.WeaselAI;
import util.design_export;
import util.random_designs;
interface RaceDesigns {
bool preCompose(DesignTarget@ target);
bool postCompose(DesignTarget@ target);
bool design(DesignTarget@ target, int size, const Design@& output);
};
enum DesignPurpose {
DP_Scout,
DP_Combat,
DP_Defense,
DP_Support,
DP_Gate,
DP_Slipstream,
DP_Mothership,
DP_Miner,
DP_COUNT,
DP_Unknown,
};
tidy final class DesignTarget {
int id = -1;
const Design@ active;
string customName;
array<const Design@> potential;
array<double> scores;
uint purpose;
double targetBuildCost = 0;
double targetMaintenance = 0;
double targetLaborCost = 0;
double dps = 0.0;
double hp = 0.0;
double supplyDrain = 0.0;
double targetSize = 0;
bool findSize = false;
Designer@ designer;
DesignTarget() {
}
DesignTarget(uint type, double targetSize) {
this.purpose = type;
this.targetSize = targetSize;
}
uint get_designType() {
switch(purpose) {
case DP_Scout: return DT_Flagship;
case DP_Combat: return DT_Flagship;
case DP_Defense: return DT_Station;
case DP_Support: return DT_Support;
case DP_Gate: return DT_Station;
case DP_Slipstream: return DT_Flagship;
case DP_Mothership: return DT_Flagship;
}
return DT_Flagship;
}
void save(Designs& designs, SaveFile& file) {
if(active !is null) {
file.write1();
file << active;
file << dps;
file << supplyDrain;
file << hp;
}
else {
file.write0();
}
file << purpose;
file << targetBuildCost;
file << targetMaintenance;
file << targetLaborCost;
file << targetSize;
file << findSize;
file << customName;
}
void load(Designs& designs, SaveFile& file) {
if(file.readBit()) {
file >> active;
file >> dps;
file >> supplyDrain;
file >> hp;
}
file >> purpose;
file >> targetBuildCost;
file >> targetMaintenance;
file >> targetLaborCost;
file >> targetSize;
file >> findSize;
file >> customName;
}
void prepare(AI& ai) {
@designer = Designer(designType, targetSize, ai.empire, compose=false);
designer.randomHull = true;
switch(purpose) {
case DP_Scout:
designer.composeScout();
break;
case DP_Combat:
designer.composeFlagship();
break;
case DP_Defense:
designer.composeStation();
break;
case DP_Support:
designer.composeSupport();
break;
case DP_Gate:
designer.composeGate();
break;
case DP_Slipstream:
designer.composeSlipstream();
break;
case DP_Mothership:
designer.composeMothership();
break;
}
}
double weight(double value, double goal) {
if(value < goal)
return sqr(value / goal);
else if(value > goal * 1.5)
return goal / value;
return 1.0;
}
double costWeight(double value, double goal) {
if(findSize) {
if(value < goal)
return 1.0;
else
return 0.000001;
}
else {
if(value < goal)
return goal / value;
else
return pow(0.2, ((value / goal) - 1.0) * 10.0);
}
}
double evaluate(AI& ai, const Design& dsg) {
double w = 1.0;
//Try to stick as close to our target as we can
if(targetBuildCost != 0)
w *= costWeight(dsg.total(HV_BuildCost), targetBuildCost);
if(targetLaborCost != 0)
w *= costWeight(dsg.total(HV_LaborCost), targetLaborCost);
if(targetMaintenance != 0)
w *= costWeight(dsg.total(HV_MaintainCost), targetMaintenance);
double predictHP = 0.0;
double predictDPS = 0.0;
double predictDrain = 0.0;
//Value support capacity where appropriate
if(purpose == DP_Combat) {
double supCap = dsg.total(SV_SupportCapacity);
double avgHP = 0, avgDPS = 0, avgDrain = 0.0;
cast<Designs>(ai.designs).getSupportAverages(avgHP, avgDPS, avgDrain);
predictHP += supCap * avgHP;
predictDPS += supCap * avgDPS;
predictDrain += supCap * avgDrain;
}
//Value combat strength where appropriate
if(purpose != DP_Scout && purpose != DP_Slipstream && purpose != DP_Mothership) {
predictDPS += dsg.total(SV_DPS);
predictHP += dsg.totalHP + dsg.total(SV_ShieldCapacity);
predictDrain += dsg.total(SV_SupplyDrain);
if(purpose != DP_Support) {
w *= (predictHP * predictDPS) * 0.001;
double supplyStores = dsg.total(SV_SupplyCapacity);
double actionTime = supplyStores / predictDrain;
w *= weight(actionTime, ai.behavior.fleetAimSupplyDuration);
}
}
//Value acceleration on a target
if(purpose != DP_Defense && purpose != DP_Gate) {
double targetAccel = 2.0;
if(purpose == DP_Support)
targetAccel *= 1.5;
else if(purpose == DP_Scout)
targetAccel *= 3.0;
w *= weight(dsg.total(SV_Thrust) / max(dsg.total(HV_Mass), 0.01), targetAccel);
}
//Penalties for having important systems easy to shoot down
uint holes = 0;
for(uint i = 0, cnt = dsg.subsystemCount; i < cnt; ++i) {
auto@ sys = dsg.subsystem(i);
if(!sys.type.hasTag(ST_Important))
continue;
//TODO: We should be able to penalize for exposed supply storage
if(sys.type.hasTag(ST_NoCore))
continue;
vec2u core = sys.core;
for(uint d = 0; d < 6; ++d) {
if(!traceContainsArmor(dsg, core, d))
holes += 1;
}
}
if(holes != 0)
w /= pow(0.9, double(holes));
//TODO: Check FTL
return w;
}
bool traceContainsArmor(const Design@ dsg, const vec2u& startPos, uint direction) {
vec2u pos = startPos;
while(dsg.hull.active.valid(pos)) {
if(!dsg.hull.active.advance(pos, HexGridAdjacency(direction)))
break;
auto@ sys = dsg.subsystem(pos.x, pos.y);
if(sys is null)
continue;
if(sys.type.hasTag(ST_IsArmor))
return true;
}
return false;
}
bool contains(const Design& dsg) {
if(active is null)
return false;
if(dsg.mostUpdated() is active.mostUpdated())
return true;
return false;
}
const Design@ design(AI& ai, Designs& designs) {
int trySize = targetSize;
if(findSize) {
trySize = randomd(0.75, 1.25) * targetSize;
trySize = 5 * round(double(designer.size) / 5.0);
}
if(designs.race !is null) {
const Design@ fromRace;
if(designs.race.design(this, trySize, fromRace))
return fromRace;
}
if(designer !is null) {
designer.size = trySize;
return designer.design(1);
}
return null;
}
void choose(AI& ai, const Design@ dsg, bool randomizeName=true) {
set(dsg);
@designer = null;
findSize = false;
string baseName = dsg.name;
if(customName.length != 0) {
baseName = customName;
}
else if(randomizeName) {
if(dsg.hasTag(ST_IsSupport))
baseName = autoSupportNames[randomi(0,autoSupportNames.length-1)];
else
baseName = autoFlagNames[randomi(0,autoFlagNames.length-1)];
}
string name = baseName;
uint try = 0;
while(ai.empire.getDesign(name) !is null) {
name = baseName + " ";
appendRoman(++try, name);
}
if(name != dsg.name)
dsg.rename(name);
//Set design settings/support behavior
if(purpose == DP_Support) {
if(dsg.total(SV_SupportSupplyCapacity) > 0.01) {
DesignSettings settings;
settings.behavior = SG_Brawler;
dsg.setSettings(settings);
}
else if(dsg.totalHP > 50 * dsg.size) {
DesignSettings settings;
settings.behavior = SG_Shield;
dsg.setSettings(settings);
}
else {
DesignSettings settings;
settings.behavior = SG_Cannon;
dsg.setSettings(settings);
}
}
ai.empire.addDesign(ai.empire.getDesignClass("AI", true), dsg);
if(cast<Designs>(ai.designs).log)
ai.print("Chose design for purpose "+uint(purpose)+" at size "+dsg.size);
}
void step(AI& ai, Designs& designs) {
if(active is null) {
if(designer is null) {
if(designs.race is null || !designs.race.preCompose(this))
prepare(ai);
if(designs.race !is null && designs.race.postCompose(this))
return;
}
if(potential.length >= ai.behavior.designEvaluateCount) {
//Find the best design out of all our potentials
const Design@ best;
double bestScore = 0.0;
for(uint i = 0, cnt = potential.length; i < cnt; ++i) {
double w = scores[i];
if(w > bestScore) {
bestScore = w;
@best = potential[i];
}
}
potential.length = 0;
scores.length = 0;
if(best !is null)
choose(ai, best);
}
else if(designer !is null && active is null) {
//Add a new design onto the list to be evaluated
const Design@ dsg = design(ai, designs);
if(dsg !is null && !dsg.hasFatalErrors()) {
potential.insertLast(dsg);
scores.insertLast(evaluate(ai, dsg));
/*if(designs.log)*/
/* ai.print("Designed for purpose "+uint(purpose)+" at size "+dsg.size+", weight "+evaluate(ai, dsg));*/
}
}
}
else {
set(active.mostUpdated());
}
}
void set(const Design@ dsg) {
if(active is dsg)
return;
@active = dsg;
targetBuildCost = dsg.total(HV_BuildCost);
targetMaintenance = dsg.total(HV_MaintainCost);
targetLaborCost = dsg.total(HV_LaborCost);
targetSize = dsg.size;
dps = dsg.total(SV_DPS);
hp = dsg.totalHP + dsg.total(SV_ShieldCapacity);
supplyDrain = dsg.total(SV_SupplyDrain);
}
};
const Design@ scaleDesign(const Design@ orig, int newSize) {
DesignDescriptor desc;
resizeDesign(orig, newSize, desc);
return makeDesign(desc);
}
final class Designs : AIComponent {
RaceDesigns@ race;
int nextTargetId = 0;
array<DesignTarget@> designing;
array<DesignTarget@> completed;
array<DesignTarget@> automatic;
void create() {
@race = cast<RaceDesigns>(ai.race);
}
void start() {
//Design some basic support sizes
design(DP_Support, 1);
design(DP_Support, 2);
design(DP_Support, 4);
design(DP_Support, 8);
design(DP_Support, 16);
}
void save(SaveFile& file) {
file << nextTargetId;
uint cnt = designing.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveDesign(file, designing[i]);
designing[i].save(this, file);
}
cnt = automatic.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveDesign(file, automatic[i]);
if(!isDesigning(automatic[i])) {
file.write1();
automatic[i].save(this, file);
}
else {
file.write0();
}
}
cnt = completed.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveDesign(file, completed[i]);
if(!isDesigning(completed[i])) {
file.write1();
completed[i].save(this, file);
}
else {
file.write0();
}
}
}
bool isDesigning(DesignTarget@ targ) {
for(uint i = 0, cnt = designing.length; i < cnt; ++i) {
if(designing[i] is targ)
return true;
}
return false;
}
void load(SaveFile& file) {
file >> nextTargetId;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ targ = loadDesign(file);
targ.load(this, file);
designing.insertLast(targ);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ targ = loadDesign(file);
if(file.readBit())
targ.load(this, file);
automatic.insertLast(targ);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ targ = loadDesign(file);
if(file.readBit())
targ.load(this, file);
completed.insertLast(targ);
}
}
array<DesignTarget@> loadIds;
DesignTarget@ loadDesign(int id) {
if(id == -1)
return null;
for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) {
if(loadIds[i].id == id)
return loadIds[i];
}
DesignTarget data;
data.id = id;
loadIds.insertLast(data);
return data;
}
DesignTarget@ loadDesign(SaveFile& file) {
int id = -1;
file >> id;
if(id == -1)
return null;
else
return loadDesign(id);
}
void saveDesign(SaveFile& file, DesignTarget@ data) {
int id = -1;
if(data !is null)
id = data.id;
file << id;
}
void postLoad(AI& ai) {
loadIds.length = 0;
}
const Design@ get_currentSupport() {
for(int i = automatic.length - 1; i >= 0; --i) {
if(automatic[i].purpose == DP_Support && automatic[i].active !is null)
return automatic[i].active;
}
return null;
}
void getSupportAverages(double& hp, double& dps, double& supDrain) {
hp = 0;
dps = 0;
supDrain = 0;
uint count = 0;
for(uint i = 0, cnt = automatic.length; i < cnt; ++i) {
auto@ targ = automatic[i];
if(targ.purpose != DP_Support)
continue;
if(targ.active is null)
continue;
hp += targ.hp / double(targ.targetSize);
dps += targ.dps / double(targ.targetSize);
supDrain += targ.supplyDrain / double(targ.targetSize);
count += 1;
}
if(count == 0) {
hp = 40.0;
dps = 0.30;
supDrain = 1.0;
}
else {
hp /= double(count);
dps /= double(count);
supDrain /= double(count);
}
}
DesignPurpose classify(Object@ obj) {
if(obj is null || !obj.isShip)
return DP_Combat;
Ship@ ship = cast<Ship>(obj);
return classify(ship.blueprint.design);
}
DesignPurpose classify(const Design@ dsg, DesignPurpose defaultPurpose = DP_Combat) {
if(dsg is null)
return defaultPurpose;
for(uint i = 0, cnt = automatic.length; i < cnt; ++i) {
if(automatic[i].contains(dsg))
return DesignPurpose(automatic[i].purpose);
}
for(uint i = 0, cnt = completed.length; i < cnt; ++i) {
if(completed[i].contains(dsg))
return DesignPurpose(completed[i].purpose);
}
if(dsg.hasTag(ST_Mothership))
return DP_Mothership;
if(dsg.hasTag(ST_Gate))
return DP_Gate;
if(dsg.hasTag(ST_Slipstream))
return DP_Slipstream;
if(dsg.hasTag(ST_Support))
return DP_Support;
if(dsg.hasTag(ST_Station))
return DP_Defense;
double dps = dsg.total(SV_DPS);
if(dsg.total(SV_MiningRate) > 0)
return DP_Miner;
if(dsg.size == 16.0 && dsg.total(SV_DPS) < 2.0)
return DP_Scout;
if(dps > 0.1 * dsg.size || dsg.total(SV_SupportCapacity) > 0)
return DP_Combat;
return defaultPurpose;
}
DesignTarget@ design(uint purpose, int size, int targetCost = 0, int targetMaint = 0, double targetLabor = 0, bool findSize = false) {
for(uint i = 0, cnt = automatic.length; i < cnt; ++i) {
auto@ target = automatic[i];
if(target.purpose != purpose)
continue;
if(target.targetSize != size)
continue;
if(targetCost != 0 && target.targetBuildCost > targetCost)
continue;
if(targetMaint != 0 && target.targetMaintenance > targetMaint)
continue;
if(targetLabor != 0 && target.targetLaborCost > targetLabor)
continue;
if(target.findSize != findSize)
continue;
return target;
}
DesignTarget targ(purpose, size);
targ.findSize = findSize;
targ.targetBuildCost = targetCost;
targ.targetMaintenance = targetMaint;
targ.targetLaborCost = targetLabor;
automatic.insertLast(targ);
return design(targ);
}
DesignTarget@ design(DesignTarget@ target) {
target.id = nextTargetId++;
designing.insertLast(target);
return target;
}
DesignTarget@ get(const Design@ dsg) {
for(uint i = 0, cnt = automatic.length; i < cnt; ++i) {
if(automatic[i].contains(dsg))
return automatic[i];
}
return null;
}
DesignTarget@ scale(const Design@ dsg, int newSize) {
if(dsg.newer !is null) {
auto@ newTarg = get(dsg.newest());
if(newTarg.targetSize == newSize)
return newTarg;
@dsg = dsg.newest();
}
DesignTarget@ previous = get(dsg);
uint purpose = DP_Combat;
if(previous !is null)
purpose = previous.purpose;
else
purpose = classify(dsg);
DesignTarget target(purpose, newSize);
target.id = nextTargetId++;
@target.active = scaleDesign(dsg, newSize);
ai.empire.changeDesign(dsg, target.active, ai.empire.getDesignClass(dsg.cls.name, true));
if(previous !is null)
automatic.remove(previous);
automatic.insertLast(target);
return target;
}
uint chkInd = 0;
void tick(double time) {
if(designing.length != 0) {
//chkInd = (chkInd+1) % designing.length;
// Getting 1 design first is better than getting all of them later
chkInd = 0;
auto@ target = designing[chkInd];
target.step(ai, this);
if(target.active !is null) {
designing.removeAt(chkInd);
if(automatic.find(target) == -1)
completed.insertLast(target);
}
}
}
};
AIComponent@ createDesigns() {
return Designs();
}
@@ -0,0 +1,764 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Colonization;
import empire_ai.weasel.Systems;
import planet_levels;
import buildings;
import ai.consider;
from ai.buildings import Buildings, BuildingAI, BuildingUse;
from ai.resources import AIResources, ResourceAI;
interface RaceDevelopment {
bool shouldBeFocus(Planet& pl, const ResourceType@ resource);
};
class DevelopmentFocus {
Object@ obj;
PlanetAI@ plAI;
int targetLevel = 0;
int requestedLevel = 0;
int maximumLevel = INT_MAX;
array<ExportData@> managedPressure;
double weight = 1.0;
void tick(AI& ai, Development& dev, double time) {
if(targetLevel != requestedLevel) {
if(targetLevel > requestedLevel) {
int nextLevel = min(targetLevel, min(obj.resourceLevel, requestedLevel)+1);
if(nextLevel != requestedLevel) {
for(int i = requestedLevel+1; i <= nextLevel; ++i)
dev.resources.organizeImports(obj, i);
requestedLevel = nextLevel;
}
}
else {
dev.resources.organizeImports(obj, targetLevel);
requestedLevel = targetLevel;
}
}
//Remove managed pressure resources that are no longer valid
for(uint i = 0, cnt = managedPressure.length; i < cnt; ++i) {
ExportData@ res = managedPressure[i];
if(res.request !is null || res.obj is null || !res.obj.valid || res.obj.owner !is ai.empire || !res.usable || res.developUse !is obj) {
if(res.developUse is obj)
@res.developUse = null;
managedPressure.removeAt(i);
--i; --cnt;
}
}
//Make sure we're not exporting our resource
if(plAI !is null && plAI.resources !is null && plAI.resources.length != 0) {
auto@ res = plAI.resources[0];
res.localOnly = true;
if(res.request !is null && res.request.obj !is res.obj)
dev.resources.breakImport(res);
}
//TODO: We should be able to bump managed pressure resources back to Development for
//redistribution if we run out of pressure capacity.
}
void save(Development& development, SaveFile& file) {
file << obj;
development.planets.saveAI(file, plAI);
file << targetLevel;
file << requestedLevel;
file << maximumLevel;
file << weight;
uint cnt = managedPressure.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
development.resources.saveExport(file, managedPressure[i]);
}
void load(Development& development, SaveFile& file) {
file >> obj;
@plAI = development.planets.loadAI(file);
file >> targetLevel;
file >> requestedLevel;
file >> maximumLevel;
file >> weight;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = development.resources.loadExport(file);
managedPressure.insertLast(data);
}
}
};
class Development : AIComponent, Buildings, ConsiderFilter, AIResources {
RaceDevelopment@ race;
Planets@ planets;
Resources@ resources;
Colonization@ colonization;
Systems@ systems;
array<DevelopmentFocus@> focuses;
array<ExportData@> managedPressure;
array<ColonizeData@> pendingFocuses;
array<ColonizeData@> pendingResources;
array<BuildingRequest@> genericBuilds;
array<ExportData@> aiResources;
double aimFTLStorage = 0.0;
bool managePlanetPressure = true;
bool manageAsteroidPressure = true;
bool buildBuildings = true;
bool colonizeResources = true;
void create() {
@planets = cast<Planets>(ai.planets);
@resources = cast<Resources>(ai.resources);
@colonization = cast<Colonization>(ai.colonization);
@systems = cast<Systems>(ai.systems);
@race = cast<RaceDevelopment>(ai.race);
//Register specialized building types
for(uint i = 0, cnt = getBuildingTypeCount(); i < cnt; ++i) {
auto@ type = getBuildingType(i);
for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) {
auto@ hook = cast<BuildingAI>(type.ai[n]);
if(hook !is null)
hook.register(this, type);
}
}
}
Empire@ get_empire() {
return ai.empire;
}
Considerer@ get_consider() {
return cast<Considerer>(ai.consider);
}
void registerUse(BuildingUse use, const BuildingType& type) {
switch(use) {
case BU_Factory:
@ai.defs.Factory = type;
break;
case BU_LaborStorage:
@ai.defs.LaborStorage = type;
break;
}
}
void save(SaveFile& file) {
file << aimFTLStorage;
uint cnt = focuses.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ focus = focuses[i];
focus.save(this, file);
}
cnt = managedPressure.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
resources.saveExport(file, managedPressure[i]);
cnt = pendingFocuses.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
colonization.saveColonize(file, pendingFocuses[i]);
cnt = pendingResources.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
colonization.saveColonize(file, pendingResources[i]);
cnt = genericBuilds.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
planets.saveBuildingRequest(file, genericBuilds[i]);
cnt = aiResources.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
resources.saveExport(file, aiResources[i]);
}
void load(SaveFile& file) {
file >> aimFTLStorage;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ focus = DevelopmentFocus();
focus.load(this, file);
if(focus.obj !is null)
focuses.insertLast(focus);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = resources.loadExport(file);
if(data !is null)
managedPressure.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = colonization.loadColonize(file);
if(data !is null)
pendingFocuses.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = colonization.loadColonize(file);
if(data !is null)
pendingResources.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = planets.loadBuildingRequest(file);
if(data !is null)
genericBuilds.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = resources.loadExport(file);
if(data !is null)
aiResources.insertLast(data);
}
}
bool requestsFTLStorage() {
double capacity = ai.empire.FTLCapacity;
if(aimFTLStorage <= capacity)
return false;
if(ai.empire.FTLStored < capacity * 0.5)
return false;
return true;
}
bool isBuilding(const BuildingType& type) {
for(uint i = 0, cnt = genericBuilds.length; i < cnt; ++i) {
if(genericBuilds[i].type is type)
return true;
}
return false;
}
bool isLeveling() {
for(uint i = 0, cnt = focuses.length; i < cnt; ++i) {
if(focuses[i].obj.resourceLevel < uint(focuses[i].targetLevel)) {
auto@ focus = focuses[i].obj;
//If all our requirements are resolved, then we can safely assume it will be leveled up
bool allResolved = true;
for(uint n = 0, ncnt = resources.requested.length; n < ncnt; ++n) {
auto@ req = resources.requested[n];
if(req.obj !is focus)
continue;
if(req.beingMet)
continue;
if(!req.isColonizing) {
allResolved = false;
break;
}
if(!colonization.isResolved(req)) {
allResolved = false;
break;
}
}
if(!allResolved)
return true;
}
}
return false;
}
bool isBusy() {
if(pendingFocuses.length != 0)
return true;
if(pendingResources.length != 0)
return true;
if(isLeveling())
return true;
return false;
}
bool isFocus(Object@ obj) {
for(uint i = 0, cnt = focuses.length; i < cnt; ++i) {
if(focuses[i].obj is obj)
return true;
}
return false;
}
bool isManaging(ExportData@ res) {
for(uint i = 0, cnt = managedPressure.length; i < cnt; ++i) {
if(managedPressure[i] is res)
return true;
}
for(uint i = 0, cnt = aiResources.length; i < cnt; ++i) {
if(aiResources[i] is res)
return true;
}
for(uint n = 0, ncnt = focuses.length; n < ncnt; ++n) {
auto@ f = focuses[n];
if(f.obj is res.obj)
return true;
for(uint i = 0, cnt = f.managedPressure.length; i < cnt; ++i) {
if(f.managedPressure[i] is res)
return true;
}
}
return false;
}
bool isDevelopingIn(Region@ reg) {
if(reg is null)
return false;
for(uint i = 0, cnt = focuses.length; i < cnt; ++i) {
if(focuses[i].obj.region is reg)
return true;
}
return false;
}
void start() {
//Level up the homeworld to level 3 to start with
for(uint i = 0, cnt = ai.empire.planetCount; i < cnt; ++i) {
Planet@ homeworld = ai.empire.planetList[i];
if(homeworld !is null && homeworld.valid) {
auto@ hwFocus = addFocus(planets.register(homeworld));
if(homeworld.nativeResourceCount >= 2 || homeworld.primaryResourceLimitLevel >= 3 || cnt == 1)
hwFocus.targetLevel = 3;
}
}
}
double idlePenalty = 0;
void findSomethingToDo() {
if(idlePenalty > gameTime)
return;
double totalChance =
ai.behavior.focusDevelopWeight
+ ai.behavior.focusColonizeNewWeight * sqr(1.0 / double(focuses.length))
+ ai.behavior.focusColonizeHighTierWeight;
double roll = randomd(0.0, totalChance);
//Level up one of our existing focuses
roll -= ai.behavior.focusDevelopWeight;
if(roll <= 0) {
DevelopmentFocus@ levelup;
double totalWeight = 0.0;
for(uint i = 0, cnt = focuses.length; i < cnt; ++i) {
auto@ f = focuses[i];
if(f.weight == 0)
continue;
if(f.targetLevel >= f.maximumLevel)
continue;
totalWeight += f.weight;
if(randomd() < f.weight / totalWeight)
@levelup = f;
}
if(levelup !is null) {
levelup.targetLevel += 1;
if(log)
ai.print("Develop chose to level this up to "+levelup.targetLevel, levelup.obj);
return;
}
else {
if(log)
ai.print("Develop ran out of things to level up.");
}
}
if(!colonizeResources)
return;
//Find a scalable or high tier resource to colonize and turn into a focus
roll -= ai.behavior.focusColonizeNewWeight * sqr(1.0 / double(focuses.length));
if(roll <= 0) {
Planet@ newFocus;
double totalWeight = 0.0;
for(uint i = 0, cnt = colonization.potentials.length; i < cnt; ++i) {
auto@ p = colonization.potentials[i];
if(p.resource.level < 3 && p.resource.cls !is colonization.scalableClass)
continue;
Region@ reg = p.pl.region;
if(reg is null)
continue;
if(colonization.isColonizing(p.pl))
continue;
vec2i surfaceSize = p.pl.surfaceGridSize;
int tiles = surfaceSize.width * surfaceSize.height;
if(tiles < 144)
continue;
auto@ sys = systems.getAI(reg);
double w = 1.0;
if(sys.border)
w *= 0.25;
if(sys.obj.PlanetsMask & ~ai.mask != 0)
w *= 0.25;
if(p.resource.cls is colonization.scalableClass)
w *= 10.0;
totalWeight += w;
if(randomd() < w / totalWeight)
@newFocus = p.pl;
}
if(newFocus !is null) {
auto@ data = colonization.colonize(newFocus);
if(data !is null)
pendingFocuses.insertLast(data);
if(log)
ai.print("Colonize to become develop focus", data.target);
return;
}
else {
if(log)
ai.print("Develop could not find a scalable or high tier resource to make a focus.");
}
}
if(focuses.length == 0)
return;
//Find a high tier resource to import to one of our focuses
roll -= ai.behavior.focusColonizeHighTierWeight;
if(roll <= 0) {
ResourceSpec spec;
spec.type = RST_Level_Minimum;
spec.level = 3;
spec.isLevelRequirement = false;
auto@ data = colonization.colonize(spec);
if(data !is null) {
if(log)
ai.print("Colonize as free resource", data.target);
pendingResources.insertLast(data);
return;
}
else {
if(log)
ai.print("Develop could not find a high tier resource to colonize as free resource.");
}
}
//Try to find a level 2 resource if everything else failed
{
ResourceSpec spec;
spec.type = RST_Level_Minimum;
spec.level = 2;
spec.isLevelRequirement = false;
if(colonization.shouldQueueFor(spec)) {
auto@ data = colonization.colonize(spec);
if(data !is null) {
if(log)
ai.print("Colonize as free resource", data.target);
pendingResources.insertLast(data);
return;
}
else {
if(log)
ai.print("Develop could not find a level 2 resource to colonize as free resource.");
}
}
}
idlePenalty = gameTime + randomd(10.0, 40.0);
}
uint bldIndex = 0;
uint aiInd = 0;
uint presInd = 0;
uint chkInd = 0;
void focusTick(double time) override {
//Remove any resources we're managing that got used
for(uint i = 0, cnt = managedPressure.length; i < cnt; ++i) {
ExportData@ res = managedPressure[i];
if(res.request !is null || res.obj is null || !res.obj.valid || res.obj.owner !is ai.empire || !res.usable) {
managedPressure.removeAt(i);
--i; --cnt;
}
}
//Find new resources that we can put in our pressure manager
uint avCnt = resources.available.length;
if(avCnt != 0) {
uint index = randomi(0, avCnt-1);
for(uint i = 0, cnt = min(avCnt, 3); i < cnt; ++i) {
uint resInd = (index+i) % avCnt;
ExportData@ res = resources.available[resInd];
if(res.usable && res.request is null && res.obj !is null && res.obj.valid && res.obj.owner is ai.empire && res.developUse is null) {
if(res.resource.ai.length != 0) {
if(!isManaging(res))
aiResources.insertLast(res);
}
else if(res.resource.totalPressure > 0 && res.resource.exportable) {
if(!managePlanetPressure && res.obj.isPlanet)
continue;
if(!manageAsteroidPressure && res.obj.isAsteroid)
continue;
if(!isManaging(res))
managedPressure.insertLast(res);
}
}
}
}
//Distribute managed pressure resources
if(managedPressure.length != 0) {
presInd = (presInd+1) % managedPressure.length;
ExportData@ res = managedPressure[presInd];
int pressure = res.resource.totalPressure;
DevelopmentFocus@ onFocus;
double bestWeight = 0;
bool havePressure = ai.empire.HasPressure != 0.0;
for(uint i = 0, cnt = focuses.length; i < cnt; ++i) {
auto@ f = focuses[i];
int cap = f.obj.pressureCap;
if(!havePressure)
cap = 10000;
int cur = f.obj.totalPressure;
if(cur + pressure > 2 * cap)
continue;
double w = 1.0;
if(cur + pressure > cap)
w *= 0.1;
if(w > bestWeight) {
bestWeight = w;
@onFocus = f;
}
}
if(onFocus !is null) {
if(res.obj !is onFocus.obj)
res.obj.exportResourceByID(res.resourceId, onFocus.obj);
else
res.obj.exportResourceByID(res.resourceId, null);
@res.developUse = onFocus.obj;
onFocus.managedPressure.insertLast(res);
managedPressure.removeAt(presInd);
if(log)
ai.print("Take "+res.resource.name+" from "+res.obj.name+" for pressure", onFocus.obj);
}
}
//Use generic AI distribution hooks
if(aiResources.length != 0) {
aiInd = (aiInd+1) % aiResources.length;
ExportData@ res = aiResources[aiInd];
if(res.request !is null || res.obj is null || !res.obj.valid || res.obj.owner !is ai.empire || !res.usable) {
aiResources.removeAt(aiInd);
}
else {
Object@ newTarget = res.developUse;
if(newTarget !is null) {
if(!newTarget.valid || newTarget.owner !is ai.empire)
@newTarget = null;
}
for(uint i = 0, cnt = res.resource.ai.length; i < cnt; ++i) {
auto@ hook = cast<ResourceAI>(res.resource.ai[i]);
if(hook !is null)
@newTarget = hook.distribute(this, res.resource, newTarget);
}
if(newTarget !is res.developUse) {
if(res.obj !is newTarget)
res.obj.exportResourceByID(res.resourceId, newTarget);
else
res.obj.exportResourceByID(res.resourceId, null);
@res.developUse = newTarget;
}
}
}
//Deal with focuses we're colonizing
for(uint i = 0, cnt = pendingFocuses.length; i < cnt; ++i) {
auto@ data = pendingFocuses[i];
if(data.completed) {
auto@ focus = addFocus(planets.register(data.target));
focus.targetLevel = 3;
pendingFocuses.removeAt(i);
--i; --cnt;
}
else if(data.canceled) {
pendingFocuses.removeAt(i);
--i; --cnt;
}
}
for(uint i = 0, cnt = pendingResources.length; i < cnt; ++i) {
auto@ data = pendingResources[i];
if(data.completed) {
planets.requestLevel(planets.register(data.target), data.target.primaryResourceLevel);
pendingResources.removeAt(i);
--i; --cnt;
}
else if(data.canceled) {
pendingResources.removeAt(i);
--i; --cnt;
}
}
//If we're not currently leveling something up, find something else to do
if(!isBusy())
findSomethingToDo();
//Deal with building AI hints
for(uint i = 0, cnt = genericBuilds.length; i < cnt; ++i) {
auto@ build = genericBuilds[i];
if(build.canceled) {
genericBuilds.removeAt(i);
--i; --cnt;
}
else if(build.built) {
if(build.getProgress() >= 1.f) {
if(build.expires < gameTime) {
genericBuilds.removeAt(i);
--i; --cnt;
}
}
else
build.expires = gameTime + 60.0;
}
}
if(buildBuildings) {
for(uint i = 0, cnt = getBuildingTypeCount(); i < cnt; ++i) {
bldIndex = (bldIndex+1) % cnt;
auto@ type = getBuildingType(bldIndex);
if(type.ai.length == 0)
continue;
//If we're already generically building something of this type, wait
bool existing = false;
for(uint n = 0, ncnt = genericBuilds.length; n < ncnt; ++n) {
auto@ build = genericBuilds[n];
if(build.type is type && !build.built) {
existing = true;
break;
}
}
if(existing)
break;
@filterType = type;
@consider.filter = this;
//See if we should generically build something of this type
for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) {
auto@ hook = cast<BuildingAI>(type.ai[n]);
if(hook !is null) {
Object@ buildOn = hook.considerBuild(this, type);
if(buildOn !is null && buildOn.isPlanet) {
auto@ plAI = planets.getAI(cast<Planet>(buildOn));
if(plAI !is null) {
if(log)
ai.print("AI hook generically requested building of type "+type.name, buildOn);
auto@ req = planets.requestBuilding(plAI, type, expire=ai.behavior.genericBuildExpire);
if(req !is null)
genericBuilds.insertLast(req);
break;
}
}
}
}
break;
}
}
//Find planets we've acquired 'somehow' that have scalable resources and should be development focuses
if(planets.planets.length != 0) {
chkInd = (chkInd+1) % planets.planets.length;
auto@ plAI = planets.planets[chkInd];
if(plAI.resources.length != 0) {
auto@ res = plAI.resources[0];
if(res.resource.cls is colonization.scalableClass
|| focuses.length == 0 && res.resource.level >= 2
|| (race !is null && race.shouldBeFocus(plAI.obj, res.resource))) {
if(!isFocus(plAI.obj)) {
auto@ focus = addFocus(plAI);
focus.targetLevel = max(1, res.resource.level);
}
}
}
}
}
DevelopmentFocus@ addFocus(PlanetAI@ plAI) {
DevelopmentFocus focus;
@focus.obj = plAI.obj;
@focus.plAI = plAI;
focus.maximumLevel = getMaxPlanetLevel(plAI.obj);
focuses.insertLast(focus);
return focus;
}
DevelopmentFocus@ getFocus(Planet& pl) {
for(uint i = 0, cnt = focuses.length; i < cnt; ++i) {
if(focuses[i].obj is pl)
return focuses[i];
}
return null;
}
void tick(double time) override {
for(uint i = 0, cnt = focuses.length; i < cnt; ++i)
focuses[i].tick(ai, this, time);
}
const BuildingType@ filterType;
bool filter(Object@ obj) {
for(uint i = 0, cnt = genericBuilds.length; i < cnt; ++i) {
auto@ build = genericBuilds[i];
if(build.type is filterType && build.plAI.obj is obj)
return false;
}
return true;
}
};
AIComponent@ createDevelopment() {
return Development();
}
@@ -0,0 +1,395 @@
// Diplomacy
// ---------
// Acts as an adaptor for using the generically developed DiplomacyAI.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Development;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Resources;
import empire_ai.weasel.War;
import empire_ai.weasel.Intelligence;
import influence;
from empire_ai.DiplomacyAI import DiplomacyAI, VoteData, CardAI, VoteState;
import systems;
class Diplomacy : DiplomacyAI, IAIComponent {
Systems@ systems;
Fleets@ fleets;
Planets@ planets;
Construction@ construction;
Development@ development;
Resources@ resources;
War@ war;
Intelligence@ intelligence;
//Adapt to AI component
AI@ ai;
double prevFocus = 0;
bool logCritical = false;
bool logErrors = true;
double getPrevFocus() { return prevFocus; }
void setPrevFocus(double value) { prevFocus = value; }
void setLog() { log = true; }
void setLogCritical() { logCritical = true; }
void set(AI& ai) { @this.ai = ai; }
void start() {}
void tick(double time) {}
void turn() {}
void postLoad(AI& ai) {}
void postSave(AI& ai) {}
void loadFinalize(AI& ai) {}
//Actual AI component implementations
void create() {
@systems = cast<Systems>(ai.systems);
@fleets = cast<Fleets>(ai.fleets);
@planets = cast<Planets>(ai.planets);
@development = cast<Development>(ai.development);
@construction = cast<Construction>(ai.construction);
@resources = cast<Resources>(ai.resources);
@war = cast<War>(ai.war);
@intelligence = cast<Intelligence>(ai.intelligence);
}
//IMPLEMENTED BY DiplomacyAI
/*void save(SaveFile& file) {}*/
/*void load(SaveFile& file) {}*/
uint nextStep = 0;
void focusTick(double time) {
summarize();
switch(nextStep++ % 3) {
case 0:
buyCards();
break;
case 1:
considerActions();
break;
case 2:
considerVotes();
break;
}
}
//Adapt to diplomacy AI
Empire@ get_empire() {
return ai.empire;
}
uint get_allyMask() {
return ai.allyMask;
}
int getStanding(Empire@ emp) {
//TODO: Use relations module for this generically
if(emp.isHostile(ai.empire))
return -50;
if(ai.allyMask & emp.mask != 0)
return 100;
return 0;
}
void print(const string& str) {
ai.print(str);
}
Object@ considerImportantPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
Object@ obj = development.focuses[i].obj;
double w = hook.consider(this, targets, vote, card, obj);
if(w > bestWeight) {
@best = obj;
bestWeight = w;
}
}
return best;
}
Object@ considerOwnedPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
//Consider our important ones first
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
Object@ obj = development.focuses[i].obj;
double w = hook.consider(this, targets, vote, card, obj);
if(w > bestWeight) {
@best = obj;
bestWeight = w;
}
}
//Consider some random other ones
uint planetCount = planets.planets.length;
if(planetCount != 0) {
uint offset = randomi(0, planetCount-1);
for(uint n = 0; n < 5; ++n) {
Object@ obj = planets.planets[(offset+n) % planetCount].obj;
double w = hook.consider(this, targets, vote, card, obj);
if(w > bestWeight) {
@best = obj;
bestWeight = w;
}
}
}
return best;
}
Object@ considerImportantSystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
Object@ obj = development.focuses[i].obj.region;
if(obj is null)
continue;
double w = hook.consider(this, targets, vote, card, obj);
if(w > bestWeight) {
@best = obj;
bestWeight = w;
}
}
return best;
}
Object@ considerOwnedSystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
//Consider our important ones first
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
Object@ obj = development.focuses[i].obj.region;
if(obj is null)
continue;
double w = hook.consider(this, targets, vote, card, obj);
if(w > bestWeight) {
@best = obj;
bestWeight = w;
}
}
//Consider some random other ones
uint sysCount = systems.owned.length;
if(sysCount != 0) {
uint offset = randomi(0, sysCount-1);
for(uint n = 0; n < 5; ++n) {
Object@ obj = systems.owned[(offset+n) % sysCount].obj;
double w = hook.consider(this, targets, vote, card, obj);
if(w > bestWeight) {
@best = obj;
bestWeight = w;
}
}
}
return best;
}
Object@ considerDefendingSystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = war.battles.length; i < cnt; ++i) {
auto@ battle = war.battles[i];
Region@ sys = battle.system.obj;
if(sys.SiegedMask & empire.mask == 0)
continue;
double w = hook.consider(this, targets, vote, card, sys);
if(w > bestWeight) {
@best = sys;
bestWeight = w;
}
}
return best;
}
Object@ considerDefendingPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = war.battles.length; i < cnt; ++i) {
auto@ battle = war.battles[i];
Region@ sys = battle.system.obj;
if(sys.SiegedMask & empire.mask == 0)
continue;
for(uint n = 0, ncnt = battle.system.planets.length; n < ncnt; ++n) {
Object@ pl = battle.system.planets[n];
if(pl.owner !is empire)
continue;
double w = hook.consider(this, targets, vote, card, pl);
if(w > bestWeight) {
@best = pl;
bestWeight = w;
}
}
}
return best;
}
Object@ considerEnemySystems(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major)
continue;
if(!empire.isHostile(emp))
continue;
auto@ intel = intelligence.get(emp);
if(intel is null)
continue;
for(uint n = 0, ncnt = intel.theirBorder.length; n < ncnt; ++n) {
auto@ sysIntel = intel.theirBorder[n];
double w = hook.consider(this, targets, vote, card, sysIntel.obj);
if(w > bestWeight) {
@best = sysIntel.obj;
bestWeight = w;
}
}
}
return best;
}
Object@ considerEnemyPlanets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major)
continue;
if(!empire.isHostile(emp))
continue;
auto@ intel = intelligence.get(emp);
if(intel is null)
continue;
for(uint n = 0, ncnt = intel.theirBorder.length; n < ncnt; ++n) {
auto@ sysIntel = intel.theirBorder[n];
for(uint j = 0, jcnt = sysIntel.planets.length; j < jcnt; ++j) {
Planet@ pl = sysIntel.planets[j];
if(pl.visibleOwnerToEmp(empire) !is emp)
continue;
double w = hook.consider(this, targets, vote, card, pl);
if(w > bestWeight) {
@best = pl;
bestWeight = w;
}
}
}
}
return best;
}
Object@ considerFleets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
Object@ best;
double bestWeight = 0.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
Object@ fleet = fleets.fleets[i].obj;
if(fleet !is null) {
double w = hook.consider(this, targets, vote, card, fleet);
if(w > bestWeight) {
@best = fleet;
bestWeight = w;
}
}
}
return best;
}
Object@ considerEnemyFleets(const CardAI& hook, Targets& targets, VoteState@ vote = null, const InfluenceCard@ card = null) {
double bestWeight = 0.0;
Object@ best;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major)
continue;
if(!empire.isHostile(emp))
continue;
auto@ intel = intelligence.get(emp);
if(intel is null)
continue;
for(uint n = 0, ncnt = intel.fleets.length; n < ncnt; ++n) {
auto@ flIntel = intel.fleets[n];
if(!flIntel.known)
continue;
double w = hook.consider(this, targets, vote, card, flIntel.obj);
if(w > bestWeight) {
@best = flIntel.obj;
bestWeight = w;
}
}
}
return best;
}
Object@ considerMatchingImportRequests(const CardAI& hook, Targets& targets, VoteState@ vote, const InfluenceCard@ card, const ResourceType@ type, bool considerExisting) {
Object@ best;
double bestWeight = 0.0;
for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) {
ImportData@ req = resources.requested[i];
if(req.spec.meets(type)) {
double w = hook.consider(this, targets, vote, card, req.obj, null);
if(w > bestWeight) {
bestWeight = w;
@best = req.obj;
}
}
}
if(considerExisting) {
for(uint i = 0, cnt = resources.used.length; i < cnt; ++i) {
ExportData@ res = resources.used[i];
ImportData@ req = res.request;
if(req !is null && req.spec.meets(type)) {
double w = hook.consider(this, targets, vote, card, req.obj, res.obj);
if(w > bestWeight) {
bestWeight = w;
@best = req.obj;
}
}
}
}
return best;
}
};
IAIComponent@ createDiplomacy() {
return Diplomacy();
}
+380
View File
@@ -0,0 +1,380 @@
// Energy
// ------
// Manage the use of energy on artifacts and other things.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Systems;
import ai.consider;
import artifacts;
import abilities;
import systems;
from ai.artifacts import Artifacts, ArtifactConsider, ArtifactAI;
double effCostEstimate(double cost, double freeStorage) {
double free = min(cost, freeStorage);
cost -= free;
double effStep = config::ENERGY_EFFICIENCY_STEP;
double eff = 0.0;
double step = 1.0;
while(cost > 0) {
eff += (cost / effStep) * step;
cost -= effStep;
step *= 2.0;
}
return eff * effStep + free;
}
class ConsiderEnergy : ArtifactConsider {
int id = -1;
const ArtifactType@ type;
Artifact@ artifact;
Ability@ ability;
Object@ target;
vec3d pointTarget;
double cost = 0.0;
double value = 0.0;
void save(AI& ai, SaveFile& file) {
file << id;
file << artifact;
file << target;
file << cost;
file << value;
file << pointTarget;
}
void load(AI& ai, SaveFile& file) {
file >> id;
file >> artifact;
file >> target;
file >> cost;
file >> value;
file >> pointTarget;
if(artifact !is null)
init(ai, artifact);
}
void setTarget(Object@ obj) {
@target = obj;
}
Object@ getTarget() {
return target;
}
bool canTarget(Object@ obj) {
if(ability.targets.length != 0) {
auto@ targ = ability.targets[0];
@targ.obj = obj;
targ.filled = true;
return ability.isValidTarget(0, targ);
}
else
return false;
}
void setTargetPosition(const vec3d& point) {
pointTarget = point;
}
vec3d getTargetPosition() {
return pointTarget;
}
bool canTargetPosition(const vec3d& point) {
if(ability.targets.length != 0) {
auto@ targ = ability.targets[0];
targ.point = point;
targ.filled = true;
return ability.isValidTarget(0, targ);
}
else
return false;
}
void init(AI& ai, Artifact@ artifact) {
@this.artifact = artifact;
@type = getArtifactType(artifact.ArtifactType);
if(ability is null)
@ability = Ability();
if(type.secondaryChance > 0 && type.abilities.length >= 2
&& randomd() < type.secondaryChance) {
ability.id = 1;
@ability.type = type.abilities[1];
}
else {
ability.id = 0;
@ability.type = type.abilities[0];
}
ability.targets = Targets(ability.type.targets);
@ability.obj = artifact;
@ability.emp = ai.empire;
}
bool isValid(AI& ai, Energy& energy) {
return energy.canUse(artifact);
}
void considerEnergy(AI& ai, Energy& energy) {
if(type !is null && type.abilities.length != 0) {
value = 1.0;
for(uint i = 0, cnt = type.ai.length; i < cnt; ++i) {
ArtifactAI@ ai;
if(ability.id == 0)
@ai = cast<ArtifactAI>(type.ai[i]);
else
@ai = cast<ArtifactAI>(type.secondaryAI[i]);
if(ai !is null) {
if(!ai.consider(energy, this, value)) {
value = 0.0;
break;
}
}
}
if(type.ai.length == 0)
value = 0.0;
if(ability.targets.length != 0) {
if(ability.targets[0].type == TT_Object) {
@ability.targets[0].obj = target;
ability.targets[0].filled = true;
if(target is null)
value = 0.0;
}
else if(ability.targets[0].type == TT_Point) {
ability.targets[0].point = pointTarget;
ability.targets[0].filled = true;
}
}
if(value > 0.0) {
if(!ability.canActivate(ability.targets, ignoreCost=true)) {
value = 0.0;
}
else {
cost = ability.getEnergyCost(ability.targets);
if(cost != 0.0) {
//Estimate the amount of turns it would take to trigger this,
//and devalue it based on that. This is ceiled in order to allow
//for artifacts of similar cost to not be affected by cost differences.
double effCost = effCostEstimate(cost, energy.freeStorage);
double estTime = effCost / max(energy.baseIncome, 0.01);
double turns = ceil(estTime / (3.0 * 60.0));
value /= turns;
}
else {
value *= 1000.0;
}
}
}
}
else {
value = 0.0;
}
}
void execute(AI& ai, Energy& energy) {
if(artifact !is null && type.abilities.length != 0) {
if(energy.log)
ai.print("Activate artifact "+artifact.name, artifact.region);
if(ability.type.targets.length != 0) {
if(ability.type.targets[0].type == TT_Object)
artifact.activateAbilityTypeFor(ai.empire, ability.type.id, target);
else if(ability.type.targets[0].type == TT_Point)
artifact.activateAbilityTypeFor(ai.empire, ability.type.id, pointTarget);
}
else {
artifact.activateAbilityTypeFor(ai.empire, ability.type.id);
}
}
}
int opCmp(const ConsiderEnergy@ other) const {
if(value < other.value)
return -1;
if(value > other.value)
return 1;
return 0;
}
};
class Energy : AIComponent, Artifacts {
Systems@ systems;
double baseIncome;
double freeStorage;
array<ConsiderEnergy@> queue;
int nextEnergyId = 0;
void save(SaveFile& file) {
file << nextEnergyId;
uint cnt = queue.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
queue[i].save(ai, file);
}
void load(SaveFile& file) {
file >> nextEnergyId;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
ConsiderEnergy c;
c.load(ai, file);
if(c.artifact !is null)
queue.insertLast(c);
}
}
void create() {
@systems = cast<Systems>(ai.systems);
}
Considerer@ get_consider() {
return cast<Considerer>(ai.consider);
}
Empire@ get_empire() {
return ai.empire;
}
bool canUse(Artifact@ artifact) {
if(artifact is null || !artifact.valid)
return false;
Empire@ owner = artifact.owner;
if(owner.valid && owner !is ai.empire)
return false;
Region@ reg = artifact.region;
if(reg is null)
return false;
if(reg.PlanetsMask != 0)
return reg.PlanetsMask & ai.mask != 0;
else
return hasTradeAdjacent(ai.empire, reg);
}
ConsiderEnergy@ registerArtifact(Artifact@ artifact) {
if(!canUse(artifact))
return null;
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
if(queue[i].artifact is artifact)
return queue[i];
}
ConsiderEnergy c;
c.id = nextEnergyId++;
c.init(ai, artifact);
if(log)
ai.print("Detect artifact "+artifact.name, artifact.region);
queue.insertLast(c);
return c;
}
uint updateIdx = 0;
bool update() {
if(queue.length == 0)
return false;
updateIdx = (updateIdx+1) % queue.length;
auto@ c = queue[updateIdx];
double prevValue = c.value;
//Make sure this is still valid
if(!c.isValid(ai, this)) {
queue.removeAt(updateIdx);
return false;
}
//Update the current target and value
c.considerEnergy(ai, this);
/*if(log)*/
/* ai.print(c.artifact.name+": consider "+c.value+" for cost "+c.cost, c.target);*/
//Only re-sort when needed
bool changed = false;
if(prevValue != c.value) {
if(updateIdx > 0) {
if(c.value > queue[updateIdx-1].value)
changed = true;
}
if(updateIdx < queue.length-1) {
if(c.value < queue[updateIdx+1].value)
changed = true;
}
}
return changed;
}
uint sysIdx = 0;
void updateSystem() {
uint totCnt = systems.owned.length + systems.outsideBorder.length;
if(totCnt == 0)
return;
sysIdx = (sysIdx+1) % totCnt;
SystemAI@ sys;
if(sysIdx < systems.owned.length)
@sys = systems.owned[sysIdx];
else
@sys = systems.outsideBorder[sysIdx - systems.owned.length];
for(uint i = 0, cnt = sys.artifacts.length; i < cnt; ++i)
registerArtifact(sys.artifacts[i]);
}
void tick(double time) {
//Update current income
baseIncome = empire.EnergyIncome;
freeStorage = empire.FreeEnergyStorage;
//See if we can use anything right now
if(queue.length != 0) {
auto@ c = queue[0];
if(!c.isValid(ai, this)) {
queue.removeAt(0);
}
else if(c.value > 0.0 && ai.empire.EnergyStored >= c.cost) {
c.execute(ai, this);
queue.removeAt(0);
}
}
}
void focusTick(double time) {
//Consider artifact usage
bool changed = false;
for(uint n = 0; n < min(queue.length, max(ai.behavior.artifactFocusConsiderCount, queue.length/20)); ++n) {
if(update())
changed = true;
}
//Re-sort consideration
if(changed)
queue.sortDesc();
//Try to find new artifacts
updateSystem();
}
};
AIComponent@ createEnergy() {
return Energy();
}
+617
View File
@@ -0,0 +1,617 @@
// Fleets
// ------
// Manages data about fleets and missions, as well as making sure fleets
// return to their station after a mission.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Movement;
enum FleetClass {
FC_Scout,
FC_Combat,
FC_Slipstream,
FC_Mothership,
FC_ALL
};
enum MissionPriority {
MiP_Background,
MiP_Normal,
MiP_High,
MiP_Critical,
}
class Mission {
int id = -1;
bool completed = false;
bool canceled = false;
uint priority = MiP_Normal;
void _save(Fleets& fleets, SaveFile& file) {
file << completed;
file << canceled;
file << priority;
save(fleets, file);
}
void _load(Fleets& fleets, SaveFile& file) {
file >> completed;
file >> canceled;
file >> priority;
load(fleets, file);
}
void save(Fleets& fleets, SaveFile& file) {
}
void load(Fleets& fleets, SaveFile& file) {
}
bool get_isActive() {
return true;
}
double getPerformWeight(AI& ai, FleetAI& fleet) {
return 1.0;
}
void start(AI& ai, FleetAI& fleet) {
}
void cancel(AI& ai, FleetAI& fleet) {
}
void tick(AI& ai, FleetAI& fleet, double time) {
}
};
final class FleetAI {
uint fleetClass;
Object@ obj;
Mission@ mission;
Region@ stationed;
bool stationedFactory = true;
double filled = 0.0;
double idleSince = 0.0;
double fillStaticSince = 0.0;
void save(Fleets& fleets, SaveFile& file) {
file << fleetClass;
file << stationed;
file << filled;
file << idleSince;
file << fillStaticSince;
file << stationedFactory;
fleets.saveMission(file, mission);
}
void load(Fleets& fleets, SaveFile& file) {
file >> fleetClass;
file >> stationed;
file >> filled;
file >> idleSince;
file >> fillStaticSince;
file >> stationedFactory;
@mission = fleets.loadMission(file);
}
bool get_isHome() {
if(stationed is null)
return true;
return obj.region is stationed;
}
bool get_busy() {
return mission !is null;
}
double get_strength() {
return obj.getFleetStrength();
}
double get_supplies() {
Ship@ ship = cast<Ship>(obj);
if(ship is null)
return 1.0;
double maxSupply = ship.MaxSupply;
if(maxSupply <= 0)
return 1.0;
return ship.Supply / maxSupply;
}
double get_remainingSupplies() {
Ship@ ship = cast<Ship>(obj);
if(ship is null)
return 0.0;
return ship.Supply;
}
double get_radius() {
return obj.getFormationRadius();
}
double get_fleetHealth() {
return obj.getFleetStrength() / obj.getFleetMaxStrength();
}
double get_flagshipHealth() {
Ship@ ship = cast<Ship>(obj);
if(ship is null)
return 1.0;
return ship.blueprint.currentHP / ship.blueprint.design.totalHP;
}
bool get_actionableState() {
if(isHome && obj.hasOrderedSupports && stationedFactory)
return false;
if(supplies < 0.75)
return false;
if(filled < 0.5)
return false;
if(filled < 1.0 && gameTime < fillStaticSince + 90.0)
return false;
return true;
}
bool get_readyForAction() {
if(mission !is null)
return false;
if(isHome && obj.hasOrderedSupports && stationedFactory)
return false;
if(supplies < 0.75)
return false;
if(filled < 0.5)
return false;
if(filled < 1.0 && gameTime < fillStaticSince + 90.0)
return false;
if(obj.isMoving) {
if(obj.velocity.length / obj.maxAcceleration > 16.0)
return false;
}
return true;
}
bool tick(AI& ai, Fleets& fleets, double time) {
//Make sure we still exist
if(!obj.valid || obj.owner !is ai.empire) {
if(mission !is null) {
mission.canceled = true;
@mission = null;
}
return false;
}
//Record data
int supUsed = obj.SupplyUsed;
int supCap = obj.SupplyCapacity;
int supGhost = obj.SupplyGhost;
int supOrdered = obj.SupplyOrdered;
double newFill = 1.0;
if(supCap > 0.0)
newFill = double(supUsed - supGhost - supOrdered) / double(supCap);
if(newFill != filled) {
fillStaticSince = gameTime;
filled = newFill;
}
//Perform our mission
if(mission !is null) {
if(!mission.completed && !mission.canceled)
mission.tick(ai, this, time);
if(mission.completed || mission.canceled) {
@mission = null;
idleSince = gameTime;
}
}
//Return to where we're stationed if we're not doing anything
if(mission is null && stationed !is null && fleetClass != FC_Scout) {
if(gameTime >= idleSince + ai.behavior.fleetIdleReturnStationedTime) {
if(obj.region !is stationed && !obj.hasOrders) {
if(fleets.log)
ai.print("Returning to station in "+stationed.name, obj);
fleets.movement.move(obj, stationed, spread=true);
}
}
}
return true;
}
};
class Fleets : AIComponent {
Systems@ systems;
Designs@ designs;
Movement@ movement;
array<FleetAI@> fleets;
int nextMissionId = 0;
double totalStrength = 0;
double totalMaxStrength = 0;
void create() {
@systems = cast<Systems>(ai.systems);
@designs = cast<Designs>(ai.designs);
@movement = cast<Movement>(ai.movement);
}
void save(SaveFile& file) {
file << nextMissionId;
file << totalStrength;
file << totalMaxStrength;
uint cnt = fleets.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveAI(file, fleets[i]);
fleets[i].save(this, file);
}
}
void load(SaveFile& file) {
file >> nextMissionId;
file >> totalStrength;
file >> totalMaxStrength;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
FleetAI@ flAI = loadAI(file);
if(flAI !is null)
flAI.load(this, file);
else
FleetAI().load(this, file);
}
}
void saveAI(SaveFile& file, FleetAI@ flAI) {
if(flAI is null) {
file.write0();
return;
}
file.write1();
file << flAI.obj;
}
FleetAI@ loadAI(SaveFile& file) {
if(!file.readBit())
return null;
Object@ obj;
file >> obj;
if(obj is null)
return null;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].obj is obj)
return fleets[i];
}
FleetAI flAI;
@flAI.obj = obj;
fleets.insertLast(flAI);
return flAI;
}
array<Mission@> savedMissions;
array<Mission@> loadedMissions;
void postSave(AI& ai) {
savedMissions.length = 0;
}
void postLoad(AI& ai) {
loadedMissions.length = 0;
}
void saveMission(SaveFile& file, Mission@ mission) {
if(mission is null) {
file.write0();
return;
}
file.write1();
file << mission.id;
if(mission.id == -1) {
storeMission(file, mission);
}
else {
bool found = false;
for(uint i = 0, cnt = savedMissions.length; i < cnt; ++i) {
if(savedMissions[i] is mission) {
found = true;
break;
}
}
if(!found) {
storeMission(file, mission);
savedMissions.insertLast(mission);
}
}
}
Mission@ loadMission(SaveFile& file) {
if(!file.readBit())
return null;
int id = 0;
file >> id;
if(id == -1) {
Mission@ miss = createMission(file);
miss.id = id;
return miss;
}
else {
for(uint i = 0, cnt = loadedMissions.length; i < cnt; ++i) {
if(loadedMissions[i].id == id)
return loadedMissions[i];
}
Mission@ miss = createMission(file);
miss.id = id;
loadedMissions.insertLast(miss);
return miss;
}
}
void storeMission(SaveFile& file, Mission@ mission) {
auto@ cls = getClass(mission);
auto@ mod = cls.module;
file << mod.name;
file << cls.name;
mission._save(this, file);
}
Mission@ createMission(SaveFile& file) {
string modName;
string clsName;
file >> modName;
file >> clsName;
auto@ mod = getScriptModule(modName);
if(mod is null) {
error("ERROR: AI Load could not find module for mission "+modName+"::"+clsName);
return null;
}
auto@ cls = mod.getClass(clsName);
if(cls is null) {
error("ERROR: AI Load could not find class for mission "+modName+"::"+clsName);
return null;
}
auto@ miss = cast<Mission>(cls.create());
if(miss is null) {
error("ERROR: AI Load could not create class instance for mission "+modName+"::"+clsName);
return null;
}
miss._load(this, file);
return miss;
}
void checkForFleets() {
auto@ data = ai.empire.getFlagships();
Object@ obj;
while(receive(data, obj)) {
if(obj !is null)
register(obj);
}
}
bool haveCombatReadyFleets() {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
continue;
return true;
}
return false;
}
uint countCombatReadyFleets() {
uint count = 0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
continue;
count += 1;
}
return count;
}
bool allFleetsCombatReady() {
bool have = false;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
return false;
have = true;
}
return have;
}
uint prevFleetCount = 0;
double checkTimer = 0;
void focusTick(double time) override {
//Check for any newly obtained fleets
uint curFleetCount = ai.empire.fleetCount;
checkTimer += time;
if(curFleetCount != prevFleetCount || checkTimer > 60.0) {
checkForFleets();
prevFleetCount = curFleetCount;
checkTimer = 0;
}
//Calculate our current strengths
totalStrength = 0;
totalMaxStrength = 0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
totalStrength += sqrt(fleets[i].obj.getFleetStrength());
totalMaxStrength += sqrt(fleets[i].obj.getFleetMaxStrength());
}
totalStrength = sqr(totalStrength);
totalMaxStrength = sqr(totalMaxStrength);
}
double getTotalStrength(uint checkClass, bool idleOnly = false, bool readyOnly = false) {
double str = 0.0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if((flAI.fleetClass == checkClass || checkClass == FC_ALL)
&& (!idleOnly || flAI.mission is null)
&& (!readyOnly || flAI.readyForAction))
str += sqrt(fleets[i].obj.getFleetStrength());
}
return str*str;
}
void tick(double time) override {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(!fleets[i].tick(ai, this, time)) {
fleets.removeAt(i);
--i; --cnt;
continue;
}
Region@ reg = fleets[i].obj.region;
if(reg !is null)
systems.focus(reg);
}
}
MoveOrder@ returnToBase(FleetAI@ fleet, uint priority = MP_Normal) {
if(fleet.stationed !is null)
return movement.move(fleet.obj, fleet.stationed, priority, spread=true);
return null;
}
FleetAI@ register(Object@ obj) {
FleetAI@ flAI = getAI(obj);
if(flAI is null) {
@flAI = FleetAI();
@flAI.obj = obj;
@flAI.stationed = obj.region;
obj.setHoldPosition(true);
uint designClass = designs.classify(obj);
if(designClass == DP_Scout)
flAI.fleetClass = FC_Scout;
else if(designClass == DP_Slipstream)
flAI.fleetClass = FC_Slipstream;
else if(designClass == DP_Mothership)
flAI.fleetClass = FC_Mothership;
else
flAI.fleetClass = FC_Combat;
fleets.insertLast(flAI);
}
return flAI;
}
void register(Mission@ mission) {
if(mission.id == -1)
mission.id = nextMissionId++;
}
FleetAI@ getAI(Object@ obj) {
if(obj is null)
return null;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].obj is obj)
return fleets[i];
}
return null;
}
uint count(uint checkClass) {
uint amount = 0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.fleetClass == checkClass || checkClass == FC_ALL)
amount += 1;
}
return amount;
}
bool haveIdle(uint checkClass) {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if((flAI.fleetClass == checkClass || checkClass == FC_ALL) && flAI.mission is null)
return true;
}
return false;
}
double closestIdleTo(uint checkClass, const vec3d& position) {
double closest = INFINITY;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if((flAI.fleetClass != checkClass && checkClass != FC_ALL) || flAI.mission !is null)
continue;
double d = flAI.obj.position.distanceTo(position);
if(d < closest)
closest = d;
}
return closest;
}
FleetAI@ performMission(Mission@ mission) {
FleetAI@ perform;
double bestWeight = 0.0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flAI = fleets[i];
if(flAI.mission !is null)
continue;
double w = mission.getPerformWeight(ai, flAI);
if(w > bestWeight) {
bestWeight = w;
@perform = flAI;
}
}
if(perform !is null) {
@perform.mission = mission;
register(mission);
mission.start(ai, perform);
}
return perform;
}
FleetAI@ performMission(FleetAI@ fleet, Mission@ mission) {
if(fleet.mission !is null) {
fleet.mission.cancel(ai, fleet);
fleet.mission.canceled = true;
}
@fleet.mission = mission;
register(mission);
mission.start(ai, fleet);
return fleet;
}
};
AIComponent@ createFleets() {
return Fleets();
}
@@ -0,0 +1,330 @@
import resources;
import tile_resources;
import saving;
export ResourceSpecType;
export ResourceSpec;
export implementSpec;
export ImportData;
export ExportData;
enum ResourceSpecType {
RST_Specific,
RST_Level_Specific,
RST_Level_Minimum,
RST_Pressure_Type,
RST_Pressure_Level0,
RST_Class,
};
tidy final class ResourceSpec : Savable {
uint type = RST_Specific;
const ResourceType@ resource;
const ResourceClass@ cls;
uint level = 0;
uint pressureType = 0;
bool isLevelRequirement = false;
bool isForImport = true;
bool allowUniversal = true;
void save(SaveFile& file) {
file << type;
if(resource !is null) {
file.write1();
file.writeIdentifier(SI_Resource, resource.id);
}
else {
file.write0();
}
if(cls !is null) {
file.write1();
file << cls.ident;
}
else {
file.write0();
}
file << level;
file << pressureType;
file << isLevelRequirement;
file << isForImport;
file << allowUniversal;
}
void load(SaveFile& file) {
file >> type;
if(file.readBit())
@resource = getResource(file.readIdentifier(SI_Resource));
if(file.readBit()) {
string clsName;
file >> clsName;
@cls = getResourceClass(clsName);
}
file >> level;
file >> pressureType;
file >> isLevelRequirement;
file >> isForImport;
file >> allowUniversal;
}
bool opEquals(const ResourceSpec& other) const {
if(type != other.type)
return false;
if(isLevelRequirement != other.isLevelRequirement)
return false;
switch(type) {
case RST_Specific:
return other.resource is resource;
case RST_Level_Specific:
case RST_Level_Minimum:
return other.level == level;
case RST_Pressure_Type:
case RST_Pressure_Level0:
return other.pressureType == pressureType;
case RST_Class:
return other.cls is cls;
}
return true;
}
bool meets(const ResourceType@ check, Object@ fromObj = null, Object@ toObj = null) const {
if(check is null)
return false;
if(allowUniversal && isLevelRequirement) {
if(check.mode == RM_UniversalUnique || check.mode == RM_Universal) {
//HACK: The AI shouldn't use drugs for food and water
switch(type) {
case RST_Level_Specific:
case RST_Level_Minimum:
return level >= 2;
}
return false;
}
}
if(isForImport && !check.exportable && (fromObj is null || fromObj !is toObj))
return false;
if(isLevelRequirement && check.mode == RM_NonRequirement)
return false;
switch(type) {
case RST_Specific:
return check is resource;
case RST_Level_Specific:
return check.level == level;
case RST_Level_Minimum:
return check.level >= level;
case RST_Pressure_Type:
return check.tilePressure[pressureType] >= max(check.totalPressure * 0.4, 1.0);
case RST_Pressure_Level0:
return check.level == 0 && check.tilePressure[pressureType] >= max(check.totalPressure * 0.4, 1.0);
case RST_Class:
return check.cls is cls;
}
return false;
}
bool implements(const ResourceRequirement& req) const {
if(!isLevelRequirement)
return false;
switch(req.type) {
case RRT_Resource:
return this.type == RST_Specific && this.resource is req.resource;
case RRT_Class:
case RRT_Class_Types:
return this.type == RST_Class && this.cls is req.cls;
case RRT_Level:
case RRT_Level_Types:
return this.type == RST_Level_Specific && this.level == req.level;
}
return false;
}
string dump() {
switch(type) {
case RST_Specific:
return resource.name;
case RST_Level_Specific:
return "Tier "+level;
case RST_Level_Minimum:
return "Tier "+level+"+";
case RST_Pressure_Type:
return "Any "+getTileResourceIdent(pressureType);
case RST_Pressure_Level0:
return "Level 0 "+getTileResourceIdent(pressureType);
case RST_Class:
return "Of "+cls.ident;
}
return "??";
}
int get_resourceLevel() const {
switch(type) {
case RST_Specific:
return 0;
case RST_Level_Specific:
return level;
case RST_Level_Minimum:
return level;
case RST_Pressure_Type:
return 0;
case RST_Pressure_Level0:
return 0;
case RST_Class:
return 0;
}
return 0;
}
int opCmp(const ResourceSpec@ other) const {
int level = this.resourceLevel;
int otherLevel = other.resourceLevel;
if(level > otherLevel)
return 1;
if(level < otherLevel)
return -1;
return 0;
}
};
ResourceSpec@ implementSpec(const ResourceRequirement& req) {
ResourceSpec spec;
spec.isLevelRequirement = true;
switch(req.type) {
case RRT_Resource:
spec.type = RST_Specific;
@spec.resource = req.resource;
break;
case RRT_Class:
case RRT_Class_Types:
spec.type = RST_Class;
@spec.cls = req.cls;
break;
case RRT_Level:
case RRT_Level_Types:
spec.type = RST_Level_Specific;
spec.level = req.level;
break;
}
return spec;
}
tidy final class ImportData : Savable {
int id = -1;
Object@ obj;
ResourceSpec@ spec;
const ResourceType@ resource;
Object@ fromObject;
int resourceId = -1;
bool beingMet = false;
bool forLevel = false;
bool cycled = false;
bool isColonizing = false;
bool claimedFor = false;
double idleSince = 0.0;
void save(SaveFile& file) {
file << obj;
file << spec;
if(resource !is null) {
file.write1();
file.writeIdentifier(SI_Resource, resource.id);
}
else {
file.write0();
}
file << fromObject;
file << resourceId;
file << beingMet;
file << forLevel;
file << cycled;
file << isColonizing;
file << claimedFor;
file << idleSince;
}
void load(SaveFile& file) {
file >> obj;
@spec = ResourceSpec();
file >> spec;
if(file.readBit())
@resource = getResource(file.readIdentifier(SI_Resource));
file >> fromObject;
file >> resourceId;
file >> beingMet;
file >> forLevel;
file >> cycled;
file >> isColonizing;
file >> claimedFor;
file >> idleSince;
}
void set(ExportData@ source) {
@fromObject = source.obj;
resourceId = source.resourceId;
@resource = source.resource;
}
int opCmp(const ImportData@ other) const {
return spec.opCmp(other.spec);
}
bool get_isOpen() const {
return !beingMet;
}
};
tidy final class ExportData : Savable {
int id = -1;
Object@ obj;
const ResourceType@ resource;
int resourceId = -1;
ImportData@ request;
Object@ developUse;
bool localOnly = false;
bool get_usable() const {
if(obj is null)
return false;
if(resourceId == obj.primaryResourceId)
return obj.primaryResourceUsable;
else
return obj.getNativeResourceUsableByID(resourceId);
}
bool get_isPrimary() const {
return resourceId == obj.primaryResourceId;
}
bool isExportedTo(Object@ check) const {
if(check is obj)
return true;
if(resourceId == obj.primaryResourceId)
return obj.isPrimaryDestination(check);
else
return obj.getNativeResourceDestinationByID(obj.owner, resourceId) is check;
}
void save(SaveFile& file) {
//Does not save the request link, this is done by Resources
file << obj;
if(resource !is null) {
file.write1();
file.writeIdentifier(SI_Resource, resource.id);
}
else {
file.write0();
}
file << resourceId;
file << developUse;
file << localOnly;
}
void load(SaveFile& file) {
file >> obj;
if(file.readBit())
@resource = getResource(file.readIdentifier(SI_Resource));
file >> resourceId;
file >> developUse;
file >> localOnly;
}
};
@@ -0,0 +1,542 @@
// Intelligence
// ------------
// Keeps track of the existence and movement of enemy fleets and other assets.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Systems;
import regions.regions;
final class FleetIntel {
Object@ obj;
bool known = false;
bool visible = false;
double lastSeen = 0;
double seenStrength = 0;
double predictStrength = 0;
vec3d seenPosition;
Region@ seenRegion;
vec3d seenDestination;
Region@ seenTarget;
void save(SaveFile& file) {
file << obj;
file << known;
file << visible;
file << lastSeen;
file << seenStrength;
file << predictStrength;
file << seenPosition;
file << seenRegion;
file << seenDestination;
file << seenTarget;
}
void load(SaveFile& file) {
file >> obj;
file >> known;
file >> visible;
file >> lastSeen;
file >> seenStrength;
file >> predictStrength;
file >> seenPosition;
file >> seenRegion;
file >> seenDestination;
file >> seenTarget;
}
bool get_isSignificant() {
return obj.getFleetStrength() > 0.1;
}
bool tick(AI& ai, Intelligence& intelligence, Intel& intel) {
if(visible) {
if(!obj.valid || obj.owner !is intel.empire)
return false;
}
else {
if(!obj.valid || obj.owner !is intel.empire) {
if(!known || lastSeen < gameTime - 300.0)
return false;
}
}
if(obj.isVisibleTo(ai.empire)) {
known = true;
visible = true;
lastSeen = gameTime;
seenStrength = obj.getFleetStrength();
predictStrength = obj.getFleetMaxStrength();
int supCap = obj.SupplyCapacity;
double fillPct = 1.0;
if(supCap != 0) {
double fillPct = double(obj.SupplyUsed) / double(supCap);
if(fillPct > 0.5)
predictStrength /= fillPct;
else
predictStrength *= 2.0;
}
seenPosition = obj.position;
@seenRegion = obj.region;
if(obj.isMoving) {
seenDestination = obj.computedDestination;
if(seenRegion !is null && inRegion(seenRegion, seenDestination))
@seenTarget = seenRegion;
else if(seenTarget !is null && inRegion(seenTarget, seenDestination))
@seenTarget = seenTarget;
else
@seenTarget = getRegion(seenDestination);
}
else {
seenDestination = seenPosition;
@seenTarget = seenRegion;
}
}
else {
visible = false;
}
return true;
}
};
final class Intel {
Empire@ empire;
uint borderedTo = 0;
array<FleetIntel@> fleets;
array<SystemAI@> shared;
array<SystemAI@> theirBorder;
array<SystemAI@> theirOwned;
void save(Intelligence& intelligence, SaveFile& file) {
uint cnt = fleets.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets[i].save(file);
cnt = shared.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
intelligence.systems.saveAI(file, shared[i]);
cnt = theirBorder.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
intelligence.systems.saveAI(file, theirBorder[i]);
cnt = theirOwned.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
intelligence.systems.saveAI(file, theirOwned[i]);
file << borderedTo;
}
void load(Intelligence& intelligence, SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
FleetIntel flIntel;
flIntel.load(file);
if(flIntel.obj !is null)
fleets.insertLast(flIntel);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ sys = intelligence.systems.loadAI(file);
if(sys !is null)
shared.insertLast(sys);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ sys = intelligence.systems.loadAI(file);
if(sys !is null)
theirBorder.insertLast(sys);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ sys = intelligence.systems.loadAI(file);
if(sys !is null)
theirOwned.insertLast(sys);
}
file >> borderedTo;
}
//TODO: If a fleet is going to drop out of cutoff range soon,
// queue up a scouting mission to its last known position so we
// can try to regain intel on it.
double getSeenStrength(double cutOff = 600.0) {
double total = 0.0;
cutOff = gameTime - cutOff;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flIntel = fleets[i];
if(!flIntel.known)
continue;
if(flIntel.lastSeen < cutOff)
continue;
total += sqrt(fleets[i].seenStrength);
}
return total * total;
}
double getPredictiveStrength(double cutOff = 600.0) {
double total = 0.0;
cutOff = gameTime - cutOff;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flIntel = fleets[i];
if(!flIntel.known)
continue;
if(flIntel.lastSeen < cutOff)
continue;
total += sqrt(fleets[i].predictStrength);
}
return total * total;
}
double accuracy(AI& ai, Intelligence& intelligence, double cutOff = 600.0) {
uint total = 0;
uint known = 0;
cutOff = gameTime - cutOff;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flIntel = fleets[i];
if(!flIntel.isSignificant)
continue;
total += 1;
if(flIntel.known && flIntel.lastSeen >= cutOff)
known += 1;
}
if(total == 0)
return 1.0;
return double(known) / double(total);
}
double defeatability(AI& ai, Intelligence& intelligence, double cutOff = 600.0) {
double acc = accuracy(ai, intelligence, cutOff);
double ourStrength = 0, theirStrength = 0;
if(acc < 0.6) {
//In low-accuracy situations, base it on the empire overall strength metric
theirStrength = empire.TotalMilitary;
ourStrength = ai.empire.TotalMilitary;
}
else {
theirStrength = getPredictiveStrength(cutOff * 10.0);
ourStrength = intelligence.fleets.totalStrength;
}
if(theirStrength == 0)
return 10.0;
return ourStrength / theirStrength;
}
void tick(AI& ai, Intelligence& intelligence) {
//Keep known fleets updated
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(!fleets[i].tick(ai, intelligence, this)) {
fleets.removeAt(i);
--i; --cnt;
}
}
}
bool isShared(AI& ai, SystemAI@ sys) {
return sys.seenPresent & ai.empire.mask != 0 && sys.seenPresent & empire.mask != 0;
}
bool isBorder(AI& ai, SystemAI@ sys) {
return sys.outsideBorder && sys.seenPresent & empire.mask != 0;
}
void focusTick(AI& ai, Intelligence& intelligence) {
//Detect newly created fleets
auto@ data = empire.getFlagships();
Object@ obj;
while(receive(data, obj)) {
if(obj is null)
continue;
bool found = false;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].obj is obj) {
found = true;
break;
}
}
if(!found) {
FleetIntel flIntel;
@flIntel.obj = obj;
fleets.insertLast(flIntel);
}
}
//Remove no longer shared and border systems
for(uint i = 0, cnt = shared.length; i < cnt; ++i) {
if(!isShared(ai, shared[i])) {
shared.removeAt(i);
--i; --cnt;
}
}
for(uint i = 0, cnt = theirBorder.length; i < cnt; ++i) {
if(!isBorder(ai, theirBorder[i])) {
theirBorder.removeAt(i);
--i; --cnt;
}
}
borderedTo = 0;
for(uint i = 0, cnt = theirOwned.length; i < cnt; ++i) {
auto@ sys = theirOwned[i];
uint seen = sys.seenPresent;
if(seen & empire.mask == 0) {
theirOwned.removeAt(i);
--i; --cnt;
continue;
}
for(uint n = 0, ncnt = sys.desc.adjacent.length; n < ncnt; ++n) {
auto@ other = intelligence.systems.getAI(sys.desc.adjacent[n]);
if(other !is null)
borderedTo |= other.seenPresent & ~empire.mask;
}
for(uint n = 0, ncnt = sys.desc.wormholes.length; n < ncnt; ++n) {
auto@ other = intelligence.systems.getAI(sys.desc.wormholes[n]);
if(other !is null)
borderedTo |= other.seenPresent & ~empire.mask;
}
}
//Detect shared and border systems
for(uint i = 0, cnt = intelligence.systems.owned.length; i < cnt; ++i) {
auto@ sys = intelligence.systems.owned[i];
if(isShared(ai, sys)) {
if(shared.find(sys) == -1)
shared.insertLast(sys);
}
}
for(uint i = 0, cnt = intelligence.systems.outsideBorder.length; i < cnt; ++i) {
auto@ sys = intelligence.systems.outsideBorder[i];
if(isBorder(ai, sys)) {
if(theirBorder.find(sys) == -1)
theirBorder.insertLast(sys);
}
}
for(uint i = 0, cnt = intelligence.systems.all.length; i < cnt; ++i) {
auto@ sys = intelligence.systems.all[i];
if(sys.seenPresent & empire.mask != 0) {
if(theirOwned.find(sys) == -1)
theirOwned.insertLast(sys);
}
}
//Try to update some stuff
SystemAI@ check;
double lru = 0;
for(uint i = 0, cnt = shared.length; i < cnt; ++i) {
auto@ sys = shared[i];
double update = sys.lastStrengthCheck;
if(update < lru && sys.visible) {
@check = sys;
lru = update;
}
}
for(uint i = 0, cnt = theirBorder.length; i < cnt; ++i) {
auto@ sys = theirBorder[i];
double update = sys.lastStrengthCheck;
if(update < lru && sys.visible) {
@check = sys;
lru = update;
}
}
if(check !is null)
check.strengthCheck(ai);
}
};
class Intelligence : AIComponent {
Fleets@ fleets;
Systems@ systems;
array<Intel@> intel;
void create() {
@fleets = cast<Fleets>(ai.fleets);
@systems = cast<Systems>(ai.systems);
}
void start() {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(emp is ai.empire)
continue;
if(!emp.major)
continue;
Intel empIntel;
@empIntel.empire = emp;
if(intel.length <= uint(emp.index))
intel.length = uint(emp.index)+1;
@intel[emp.index] = empIntel;
}
}
void save(SaveFile& file) {
uint cnt = intel.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
if(intel[i] is null) {
file.write0();
continue;
}
file.write1();
intel[i].save(this, file);
}
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
intel.length = cnt;
for(uint i = 0; i < cnt; ++i) {
if(!file.readBit())
continue;
@intel[i] = Intel();
@intel[i].empire = getEmpire(i);
intel[i].load(this, file);
}
}
Intel@ get(Empire@ emp) {
if(emp is null)
return null;
if(!emp.major)
return null;
if(uint(emp.index) >= intel.length)
return null;
return intel[emp.index];
}
uint ind = 0;
void tick(double time) override {
if(intel.length == 0)
return;
ind = (ind+1)%intel.length;
if(intel[ind] !is null)
intel[ind].tick(ai, this);
}
uint fInd = 0;
void focusTick(double time) override {
if(intel.length == 0)
return;
fInd = (fInd+1)%intel.length;
if(intel[fInd] !is null)
intel[fInd].focusTick(ai, this);
}
string strdisplay(double str) {
return standardize(str * 0.001, true);
}
double defeatability(Empire@ emp) {
auto@ empIntel = get(emp);
if(empIntel is null)
return 0.0;
return empIntel.defeatability(ai, this);
}
double defeatability(uint theirMask, uint myMask = 0, double cutOff = 600.0) {
if(myMask == 0)
myMask = ai.empire.mask;
double minAcc = 1.0;
for(uint i = 0, cnt = intel.length; i < cnt; ++i) {
auto@ itl = intel[i];
if(itl is null || itl.empire is null)
continue;
if((theirMask | myMask) & itl.empire.mask == 0)
continue;
minAcc = min(itl.accuracy(ai, this, cutOff), minAcc);
}
double ourStrength = 0, theirStrength = 0;
for(uint i = 0, cnt = intel.length; i < cnt; ++i) {
auto@ itl = intel[i];
if(itl is null || itl.empire is null)
continue;
if((theirMask | myMask) & itl.empire.mask == 0)
continue;
double str = 0.0;
if(minAcc < 0.6)
str = itl.empire.TotalMilitary;
else
str = itl.getPredictiveStrength(cutOff * 10.0);
if(itl.empire.mask & theirMask != 0)
theirStrength += str;
if(itl.empire.mask & myMask != 0)
ourStrength += str;
}
if(myMask & ai.empire.mask != 0) {
if(minAcc < 0.6)
ourStrength += ai.empire.TotalMilitary;
else
ourStrength += fleets.totalStrength;
}
if(theirMask & ai.empire.mask != 0) {
if(minAcc < 0.6)
theirStrength += ai.empire.TotalMilitary;
else
theirStrength += fleets.totalStrength;
}
if(theirStrength == 0)
return 10.0;
return ourStrength / theirStrength;
}
void turn() override {
if(log) {
ai.print("Intelligence Report on Empires:");
ai.print(ai.pad(" Our strength: ", 18)+strdisplay(fleets.totalStrength)+" / "+strdisplay(fleets.totalMaxStrength));
for(uint i = 0, cnt = intel.length; i < cnt; ++i) {
auto@ empIntel = intel[i];
if(empIntel is null)
continue;
ai.print(" "+ai.pad(empIntel.empire.name, 16)
+ai.pad(" "+strdisplay(empIntel.getSeenStrength())
+" / "+strdisplay(empIntel.getPredictiveStrength()), 20)
+" defeatability "+toString(empIntel.defeatability(ai, this), 2)
+" accuracy "+toString(empIntel.accuracy(ai, this), 2));
}
}
}
};
AIComponent@ createIntelligence() {
return Intelligence();
}
+749
View File
@@ -0,0 +1,749 @@
// Military
// --------
// Military construction logic. Builds and restores fleets and defensive stations,
// but does not deal with actually using those fleets to fight - that is the purview of
// the War component.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Budget;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Development;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Orbitals;
import resources;
class SupportOrder {
DesignTarget@ design;
Object@ onObject;
AllocateBudget@ alloc;
bool isGhostOrder = false;
double expires = INFINITY;
uint count = 0;
void save(Military& military, SaveFile& file) {
military.designs.saveDesign(file, design);
file << onObject;
military.budget.saveAlloc(file, alloc);
file << isGhostOrder;
file << expires;
file << count;
}
void load(Military& military, SaveFile& file) {
@design = military.designs.loadDesign(file);
file >> onObject;
@alloc = military.budget.loadAlloc(file);
file >> isGhostOrder;
file >> expires;
file >> count;
}
bool tick(AI& ai, Military& military, double time) {
if(alloc !is null) {
if(alloc.allocated) {
if(isGhostOrder)
onObject.rebuildAllGhosts();
else
onObject.orderSupports(design.active.mostUpdated(), count);
if(military.log && design !is null)
ai.print("Support order completed for "+count+"x "+design.active.name+" ("+design.active.size+")", onObject);
return false;
}
}
else if(design !is null) {
if(design.active !is null)
@alloc = military.budget.allocate(BT_Military, getBuildCost(design.active.mostUpdated()) * count);
}
if(expires < gameTime) {
if(alloc !is null && !alloc.allocated)
military.budget.remove(alloc);
if(isGhostOrder)
onObject.clearAllGhosts();
if(military.log)
ai.print("Support order expired", onObject);
return false;
}
return true;
}
};
class StagingBase {
Region@ region;
array<FleetAI@> fleets;
double idleTime = 0.0;
double occupiedTime = 0.0;
OrbitalAI@ shipyard;
BuildOrbital@ shipyardBuild;
Factory@ factory;
bool isUnderAttack = false;
void save(Military& military, SaveFile& file) {
file << region;
file << idleTime;
file << occupiedTime;
file << isUnderAttack;
military.orbitals.saveAI(file, shipyard);
military.construction.saveConstruction(file, shipyardBuild);
uint cnt = fleets.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
military.fleets.saveAI(file, fleets[i]);
}
void load(Military& military, SaveFile& file) {
file >> region;
file >> idleTime;
file >> occupiedTime;
file >> isUnderAttack;
@shipyard = military.orbitals.loadAI(file);
@shipyardBuild = cast<BuildOrbital>(military.construction.loadConstruction(file));
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
if(i > 200 && file < SV_0158) {
//Something went preeeetty wrong in an old save
if(file.readBit()) {
Object@ obj;
file >> obj;
}
}
else {
auto@ fleet = military.fleets.loadAI(file);
if(fleet !is null)
fleets.insertLast(fleet);
}
}
}
bool tick(AI& ai, Military& military, double time) {
if(fleets.length == 0) {
occupiedTime = 0.0;
idleTime += time;
}
else {
occupiedTime += time;
idleTime = 0.0;
}
isUnderAttack = region.ContestedMask & ai.mask != 0;
//Manage building our shipyard
if(shipyardBuild !is null) {
if(shipyardBuild.completed) {
@shipyard = military.orbitals.getInSystem(ai.defs.Shipyard, region);
if(shipyard !is null)
@shipyardBuild = null;
}
}
if(shipyard !is null) {
if(!shipyard.obj.valid) {
@shipyard = null;
@shipyardBuild = null;
}
}
if(factory !is null && (!factory.valid || factory.obj.region !is region))
@factory = null;
if(factory is null)
@factory = military.construction.getFactory(region);
if(factory !is null) {
factory.needsSupportLabor = false;
factory.waitingSupportLabor = 0.0;
if(factory.obj.hasOrderedSupports) {
factory.needsSupportLabor = true;
factory.waitingSupportLabor += double(factory.obj.SupplyOrdered) * ai.behavior.estSizeSupportLabor;
}
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].isHome && fleets[i].obj.hasOrderedSupports) {
factory.needsSupportLabor = true;
factory.waitingSupportLabor += double(fleets[i].obj.SupplyOrdered) * ai.behavior.estSizeSupportLabor;
break;
}
}
if(factory.waitingSupportLabor > 0)
factory.aimForLabor(factory.waitingSupportLabor / ai.behavior.constructionMaxTime);
}
bool isFactorySufficient = false;
if(factory !is null) {
if(factory.waitingSupportLabor <= factory.laborIncome * ai.behavior.constructionMaxTime
|| factory.obj.canImportLabor || factory !is military.construction.primaryFactory)
isFactorySufficient = true;
}
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
Object@ obj = fleets[i].obj;
if(obj is null || !obj.valid) {
fleets.removeAt(i);
--i; --cnt;
continue;
}
fleets[i].stationedFactory = isFactorySufficient;
}
if(occupiedTime >= 3.0 * 60.0 && ai.defs.Shipyard !is null && shipyard is null && shipyardBuild is null
&& !isUnderAttack && (!isFactorySufficient && factory !is military.construction.primaryFactory)) {
//If any fleets need construction try to queue up a shipyard
bool needYard = false;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ flt = fleets[i];
if(flt.obj.hasOrderedSupports || flt.filled < 0.8) {
needYard = true;
break;
}
}
if(needYard) {
@shipyard = military.orbitals.getInSystem(ai.defs.Shipyard, region);
if(shipyard is null) {
vec3d pos = region.position;
vec2d offset = random2d(region.radius * 0.4, region.radius * 0.8);
pos.x += offset.x;
pos.z += offset.y;
@shipyardBuild = military.construction.buildOrbital(ai.defs.Shipyard, pos);
}
}
}
if((idleTime >= 10.0 * 60.0 || region.PlanetsMask & ai.mask == 0) && (shipyardBuild is null || shipyard !is null) && (factory is null || (shipyard !is null && factory.obj is shipyard.obj)) && military.stagingBases.length >= 2) {
if(shipyard !is null) {
cast<Orbital>(shipyard.obj).scuttle();
}
else {
if(factory !is null) {
factory.needsSupportLabor = false;
@factory = null;
}
return false;
}
}
return true;
}
};
class Military : AIComponent {
Fleets@ fleets;
Development@ development;
Designs@ designs;
Construction@ construction;
Budget@ budget;
Systems@ systems;
Orbitals@ orbitals;
array<SupportOrder@> supportOrders;
array<StagingBase@> stagingBases;
AllocateConstruction@ mainWait;
bool spentMoney = true;
void create() {
@fleets = cast<Fleets>(ai.fleets);
@development = cast<Development>(ai.development);
@designs = cast<Designs>(ai.designs);
@construction = cast<Construction>(ai.construction);
@budget = cast<Budget>(ai.budget);
@systems = cast<Systems>(ai.systems);
@orbitals = cast<Orbitals>(ai.orbitals);
}
void save(SaveFile& file) {
uint cnt = supportOrders.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
supportOrders[i].save(this, file);
construction.saveConstruction(file, mainWait);
file << spentMoney;
cnt = stagingBases.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveStaging(file, stagingBases[i]);
stagingBases[i].save(this, file);
}
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
SupportOrder ord;
ord.load(this, file);
if(ord.onObject !is null)
supportOrders.insertLast(ord);
}
@mainWait = construction.loadConstruction(file);
file >> spentMoney;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
StagingBase@ base = loadStaging(file);
if(base !is null) {
base.load(this, file);
if(stagingBases.find(base) == -1)
stagingBases.insertLast(base);
}
else {
StagingBase().load(this, file);
}
}
}
void loadFinalize(AI& ai) override {
for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) {
auto@ base = stagingBases[i];
for(uint n = 0, ncnt = base.fleets.length; n < ncnt; ++n) {
Object@ obj = base.fleets[n].obj;
if(obj is null || !obj.valid || !obj.initialized) {
base.fleets.removeAt(n);
--n; --ncnt;
}
}
}
}
StagingBase@ loadStaging(SaveFile& file) {
Region@ reg;
file >> reg;
if(reg is null)
return null;
StagingBase@ base = getBase(reg);
if(base is null) {
@base = StagingBase();
@base.region = reg;
stagingBases.insertLast(base);
}
return base;
}
void saveStaging(SaveFile& file, StagingBase@ base) {
Region@ reg;
if(base !is null)
@reg = base.region;
file << reg;
}
Region@ getClosestStaging(Region& targetRegion) {
//Check if we have anything close enough
StagingBase@ best;
int minHops = INT_MAX;
for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) {
int d = systems.hopDistance(stagingBases[i].region, targetRegion);
if(d < minHops) {
minHops = d;
@best = stagingBases[i];
}
}
if(best !is null)
return best.region;
return null;
}
Region@ getStagingFor(Region& targetRegion) {
//Check if we have anything close enough
StagingBase@ best;
int minHops = INT_MAX;
for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) {
int d = systems.hopDistance(stagingBases[i].region, targetRegion);
if(d < minHops) {
minHops = d;
@best = stagingBases[i];
}
}
if(minHops < ai.behavior.stagingMaxHops)
return best.region;
//Create a new staging base for this
Region@ bestNew;
minHops = INT_MAX;
for(uint i = 0, cnt = systems.border.length; i < cnt; ++i) {
auto@ sys = systems.border[i].obj;
int d = systems.hopDistance(sys, targetRegion);
if(d < minHops) {
minHops = d;
@bestNew = sys;
}
}
if(minHops > ai.behavior.stagingMaxHops && best !is null)
return best.region;
auto@ base = getBase(bestNew);
if(base !is null)
return base.region;
else
return createStaging(bestNew).region;
}
StagingBase@ createStaging(Region@ region) {
if(region is null)
return null;
if(log)
ai.print("Create new staging base.", region);
StagingBase newBase;
@newBase.region = region;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
if(fleets.fleets[i].stationed is region)
newBase.fleets.insertLast(fleets.fleets[i]);
}
stagingBases.insertLast(newBase);
return newBase;
}
StagingBase@ getBase(Region@ inRegion) {
if(inRegion is null)
return null;
for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) {
if(stagingBases[i].region is inRegion)
return stagingBases[i];
}
return null;
}
vec3d getStationPosition(Region& inRegion, double distance = 100.0) {
auto@ base = getBase(inRegion);
if(base !is null) {
if(base.shipyard !is null) {
vec3d pos = base.shipyard.obj.position;
vec2d offset = random2d(distance * 0.5, distance * 1.5);
pos.x += offset.x;
pos.z += offset.y;
return pos;
}
}
vec3d pos = inRegion.position;
vec2d offset = random2d(inRegion.radius * 0.4, inRegion.radius * 0.8);
pos.x += offset.x;
pos.z += offset.y;
return pos;
}
void stationFleet(FleetAI@ fleet, Region@ inRegion) {
if(inRegion is null || fleet.stationed is inRegion)
return;
auto@ prevBase = getBase(fleet.stationed);
if(prevBase !is null)
prevBase.fleets.remove(fleet);
auto@ base = getBase(inRegion);
if(base !is null)
base.fleets.insertLast(fleet);
@fleet.stationed = inRegion;
fleet.stationedFactory = construction.getFactory(inRegion) !is null;
if(fleet.mission is null)
fleets.returnToBase(fleet);
}
void orderSupportsOn(Object& obj, double expire = 60.0) {
if(obj.SupplyGhost > 0) {
if(ai.behavior.fleetsRebuildGhosts) {
//Try to rebuild the fleet's ghosts
SupportOrder ord;
@ord.onObject = obj;
@ord.alloc = budget.allocate(BT_Military, obj.rebuildGhostsCost());
ord.expires = gameTime + expire;
ord.isGhostOrder = true;
supportOrders.insertLast(ord);
if(log)
ai.print("Attempting to rebuild ghosts", obj);
return;
}
else {
obj.clearAllGhosts();
}
}
int supCap = obj.SupplyCapacity;
int supHave = obj.SupplyUsed - obj.SupplyGhost;
//Build some supports
int supSize = pow(2, round(::log(double(supCap) * randomd(0.005, 0.03))/::log(2.0)));
supSize = max(min(supSize, supCap - supHave), 1);
SupportOrder ord;
@ord.onObject = obj;
@ord.design = designs.design(DP_Support, supSize);
ord.expires = gameTime + expire;
ord.count = clamp((supCap - supHave)/supSize, 1, int(ceil((randomd(0.01, 0.1)*supCap)/double(supSize))));
if(log)
ai.print("Attempting to build supports: "+ord.count+"x size "+supSize, obj);
supportOrders.insertLast(ord);
}
void findSomethingToDo() {
//See if we should retrofit anything
if(mainWait is null && !spentMoney && gameTime > ai.behavior.flagshipBuildMinGameTime) {
int availMoney = budget.spendable(BT_Military);
int moneyTargetSize = floor(double(availMoney) * ai.behavior.shipSizePerMoney);
//See if one of our fleets is old enough that we can retrofit it
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
FleetAI@ fleet = fleets.fleets[i];
if(fleet.mission !is null && fleet.mission.isActive)
continue;
if(fleet.fleetClass != FC_Combat)
continue;
if(fleet.obj.hasOrders)
continue;
Ship@ ship = cast<Ship>(fleet.obj);
if(ship is null)
continue;
//Don't retrofit free fleets
if(ship.isFree && !ai.behavior.retrofitFreeFleets)
continue;
//Find the factory assigned to this
Factory@ factory;
if(fleet.isHome) {
Region@ reg = fleet.obj.region;
@factory = construction.getFactory(reg);
}
if(factory is null)
continue;
if(factory.busy)
continue;
//Find how large we can make this flagship
const Design@ dsg = ship.blueprint.design;
int targetSize = min(int(moneyTargetSize * 1.2), int(factory.laborToBear(ai) * 1.3 * ai.behavior.shipSizePerLabor));
targetSize = 5 * floor(double(targetSize) / 5.0);
//See if we should retrofit this
int size = ship.blueprint.design.size;
if(size > targetSize)
continue;
double pctDiff = (double(targetSize) / double(size)) - 1.0;
if(pctDiff < ai.behavior.shipRetrofitThreshold)
continue;
DesignTarget@ newDesign = designs.scale(dsg, targetSize);
spentMoney = true;
auto@ retrofit = construction.retrofit(ship);
@mainWait = construction.buildNow(retrofit, factory);
if(log)
ai.print("Retrofitting to size "+targetSize, fleet.obj);
//TODO: This should mark the fleet as occupied for missions while we retrofit
return;
}
//See if we should build a new fleet
Factory@ factory = construction.primaryFactory;
if(factory !is null && !factory.busy) {
//Figure out how large our flagship would be if we built one
factory.aimForLabor((double(moneyTargetSize) / ai.behavior.shipSizePerLabor) / ai.behavior.constructionMaxTime);
int targetSize = min(moneyTargetSize, int(factory.laborToBear(ai) * ai.behavior.shipSizePerLabor));
targetSize = 5 * floor(double(targetSize) / 5.0);
int expMaint = double(targetSize) * ai.behavior.maintenancePerShipSize;
int expCost = double(targetSize) / ai.behavior.shipSizePerMoney;
if(budget.canSpend(BT_Military, expCost, expMaint)) {
//Make sure we're building an adequately sized flagship
uint count = 0;
double avgSize = 0.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
FleetAI@ fleet = fleets.fleets[i];
Ship@ ship = cast<Ship>(fleet.obj);
if(ship !is null && fleet.fleetClass == FC_Combat) {
avgSize += ship.blueprint.design.size;
count += 1;
}
}
if(count != 0)
avgSize /= double(count);
if(count < ai.behavior.maxActiveFleets && targetSize >= avgSize * ai.behavior.flagshipBuildMinAvgSize) {
//Build the flagship
DesignTarget@ design = designs.design(DP_Combat, targetSize,
availMoney, budget.maintainable(BT_Military),
factory.laborToBear(ai),
findSize=true);
@mainWait = construction.buildFlagship(design);
mainWait.maxTime *= 1.5;
spentMoney = true;
if(log)
ai.print("Ordering a new fleet at size "+targetSize);
return;
}
}
}
}
//See if any of our fleets need refilling
//TODO: Aim for labor on the factory so that the supports are built in reasonable time
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
FleetAI@ fleet = fleets.fleets[i];
if(fleet.mission !is null && fleet.mission.isActive)
continue;
if(fleet.fleetClass != FC_Combat)
continue;
if(fleet.obj.hasOrders)
continue;
if(fleet.filled >= 1.0)
continue;
if(hasSupportOrderFor(fleet.obj))
continue;
if(!fleet.isHome)
continue;
//Re-station to our factory if we're idle and need refill without being near a factory
Factory@ f = construction.getFactory(fleet.obj.region);
if(f is null) {
if(fleet.filled < ai.behavior.stagingToFactoryFill && construction.primaryFactory !is null)
stationFleet(fleet, construction.primaryFactory.obj.region);
continue;
}
//Don't order if the factory has support orders, it'll just make everything take longer
if(f !is null && ai.behavior.supportOrderWaitOnFactory && fleet.filled < 0.9 && fleet.obj.SupplyGhost == 0) {
if(f.obj.hasOrderedSupports && f.obj.SupplyUsed < f.obj.SupplyCapacity)
continue;
}
int supCap = fleet.obj.SupplyCapacity;
int supHave = fleet.obj.SupplyUsed - fleet.obj.SupplyGhost;
if(supHave < supCap) {
orderSupportsOn(fleet.obj);
spentMoney = true;
return;
}
}
budget.checkedMilitarySpending = spentMoney;
//TODO: Build defense stations
}
bool hasSupportOrderFor(Object& obj) {
for(uint i = 0, cnt = supportOrders.length; i < cnt; ++i) {
if(supportOrders[i].onObject is obj)
return true;
}
return false;
}
void tick(double time) override {
//Manage our orders for support ships
for(uint i = 0, cnt = supportOrders.length; i < cnt; ++i) {
if(!supportOrders[i].tick(ai, this, time)) {
supportOrders.removeAt(i);
--i; --cnt;
}
}
}
void focusTick(double time) override {
//Find something for us to do
findSomethingToDo();
//If we're far into the budget, spend our money on building supports at our factories
if(budget.Progress > 0.9 && budget.canSpend(BT_Military, 10)) {
for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) {
//TODO: Build on planets in the system if this is full
auto@ f = construction.factories[i];
if(f.obj.SupplyUsed < f.obj.SupplyCapacity && !hasSupportOrderFor(f.obj)) {
orderSupportsOn(f.obj, expire=budget.RemainingTime);
break;
}
}
}
//Check if we should re-station any of our fleets
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.stationed is null) {
Region@ reg = flAI.obj.region;
if(reg !is null && reg.PlanetsMask & ai.mask != 0)
stationFleet(flAI, reg);
}
}
//Make sure all our major factories are considered staging bases
for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) {
auto@ f = construction.factories[i];
if(f.obj.isShip)
continue;
Region@ reg = f.obj.region;
if(reg is null)
continue;
auto@ base = getBase(reg);
if(base is null)
createStaging(reg);
}
//If we don't have any staging bases, make one at a focus
if(stagingBases.length == 0 && development.focuses.length != 0) {
Region@ reg = development.focuses[0].obj.region;
if(reg !is null)
createStaging(reg);
}
//Update our staging bases
for(uint i = 0, cnt = stagingBases.length; i < cnt; ++i) {
auto@ base = stagingBases[i];
if(!base.tick(ai, this, time)) {
stagingBases.removeAt(i);
--i; --cnt;
}
}
}
void turn() override {
//Fleet construction happens in the beginning of the turn, because we want
//to use our entire military budget on it.
if(mainWait !is null) {
if(mainWait.completed) {
@mainWait = null;
}
else if(!mainWait.started) {
if(log)
ai.print("Failed current main construction wait.");
construction.cancel(mainWait);
@mainWait = null;
}
}
spentMoney = false;
}
};
AIComponent@ createMilitary() {
return Military();
}
+354
View File
@@ -0,0 +1,354 @@
// Movement
// --------
// Manages FTL travel modes, expenditure of FTL energy, and general movement patterns.
//
import empire_ai.weasel.WeaselAI;
import oddity_navigation;
import ftl;
enum FTLReturn {
F_Pass,
F_Continue,
F_Done,
F_Kill,
};
class FTL : AIComponent {
uint order(MoveOrder& order) { return F_Pass; }
uint tick(MoveOrder& order, double time) { return F_Pass; }
};
bool getNearPosition(Object& obj, Object& target, vec3d& pos, bool spread = false) {
if(target !is null) {
if(target.isPlanet) {
Planet@ toPl = cast<Planet>(target);
vec3d dir = obj.position - toPl.position;
dir = dir.normalized(toPl.OrbitSize * 0.9);
if(spread)
dir = quaterniond_fromAxisAngle(vec3d_up(), randomd(-0.15,0.15) * pi) * dir;
pos = toPl.position + dir;
return true;
}
else if(obj.hasLeaderAI && (target.isShip || target.isOrbital)) {
vec3d dir = obj.position - target.position;
dir = dir.normalized(obj.getEngagementRange());
pos = target.position + dir;
return true;
}
else {
Region@ reg = cast<Region>(target);
if(reg is null)
@reg = target.region;
if(reg !is null) {
vec3d dir = obj.position - reg.position;
dir = dir.normalized(reg.radius * 0.85);
if(spread)
dir = quaterniond_fromAxisAngle(vec3d_up(), randomd(-0.15,0.15) * pi) * dir;
pos = reg.position + dir;
return true;
}
}
}
return false;
}
bool targetPosition(MoveOrder& ord, vec3d& toPosition) {
if(ord.target !is null) {
return getNearPosition(ord.obj, ord.target, toPosition);
}
else {
toPosition = ord.position;
return true;
}
}
double usableFTL(AI& ai, MoveOrder& ord) {
double storage = ai.empire.FTLCapacity;
double avail = ai.empire.FTLStored;
double reserved = 0.0;
if(ord.priority < MP_Critical)
reserved += ai.behavior.ftlReservePctCritical;
if(ord.priority < MP_Normal)
reserved += ai.behavior.ftlReservePctNormal;
avail -= reserved * storage;
return avail;
}
enum MovePriority {
MP_Background,
MP_Normal,
MP_Critical
};
class MoveOrder {
int id = -1;
uint priority = MP_Normal;
Object@ obj;
Object@ target;
vec3d position;
bool completed = false;
bool failed = false;
void save(Movement& movement, SaveFile& file) {
file << priority;
file << obj;
file << target;
file << position;
file << completed;
file << failed;
}
void load(Movement& movement, SaveFile& file) {
file >> priority;
file >> obj;
file >> target;
file >> position;
file >> completed;
file >> failed;
}
void cancel() {
failed = true;
obj.clearOrders();
}
bool tick(AI& ai, Movement& movement, double time) {
//Check if we still exist
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
failed = true;
return false;
}
uint ftlMode = F_Pass;
if(movement.ftl !is null) {
ftlMode = movement.ftl.tick(this, time);
if(ftlMode == F_Kill)
return false;
if(ftlMode == F_Done)
return true;
}
//Check if we've arrived
if(target !is null) {
if(!target.valid) {
failed = true;
return false;
}
double targDist = target.radius + 45.0 + obj.radius;
if(target.isRegion)
targDist = target.radius * 0.86 + obj.radius;
if(target.position.distanceTo(obj.position) < targDist) {
completed = true;
return false;
}
}
else {
double targDist = obj.radius * 2.0;
if(obj.position.distanceTo(position) < targDist) {
completed = true;
return false;
}
}
//Fail out if our order failed
if(ftlMode == F_Pass) {
if(!obj.hasOrders) {
failed = true;
return false;
}
}
return true;
}
};
class Movement : AIComponent {
int nextMoveOrderId = 0;
array<MoveOrder@> moveOrders;
array<Oddity@> oddities;
FTL@ ftl;
void create() {
@ftl = cast<FTL>(ai.ftl);
}
void save(SaveFile& file) {
file << nextMoveOrderId;
uint cnt = moveOrders.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveMoveOrder(file, moveOrders[i]);
moveOrders[i].save(this, file);
}
}
void load(SaveFile& file) {
file >> nextMoveOrderId;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ ord = loadMoveOrder(file);
if(ord !is null) {
ord.load(this, file);
if(ord.obj !is null)
moveOrders.insertLast(ord);
}
else {
MoveOrder().load(this, file);
}
}
}
array<MoveOrder@> loadIds;
MoveOrder@ loadMoveOrder(SaveFile& file) {
int id = -1;
file >> id;
bool failed = false, completed = false;
file >> failed;
file >> completed;
if(id == -1) {
return null;
}
else {
for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) {
if(loadIds[i].id == id)
return loadIds[i];
}
MoveOrder data;
data.id = id;
data.failed = failed;
data.completed = completed;
loadIds.insertLast(data);
return data;
}
}
void saveMoveOrder(SaveFile& file, MoveOrder@ data) {
int id = -1;
bool failed = false, completed = false;
if(data !is null) {
id = data.id;
failed = data.failed;
completed = data.completed;
}
file << id;
file << failed;
file << completed;
}
void postLoad(AI& ai) {
loadIds.length = 0;
getOddityGates(oddities);
}
array<PathNode@> path;
double getPathDistance(const vec3d& fromPosition, const vec3d& toPosition, double accel = 1.0) {
pathOddityGates(oddities, ai.empire, path, fromPosition, toPosition, accel);
return ::getPathDistance(fromPosition, toPosition, path);
}
double eta(Object& obj, Object& toObject, uint priority = MP_Normal) {
return eta(obj, toObject.position, priority);
}
double eta(Object& obj, const vec3d& position, uint priority = MP_Normal) {
//TODO: Use FTL
//TODO: Path through gates/wormholes
return newtonArrivalTime(obj.maxAcceleration, position - obj.position, obj.velocity);
}
void order(MoveOrder& ord) {
if(ord.target !is null && ord.target is ord.obj.region)
return;
bool madeOrder = false;
if(ftl !is null) {
uint mode = ftl.order(ord);
if(mode == F_Kill || mode == F_Done)
return;
madeOrder = (mode == F_Continue);
}
if(ord.target !is null) {
ord.obj.addGotoOrder(ord.target, append=madeOrder);
ord.position = ord.target.position;
}
else
ord.obj.addMoveOrder(ord.position, append=madeOrder);
}
void add(MoveOrder& ord) {
for(uint i = 0, cnt = moveOrders.length; i < cnt; ++i) {
if(moveOrders[i].obj is ord.obj) {
moveOrders[i].failed = true;
moveOrders.removeAt(i);
--i; --cnt;
}
}
moveOrders.insertLast(ord);
order(ord);
}
MoveOrder@ move(Object& obj, Object& toObject, uint priority = MP_Normal, bool spread = false, bool nearOnly = false) {
if(toObject.isRegion) {
if(obj.region is toObject)
nearOnly = false;
else
nearOnly = true;
}
if(nearOnly) {
vec3d pos;
bool canNear = getNearPosition(obj, toObject, pos, spread);
if(canNear)
return move(obj, pos, priority);
}
MoveOrder ord;
ord.id = nextMoveOrderId++;
@ord.obj = obj;
@ord.target = toObject;
ord.priority = priority;
add(ord);
return ord;
}
MoveOrder@ move(Object& obj, const vec3d& position, uint priority = MP_Normal, bool spread = false) {
MoveOrder ord;
ord.id = nextMoveOrderId++;
@ord.obj = obj;
ord.position = position;
ord.priority = priority;
add(ord);
return ord;
}
void tick(double time) override {
for(uint i = 0, cnt = moveOrders.length; i < cnt; ++i) {
if(!moveOrders[i].tick(ai, this, time)) {
moveOrders.removeAt(i);
--i; --cnt;
}
}
}
void focusTick(double time) override {
//Update our gate navigation list
getOddityGates(oddities);
}
};
AIComponent@ createMovement() {
return Movement();
}
+238
View File
@@ -0,0 +1,238 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Budget;
import empire_ai.weasel.Systems;
from ai.orbitals import AIOrbitals, OrbitalAIHook, OrbitalUse;
import ai.consider;
import orbitals;
import saving;
final class OrbitalAI {
Object@ obj;
const OrbitalModule@ type;
double prevTick = 0;
Object@ around;
void init(AI& ai, Orbitals& orbitals) {
if(obj.isOrbital)
@type = getOrbitalModule(cast<Orbital>(obj).coreModule);
}
void save(Orbitals& orbitals, SaveFile& file) {
file << obj;
}
void load(Orbitals& orbitals, SaveFile& file) {
file >> obj;
}
void remove(AI& ai, Orbitals& orbitals) {
}
void tick(AI& ai, Orbitals& orbitals, double time) {
//Deal with losing planet ownership
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
orbitals.remove(this);
return;
}
//Record what we're orbiting around
if(around !is null) {
if(!obj.isOrbitingAround(around))
@around = obj.getOrbitingAround();
}
else {
if(obj.hasOrbitCenter)
@around = obj.getOrbitingAround();
}
}
};
class Orbitals : AIComponent, AIOrbitals {
Budget@ budget;
Systems@ systems;
array<OrbitalAI@> orbitals;
uint orbIdx = 0;
void create() {
@budget = cast<Budget>(ai.budget);
@systems = cast<Systems>(ai.systems);
//Register specialized orbital types
for(uint i = 0, cnt = getOrbitalModuleCount(); i < cnt; ++i) {
auto@ type = getOrbitalModule(i);
for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) {
auto@ hook = cast<OrbitalAIHook>(type.ai[n]);
if(hook !is null)
hook.register(this, type);
}
}
}
Empire@ get_empire() {
return ai.empire;
}
Considerer@ get_consider() {
return cast<Considerer>(ai.consider);
}
OrbitalAI@ getInSystem(const OrbitalModule@ module, Region@ reg) {
if(module is null)
return null;
for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) {
if(orbitals[i].type is module) {
if(orbitals[i].obj.region is reg)
return orbitals[i];
}
}
return null;
}
bool haveInSystem(const OrbitalModule@ module, Region@ reg) {
if(module is null)
return false;
for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) {
if(orbitals[i].type is module) {
if(orbitals[i].obj.region is reg)
return true;
}
}
return false;
}
bool haveAround(const OrbitalModule@ module, Object@ around) {
if(module is null)
return false;
for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) {
if(orbitals[i].type is module) {
if(orbitals[i].around is around)
return true;
}
}
return false;
}
void registerUse(OrbitalUse use, const OrbitalModule& type) {
switch(use) {
case OU_Shipyard:
@ai.defs.Shipyard = type;
break;
}
}
void save(SaveFile& file) {
uint cnt = orbitals.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = orbitals[i];
saveAI(file, data);
data.save(this, file);
}
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadAI(file);
if(data !is null)
data.load(this, file);
else
OrbitalAI().load(this, file);
}
}
OrbitalAI@ loadAI(SaveFile& file) {
Object@ obj;
file >> obj;
if(obj is null)
return null;
OrbitalAI@ data = getAI(obj);
if(data is null) {
@data = OrbitalAI();
@data.obj = obj;
data.prevTick = gameTime;
orbitals.insertLast(data);
}
return data;
}
void saveAI(SaveFile& file, OrbitalAI@ ai) {
Object@ obj;
if(ai !is null)
@obj = ai.obj;
file << obj;
}
void start() {
checkForOrbitals();
}
void checkForOrbitals() {
auto@ data = ai.empire.getOrbitals();
Object@ obj;
while(receive(data, obj)) {
if(obj !is null)
register(obj);
}
}
void tick(double time) {
double curTime = gameTime;
if(orbitals.length != 0) {
orbIdx = (orbIdx+1) % orbitals.length;
auto@ data = orbitals[orbIdx];
data.tick(ai, this, curTime - data.prevTick);
data.prevTick = curTime;
}
}
uint prevCount = 0;
double checkTimer = 0;
void focusTick(double time) override {
//Check for any newly obtained planets
uint curCount = ai.empire.orbitalCount;
checkTimer += time;
if(curCount != prevCount || checkTimer > 60.0) {
checkForOrbitals();
prevCount = curCount;
checkTimer = 0;
}
}
OrbitalAI@ getAI(Object& obj) {
for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) {
if(orbitals[i].obj is obj)
return orbitals[i];
}
return null;
}
OrbitalAI@ register(Object& obj) {
OrbitalAI@ data = getAI(obj);
if(data is null) {
@data = OrbitalAI();
@data.obj = obj;
data.prevTick = gameTime;
orbitals.insertLast(data);
data.init(ai, this);
}
return data;
}
void remove(OrbitalAI@ data) {
data.remove(ai, this);
orbitals.remove(data);
}
};
AIComponent@ createOrbitals() {
return Orbitals();
}
+755
View File
@@ -0,0 +1,755 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Budget;
import empire_ai.weasel.Systems;
import planets.PlanetSurface;
import void relationRecordLost(AI& ai, Empire& emp, Object@ obj) from "empire_ai.weasel.Relations";
import buildings;
import saving;
final class BuildingRequest {
int id = -1;
PlanetAI@ plAI;
AllocateBudget@ alloc;
const BuildingType@ type;
double expires = INFINITY;
bool built = false;
bool canceled = false;
bool scatter = false;
vec2i builtAt;
BuildingRequest(Budget& budget, const BuildingType@ type, double priority, uint moneyType) {
@this.type = type;
@alloc = budget.allocate(moneyType, type.buildCostEst, type.maintainCostEst, priority=priority);
}
BuildingRequest() {
}
void save(Planets& planets, SaveFile& file) {
planets.saveAI(file, plAI);
planets.budget.saveAlloc(file, alloc);
file.writeIdentifier(SI_Building, type.id);
file << expires;
file << built;
file << canceled;
file << builtAt;
file << scatter;
}
void load(Planets& planets, SaveFile& file) {
@plAI = planets.loadAI(file);
@alloc = planets.budget.loadAlloc(file);
@type = getBuildingType(file.readIdentifier(SI_Building));
file >> expires;
file >> built;
file >> canceled;
file >> builtAt;
if(file >= SV_0153)
file >> scatter;
}
double cachedProgress = 0.0;
double nextProgressCache = 0.0;
double getProgress() {
if(!built)
return 0.0;
if(gameTime < nextProgressCache)
return cachedProgress;
cachedProgress = plAI.obj.getBuildingProgressAt(builtAt.x, builtAt.y);
if(cachedProgress > 0.95)
nextProgressCache = gameTime + 1.0;
else if(cachedProgress < 0.5)
nextProgressCache = gameTime + 30.0;
else
nextProgressCache = gameTime + 10.0;
return cachedProgress;
}
bool tick(AI& ai, Planets& planets, double time) {
if(expires < gameTime) {
if(planets.log)
ai.print(type.name+" build request expired", plAI.obj);
canceled = true;
return false;
}
if(alloc is null || alloc.allocated) {
builtAt = plAI.buildBuilding(ai, planets, type, scatter=scatter);
if(builtAt == vec2i(-1,-1)) {
planets.budget.remove(alloc);
canceled = true;
}
else
built = true;
return false;
}
return true;
}
};
final class PlanetAI {
Planet@ obj;
int targetLevel = 0;
int requestedLevel = 0;
double prevTick = 0;
array<ExportData@>@ resources;
ImportData@ claimedChain;
void init(AI& ai, Planets& planets) {
@resources = planets.resources.availableResource(obj);
}
void save(Planets& planets, SaveFile& file) {
file << obj;
file << targetLevel;
file << requestedLevel;
file << prevTick;
uint cnt = 0;
if(resources !is null)
cnt = resources.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
planets.resources.saveExport(file, resources[i]);
planets.resources.saveImport(file, claimedChain);
}
void load(Planets& planets, SaveFile& file) {
file >> obj;
file >> targetLevel;
file >> requestedLevel;
file >> prevTick;
uint cnt = 0;
file >> cnt;
@resources = array<ExportData@>();
for(uint i = 0; i < cnt; ++i) {
auto@ data = planets.resources.loadExport(file);
if(data !is null)
resources.insertLast(data);
}
@claimedChain = planets.resources.loadImport(file);
}
void remove(AI& ai, Planets& planets) {
if(claimedChain !is null) {
claimedChain.claimedFor = false;
@claimedChain = null;
}
@resources = null;
}
void tick(AI& ai, Planets& planets, double time) {
//Deal with losing planet ownership
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
if(obj.owner !is ai.empire)
relationRecordLost(ai, obj.owner, obj);
planets.remove(this);
return;
}
//Handle when the planet's native resources change
if(obj.nativeResourceCount != resources.length || (resources.length != 0 && obj.primaryResourceId != resources[0].resourceId))
planets.updateResourceList(obj, resources);
//Level up resources if we need them
if(resources.length != 0 && claimedChain is null) {
int resLevel = resources[0].resource.level;
if(resLevel > 0 && !resources[0].resource.exportable)
resLevel += 1;
if(targetLevel < resLevel) {
//See if we need it for anything first
@claimedChain = planets.resources.findUnclaimed(resources[0]);
if(claimedChain !is null)
claimedChain.claimedFor = true;
//Chain the levelup before what needs it
planets.requestLevel(this, resLevel, before=claimedChain);
}
}
//Request imports if the planet needs to level up
if(targetLevel > requestedLevel) {
int nextLevel = min(targetLevel, min(obj.resourceLevel, requestedLevel)+1);
if(nextLevel != requestedLevel) {
planets.resources.organizeImports(obj, nextLevel);
requestedLevel = nextLevel;
}
}
else if(targetLevel < requestedLevel) {
planets.resources.organizeImports(obj, targetLevel);
requestedLevel = targetLevel;
}
}
double get_colonizeWeight() {
if(obj.isColonizing)
return 0.0;
if(obj.level == 0)
return 0.0;
if(!obj.canSafelyColonize)
return 0.0;
double w = 1.0;
double pop = obj.population;
double maxPop = obj.maxPopulation;
if(pop < maxPop-0.1) {
if(obj.resourceLevel > 1 && pop/maxPop < 0.9)
return 0.0;
w *= 0.01 * (pop / maxPop);
}
return w;
}
vec2i buildBuilding(AI& ai, Planets& planets, const BuildingType@ type, bool scatter = true) {
if(type is null || !type.canBuildOn(obj))
return vec2i(-1,-1);
if(planets.log)
ai.print("Attempt to construct "+type.name, obj);
PlanetSurface@ surface = planets.surface;
receive(obj.getPlanetSurface(), surface);
//Find the best place to build this building
int bestPenalty = INT_MAX;
int possibs = 0;
vec2i best;
vec2i center = vec2i(type.getCenter());
for(int x = 0, w = surface.size.x; x < w; ++x) {
for(int y = 0, h = surface.size.y; y < h; ++y) {
vec2i pos(x, y);
bool valid = true;
int penalty = 0;
for(int xoff = 0; xoff < int(type.size.x); ++xoff) {
for(int yoff = 0; yoff < int(type.size.y); ++yoff) {
vec2i rpos = pos - center + vec2i(xoff, yoff);
if(rpos.x < 0 || rpos.y < 0 || rpos.x >= w || rpos.y >= h) {
valid = false;
break;
}
auto@ biome = surface.getBiome(rpos.x, rpos.y);
if(biome is null || !biome.buildable) {
valid = false;
break;
}
uint flags = surface.getFlags(rpos.x, rpos.y);
if(flags & SuF_Usable == 0) {
bool affinity = false;
if(type.buildAffinities.length != 0) {
for(uint i = 0, cnt = type.buildAffinities.length; i < cnt; ++i) {
if(biome is type.buildAffinities[i].biome) {
affinity = true;
break;
}
}
}
if(!affinity && type.tileBuildCost > 0) {
penalty += 1;
if(biome.buildCost > 1.0)
penalty += ceil((biome.buildCost - 1.0) / 0.1);
}
affinity = false;
if(type.maintainAffinities.length != 0) {
for(uint i = 0, cnt = type.maintainAffinities.length; i < cnt; ++i) {
if(biome is type.maintainAffinities[i].biome) {
affinity = true;
break;
}
}
}
if(!affinity && type.tileMaintainCost > 0)
penalty += 2;
}
auto@ bld = surface.getBuilding(rpos.x, rpos.y);
if(bld !is null) {
if(bld.type.civilian) {
penalty += 2;
}
else {
valid = false;
break;
}
}
}
if(!valid)
break;
}
if(valid) {
if(penalty < bestPenalty) {
possibs = 1;
bestPenalty = penalty;
best = pos;
}
else if(penalty == bestPenalty && scatter) {
possibs += 1;
if(randomd() < 1.0 / double(possibs))
best = pos;
}
}
}
}
if(bestPenalty != INT_MAX) {
if(planets.log)
ai.print("Construct "+type.name+" at "+best+" with penalty "+bestPenalty, obj);
obj.buildBuilding(type.id, best);
return best;
}
if(planets.log)
ai.print("Could not find place to construct "+type.name, obj);
return vec2i(-1,-1);
}
}
final class PotentialSource {
Planet@ pl;
double weight = 0;
};
final class AsteroidData {
Asteroid@ asteroid;
array<ExportData@>@ resources;
void save(Planets& planets, SaveFile& file) {
file << asteroid;
uint cnt = 0;
if(resources !is null)
cnt = resources.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
planets.resources.saveExport(file, resources[i]);
}
void load(Planets& planets, SaveFile& file) {
file >> asteroid;
uint cnt = 0;
file >> cnt;
if(cnt != 0)
@resources = array<ExportData@>();
for(uint i = 0; i < cnt; ++i) {
auto@ res = planets.resources.loadExport(file);
if(res !is null)
resources.insertLast(res);
}
}
bool tick(AI& ai, Planets& planets) {
if(asteroid is null || !asteroid.valid || asteroid.owner !is ai.empire) {
planets.resources.killImportsTo(asteroid);
planets.resources.killResourcesFrom(asteroid);
return false;
}
if(resources is null) {
@resources = planets.resources.availableResource(asteroid);
}
else {
if(asteroid.nativeResourceCount != resources.length || (resources.length != 0 && asteroid.primaryResourceId != resources[0].resourceId))
planets.updateResourceList(asteroid, resources);
}
return true;
}
};
class Planets : AIComponent {
Resources@ resources;
Budget@ budget;
Systems@ systems;
PlanetSurface surface;
array<AsteroidData@> ownedAsteroids;
array<PlanetAI@> planets;
array<PlanetAI@> bumped;
uint planetIdx = 0;
array<BuildingRequest@> building;
int nextBuildingRequestId = 0;
void create() {
@resources = cast<Resources>(ai.resources);
@budget = cast<Budget>(ai.budget);
@systems = cast<Systems>(ai.systems);
}
void save(SaveFile& file) {
file << nextBuildingRequestId;
uint cnt = planets.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ plAI = planets[i];
saveAI(file, plAI);
plAI.save(this, file);
}
cnt = building.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveBuildingRequest(file, building[i]);
building[i].save(this, file);
}
cnt = ownedAsteroids.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
ownedAsteroids[i].save(this, file);
}
void load(SaveFile& file) {
file >> nextBuildingRequestId;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ plAI = loadAI(file);
if(plAI !is null)
plAI.load(this, file);
else
PlanetAI().load(this, file);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ req = loadBuildingRequest(file);
if(req !is null) {
req.load(this, file);
building.insertLast(req);
}
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
AsteroidData data;
data.load(this, file);
if(data.asteroid !is null)
ownedAsteroids.insertLast(data);
}
}
PlanetAI@ loadAI(SaveFile& file) {
Planet@ obj;
file >> obj;
if(obj is null)
return null;
PlanetAI@ plAI = getAI(obj);
if(plAI is null) {
@plAI = PlanetAI();
@plAI.obj = obj;
plAI.prevTick = gameTime;
planets.insertLast(plAI);
}
return plAI;
}
void saveAI(SaveFile& file, PlanetAI@ ai) {
Planet@ pl;
if(ai !is null)
@pl = ai.obj;
file << pl;
}
array<BuildingRequest@> loadIds;
BuildingRequest@ loadBuildingRequest(int id) {
if(id == -1)
return null;
for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) {
if(loadIds[i].id == id)
return loadIds[i];
}
BuildingRequest data;
data.id = id;
loadIds.insertLast(data);
return data;
}
BuildingRequest@ loadBuildingRequest(SaveFile& file) {
int id = -1;
file >> id;
if(id == -1)
return null;
else
return loadBuildingRequest(id);
}
void saveBuildingRequest(SaveFile& file, BuildingRequest@ data) {
int id = -1;
if(data !is null)
id = data.id;
file << id;
}
void postLoad(AI& ai) {
loadIds.length = 0;
}
void start() {
checkForPlanets();
}
void checkForPlanets() {
auto@ data = ai.empire.getPlanets();
Object@ obj;
while(receive(data, obj)) {
Planet@ pl = cast<Planet>(obj);
if(pl !is null)
register(cast<Planet>(obj));
}
}
uint roidIdx = 0;
void tick(double time) {
double curTime = gameTime;
if(planets.length != 0) {
planetIdx = (planetIdx+1) % planets.length;
auto@ plAI = planets[planetIdx];
plAI.tick(ai, this, curTime - plAI.prevTick);
plAI.prevTick = curTime;
}
for(int i = bumped.length-1; i >= 0; --i) {
auto@ plAI = bumped[i];
double tickTime = curTime - plAI.prevTick;
if(tickTime != 0) {
plAI.tick(ai, this, tickTime);
plAI.prevTick = curTime;
}
}
bumped.length = 0;
if(ownedAsteroids.length != 0) {
roidIdx = (roidIdx+1) % ownedAsteroids.length;
if(!ownedAsteroids[roidIdx].tick(ai, this))
ownedAsteroids.removeAt(roidIdx);
}
//Construct any buildings we are waiting on
for(uint i = 0, cnt = building.length; i < cnt; ++i) {
if(!building[i].tick(ai, this, time)) {
building.removeAt(i);
--i; --cnt;
break;
}
}
}
uint prevCount = 0;
double checkTimer = 0;
uint sysIdx = 0, ownedIdx = 0;
void focusTick(double time) override {
//Check for any newly obtained planets
uint curCount = ai.empire.planetCount;
checkTimer += time;
if(curCount != prevCount || checkTimer > 60.0) {
checkForPlanets();
prevCount = curCount;
checkTimer = 0;
}
//Find any asteroids we've gained
if(systems.all.length != 0) {
sysIdx = (sysIdx+1) % systems.all.length;
auto@ sys = systems.all[sysIdx];
for(uint i = 0, cnt = sys.asteroids.length; i < cnt; ++i)
register(sys.asteroids[i]);
}
if(systems.owned.length != 0) {
ownedIdx = (ownedIdx+1) % systems.owned.length;
auto@ sys = systems.owned[ownedIdx];
for(uint i = 0, cnt = sys.asteroids.length; i < cnt; ++i)
register(sys.asteroids[i]);
}
}
void bump(Planet@ pl) {
if(pl !is null)
bump(getAI(pl));
}
void bump(PlanetAI@ plAI) {
if(plAI !is null)
bumped.insertLast(plAI);
}
PlanetAI@ getAI(Planet& obj) {
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
if(planets[i].obj is obj)
return planets[i];
}
return null;
}
PlanetAI@ register(Planet& obj) {
PlanetAI@ plAI = getAI(obj);
if(plAI is null) {
@plAI = PlanetAI();
@plAI.obj = obj;
plAI.prevTick = gameTime;
planets.insertLast(plAI);
plAI.init(ai, this);
}
return plAI;
}
AsteroidData@ register(Asteroid@ obj) {
if(obj is null || !obj.valid || obj.owner !is ai.empire)
return null;
for(uint i = 0, cnt = ownedAsteroids.length; i < cnt; ++i) {
if(ownedAsteroids[i].asteroid is obj)
return ownedAsteroids[i];
}
AsteroidData data;
@data.asteroid = obj;
ownedAsteroids.insertLast(data);
if(log)
ai.print("Detected asteroid: "+obj.name, obj.region);
return data;
}
void remove(PlanetAI@ plAI) {
resources.killImportsTo(plAI.obj);
resources.killResourcesFrom(plAI.obj);
plAI.remove(ai, this);
planets.remove(plAI);
bumped.remove(plAI);
}
void requestLevel(PlanetAI@ plAI, int toLevel, ImportData@ before = null) {
if(plAI is null)
return;
plAI.targetLevel = toLevel;
if(before !is null) {
for(int lv = max(plAI.requestedLevel, 1); lv <= toLevel; ++lv)
resources.organizeImports(plAI.obj, lv, before);
plAI.requestedLevel = toLevel;
}
else {
bump(plAI);
}
}
BuildingRequest@ requestBuilding(PlanetAI@ plAI, const BuildingType@ type, double priority = 1.0, double expire = INFINITY, bool scatter = true, uint moneyType = BT_Development) {
if(plAI is null)
return null;
if(log)
ai.print("Requested building of type "+type.name, plAI.obj);
BuildingRequest req(budget, type, priority, moneyType);
req.scatter = scatter;
req.id = nextBuildingRequestId++;
req.expires = gameTime + expire;
@req.plAI = plAI;
building.insertLast(req);
return req;
}
bool isBuilding(Planet@ planet, const BuildingType@ type) {
for(uint i = 0, cnt = building.length; i < cnt; ++i) {
if(building[i].type is type && building[i].plAI.obj is planet)
return true;
}
return false;
}
void getColonizeSources(array<PotentialSource@>& sources) {
sources.length = 0;
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
auto@ plAI = planets[i];
if(!plAI.obj.valid)
continue;
double w = plAI.colonizeWeight;
if(w == 0)
continue;
if(plAI.obj.owner !is ai.empire)
continue;
PotentialSource src;
@src.pl = planets[i].obj;
src.weight = w;
sources.insertLast(src);
}
}
array<ExportData@> newResources;
array<ExportData@> removedResources;
array<Resource> checkResources;
void updateResourceList(Object@ obj, array<ExportData@>& resList) {
newResources.length = 0;
removedResources = resList;
checkResources.syncFrom(obj.getNativeResources());
uint nativeCnt = checkResources.length;
for(uint i = 0; i < nativeCnt; ++i) {
int id = checkResources[i].id;
bool found = false;
for(uint n = 0, ncnt = removedResources.length; n < ncnt; ++n) {
if(removedResources[n].resourceId == id) {
removedResources.removeAt(n);
found = true;
break;
}
}
if(!found) {
auto@ type = checkResources[i].type;
auto@ res = resources.availableResource(obj, type, id);
if(i == 0)
resList.insertAt(0, res);
else
resList.insertLast(res);
newResources.insertLast(res);
}
else if(i == 0 && resList.length > 1 && resList[0].resourceId != id) {
for(uint n = 0, ncnt = resList.length; n < ncnt; ++n) {
if(resList[n].resourceId == id) {
auto@ res = resList[n];
resList.removeAt(n);
resList.insertAt(0, res);
break;
}
}
}
}
//Get rid of resources we no longer have
for(uint i = 0, cnt = removedResources.length; i < cnt; ++i) {
resources.removeResource(removedResources[i]);
resList.remove(removedResources[i]);
}
//Tell the resources component to try to immediately use the new resources
for(uint i = 0, cnt = newResources.length; i < cnt; ++i)
resources.checkReplaceCurrent(newResources[i]);
}
};
AIComponent@ createPlanets() {
return Planets();
}
@@ -0,0 +1,865 @@
// Relations
// ---------
// Manages the relationships we have with other empires, including treaties, hatred, and wars.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Intelligence;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Planets;
import warandpeace;
import influence;
from influence_global import activeTreaties, influenceLock, joinTreaty, leaveTreaty, declineTreaty, Treaty, sendPeaceOffer, createTreaty, offerSurrender, demandSurrender, leaveTreatiesWith;
enum HateType {
HT_SystemPresence,
HT_FleetPresence,
HT_COUNT
};
class Hate {
uint type;
double amount = 0.0;
Object@ obj;
SystemAI@ sys;
void save(Relations& relations, SaveFile& file) {
file << type;
file << amount;
file << obj;
relations.systems.saveAI(file, sys);
}
void load(Relations& relations, SaveFile& file) {
file >> type;
file >> amount;
file >> obj;
@sys = relations.systems.loadAI(file);
}
bool get_valid() {
if(type == HT_SystemPresence)
return sys !is null;
if(type == HT_FleetPresence)
return obj !is null && sys !is null;
return true;
}
bool update(AI& ai, Relations& relations, Relation& rel, double time) {
if(type == HT_SystemPresence) {
amount = 0.25;
if(sys.seenPresent & rel.empire.mask == 0)
return false;
if(sys.seenPresent & ai.empire.mask == 0)
return false;
}
else if(type == HT_FleetPresence) {
if(!obj.valid || obj.owner !is rel.empire)
return false;
if(sys.seenPresent & ai.empire.mask == 0)
return false;
if(obj.region !is sys.obj)
return false;
if(obj.getFleetStrength() < 1000.0)
amount = 0.1;
else
amount = 0.5;
}
rel.hate += amount * time;
return true;
}
string dump() {
switch(type) {
case HT_SystemPresence:
return "system presence in "+sys.obj.name;
case HT_FleetPresence:
return "fleet presence "+obj.name+" in "+sys.obj.name;
}
return "unknown";
}
};
final class Relation {
Empire@ empire;
//Whether we've met this empire
bool contacted = false;
//Whether we're currently at war
bool atWar = false;
//Last time we tried to make peace
double lastPeaceTry = 0;
//Whether this is our war of aggression
bool aggressive = false;
//Whether this is our ally
bool allied = false;
//Our relationship data
double hate = 0.0;
array<Hate@> hates;
//Masks
uint borderedTo = 0;
uint alliedTo = 0;
//Whether we consider this empire a threat to us
bool isThreat = false;
//How much we would value having this empire as an ally
double allyValue = 0.0;
//How much we think we can beat this empire and all its allies
double defeatable = 0.0;
//Relative strength of this empire to us in a vacuum
double relStrength = 0.0;
//How much we've lost to them in this recent war
double warLost = 0.0;
//How much we've taken from them in this recent war
double warTaken = 0.0;
void save(Relations& relations, SaveFile& file) {
file << contacted;
file << atWar;
file << aggressive;
file << allied;
file << hate;
uint cnt = hates.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
hates[i].save(relations, file);
file << borderedTo;
file << alliedTo;
file << isThreat;
file << allyValue;
file << defeatable;
file << relStrength;
file << warTaken;
file << warLost;
file << lastPeaceTry;
}
void load(Relations& relations, SaveFile& file) {
file >> contacted;
file >> atWar;
file >> aggressive;
file >> allied;
file >> hate;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Hate ht;
ht.load(relations, file);
if(ht.valid)
hates.insertLast(ht);
}
file >> borderedTo;
file >> alliedTo;
file >> isThreat;
file >> allyValue;
file >> defeatable;
file >> relStrength;
file >> warTaken;
file >> warLost;
file >> lastPeaceTry;
}
void trackSystem(AI& ai, Relations& relations, SystemAI@ sys) {
for(uint i = 0, cnt = hates.length; i < cnt; ++i) {
auto@ ht = hates[i];
if(ht.type != HT_SystemPresence)
continue;
if(ht.sys is sys)
return;
}
Hate ht;
ht.type = HT_SystemPresence;
@ht.sys = sys;
hates.insertLast(ht);
if(relations.log)
ai.print("Gain hate of "+empire.name+": "+ht.dump());
}
void trackFleet(AI& ai, Relations& relations, FleetIntel@ intel, SystemAI@ sys) {
for(uint i = 0, cnt = hates.length; i < cnt; ++i) {
auto@ ht = hates[i];
if(ht.type != HT_FleetPresence)
continue;
if(ht.obj is intel.obj && ht.sys is sys)
return;
}
Hate ht;
ht.type = HT_FleetPresence;
@ht.sys = sys;
@ht.obj = intel.obj;
hates.insertLast(ht);
if(relations.log)
ai.print("Gain hate of "+empire.name+": "+ht.dump());
}
void tick(AI& ai, Relations& relations, double time) {
if(!contacted) {
if(ai.empire.ContactMask & empire.mask != 0)
contacted = true;
}
bool curWar = ai.empire.isHostile(empire);
if(curWar != atWar)
atWar = curWar;
if(!atWar) {
aggressive = false;
warLost = 0.0;
warTaken = 0.0;
lastPeaceTry = 0.0;
}
borderedTo = relations.intelligence.get(empire).borderedTo;
alliedTo = empire.mask | empire.mutualDefenseMask | empire.ForcedPeaceMask.value;
defeatable = relations.intelligence.defeatability(alliedTo, ai.mask | ai.allyMask);
relStrength = relations.intelligence.defeatability(ai.mask, empire.mask);
isThreat = defeatable < 0.8 && (borderedTo & ai.empire.mask) != 0;
//Check how valuable of an ally this empire would make
allyValue = 1.0;
for(uint i = 0, cnt = relations.relations.length; i < cnt; ++i) {
auto@ other = relations.relations[i];
if(other is null || other is this || other.empire is null)
continue;
if(other.borderedTo & empire.mask == 0)
continue;
if(alliedTo & empire.mask != 0)
continue;
if(other.atWar)
allyValue *= 3.0;
else if(other.isThreat)
allyValue *= 1.5;
}
//Become aggressive here if we're aggressive against one of its allies
if(atWar && !aggressive) {
for(uint i = 0, cnt = relations.relations.length; i < cnt; ++i) {
auto@ other = relations.relations[i];
if(other is null || other is this || other.empire is null)
continue;
if(other.aggressive && this.alliedTo & other.empire.mask != 0) {
aggressive = true;
break;
}
}
}
//Update our hatred of them
for(uint i = 0, cnt = hates.length; i < cnt; ++i) {
if(!hates[i].update(ai, relations, this, time)) {
if(relations.log)
ai.print("Hate with "+empire.name+" expired: "+hates[i].dump());
hates.removeAt(i);
--i; --cnt;
}
}
hate *= pow(ai.behavior.hateDecayRate, time / 60.0);
if(ai.behavior.biased && !empire.isAI)
hate += 1.0;
//If we really really hate them, declare war
if(!atWar || !aggressive) {
double reqHate = 100.0;
if(defeatable < 1.0)
reqHate *= sqr(1.0 / defeatable);
reqHate *= pow(2.0, relations.warCount());
if(hate > reqHate && (!ai.behavior.passive || atWar) && defeatable >= ai.behavior.hatredWarOverkill) {
//Make sure our other requirements for war are met
if(relations.fleets.haveCombatReadyFleets()) {
if(canDeclareWar(ai)) {
if(relations.log)
ai.print("Declaring hatred war on "+empire.name+": "+hate+" / "+reqHate);
if(atWar)
aggressive = true;
else
relations.declareWar(empire, aggressive=true);
}
}
}
}
}
bool isAllied(AI& ai) {
return alliedTo & ai.empire.mask != 0;
}
bool canDeclareWar(AI& ai) {
if(empire.SubjugatedBy !is null)
return false;
if(ai.empire.SubjugatedBy !is null)
return false;
if(!contacted)
return false;
if(ai.empire.ForcedPeaceMask & empire.mask != 0)
return false;
return true;
}
};
class Relations : AIComponent {
Intelligence@ intelligence;
Systems@ systems;
Fleets@ fleets;
Planets@ planets;
array<Relation@> relations;
bool expansionLocked = false;
double treatyRespond = 0;
double treatyConsider = 0;
double warPoints = 0.0;
void create() {
@intelligence = cast<Intelligence>(ai.intelligence);
@fleets = cast<Fleets>(ai.fleets);
@systems = cast<Systems>(ai.systems);
@planets = cast<Planets>(ai.planets);
}
void start() {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(emp is ai.empire)
continue;
if(!emp.major)
continue;
Relation r;
@r.empire = emp;
if(relations.length <= uint(emp.index))
relations.length = uint(emp.index)+1;
@relations[emp.index] = r;
}
}
void save(SaveFile& file) {
uint cnt = relations.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
if(relations[i] is null) {
file.write0();
continue;
}
file.write1();
relations[i].save(this, file);
}
file << expansionLocked;
file << treatyRespond;
file << treatyConsider;
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
relations.length = cnt;
for(uint i = 0; i < cnt; ++i) {
if(!file.readBit())
continue;
@relations[i] = Relation();
@relations[i].empire = getEmpire(i);
relations[i].load(this, file);
}
file >> expansionLocked;
file >> treatyRespond;
file >> treatyConsider;
}
double getPointValue(Object@ obj) {
if(obj is null)
return 0.0;
if(obj.isShip) {
auto@ dsg = cast<Ship>(obj).blueprint.design;
if(dsg !is null)
return dsg.size;
}
else if(obj.isPlanet) {
return 10.0 * pow(3.0, double(obj.level));
}
return 0.0;
}
void recordTakenFrom(Empire& emp, double amount) {
if(!emp.valid)
return;
if(log)
ai.print("Taken value "+amount+" from "+emp.name);
auto@ rel = get(emp);
if(rel !is null)
rel.warTaken += amount;
}
void recordLostTo(Empire& emp, double amount) {
if(!emp.valid)
return;
if(log)
ai.print("Lost value "+amount+" to "+emp.name);
auto@ rel = get(emp);
if(rel !is null)
rel.warLost += amount;
}
void recordLostTo(Empire& emp, Object@ obj) {
recordLostTo(emp, getPointValue(obj));
}
void recordTakenFrom(Empire& emp, Object@ obj) {
recordTakenFrom(emp, getPointValue(obj));
}
Relation@ get(Empire@ emp) {
if(emp is null)
return null;
if(!emp.major)
return null;
if(uint(emp.index) >= relations.length)
return null;
return relations[emp.index];
}
bool isFightingWar(bool aggressive = false) {
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
if(relations[i] is null)
continue;
if(relations[i].atWar) {
if(!aggressive || relations[i].aggressive)
return true;
}
}
return false;
}
uint warCount() {
uint count = 0;
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
if(relations[i] is null)
continue;
if(relations[i].atWar)
count += 1;
}
return count;
}
void declareWar(Empire@ onEmpire, bool aggressive = true) {
//Break all treaties
leaveTreatiesWith(ai.empire, onEmpire.mask);
//Declare actual war
auto@ rel = get(onEmpire);
rel.aggressive = aggressive;
::declareWar(ai.empire, onEmpire);
}
uint sysIdx = 0;
uint relIdx = 0;
void tick(double time) override {
//Find new ways to hate other empires
if(systems.all.length != 0) {
sysIdx = (sysIdx+1) % systems.all.length;
auto@ sys = systems.all[sysIdx];
if(sys.owned && sys.seenPresent & ~ai.mask != 0) {
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
auto@ rel = relations[i];
if(rel is null)
continue;
if(sys.seenPresent & rel.empire.mask != 0)
rel.trackSystem(ai, this, sys);
}
}
}
if(relations.length != 0) {
relIdx = (relIdx+1) % relations.length;
auto@ rel = relations[relIdx];
auto@ itl = intelligence.intel[relIdx];
if(rel !is null && itl !is null) {
for(uint i = 0, cnt = itl.fleets.length; i < cnt; ++i) {
if(!itl.fleets[i].visible)
continue;
auto@ inSys = systems.getAI(itl.fleets[i].obj.region);
if(inSys !is null && inSys.owned)
rel.trackFleet(ai, this, itl.fleets[i], inSys);
}
}
}
}
uint relInd = 0;
void focusTick(double time) override {
//Update our current relations
if(relations.length != 0) {
relInd = (relInd+1) % relations.length;
if(relations[relInd] !is null)
relations[relInd].tick(ai, this, time);
}
//Compute how many points we have in total that can be taken
warPoints = 0.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
Ship@ ship = cast<Ship>(fleets.fleets[i].obj);
if(ship !is null && ship.valid && ship.owner is ai.empire)
warPoints += getPointValue(ship);
}
for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) {
Planet@ pl = cast<Planet>(planets.planets[i].obj);
if(pl !is null && pl.valid && pl.owner is ai.empire)
warPoints += getPointValue(pl);
}
//Become aggressive if we cannot expand anywhere anymore
expansionLocked = true;
for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) {
auto@ sys = systems.outsideBorder[i];
if(sys.seenPresent == 0) {
bool havePlanets = false;
for(uint n = 0, ncnt = sys.planets.length; n < ncnt; ++n) {
if(sys.planets[n].quarantined)
continue;
havePlanets = true;
break;
}
if(havePlanets) {
expansionLocked = false;
break;
}
}
}
//Deal with our AI's aggressive behavior
if(ai.behavior.aggressive || (expansionLocked && ai.behavior.aggressiveWhenBoxedIn && !ai.behavior.passive)) {
//Try to make sure we're always fighting at least one aggressive war
bool atWar = false, aggro = false;
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
if(relations[i] is null)
continue;
if(relations[i].atWar) {
atWar = true;
if(relations[i].aggressive)
aggro = true;
}
}
if(!atWar) {
if(fleets.haveCombatReadyFleets()) {
//Declare war on people who share our border and are defeatable
Empire@ best;
double bestWeight = 0;
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
auto@ rel = relations[i];
if(rel is null)
continue;
auto@ intel = intelligence.get(rel.empire);
if(intel.shared.length == 0 && intel.theirBorder.length == 0)
continue;
if(!rel.canDeclareWar(ai))
continue;
if(!ai.behavior.biased || rel.empire.isAI) {
if(rel.defeatable < ai.behavior.aggressiveWarOverkill)
continue;
}
double w = rel.defeatable * rel.hate;
if(rel.isAllied(ai))
w *= 0.01;
if(w > bestWeight) {
bestWeight = w;
@best = rel.empire;
}
}
if(best !is null) {
if(log)
ai.print("Declare aggressive war against "+best.name);
declareWar(best, aggressive=true);
}
}
}
else if(!aggro) {
//Start going aggressive on someone defeatable we are already at war with
Empire@ best;
double bestWeight = 0;
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
auto@ rel = relations[i];
if(rel is null)
continue;
if(!rel.atWar)
continue;
if(rel.defeatable < ai.behavior.aggressiveWarOverkill)
continue;
double w = rel.defeatable * rel.hate;
if(w > bestWeight) {
bestWeight = w;
@best = rel.empire;
}
}
if(best !is null) {
//Go aggressive then!
if(log)
ai.print("Become aggressive against "+best.name);
get(best).aggressive = true;
}
}
}
//Respond to treaties
if(gameTime > treatyRespond) {
treatyRespond = gameTime + randomd(8.0, 20.0);
Treaty@ respondTreaty;
{
Lock lck(influenceLock);
for(uint i = 0, cnt = activeTreaties.length; i < cnt; ++i) {
auto@ trty = activeTreaties[i];
if(trty.inviteMask & ai.mask != 0 && trty.presentMask & ai.mask == 0) {
Message msg;
trty.write(msg);
@respondTreaty = Treaty();
respondTreaty.read(msg);
break;
}
}
}
if(respondTreaty !is null) {
bool accept = false;
Empire@ invitedBy = respondTreaty.leader;
if(invitedBy is null)
@invitedBy = respondTreaty.joinedEmpires[0];
Relation@ other = get(invitedBy);
if(respondTreaty.hasClause("SubjugateClause")) {
//This is a surrender offer or demand
if(respondTreaty.leader is null) {
//An offer
accept = true;
}
else if(respondTreaty.joinedEmpires.length != 0) {
//A demand
auto@ other = get(respondTreaty.joinedEmpires[0]);
if(other.defeatable < ai.behavior.surrenderMinStrength) {
if(warPoints / (other.warLost + warPoints) < ai.behavior.acceptSurrenderRatio) {
accept = true;
}
}
}
}
else if(respondTreaty.hasClause("MutualDefenseClause")
|| respondTreaty.hasClause("AllianceClause")) {
//This is an alliance treaty
if(other.atWar) {
//Need to be at peace first
accept = false;
}
else {
//See if this empire can help us defeat someone
if(other.allyValue >= 3.0 && other.relStrength >= 0.5)
accept = true;
}
}
else if(respondTreaty.hasClause("PeaceClause")) {
//This is a peace offering
accept = shouldPeace(other);
}
else if(respondTreaty.hasClause("VisionClause")) {
//This is a vision sharing treaty
if(other !is null)
accept = !other.isThreat && !other.atWar && other.hate <= 50.0;
}
else if(respondTreaty.hasClause("TradeClause")) {
//This is a trade sharing treaty
if(other !is null)
accept = !other.isThreat && !other.atWar && other.hate <= 10.0;
}
if(accept) {
if(log)
ai.print("Accept treaty: "+respondTreaty.name, emp=invitedBy);
joinTreaty(ai.empire, respondTreaty.id);
}
else {
if(log)
ai.print("Reject treaty: "+respondTreaty.name, emp=invitedBy);
declineTreaty(ai.empire, respondTreaty.id);
}
}
}
//See if we should send a treaty over to someone
if(gameTime > treatyConsider) {
treatyConsider = gameTime + randomd(100.0, 300.0);
uint offset = randomi(0, relations.length-1);
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
auto@ other = relations[(i+offset) % cnt];
if(other is null)
continue;
//Check if we should make peace with them
if(other.atWar) {
if(other.lastPeaceTry < gameTime - 600.0 && shouldPeace(other, isOffer=true)) {
if(other.aggressive)
other.aggressive = false;
if(log)
ai.print("Send peace offer.", emp=other.empire);
other.lastPeaceTry = gameTime;
sendPeaceOffer(ai.empire, other.empire);
break;
}
}
if(other.atWar) {
//Check if we should try to surrender to them
if(other.defeatable < ai.behavior.surrenderMinStrength) {
if(warPoints / (other.warLost + warPoints) < ai.behavior.offerSurrenderRatio) {
if(log)
ai.print("Send surrender offer.", emp=other.empire);
offerSurrender(ai.empire, other.empire);
break;
}
}
//Check if we should try to demand their surrender
if(other.defeatable >= 1.0 / ai.behavior.surrenderMinStrength && other.warTaken >= warPoints * 0.1) {
if(log)
ai.print("Demand surrender.", emp=other.empire);
demandSurrender(ai.empire, other.empire);
break;
}
}
//Check if we should try to ally with them
if(!other.atWar && !other.isThreat && other.allyValue >= 3.0) {
Treaty treaty;
treaty.addClause(getInfluenceClauseType("AllianceClause"));
treaty.addClause(getInfluenceClauseType("VisionClause"));
treaty.addClause(getInfluenceClauseType("MutualDefenseClause"));
if(treaty.canInvite(ai.empire, other.empire)) {
treaty.inviteMask = other.empire.mask;
//Generate treaty name
string genName;
uint genCount = 0;
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
auto@ reg = getSystem(i).object;
if(reg.TradeMask & (ai.mask | other.empire.mask) != 0) {
genCount += 1;
if(randomd() < 1.0 / double(genCount))
genName = reg.name;
}
}
treaty.name = format(locale::TREATY_NAME_GEN, genName);
if(log)
ai.print("Send alliance offer.", emp=other.empire);
createTreaty(ai.empire, treaty);
}
}
}
}
}
bool shouldPeace(Relation@ other, bool isOffer = false) {
bool accept = false;
if(other.aggressive) {
//We're trying to conquer these people, don't accept peace unless
//we're fighting someone scarier or we're losing
double otherWar = 0.0;
uint otherInd = uint(-1);
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
auto@ rel = relations[i];
if(rel is null || rel is other)
continue;
if(rel.empire.mask & other.alliedTo != 0)
continue;
if(!rel.atWar)
continue;
otherWar = max(otherWar, rel.defeatable);
otherInd = i;
}
if(otherInd != uint(-1) && otherWar < other.defeatable) {
accept = true;
if(!relations[otherInd].aggressive)
relations[otherInd].aggressive = otherWar >= ai.behavior.aggressiveWarOverkill;
}
else if(other.defeatable < 0.25) {
accept = true;
}
}
else {
//We don't have any ~particular qualms with these people, peace should be good
if(!isOffer) {
if(other.defeatable < 0.5 || other.hate < 50.0)
accept = true;
}
}
return accept;
}
void turn() override {
if(log) {
ai.print("Relations Report on Empires:");
ai.print(" war points: "+warPoints);
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
auto@ rel = relations[i];
if(rel is null)
continue;
ai.print(" "+ai.pad(rel.empire.name, 15)
+" war: "+ai.pad(rel.atWar+" / "+rel.aggressive, 15)
+" threat: "+ai.pad(""+rel.isThreat, 8)
+" defeatable: "+ai.pad(toString(rel.defeatable,2), 8)
+" hate: "+ai.pad(toString(rel.hate,0), 8)
+" ally value: "+ai.pad(toString(rel.allyValue,1), 8)
+" taken: "+ai.pad(toString(rel.warTaken,1), 8)
+" lost: "+ai.pad(toString(rel.warLost,1), 8)
);
}
}
}
};
AIComponent@ createRelations() {
return Relations();
}
void relationRecordLost(AI& ai, Empire& emp, Object@ obj) {
cast<Relations>(ai.relations).recordLostTo(emp, obj);
}
+200
View File
@@ -0,0 +1,200 @@
// Research
// --------
// Spends research points to unlock and improve things in the research grid.
//
import empire_ai.weasel.WeaselAI;
import research;
class Research : AIComponent {
TechnologyGrid grid;
array<TechnologyNode@> immediateQueue;
void save(SaveFile& file) {
uint cnt = immediateQueue.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << immediateQueue[i].id;
}
void load(SaveFile& file) {
updateGrid();
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
int id = 0;
file >> id;
for(uint i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
if(grid.nodes[i].id == id) {
immediateQueue.insertLast(grid.nodes[i]);
break;
}
}
}
}
void updateGrid() {
//Receive the full grid from the empire to path on
grid.nodes.length = 0;
DataList@ recvData = ai.empire.getTechnologyNodes();
TechnologyNode@ node = TechnologyNode();
while(receive(recvData, node)) {
grid.nodes.insertLast(node);
@node = TechnologyNode();
}
grid.regenBounds();
}
double getEndPointWeight(const TechnologyType& tech) {
//TODO: Might want to make this configurable by data file
return 1.0;
}
bool isEndPoint(const TechnologyType& tech) {
return tech.cls >= Tech_BigUpgrade;
}
double findResearch(int atIndex, array<TechnologyNode@>& path, array<bool>& visited, bool initial = false) {
if(visited[atIndex])
return 0.0;
visited[atIndex] = true;
auto@ node = grid.nodes[atIndex];
if(!initial) {
if(node.bought)
return 0.0;
if(!node.hasRequirements(ai.empire))
return 0.0;
path.insertLast(node);
if(isEndPoint(node.type))
return getEndPointWeight(node.type);
}
vec2i startPos = node.position;
double totalWeight = 0.0;
array<TechnologyNode@> tmp;
array<TechnologyNode@> chosen;
tmp.reserve(20);
chosen.reserve(20);
for(uint d = 0; d < 6; ++d) {
vec2i otherPos = startPos;
if(grid.doAdvance(otherPos, HexGridAdjacency(d))) {
int otherIndex = grid.getIndex(otherPos);
if(otherIndex != -1) {
tmp.length = 0;
double w = findResearch(otherIndex, tmp, visited);
if(w != 0.0) {
totalWeight += w;
if(randomd() < w / totalWeight) {
chosen = tmp;
}
}
}
}
}
for(uint i = 0, cnt = chosen.length; i < cnt; ++i)
path.insertLast(chosen[i]);
return max(totalWeight, 0.01);
}
void queueNewResearch() {
if(log)
ai.print("Attempted to find new research to queue");
//Update our grid representation
updateGrid();
//Find a good path to do
array<bool> visited(grid.nodes.length, false);
double totalWeight = 0.0;
auto@ path = array<TechnologyNode@>();
auto@ tmp = array<TechnologyNode@>();
path.reserve(20);
tmp.reserve(20);
for(int i = 0, cnt = grid.nodes.length; i < cnt; ++i) {
if(grid.nodes[i].bought) {
tmp.length = 0;
double weight = findResearch(i, tmp, visited, initial=true);
if(weight != 0.0) {
totalWeight += weight;
if(randomd() < weight / totalWeight) {
auto@ swp = path;
@path = tmp;
@tmp = swp;
}
}
}
}
if(path.length != 0) {
for(uint i = 0, cnt = path.length; i < cnt; ++i) {
if(log)
ai.print("Queue research: "+path[i].type.name+" at "+path[i].position);
immediateQueue.insertLast(path[i]);
}
}
}
double immTimer = randomd(10.0, 60.0);
void focusTick(double time) override {
//Queue some new research if we have to
if(immediateQueue.length == 0) {
immTimer -= time;
if(immTimer <= 0.0) {
immTimer = 60.0;
queueNewResearch();
}
}
else {
immTimer = 0.0;
}
//Deal with current queued research
if(immediateQueue.length != 0) {
auto@ node = immediateQueue[0];
if(!receive(ai.empire.getTechnologyNode(node.id), node)) {
immediateQueue.removeAt(0);
}
else if(!node.available || node.bought) {
immediateQueue.removeAt(0);
}
else {
double cost = node.getPointCost(ai.empire);
if(cost == 0) {
//Try it once and then give up
ai.empire.research(node.id, secondary=true);
immediateQueue.removeAt(0);
if(log)
ai.print("Attempt secondary research: "+node.type.name+" at "+node.position);
}
else if(cost <= ai.empire.ResearchPoints) {
//If we have enough to buy it, buy it
ai.empire.research(node.id);
immediateQueue.removeAt(0);
if(log)
ai.print("Purchase research: "+node.type.name+" at "+node.position);
}
}
}
}
};
AIComponent@ createResearch() {
return Research();
}
@@ -0,0 +1,694 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.ImportData;
import resources;
import planet_levels;
import system_pathing;
import systems;
interface RaceResources {
void levelRequirements(Object& obj, int targetLevel, array<ResourceSpec@>& specs);
};
final class Resources : AIComponent {
RaceResources@ race;
array<ImportData@> requested;
array<ImportData@> active;
int nextImportId = 0;
array<ExportData@> available;
array<ExportData@> used;
int nextExportId = 0;
void create() {
@race = cast<RaceResources>(ai.race);
}
void save(SaveFile& file) {
file << nextImportId;
file << nextExportId;
uint cnt = 0;
cnt = requested.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveImport(file, requested[i]);
file << requested[i];
}
cnt = active.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveImport(file, active[i]);
file << active[i];
}
cnt = available.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveExport(file, available[i]);
file << available[i];
saveImport(file, available[i].request);
}
cnt = used.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveExport(file, used[i]);
file << used[i];
saveImport(file, used[i].request);
}
}
void load(SaveFile& file) {
file >> nextImportId;
file >> nextExportId;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadImport(file);
file >> data;
requested.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadImport(file);
file >> data;
active.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadExport(file);
file >> data;
@data.request = loadImport(file);
available.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadExport(file);
file >> data;
@data.request = loadImport(file);
used.insertLast(data);
}
}
array<ImportData@> importIds;
ImportData@ loadImport(int id) {
if(id == -1)
return null;
for(uint i = 0, cnt = importIds.length; i < cnt; ++i) {
if(importIds[i].id == id)
return importIds[i];
}
ImportData data;
data.id = id;
importIds.insertLast(data);
return data;
}
ImportData@ loadImport(SaveFile& file) {
int id = -1;
file >> id;
if(id == -1)
return null;
else
return loadImport(id);
}
void saveImport(SaveFile& file, ImportData@ data) {
int id = -1;
if(data !is null)
id = data.id;
file << id;
}
array<ExportData@> exportIds;
ExportData@ loadExport(int id) {
if(id == -1)
return null;
for(uint i = 0, cnt = exportIds.length; i < cnt; ++i) {
if(exportIds[i].id == id)
return exportIds[i];
}
ExportData data;
data.id = id;
exportIds.insertLast(data);
return data;
}
ExportData@ loadExport(SaveFile& file) {
int id = -1;
file >> id;
if(id == -1)
return null;
else
return loadExport(id);
}
void saveExport(SaveFile& file, ExportData@ data) {
int id = -1;
if(data !is null)
id = data.id;
file << id;
}
void postLoad(AI& ai) {
importIds.length = 0;
exportIds.length = 0;
}
void start() {
focusTick(0);
}
void tick(double time) {
}
uint checkIdx = 0;
void focusTick(double time) {
//Do a check to make sure our resource export setup is still correct
if(used.length != 0) {
checkIdx = (checkIdx+1) % used.length;
ExportData@ res = used[checkIdx];
if(res.request !is null && res.request.obj !is null && !res.isExportedTo(res.request.obj)) {
if(log)
ai.print("Break export to "+res.request.obj.name+": link changed underfoot", res.obj);
breakImport(res);
}
else {
bool valid = true;
if(res.obj is null || res.obj.owner !is ai.empire || !res.obj.valid)
valid = false;
//Don't break these imports, we want to wait for the decay to happen
else if((res.request is null || !res.request.obj.hasSurfaceComponent || res.request.obj.decayTime <= 0) && !res.obj.isAsteroid && !res.usable) {
valid = false;
}
else if(res.request !is null) {
if(res.request.obj.owner !is ai.empire || !res.request.obj.valid)
valid = false;
}
if(!valid)
breakImport(res);
}
}
//TODO: Make sure universal unique only applies once per planet
//Match requested with available
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
auto@ req = requested[i];
req.cycled = true;
if(req.obj is null)
continue;
if(req.beingMet) {
ai.print("Error: Requested is being met", req.obj);
continue;
}
ExportData@ source;
double sourceWeight = 0.0;
for(uint j = 0, jcnt = available.length; j < jcnt; ++j) {
auto@ av = available[j];
if(av.request !is null) {
ai.print("Error: Available is being used", av.obj);
continue;
}
if(!req.spec.meets(av.resource, av.obj, req.obj))
continue;
if(!av.usable || av.obj is null || !av.obj.valid || av.obj.owner !is ai.empire)
continue;
if(!canTradeBetween(av.obj, req.obj))
continue;
if(av.localOnly && av.obj !is req.obj)
continue;
double weight = 1.0;
if(req.obj is av.obj)
weight = INFINITY;
if(weight > sourceWeight) {
sourceWeight = weight;
@source = av;
}
}
if(source !is null) {
link(req, source);
--i; --cnt;
}
}
}
void turn() {
}
bool get_hasOpenRequests() {
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
auto@ req = requested[i];
if(req.isOpen)
return true;
}
return false;
}
TradePath tradePather;
int tradeDistance(Region& fromRegion, Region& toRegion) {
@tradePather.forEmpire = ai.empire;
tradePather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true);
if(!tradePather.valid)
return -1;
return tradePather.pathSize - 1;
}
bool canTradeBetween(Object& fromObj, Object& toObj) {
Region@ fromRegion = fromObj.region;
if(fromRegion is null)
return false;
Region@ toRegion = toObj.region;
if(toRegion is null)
return false;
return canTradeBetween(fromRegion, toRegion);
}
bool canTradeBetween(Region& fromRegion, Region& toRegion) {
if(fromRegion.sharesTerritory(ai.empire, toRegion))
return true;
int dist = tradeDistance(fromRegion, toRegion);
if(dist < 0)
return false;
return true;
}
void link(ImportData@ req, ExportData@ source) {
//Manage the data
@source.request = req;
@source.developUse = null;
req.set(source);
requested.remove(req);
active.insertLast(req);
req.beingMet = true;
available.remove(source);
used.insertLast(source);
if(log)
ai.print("link "+source.resource.name+" from "+source.obj.name+" to "+req.obj.name);
//Perform the actual export
if(source.obj !is req.obj)
source.obj.exportResourceByID(source.resourceId, req.obj);
else
source.obj.exportResourceByID(source.resourceId, null);
}
ImportData@ requestResource(Object& toObject, ResourceSpec& spec, bool forLevel = false, bool activate = true, bool prioritize = false) {
ImportData data;
data.idleSince = gameTime;
data.id = nextImportId++;
@data.obj = toObject;
@data.spec = spec;
data.forLevel = forLevel;
if(log)
ai.print("requested resource: "+spec.dump(), toObject);
if(activate) {
if(prioritize)
requested.insertAt(0, data);
else
requested.insertLast(data);
}
return data;
}
ExportData@ availableResource(Object& fromObject, const ResourceType& resource, int id) {
ExportData data;
data.id = nextExportId++;
@data.obj = fromObject;
@data.resource = resource;
data.resourceId = id;
if(log)
ai.print("available resource: "+resource.name, fromObject);
available.insertLast(data);
return data;
}
void checkReplaceCurrent(ExportData@ res) {
//If the planet that this resource is on is currently importing this same resource, switch it around
if(res.request !is null)
return;
for(uint i = 0, cnt = used.length; i < cnt; ++i) {
auto@ other = used[i];
auto@ request = other.request;
if(request is null)
continue;
if(request.obj !is res.obj)
continue;
if(request.spec.meets(res.resource, res.obj, res.obj)) {
//Swap the import with using the local resource
if(other.resource.exportable) {
breakImport(other);
link(request, res);
return;
}
}
}
}
array<Resource> checkResources;
array<ExportData@>@ availableResource(Object& fromObject) {
array<ExportData@> list;
checkResources.syncFrom(fromObject.getNativeResources());
uint nativeCount = checkResources.length;
for(uint i = 0; i < nativeCount; ++i) {
auto@ r = checkResources[i].type;
if(r !is null)
list.insertLast(availableResource(fromObject, r, checkResources[i].id));
}
return list;
}
ExportData@ findResource(Object@ obj, int resourceId) {
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
if(available[i].obj is obj && available[i].resourceId == resourceId)
return available[i];
}
for(uint i = 0, cnt = used.length; i < cnt; ++i) {
if(used[i].obj is obj && used[i].resourceId == resourceId)
return used[i];
}
return null;
}
ImportData@ findUnclaimed(ExportData@ forResource) {
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
auto@ req = requested[i];
if(req.claimedFor)
continue;
if(req.beingMet)
continue;
if(!req.spec.meets(forResource.resource, forResource.obj, req.obj))
continue;
if(!canTradeBetween(req.obj, forResource.obj))
continue;
return req;
}
return null;
}
void breakImport(ImportData@ data) {
if(data.fromObject !is null) {
auto@ source = findResource(data.fromObject, data.resourceId);
if(source !is null) {
breakImport(source);
return;
}
}
@data.fromObject = null;
data.resourceId = -1;
data.beingMet = false;
data.idleSince = gameTime;
active.remove(data);
requested.insertAt(0, data);
}
void breakImport(ExportData@ data) {
if(data.request !is null) {
if(data.request.obj !is data.obj)
data.obj.exportResource(data.resourceId, null);
data.request.beingMet = false;
@data.request.fromObject = null;
data.request.resourceId = -1;
data.request.idleSince = gameTime;
active.remove(data.request);
requested.insertAt(0, data.request);
@data.request = null;
}
used.remove(data);
available.insertLast(data);
}
void cancelRequest(ImportData@ data) {
if(data.beingMet) {
breakImport(data);
active.remove(data);
}
else {
requested.remove(data);
}
}
void removeResource(ExportData@ data) {
if(data.request !is null) {
breakImport(data);
used.remove(data);
@data.obj = null;
}
else {
available.remove(data);
@data.obj = null;
}
}
ImportData@ claimImport(ImportData@ data) {
data.beingMet = true;
requested.remove(data);
active.insertLast(data);
return data;
}
void relinquishImport(ImportData@ data) {
data.beingMet = false;
active.remove(data);
requested.insertLast(data);
}
void organizeImports(Object& obj, int targetLevel, ImportData@ before = null) {
//Organize any imports for this object so it tries to get to a particular target level
if(log)
ai.print("Organizing imports for level", obj, targetLevel);
//Get the requirement list
const PlanetLevel@ lvl = getPlanetLevel(obj, targetLevel);
if(lvl is null) {
ai.print("Error: could not find planet level", obj, targetLevel);
return; //Welp, can't do nothing here
}
//Collect all the requests this planet currently has outstanding
array<ImportData@> activeRequests;
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
auto@ req = requested[i];
if(req.obj !is obj)
continue;
if(!req.forLevel)
continue;
activeRequests.insertLast(req);
}
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
auto@ req = active[i];
if(req.obj !is obj)
continue;
if(!req.forLevel)
continue;
activeRequests.insertLast(req);
}
//TODO: This needs to be able to deal with dummy resources
//Match import requests with level requirements
array<ResourceSpec@> addSpecs;
const ResourceRequirements@ reqs = lvl.reqs;
for(uint i = 0, cnt = reqs.reqs.length; i < cnt; ++i) {
auto@ need = reqs.reqs[i];
for(uint n = 0; n < need.amount; ++n)
addSpecs.insertLast(implementSpec(need));
}
if(race !is null)
race.levelRequirements(obj, targetLevel, addSpecs);
for(uint i = 0, cnt = addSpecs.length; i < cnt; ++i) {
auto@ spec = addSpecs[i];
bool foundMatch = false;
for(uint j = 0, jcnt = activeRequests.length; j < jcnt; ++j) {
if(activeRequests[j].spec == spec) {
foundMatch = true;
activeRequests.removeAt(j);
break;
}
}
if(foundMatch) {
addSpecs.removeAt(i);
--i; --cnt;
}
}
//Cancel any import requests that we don't need anymore
for(uint i = 0, cnt = activeRequests.length; i < cnt; ++i)
cancelRequest(activeRequests[i]);
//Insert any imports above any imports of the planet we're exporting to
int place = -1;
if(before !is null) {
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
if(requested[i] is before) {
place = int(i);
break;
}
}
}
//Insert everything we need to add
addSpecs.sortDesc();
for(uint i = 0, cnt = addSpecs.length; i < cnt; ++i) {
ImportData@ req = requestResource(obj, addSpecs[i], forLevel=true, activate=false);
if(place == -1) {
requested.insertLast(req);
}
else {
requested.insertAt(place, req);
place += 1;
}
}
}
void killImportsTo(Object& obj) {
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
if(requested[i].obj is obj) {
cancelRequest(requested[i]);
--i; --cnt;
}
}
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
if(active[i].obj is obj) {
cancelRequest(active[i]);
--i; --cnt;
}
}
}
void killResourcesFrom(Object& obj) {
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
if(available[i].obj is obj) {
removeResource(available[i]);
--i; --cnt;
}
}
for(uint i = 0, cnt = used.length; i < cnt; ++i) {
if(used[i].obj is obj) {
removeResource(used[i]);
--i; --cnt;
}
}
}
ImportData@ getImport(const string& fromName, uint index = 0) {
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
if(requested[i].obj.name.equals_nocase(fromName)) {
if(index == 0)
return requested[i];
else
index -= 1;
}
}
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
if(active[i].obj.name.equals_nocase(fromName)) {
if(index == 0)
return active[i];
else
index -= 1;
}
}
return null;
}
ExportData@ getExport(const string& fromName, uint index = 0) {
for(uint i = 0, cnt = available.length; i < cnt; ++i) {
if(available[i].obj.name.equals_nocase(fromName)) {
if(index == 0)
return available[i];
else
index -= 1;
}
}
for(uint i = 0, cnt = used.length; i < cnt; ++i) {
if(used[i].obj.name.equals_nocase(fromName)) {
if(index == 0)
return used[i];
else
index -= 1;
}
}
return null;
}
void getImportsOf(array<ImportData@>& output, uint resType, Planet@ toPlanet = null) {
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
auto@ req = active[i];
if(req.spec.type != RST_Specific)
continue;
if(req.spec.resource.id != resType)
continue;
if(toPlanet !is null && req.obj !is toPlanet)
continue;
output.insertLast(req);
}
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
auto@ req = requested[i];
if(req.spec.type != RST_Specific)
continue;
if(req.spec.resource.id != resType)
continue;
if(toPlanet !is null && req.obj !is toPlanet)
continue;
output.insertLast(req);
}
}
void dumpRequests(Object@ forObject = null) {
for(uint i = 0, cnt = requested.length; i < cnt; ++i) {
if(forObject !is null && requested[i].obj !is forObject)
continue;
print(requested[i].obj.name+" requests "+requested[i].spec.dump());
}
if(forObject !is null) {
for(uint i = 0, cnt = used.length; i < cnt; ++i) {
if(used[i].request is null || used[i].request.obj !is forObject)
continue;
print(used[i].request.obj.name+" is getting "+used[i].request.spec.dump()+" from "+used[i].obj.name);
}
}
}
};
AIComponent@ createResources() {
return Resources();
}
+505
View File
@@ -0,0 +1,505 @@
// Scouting
// --------
// Orders the construction of scouts, explores the galaxy with them and makes
// sure we have vision where we need vision, as well as scanning anomalies.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Creeping;
final class ScoutingMission : Mission {
Region@ region;
MoveOrder@ move;
void save(Fleets& fleets, SaveFile& file) override {
file << region;
fleets.movement.saveMoveOrder(file, move);
}
void load(Fleets& fleets, SaveFile& file) override {
file >> region;
@move = fleets.movement.loadMoveOrder(file);
}
double getPerformWeight(AI& ai, FleetAI& fleet) {
if(fleet.fleetClass != FC_Scout) {
if(fleet.fleetClass == FC_Mothership)
return 0.0;
if(gameTime > ai.behavior.scoutAllTimer)
return 0.0;
}
return 1.0 / region.position.distanceTo(fleet.obj.position);
}
void start(AI& ai, FleetAI& fleet) override {
uint mprior = MP_Background;
if(gameTime < 6.0 * 60.0)
mprior = MP_Critical;
else if(priority > MiP_Normal)
mprior = MP_Normal;
@move = cast<Movement>(ai.movement).move(fleet.obj, region, mprior);
}
void tick(AI& ai, FleetAI& fleet, double time) {
if(move.failed)
canceled = true;
if(move.completed) {
//We managed to scout this system
if(fleet.obj.region !is region) {
@move = cast<Movement>(ai.movement).move(fleet.obj, region.position + random3d(400.0));
return;
}
completed = true;
//Detect any anomalies and put them into the scanning queue
//TODO: Detect newly created anomalies in systems we already have vision over?
if(region.anomalyCount != 0) {
auto@ list = region.getAnomalies();
Object@ obj;
while(receive(list, obj)) {
Anomaly@ anom = cast<Anomaly>(obj);
if(anom !is null)
cast<Scouting>(ai.scouting).recordAnomaly(anom);
}
}
}
}
};
final class ScanningMission : Mission {
Anomaly@ anomaly;
MoveOrder@ move;
void save(Fleets& fleets, SaveFile& file) override {
file << anomaly;
fleets.movement.saveMoveOrder(file, move);
}
void load(Fleets& fleets, SaveFile& file) override {
file >> anomaly;
@move = fleets.movement.loadMoveOrder(file);
}
double getPerformWeight(AI& ai, FleetAI& fleet) {
if(fleet.fleetClass != FC_Scout) {
if(gameTime > ai.behavior.scoutAllTimer)
return 0.0;
}
return 1.0 / anomaly.position.distanceTo(fleet.obj.position);
}
void start(AI& ai, FleetAI& fleet) override {
uint mprior = MP_Background;
if(priority > MiP_Normal)
mprior = MP_Normal;
@move = cast<Movement>(ai.movement).move(fleet.obj, anomaly, mprior);
}
void tick(AI& ai, FleetAI& fleet, double time) {
if(move !is null) {
if(move.failed) {
canceled = true;
return;
}
if(move.completed)
@move = null;
}
if(move is null) {
if(anomaly is null || !anomaly.valid) {
completed = true;
return;
}
if(anomaly.getEmpireProgress(ai.empire) >= 1.f) {
uint choose = 0;
uint possibs = 0;
uint optCnt = anomaly.getOptionCount();
for(uint i = 0; i < optCnt; ++i) {
if(anomaly.isOptionSafe[i]) {
possibs += 1;
if(randomd() < 1.0 / double(possibs))
choose = i;
}
}
if(possibs != 0) {
anomaly.choose(ai.empire, choose);
}
else {
completed = true;
}
}
else {
if(!fleet.obj.hasOrders)
fleet.obj.addScanOrder(anomaly);
}
}
}
};
class Scouting : AIComponent {
Fleets@ fleets;
Systems@ systems;
Designs@ designs;
Construction@ construction;
Movement@ movement;
Creeping@ creeping;
DesignTarget@ scoutDesign;
array<ScoutingMission@> queue;
array<ScoutingMission@> active;
array<Anomaly@> anomalies;
array<ScanningMission@> scanQueue;
array<ScanningMission@> scanActive;
array<BuildFlagship@> constructing;
int exploreHops = 0;
bool buildScouts = true;
void create() {
@fleets = cast<Fleets>(ai.fleets);
@systems = cast<Systems>(ai.systems);
@designs = cast<Designs>(ai.designs);
@construction = cast<Construction>(ai.construction);
@movement = cast<Movement>(ai.movement);
@creeping = cast<Creeping>(ai.creeping);
}
void save(SaveFile& file) {
designs.saveDesign(file, scoutDesign);
file << exploreHops;
uint cnt = queue.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets.saveMission(file, queue[i]);
cnt = active.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets.saveMission(file, active[i]);
cnt = anomalies.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << anomalies[i];
cnt = scanQueue.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets.saveMission(file, scanQueue[i]);
cnt = scanActive.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets.saveMission(file, scanActive[i]);
cnt = constructing.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
construction.saveConstruction(file, constructing[i]);
}
void load(SaveFile& file) {
@scoutDesign = designs.loadDesign(file);
file >> exploreHops;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ miss = cast<ScoutingMission>(fleets.loadMission(file));
if(miss !is null)
queue.insertLast(miss);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ miss = cast<ScoutingMission>(fleets.loadMission(file));
if(miss !is null)
active.insertLast(miss);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Anomaly@ anom;
file >> anom;
if(anom !is null)
anomalies.insertLast(anom);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ miss = cast<ScanningMission>(fleets.loadMission(file));
if(miss !is null)
scanQueue.insertLast(miss);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ miss = cast<ScanningMission>(fleets.loadMission(file));
if(miss !is null)
scanActive.insertLast(miss);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ cons = cast<BuildFlagship>(construction.loadConstruction(file));
if(cons !is null)
constructing.insertLast(cons);
}
}
void start() {
@scoutDesign = DesignTarget(DP_Scout, 16);
scoutDesign.targetMaintenance = 40;
designs.design(scoutDesign);
}
bool isScouting(Region@ region) {
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
if(queue[i].region is region)
return true;
}
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
if(active[i].region is region)
return true;
}
return false;
}
ScoutingMission@ scout(Region@ region, uint priority = MiP_High) {
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
if(queue[i].region is region)
return queue[i];
}
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
if(active[i].region is region)
return active[i];
}
if(log)
ai.print("Queue scouting mission", region);
ScoutingMission mission;
@mission.region = region;
mission.priority = priority;
fleets.register(mission);
queue.insertLast(mission);
return mission;
}
void recordAnomaly(Anomaly@ anom) {
for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) {
if(scanActive[i].anomaly is anom)
return;
}
if(anomalies.find(anom) == -1)
anomalies.insertLast(anom);
}
ScanningMission@ scan(Anomaly& anomaly, uint priority = MiP_Normal) {
for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) {
if(scanActive[i].anomaly is anomaly)
return scanActive[i];
}
if(log)
ai.print("Queue scanning mission on "+anomaly.name, anomaly.region);
ScanningMission mission;
@mission.anomaly = anomaly;
mission.priority = priority;
fleets.register(mission);
anomalies.remove(anomaly);
scanQueue.insertLast(mission);
return mission;
}
void focusTick(double time) {
//Remove completed scouting missions
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
if(active[i].completed || active[i].canceled) {
active.removeAt(i);
--i; --cnt;
}
}
//Remove completed scanning missions
for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) {
if(scanActive[i].completed || scanActive[i].canceled) {
scanActive.removeAt(i);
--i; --cnt;
}
}
//Make sure we have enough scouts active and scouting
if(fleets.count(FC_Scout) + constructing.length < ai.behavior.scoutsActive && buildScouts)
constructing.insertLast(construction.buildFlagship(scoutDesign));
for(uint i = 0, cnt = constructing.length; i < cnt; ++i) {
if(constructing[i].completed && constructing[i].completedAt + 30.0 < gameTime) {
constructing.removeAt(i);
--i; --cnt;
}
}
//See if we can fill the scouting queue with something nice
uint scoutClass = FC_Scout;
if(gameTime < ai.behavior.scoutAllTimer)
scoutClass = FC_ALL;
bool haveIdle = fleets.haveIdle(scoutClass);
//See if we should queue up a new anomaly scan
if(scanQueue.length == 0 && anomalies.length != 0 && scanActive.length < ai.behavior.maxScanningMissions && haveIdle && (!ai.behavior.prioritizeScoutOverScan || active.length > 0)) {
Anomaly@ best;
double bestDist = INFINITY;
for(uint i = 0, cnt = anomalies.length; i < cnt; ++i) {
auto@ anom = anomalies[i];
if(anom is null || !anom.valid) {
anomalies.removeAt(i);
--i; --cnt;
continue;
}
if(creeping.isQuarantined(anom.region))
continue;
double d = fleets.closestIdleTo(scoutClass, anom.position);
if(d < bestDist) {
@best = anom;
bestDist = d;
}
}
if(best !is null)
scan(best);
}
//Scan anomalies in our scan queue
if(scanQueue.length != 0) {
auto@ mission = scanQueue[0];
if(mission.anomaly is null || !mission.anomaly.valid) {
scanQueue.removeAt(0);
}
else {
auto@ flAI = fleets.performMission(mission);
if(flAI !is null) {
if(log)
ai.print("Perform scanning mission with "+flAI.obj.name, mission.anomaly.region);
scanQueue.remove(mission);
scanActive.insertLast(mission);
}
}
}
//TODO: In large maps we should probably devote scouts to scouting enemies even before the map is fully explored
if(queue.length == 0 && active.length < ai.behavior.maxScoutingMissions && haveIdle) {
//Explore systems from the inside out
if(exploreHops != -1) {
double bestDist = INFINITY;
bool remainingHops = false;
bool emptyHops = true;
Region@ best;
for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) {
auto@ sys = systems.all[i];
if(sys.hopDistance == exploreHops)
emptyHops = false;
if(sys.explored || isScouting(sys.obj))
continue;
if(sys.hopDistance == exploreHops)
remainingHops = true;
double d = fleets.closestIdleTo(scoutClass, sys.obj.position);
if(sys.hopDistance != exploreHops)
d *= pow(ai.behavior.exploreBorderWeight, double(sys.hopDistance - exploreHops));
if(d < bestDist) {
bestDist = d;
@best = sys.obj;
}
}
if(best !is null)
scout(best, priority=MiP_Normal);
if(emptyHops)
exploreHops = -1;
else if(!remainingHops)
exploreHops += 1;
}
else {
//Gain vision over systems we haven't recently seen
Region@ best;
double bestWeight = 0;
double curTime = gameTime;
for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) {
auto@ sys = systems.all[i];
if(sys.visible || sys.visibleNow(ai))
continue;
if(isScouting(sys.obj))
continue;
double timer = curTime - sys.lastVisible;
if(timer < ai.behavior.minScoutingInterval)
continue;
double w = 1.0;
w *= timer / ai.behavior.minScoutingInterval;
w /= fleets.closestIdleTo(scoutClass, sys.obj.position);
if(!sys.explored)
w *= 10.0;
if(sys.seenPresent & ~ai.visionMask != 0)
w *= 2.0;
if(sys.seenPresent & ai.enemyMask != 0) {
if(sys.hopDistance < 2)
w *= 4.0;
w *= 4.0;
}
if(w > bestWeight) {
bestWeight = w;
@best = sys.obj;
}
}
if(best !is null)
scout(best, priority=MiP_Normal);
}
}
//Try to find a scout to perform our top scouting mission from the queue
if(queue.length != 0) {
auto@ mission = queue[0];
auto@ flAI = fleets.performMission(mission);
if(flAI !is null) {
if(log)
ai.print("Perform scouting mission with "+flAI.obj.name, mission.region);
active.insertLast(mission);
queue.removeAt(0);
}
}
}
};
AIComponent@ createScouting() {
return Scouting();
}
+588
View File
@@ -0,0 +1,588 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.searches;
import systems;
import system_pathing;
final class SystemAI {
const SystemDesc@ desc;
Region@ obj;
double prevTick = 0.0;
array<Planet@> planets;
array<Pickup@> pickups;
array<Object@> pickupProtectors;
array<Artifact@> artifacts;
array<Asteroid@> asteroids;
bool explored = false;
bool owned = false;
bool visible = false;
int hopDistance = 0;
bool visited = false;
bool border = false;
bool bordersEmpires = false;
bool outsideBorder = false;
double lastVisible = 0;
uint seenPresent = 0;
double focusDuration = 0;
double enemyStrength = 0;
double lastStrengthCheck = 0;
double nextDetailed = 0;
SystemAI() {
}
SystemAI(const SystemDesc@ sys) {
@desc = sys;
@obj = desc.object;
}
void save(SaveFile& file) {
file << obj;
file << prevTick;
uint cnt = planets.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << planets[i];
cnt = pickups.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << pickups[i];
cnt = pickupProtectors.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << pickupProtectors[i];
cnt = artifacts.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << artifacts[i];
cnt = asteroids.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << asteroids[i];
file << explored;
file << owned;
file << visible;
file << hopDistance;
file << border;
file << bordersEmpires;
file << outsideBorder;
file << lastVisible;
file << seenPresent;
file << enemyStrength;
file << lastStrengthCheck;
}
void load(SaveFile& file) {
file >> obj;
file >> prevTick;
uint cnt = 0;
file >> cnt;
planets.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> planets[i];
file >> cnt;
pickups.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> pickups[i];
file >> cnt;
pickupProtectors.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> pickupProtectors[i];
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Artifact@ artif;
file >> artif;
if(artif !is null)
artifacts.insertLast(artif);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Asteroid@ roid;
file >> roid;
if(roid !is null)
asteroids.insertLast(roid);
}
file >> explored;
file >> owned;
file >> visible;
file >> hopDistance;
file >> border;
file >> bordersEmpires;
file >> outsideBorder;
file >> lastVisible;
file >> seenPresent;
file >> enemyStrength;
file >> lastStrengthCheck;
}
bool visibleNow(AI& ai) {
return obj.VisionMask & ai.visionMask != 0;
}
void strengthCheck(AI& ai, double minInterval = 30.0) {
if(lastStrengthCheck + minInterval > gameTime)
return;
if(!visible && lastVisible < gameTime - 30.0)
return;
lastStrengthCheck = gameTime;
enemyStrength = getTotalFleetStrength(obj, ai.enemyMask);
}
void tick(AI& ai, Systems& systems, double time) {
//Check if we should be visible
bool shouldVisible = obj.VisionMask & ai.visionMask != 0;
if(visible != shouldVisible) {
if(visible)
lastVisible = gameTime;
visible = shouldVisible;
}
//Check if we should be owned
bool shouldOwned = obj.PlanetsMask & ai.mask != 0;
if(owned != shouldOwned) {
if(shouldOwned) {
systems.owned.insertLast(this);
systems.hopsChanged = true;
hopDistance = 0;
}
else {
hopDistance = 1;
systems.owned.remove(this);
systems.hopsChanged = true;
}
owned = shouldOwned;
}
//Check if we should be border
bool shouldBorder = false;
bordersEmpires = false;
if(owned) {
for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) {
auto@ other = systems.getAI(desc.adjacent[i]);
if(other !is null && !other.owned) {
if(other.seenPresent & ~ai.teamMask != 0)
bordersEmpires = true;
shouldBorder = true;
break;
}
}
for(uint i = 0, cnt = desc.wormholes.length; i < cnt; ++i) {
auto@ other = systems.getAI(desc.wormholes[i]);
if(other !is null && !other.owned) {
if(other.seenPresent & ~ai.teamMask != 0)
bordersEmpires = true;
shouldBorder = true;
break;
}
}
}
if(border != shouldBorder) {
if(shouldBorder) {
systems.border.insertLast(this);
}
else {
systems.border.remove(this);
}
border = shouldBorder;
}
//Check if we should be outsideBorder
bool shouldOutsideBorder = !owned && hopDistance == 1;
if(outsideBorder != shouldOutsideBorder) {
if(shouldOutsideBorder) {
systems.outsideBorder.insertLast(this);
}
else {
systems.outsideBorder.remove(this);
}
outsideBorder = shouldOutsideBorder;
}
//Check if we've been explored
if(visible && !explored) {
//Find all remnants in this system
auto@ objs = findType(obj, null, OT_Pickup);
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
Pickup@ p = cast<Pickup>(objs[i]);
if(p !is null) {
pickups.insertLast(p);
pickupProtectors.insertLast(p.getProtector());
}
}
explored = true;
}
//Deal with recording new data on this system
if(explored) {
uint plCnt = obj.planetCount;
if(plCnt != planets.length) {
auto@ objs = findType(obj, null, OT_Planet);
planets.length = 0;
planets.reserve(objs.length);
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
Planet@ pl = cast<Planet>(objs[i]);
if(pl !is null)
planets.insertLast(pl);
}
}
}
if(visible) {
seenPresent = obj.PlanetsMask;
uint astrCount = obj.asteroidCount;
if(astrCount != asteroids.length) {
auto@ objs = findType(obj, null, OT_Asteroid);
asteroids.length = 0;
asteroids.reserve(objs.length);
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
Asteroid@ a = cast<Asteroid>(objs[i]);
if(a !is null)
asteroids.insertLast(a);
}
}
for(uint i = 0, cnt = pickups.length; i < cnt; ++i) {
if(!pickups[i].valid) {
pickups.removeAt(i);
pickupProtectors.removeAt(i);
break;
}
}
if(nextDetailed < gameTime) {
nextDetailed = gameTime + randomd(40.0, 100.0);
auto@ objs = findType(obj, null, OT_Artifact);
artifacts.length = 0;
artifacts.reserve(objs.length);
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
Artifact@ a = cast<Artifact>(objs[i]);
if(a !is null)
artifacts.insertLast(a);
}
}
}
}
};
class Systems : AIComponent {
//All owned systems
array<SystemAI@> owned;
//All owned systems that are considered our empire's border
array<SystemAI@> border;
//All systems just outside our border
array<SystemAI@> outsideBorder;
//All systems
array<SystemAI@> all;
//Systems that need to be processed soon
array<SystemAI@> bumped;
array<SystemAI@> focused;
uint sysIdx = 0;
bool hopsChanged = false;
void save(SaveFile& file) {
uint cnt = all.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
all[i].save(file);
cnt = owned.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
saveAI(file, owned[i]);
cnt = border.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
saveAI(file, border[i]);
cnt = outsideBorder.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
saveAI(file, outsideBorder[i]);
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
all.length = max(all.length, cnt);
for(uint i = 0; i < cnt; ++i) {
if(all[i] is null)
@all[i] = SystemAI();
all[i].load(file);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadAI(file);
if(data !is null)
owned.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadAI(file);
if(data !is null)
border.insertLast(data);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ data = loadAI(file);
if(data !is null)
outsideBorder.insertLast(data);
}
}
void loadFinalize(AI& ai) override {
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
auto@ sys = getSystem(i);
@all[i].desc = sys;
@all[i].obj = sys.object;
}
}
void saveAI(SaveFile& file, SystemAI@ ai) {
Region@ reg;
if(ai !is null)
@reg = ai.obj;
file << reg;
}
SystemAI@ loadAI(SaveFile& file) {
Region@ reg;
file >> reg;
if(reg is null)
return null;
uint id = reg.SystemId;
if(id >= all.length) {
all.length = id+1;
@all[id] = SystemAI();
@all[id].obj = reg;
}
return all[id];
}
void focusTick(double time) {
if(all.length != systemCount) {
uint prevCount = all.length;
all.length = systemCount;
for(uint i = prevCount, cnt = all.length; i < cnt; ++i)
@all[i] = SystemAI(getSystem(i));
}
if(hopsChanged)
calculateHops();
}
void tick(double time) override {
double curTime = gameTime;
if(all.length != 0) {
uint tcount = max(ceil(time / 0.2), double(all.length)/20.0);
for(uint n = 0; n < tcount; ++n) {
sysIdx = (sysIdx+1) % all.length;
auto@ sys = all[sysIdx];
sys.tick(ai, this, curTime - sys.prevTick);
sys.prevTick = curTime;
}
}
for(uint i = 0, cnt = bumped.length; i < cnt; ++i) {
auto@ sys = bumped[i];
double tickTime = curTime - sys.prevTick;
if(tickTime != 0) {
sys.tick(ai, this, tickTime);
sys.prevTick = curTime;
}
}
bumped.length = 0;
for(uint i = 0, cnt = focused.length; i < cnt; ++i) {
auto@ sys = focused[i];
sys.focusDuration -= time;
double tickTime = curTime - sys.prevTick;
if(tickTime != 0) {
sys.tick(ai, this, tickTime);
sys.prevTick = curTime;
}
if(sys.focusDuration <= 0) {
focused.removeAt(i);
--i; --cnt;
}
}
}
void calculateHops() {
if(!hopsChanged)
return;
hopsChanged = false;
priority_queue q;
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
auto@ sys = all[i];
sys.visited = false;
if(sys.owned) {
sys.hopDistance = 0;
q.push(int(i), 0);
}
else
sys.hopDistance = INT_MAX;
}
while(!q.empty()) {
uint index = uint(q.top());
q.pop();
auto@ sys = all[index];
if(sys.visited)
continue;
int dist = sys.hopDistance;
sys.visited = true;
for(uint i = 0, cnt = sys.desc.adjacent.length; i < cnt; ++i) {
uint otherInd = sys.desc.adjacent[i];
if(otherInd < all.length) {
auto@ other = all[otherInd];
if(other.hopDistance > dist+1) {
other.hopDistance = dist+1;
q.push(otherInd, -other.hopDistance);
}
}
}
for(uint i = 0, cnt = sys.desc.wormholes.length; i < cnt; ++i) {
uint otherInd = sys.desc.wormholes[i];
if(otherInd < all.length) {
auto@ other = all[otherInd];
if(other.hopDistance > dist+1) {
other.hopDistance = dist+1;
q.push(otherInd, -other.hopDistance);
}
}
}
}
}
void focus(Region@ reg, double duration = 30.0) {
bool found = false;
for(uint i = 0, cnt = focused.length; i < cnt; ++i) {
if(focused[i].obj is reg) {
focused[i].focusDuration = max(focused[i].focusDuration, duration);
found = true;
break;
}
}
if(!found) {
auto@ sys = getAI(reg);
if(sys !is null) {
sys.focusDuration = duration;
focused.insertLast(sys);
}
}
}
void bump(Region@ sys) {
if(sys !is null)
bump(getAI(sys));
}
void bump(SystemAI@ sys) {
if(sys !is null)
bumped.insertLast(sys);
}
SystemAI@ getAI(uint idx) {
if(idx < all.length)
return all[idx];
return null;
}
SystemAI@ getAI(Region@ region) {
if(region is null)
return null;
uint idx = region.SystemId;
if(idx < all.length)
return all[idx];
return null;
}
SystemPath pather;
int hopDistance(Region& fromRegion, Region& toRegion){
pather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true);
if(!pather.valid)
return INT_MAX;
return pather.pathSize - 1;
}
TradePath tradePather;
int tradeDistance(Region& fromRegion, Region& toRegion) {
@tradePather.forEmpire = ai.empire;
tradePather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true);
if(!tradePather.valid)
return -1;
return tradePather.pathSize - 1;
}
bool canTrade(Region& fromRegion, Region& toRegion) {
if(fromRegion.sharesTerritory(ai.empire, toRegion))
return true;
int dist = tradeDistance(fromRegion, toRegion);
if(dist < 0)
return false;
return true;
}
SystemAI@ getAI(const string& name) {
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
if(all[i].obj.name.equals_nocase(name))
return all[i];
}
return null;
}
uint index(const string& name) {
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
if(all[i].obj.name.equals_nocase(name))
return i;
}
return uint(-1);
}
};
AIComponent@ createSystems() {
return Systems();
}
+893
View File
@@ -0,0 +1,893 @@
// War
// ---
// Attacks and defends from enemy attacks during situations of war.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Intelligence;
import empire_ai.weasel.Relations;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Scouting;
import empire_ai.weasel.Military;
import empire_ai.weasel.searches;
import regions.regions;
class BattleMission : Mission {
Region@ battleIn;
FleetAI@ fleet;
MoveOrder@ move;
Object@ defending;
Planet@ capturing;
Empire@ captureFrom;
bool arrived = false;
void save(Fleets& fleets, SaveFile& file) override {
file << battleIn;
fleets.saveAI(file, fleet);
fleets.movement.saveMoveOrder(file, move);
file << defending;
file << capturing;
file << captureFrom;
file << arrived;
}
void load(Fleets& fleets, SaveFile& file) override {
file >> battleIn;
@fleet = fleets.loadAI(file);
@move = fleets.movement.loadMoveOrder(file);
file >> defending;
file >> capturing;
file >> captureFrom;
file >> arrived;
}
};
double captureSupply(Empire& emp, Object& check) {
double loy = check.getLoyaltyFacing(emp);
double cost = config::SIEGE_LOYALTY_SUPPLY_COST * loy;
cost *= emp.CaptureSupplyFactor;
cost *= check.owner.CaptureSupplyDifficulty;
return cost;
}
class Battle {
SystemAI@ system;
Region@ staging;
array<BattleMission@> fleets;
uint curPriority = MiP_Critical;
bool isAttack = false;
double enemyStrength;
double ourStrength;
double lastCombat = 0;
double bestCapturePct;
double lastHadFleets = 0;
bool inCombat = false;
bool isUnderSiege = false;
Planet@ defendPlanet;
Object@ eliminate;
Battle() {
lastHadFleets = gameTime;
lastCombat = gameTime;
}
void save(War& war, SaveFile& file) {
war.systems.saveAI(file, system);
uint cnt = fleets.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
war.fleets.saveMission(file, fleets[i]);
file << curPriority;
file << isAttack;
file << lastCombat;
file << inCombat;
file << defendPlanet;
file << eliminate;
file << isUnderSiege;
file << bestCapturePct;
}
void load(War& war, SaveFile& file) {
@system = war.systems.loadAI(file);
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ miss = cast<BattleMission>(war.fleets.loadMission(file));
if(miss !is null)
fleets.insertLast(miss);
}
file >> curPriority;
file >> isAttack;
file >> lastCombat;
file >> inCombat;
file >> defendPlanet;
file >> eliminate;
file >> isUnderSiege;
file >> bestCapturePct;
}
BattleMission@ join(AI& ai, War& war, FleetAI@ flAI) {
BattleMission mission;
@mission.fleet = flAI;
@mission.battleIn = system.obj;
mission.priority = curPriority;
Object@ moveTo = system.obj;
if(defendPlanet !is null)
@moveTo = defendPlanet;
else if(eliminate !is null && eliminate.isShip)
@moveTo = eliminate;
@mission.move = war.movement.move(flAI.obj, moveTo, MP_Critical, spread=true, nearOnly=true);
//Station this fleet nearby after the battle is over
if(staging !is null)
war.military.stationFleet(flAI, staging);
if(war.log)
ai.print("Assign to battle at "+system.obj.name
+" for strength "+standardize(ourStrength * 0.001, true)
+ " vs their "+standardize(enemyStrength * 0.001, true), flAI.obj);
fleets.insertLast(mission);
war.fleets.performMission(flAI, mission);
return mission;
}
bool stayingHere(Object@ other) {
if(other is null || !other.hasMover)
return true;
if(!inRegion(system.obj, other.position))
return false;
double acc = other.maxAcceleration;
if(acc <= 0.0001)
return true;
vec3d compDest = other.computedDestination;
if(inRegion(system.obj, compDest))
return true;
if(inRegion(system.obj, other.position + other.velocity * 10.0))
return true;
return false;
}
bool tick(AI& ai, War& war, double time) {
//Compute strength values
enemyStrength = getTotalFleetStrength(system.obj, ai.enemyMask, planets=true);
ourStrength = 0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
ourStrength += sqrt(fleets[i].fleet.strength);
ourStrength *= ourStrength;
inCombat = false;
bool ourPlanetsPresent = system.obj.PlanetsMask & (ai.allyMask | ai.mask) != 0;
if((enemyStrength < 0.01 || !ourPlanetsPresent) && defendPlanet is null)
isUnderSiege = false;
//Remove lost fleets
bool anyArrived = false;
bestCapturePct = 0.0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ miss = fleets[i];
miss.priority = curPriority;
if(!miss.fleet.obj.valid || miss.canceled) {
if(!miss.fleet.obj.valid) {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ other = getEmpire(i);
if(!other.major || !ai.empire.isHostile(other))
continue;
if(system.obj.ContestedMask & other.mask != 0)
war.relations.recordLostTo(other, miss.fleet.obj);
}
}
miss.canceled = true;
if(war.log)
ai.print("BATTLE: lost fleet "+miss.fleet.obj.name, system.obj);
fleets.removeAt(i);
--i; --cnt;
if(fleets.length == 0)
lastHadFleets = gameTime;
continue;
}
if(miss.move !is null) {
if(miss.move.failed) {
miss.canceled = true;
if(war.log)
ai.print("BATTLE: move failed on lost fleet "+miss.fleet.obj.name, system.obj);
fleets.removeAt(i);
--i; --cnt;
if(fleets.length == 0)
lastHadFleets = gameTime;
continue;
}
if(miss.move.completed) {
miss.arrived = true;
@miss.move = null;
}
}
if(miss.arrived) {
anyArrived = true;
bool shouldRetreat = false;
if(miss.fleet.supplies < 0.05) {
if(isCapturingAny && eliminate is null)
shouldRetreat = true;
else if(ourStrength < enemyStrength * 0.75)
shouldRetreat = true;
}
if(miss.fleet.fleetHealth < 0.25) {
if(ourStrength < enemyStrength * 0.5)
shouldRetreat = true;
}
if(shouldRetreat) {
war.fleets.returnToBase(miss.fleet);
fleets.removeAt(i);
miss.canceled = true;
--i; --cnt;
if(fleets.length == 0)
lastHadFleets = gameTime;
continue;
}
}
if(miss.capturing !is null)
bestCapturePct = max(bestCapturePct, miss.capturing.capturePct);
}
//Defend our planets
if(defendPlanet is null) {
Planet@ defPl;
double bestWeight = 0.0;
for(uint i = 0, cnt = system.planets.length; i < cnt; ++i) {
Planet@ pl = system.planets[i];
double w = 1.0;
if(pl.owner is ai.empire)
w *= 2.0;
else if(pl.owner.mask & ai.allyMask != 0)
w *= 0.5;
else
continue;
double capt = pl.capturePct;
if(capt <= 0.01)
continue;
w *= capt;
if(!pl.enemiesInOrbit)
continue;
if(w > bestWeight) {
bestWeight = w;
@defPl = pl;
}
}
if(defPl !is null) {
if(war.log)
ai.print("BATTLE: protect planet "+defPl.name, system.obj);
@defendPlanet = defPl;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
moveTo(fleets[i], defendPlanet, force=true);
}
}
else {
//Check if there are still enemies in orbit
if(!defendPlanet.enemiesInOrbit || !defendPlanet.valid || defendPlanet.owner.isHostile(ai.empire))
@defendPlanet = null;
}
if(defendPlanet !is null) {
//Make sure one fleet is in orbit
inCombat = true;
isUnderSiege = true;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
moveTo(fleets[i], defendPlanet);
}
//Eliminate any remaining threats
if(!inCombat) {
//Eliminate any hostile targets in the system
if(eliminate !is null) {
//Make sure this is still a valid target to eliminate
bool valid = true;
if(!eliminate.valid) {
valid = false;
war.relations.recordTakenFrom(eliminate.owner, eliminate);
}
else if(!stayingHere(eliminate))
valid = false;
else if(!eliminate.isVisibleTo(ai.empire))
valid = false;
else if(!ai.empire.isHostile(eliminate.owner))
valid = false;
if(!valid) {
@eliminate = null;
clearOrders();
}
else {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
attack(fleets[i], eliminate);
inCombat = true;
}
}
else {
//Find a new target to eliminate
Object@ check = findEnemy(system.obj, ai.empire, ai.enemyMask);
if(check !is null) {
if(stayingHere(check)) {
@eliminate = check;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ obj = fleets[i].fleet.obj;
if(!fleets[i].arrived)
continue;
obj.addAttackOrder(eliminate);
}
if(war.log)
ai.print("BATTLE: Eliminate "+eliminate.name, system.obj);
inCombat = true;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
attack(fleets[i], eliminate, force=true);
}
}
}
}
else {
@eliminate = null;
}
//Capture enemy planets if possible
//TODO: Respond to defense by abandoning all but 1 capture and swarming around the best one
if(!inCombat) {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ miss = fleets[i];
if(miss.capturing !is null) {
if(canCapture(ai, miss, miss.capturing) && miss.fleet.remainingSupplies > captureSupply(ai.empire, miss.capturing) && miss.fleet.obj.hasOrders) {
inCombat = true;
continue;
}
else {
if(miss.capturing.owner is ai.empire && miss.captureFrom !is null)
war.relations.recordTakenFrom(miss.captureFrom, miss.capturing);
@miss.capturing = null;
@miss.captureFrom = null;
}
}
if(!miss.arrived)
continue;
Planet@ bestCapture;
double totalWeight = 0;
for(uint i = 0, cnt = system.planets.length; i < cnt; ++i) {
Planet@ check = system.planets[i];
if(!canCapture(ai, miss, check))
continue;
//Don't send two fleets to the same thing
if(isCapturing(check))
continue;
//Make sure we have the supplies remaining to capture
if(miss.fleet.remainingSupplies < captureSupply(ai.empire, check) * ai.behavior.captureSupplyEstimate)
continue;
double str = check.getFleetStrength();
double w = 1.0;
w *= check.getLoyaltyFacing(ai.empire);
if(str != 0)
w /= str;
totalWeight += w;
if(randomd() < w / totalWeight)
@bestCapture = check;
}
if(bestCapture !is null) {
if(war.log)
ai.print("BATTLE: Capture "+bestCapture.name+" with "+miss.fleet.obj.name, system.obj);
@miss.capturing = bestCapture;
@miss.captureFrom = bestCapture.owner;
miss.fleet.obj.addCaptureOrder(bestCapture);
inCombat = true;
}
}
}
else {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ miss = fleets[i];
@miss.capturing = null;
@miss.captureFrom = null;
}
}
//Keep fleets here in non-critical mode for a few minutes
if(!inCombat && (anyArrived || !isAttack)) {
//TODO: Don't start this countdown until we've actually arrived
if(gameTime > lastCombat + 90.0) {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
fleets[i].completed = true;
if(war.log)
ai.print("BATTLE: ended", system.obj);
return false;
}
else if(gameTime > lastCombat + 30.0) {
curPriority = MiP_Normal;
}
else {
curPriority = MiP_High;
}
}
else {
if(ourPlanetsPresent && isUnderSiege) {
curPriority = MiP_Critical;
if(war.winnability(this) < 0.5)
curPriority = MiP_High;
}
else if(bestCapturePct > 0.75)
curPriority = MiP_Critical;
else
curPriority = MiP_High;
lastCombat = gameTime;
}
//If needed, claim fleets
if(ourStrength < enemyStrength * ai.behavior.battleStrengthOverkill) {
FleetAI@ claim;
double bestWeight = 0;
for(uint i = 0, cnt = war.fleets.fleets.length; i < cnt; ++i) {
auto@ fleet = war.fleets.fleets[i];
double w = war.assignable(this, fleet);
if(w > bestWeight) {
bestWeight = w;
@claim = fleet;
}
}
if(claim !is null)
join(ai, war, claim);
}
//Give up the battle when we should
if(fleets.length == 0) {
if(!ourPlanetsPresent && !isAttack) {
//We lost all our planets before we could respond with anything.
// We might be able to use an attack to claim it back later, but for now we just give up on it.
if(war.log)
ai.print("BATTLE: aborted defense, no fleets and no planets left", system.obj);
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
fleets[i].canceled = true;
return false;
}
if(isAttack) {
//We haven't been able to find any fleets to assign here for a while,
//so just abort the attack
if(gameTime - lastHadFleets > 60.0) {
if(war.log)
ai.print("BATTLE: aborted attack, no fleets available", system.obj);
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
fleets[i].canceled = true;
return false;
}
}
}
return true;
}
void clearOrders() {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
auto@ obj = fleets[i].fleet.obj;
if(!fleets[i].arrived)
continue;
if(obj.hasOrders)
obj.clearOrders();
}
}
bool canCapture(AI& ai, BattleMission@ miss, Planet@ check) {
if(!ai.empire.isHostile(check.owner))
return false;
//TODO: Wait around a while maybe?
if(check.isProtected(ai.empire))
return false;
return true;
}
void moveTo(BattleMission@ miss, Planet@ defPl, bool force = false) {
if(!miss.arrived)
return;
if(!force) {
if(miss.fleet.obj.hasOrders)
return;
double dist = miss.fleet.obj.position.distanceTo(defPl.position);
if(dist < defPl.OrbitSize)
return;
}
vec3d pos = defPl.position;
vec2d offset = random2d(defPl.OrbitSize * 0.85);
pos.x += offset.x;
pos.z += offset.y;
miss.fleet.obj.addMoveOrder(pos);
}
void attack(BattleMission@ miss, Object@ target, bool force = false) {
//TODO: make this not chase stuff out of the system like a madman?
// (in attack logic as well)
if(!miss.arrived)
return;
if(!force) {
if(miss.fleet.obj.hasOrders)
return;
}
miss.fleet.obj.addAttackOrder(target);
}
bool isCapturing(Planet@ pl) {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].capturing is pl)
return true;
}
return false;
}
bool get_isCapturingAny() {
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
if(fleets[i].capturing !is null)
return true;
}
return false;
}
};
class War : AIComponent {
Fleets@ fleets;
Intelligence@ intelligence;
Relations@ relations;
Movement@ movement;
Scouting@ scouting;
Systems@ systems;
Military@ military;
array<Battle@> battles;
ScoutingMission@ currentScout;
void create() {
@fleets = cast<Fleets>(ai.fleets);
@intelligence = cast<Intelligence>(ai.intelligence);
@relations = cast<Relations>(ai.relations);
@movement = cast<Movement>(ai.movement);
@scouting = cast<Scouting>(ai.scouting);
@systems = cast<Systems>(ai.systems);
@military = cast<Military>(ai.military);
}
void save(SaveFile& file) {
uint cnt = battles.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
battles[i].save(this, file);
fleets.saveMission(file, currentScout);
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
battles.length = cnt;
for(uint i = 0; i < cnt; ++i) {
@battles[i] = Battle();
battles[i].load(this, file);
}
@currentScout = cast<ScoutingMission>(fleets.loadMission(file));
ai.behavior.remnantAllowArbitraryClear = false;
}
double getCombatReadyStrength() {
double str = 0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
continue;
str += flAI.strength;
}
return str * str;
}
Battle@ attack(SystemAI@ sys) {
Battle atk;
@atk.system = sys;
atk.isAttack = true;
atk.curPriority = MiP_High;
@atk.staging = military.getStagingFor(sys.obj);
if(log)
ai.print("Organizing an attack against "+sys.obj.name);
claimFleetsFor(atk);
battles.insertLast(atk);
return atk;
}
Battle@ defend(SystemAI@ sys) {
Battle def;
@def.system = sys;
@def.staging = military.getClosestStaging(sys.obj);
if(log)
ai.print("Organizing defense for "+sys.obj.name);
battles.insertLast(def);
return def;
}
void claimFleetsFor(Battle@ atk) {
//TODO: This currently claims everything not in use, should it
//leave some reserves for defense? Is that good?
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
continue;
atk.join(ai, this, flAI);
}
}
void sendFleetToJoin(Battle@ atk) {
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!flAI.readyForAction)
continue;
atk.join(ai, this, flAI);
break;
}
}
bool isFightingIn(Region@ reg) {
for(uint i = 0, cnt = battles.length; i < cnt; ++i) {
if(battles[i].system.obj is reg)
return true;
}
return false;
}
void tick(double time) override {
for(uint i = 0, cnt = battles.length; i < cnt; ++i) {
if(!battles[i].tick(ai, this, time)) {
battles.removeAt(i);
--i; --cnt;
}
}
}
double ourStrength;
double curTime;
SystemAI@ best;
double totalWeight;
SystemAI@ scout;
uint scoutCount;
void check(SystemAI@ sys, double baseWeight) {
if(isFightingIn(sys.obj))
return;
if(!sys.visible) {
sys.strengthCheck(ai, minInterval=5*60.0);
if(sys.lastStrengthCheck < curTime - 5 * 60.0) {
scoutCount += 1;
if(randomd() < 1.0 / double(scoutCount))
@scout = sys;
return;
}
}
else {
sys.strengthCheck(ai, minInterval=60.0);
}
double theirStrength = sys.enemyStrength;
if(ourStrength < theirStrength * ai.behavior.attackStrengthOverkill)
return;
double w = baseWeight;
//Try to capture less important systems at first
//TODO: This should flip when we go from border skirmishes to subjugation war
uint capturable = 0;
for(uint i = 0, cnt = sys.planets.length; i < cnt; ++i) {
Planet@ pl = sys.planets[i];
if(!ai.empire.isHostile(pl.owner))
continue;
if(pl.isProtected(ai.empire))
continue;
w /= 1.0 + double(pl.level);
capturable += 1;
}
//Ignore protected systems
if(capturable == 0)
return;
//See where their defenses are low
if(theirStrength != 0) {
double defRatio = ourStrength / theirStrength;
if(defRatio > 4.0) {
//We prefer destroying some minor assets over fighting an entirely undefended system,
//because it hurts more to lose stuff.
w *= 6.0;
}
}
else {
w *= 2.0;
}
totalWeight += w;
if(randomd() < w / totalWeight)
@best = sys;
}
int totalEnemySize(SystemAI@ sys) {
int size = 0;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ other = getEmpire(i);
if(other.major && ai.empire.isHostile(other))
size += sys.obj.getStrength(other);
}
return size;
}
bool isUnderAttack(SystemAI@ sys) {
if(sys.obj.ContestedMask & ai.mask == 0)
return false;
if(totalEnemySize(sys) < 100) {
if(sys.obj.SiegedMask & ai.mask == 0)
return false;
}
return true;
}
double assignable(Battle& battle, FleetAI& fleet) {
if(fleet.fleetClass != FC_Combat)
return 0.0;
double w = 1.0;
if(fleet.mission !is null) {
w *= 0.1;
if(fleet.mission.priority >= MiP_High)
w *= 0.1;
if(fleet.mission.priority >= battle.curPriority)
return 0.0;
auto@ miss = cast<BattleMission>(fleet.mission);
if(miss !is null && miss.battleIn is battle.system.obj)
return 0.0;
}
else if(fleet.isHome && fleet.stationed is battle.system.obj) {
//This should be allowed to fight always
}
else if(battle.curPriority >= MiP_Critical) {
if(fleet.supplies < 0.25)
return 0.0;
if(fleet.fleetHealth < 0.25)
return 0.0;
if(fleet.filled < 0.2)
return 0.0;
if(fleet.obj.isMoving) {
if(fleet.obj.velocity.length / fleet.obj.maxAcceleration > 16.0)
w *= 0.1;
}
}
else {
if(!fleet.readyForAction)
return 0.0;
}
double fleetStrength = fleet.strength;
if(battle.ourStrength + fleetStrength < battle.enemyStrength * ai.behavior.battleStrengthOverkill)
w *= 0.25;
return w;
}
double winnability(Battle& battle) {
double ours = sqrt(battle.ourStrength);
double theirs = battle.enemyStrength;
if(theirs <= 0.0)
return 10.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ fleet = fleets.fleets[i];
double w = assignable(battle, fleet);
if(w != 0.0)
ours += sqrt(fleet.strength);
}
ours *= ours;
return ours / theirs;
}
void focusTick(double time) override {
if(currentScout !is null) {
if(currentScout.canceled || currentScout.completed)
@currentScout = null;
}
//Change our behavior a little depending on the state
ai.behavior.remnantAllowArbitraryClear = !relations.isFightingWar(aggressive=true) && battles.length == 0;
//Find any systems that need defending
//TODO: Defend allies at lowered priority
for(uint i = 0, cnt = systems.owned.length; i < cnt; ++i) {
SystemAI@ sys = systems.owned[i];
if(!isUnderAttack(sys))
continue;
if(isFightingIn(sys.obj))
continue;
defend(sys);
return;
}
//Do attacks
uint ready = fleets.countCombatReadyFleets();
if(ready != 0) {
//See if we can start a new attack
if(battles.length < ai.behavior.maxBattles && relations.isFightingWar(aggressive=true)
&& (battles.length == 0 || ready > ai.behavior.battleReserveFleets)) {
//Determine our own strength
ourStrength = getCombatReadyStrength();
//Evaluate systems to attack in our aggressive war
@best = null;
totalWeight = 0;
curTime = gameTime;
@scout = null;
scoutCount = 0;
//TODO: Consider aggressive wars against an empire to also be against that empire's vassals
for(uint i = 0, cnt = intelligence.intel.length; i < cnt; ++i) {
auto@ intel = intelligence.intel[i];
if(intel is null)
continue;
auto@ relation = relations.get(intel.empire);
if(!relation.atWar || !relation.aggressive)
continue;
for(uint n = 0, ncnt = intel.shared.length; n < ncnt; ++n) {
auto@ sys = intel.shared[n];
check(sys, 20.0);
}
for(uint n = 0, ncnt = intel.theirBorder.length; n < ncnt; ++n) {
auto@ sys = intel.theirBorder[n];
check(sys, 1.0);
}
}
//Make the attack with our fleets
if(best !is null)
attack(best);
else if(scout !is null && currentScout is null) {
if(log)
ai.print("War requests scout to flyby "+scout.obj.name);
@currentScout = scouting.scout(scout.obj);
}
}
}
}
};
AIComponent@ createWar() {
return War();
}
+712
View File
@@ -0,0 +1,712 @@
import settings.game_settings;
from empire_ai.EmpireAI import AIController;
import AIComponent@ createColonization() from "empire_ai.weasel.Colonization";
import AIComponent@ createResources() from "empire_ai.weasel.Resources";
import AIComponent@ createPlanets() from "empire_ai.weasel.Planets";
import AIComponent@ createSystems() from "empire_ai.weasel.Systems";
import AIComponent@ createFleets() from "empire_ai.weasel.Fleets";
import AIComponent@ createScouting() from "empire_ai.weasel.Scouting";
import AIComponent@ createDevelopment() from "empire_ai.weasel.Development";
import AIComponent@ createDesigns() from "empire_ai.weasel.Designs";
import AIComponent@ createBudget() from "empire_ai.weasel.Budget";
import AIComponent@ createConstruction() from "empire_ai.weasel.Construction";
import AIComponent@ createMilitary() from "empire_ai.weasel.Military";
import AIComponent@ createMovement() from "empire_ai.weasel.Movement";
import AIComponent@ createCreeping() from "empire_ai.weasel.Creeping";
import AIComponent@ createRelations() from "empire_ai.weasel.Relations";
import AIComponent@ createIntelligence() from "empire_ai.weasel.Intelligence";
import AIComponent@ createWar() from "empire_ai.weasel.War";
import AIComponent@ createResearch() from "empire_ai.weasel.Research";
import AIComponent@ createEnergy() from "empire_ai.weasel.Energy";
import IAIComponent@ createDiplomacy() from "empire_ai.weasel.Diplomacy";
import AIComponent@ createConsider() from "empire_ai.weasel.Consider";
import AIComponent@ createOrbitals() from "empire_ai.weasel.Orbitals";
import AIComponent@ createHyperdrive() from "empire_ai.weasel.ftl.Hyperdrive";
import AIComponent@ createGate() from "empire_ai.weasel.ftl.Gate";
import AIComponent@ createFling() from "empire_ai.weasel.ftl.Fling";
import AIComponent@ createSlipstream() from "empire_ai.weasel.ftl.Slipstream";
import AIComponent@ createJumpdrive() from "empire_ai.weasel.ftl.Jumpdrive";
import AIComponent@ createVerdant() from "empire_ai.weasel.race.Verdant";
import AIComponent@ createMechanoid() from "empire_ai.weasel.race.Mechanoid";
import AIComponent@ createStarChildren() from "empire_ai.weasel.race.StarChildren";
import AIComponent@ createExtragalactic() from "empire_ai.weasel.race.Extragalactic";
import AIComponent@ createLinked() from "empire_ai.weasel.race.Linked";
import AIComponent@ createDevout() from "empire_ai.weasel.race.Devout";
import AIComponent@ createAncient() from "empire_ai.weasel.race.Ancient";
import AIComponent@ createInvasion() from "empire_ai.weasel.misc.Invasion";
import bool hasInvasionMap() from "Invasion.InvasionMap";
from buildings import BuildingType;
from orbitals import OrbitalModule;
import util.formatting;
from empire import ai_full_speed;
from traits import getTraitID;
export IAIComponent, AIComponent, AI;
uint GUARD = 0xDEADBEEF;
enum AddedComponent {
AC_Invasion = 0x1,
};
interface IAIComponent : Savable {
void set(AI& ai);
void setLog();
void setLogCritical();
double getPrevFocus();
void setPrevFocus(double value);
void create();
void start();
void tick(double time);
void focusTick(double time);
void turn();
void save(SaveFile& file);
void load(SaveFile& file);
void postLoad(AI& ai);
void postSave(AI& ai);
void loadFinalize(AI& ai);
};
class AIComponent : IAIComponent, Savable {
AI@ ai;
double prevFocus = 0;
bool log = false;
bool logCritical = false;
bool logErrors = true;
double getPrevFocus() { return prevFocus; }
void setPrevFocus(double value) { prevFocus = value; }
void setLog() { log = true; }
void setLogCritical() { logCritical = true; }
void set(AI& ai) { @this.ai = ai; }
void create() {}
void start() {}
void tick(double time) {}
void focusTick(double time) {}
void turn() {}
void save(SaveFile& file) {}
void load(SaveFile& file) {}
void postLoad(AI& ai) {}
void postSave(AI& ai) {}
void loadFinalize(AI& ai) {}
};
class ProfileData {
double tickPeak = 0.0;
double tickAvg = 0.0;
double tickCount = 0.0;
double focusPeak = 0.0;
double focusAvg = 0.0;
double focusCount = 0.0;
};
final class AIBehavior {
//How many focuses we can manage in a tick
uint focusPerTick = 2;
//The maximum colonizations this AI can do in one turn
uint maxColonizations = UINT_MAX;
//How many colonizations we're guaranteed to be able to do in one turn regardless of finances
uint guaranteeColonizations = 2;
//How many colonizations at most we can be doing at once
uint maxConcurrentColonizations = UINT_MAX;
//Whether this AI will colonize planets in systems owned by someone else
// TODO: This should be partially ignored for border systems, so it can try to aggressively expand into the border
bool colonizeEnemySystems = false;
bool colonizeNeutralOwnedSystems = false;
bool colonizeAllySystems = false;
//How much this AI values claiming new systems instead of colonizing stuff in its existing ones
double weightOutwardExpand = 2.0;
//How much money this AI considers a colonization event to cost out of the budget
int colonizeBudgetCost = 80;
//Whether to do any generic expansion beyond any requests
bool colonizeGenericExpand = true;
//Latest percentage into a budget cycle that we still allow colonization
double colonizeMaxBudgetProgress = 0.66;
//Time after initial ownership change that an incomplete colonization is canceled
double colonizeFailGraceTime = 100.0;
//Time a planet that we failed to colonize is disregarded for colonization
double colonizePenalizeTime = 9.0 * 60.0;
//Maximum amount of scouting missions that can be performed simultaneously
uint maxScoutingMissions = UINT_MAX;
//Minimum time after losing vision over a system that we can scout it again
double minScoutingInterval = 3.0 * 60.0;
//Weight that it gives to exploring things near our empire instead of greedily exploring nearby things
double exploreBorderWeight = 2.0;
//How long we consider all fleets viable for scouting with
double scoutAllTimer = 3.0 * 60.0;
//How many scouts we want to have active
uint scoutsActive = 2;
//How many scanning missions we can do at once
uint maxScanningMissions = 1;
//Whether to prioritize scouting over scanning if we only have one scout
bool prioritizeScoutOverScan = true;
//Weights for what to do in generic planet development
// Leveling up an existing development focus
double focusDevelopWeight = 1.0;
// Colonizing a new scalable or high tier to focus on
double focusColonizeNewWeight = 4.0;
// Colonizing a new high tier resource to import to one of our focuses
double focusColonizeHighTierWeight = 1.0;
//How many potential designs are evaluated before choosing the best one
uint designEvaluateCount = 10;
//How long a fleet has to be fully idle before it returns to its stationed system
double fleetIdleReturnStationedTime = 60.0;
//How long we try to have a fleet be capable of firing before running out of supplies
double fleetAimSupplyDuration = 2.0 * 60.0;
//How long a potential construction can take at most before we consider it unreasonable
double constructionMaxTime = 10.0 * 60.0;
//How long a factory has to have been idle for us to consider constructing labor storage
double laborStoreIdleTimer = 60.0;
//Maximum amount of time worth of labor we want to store in our warehouses
double laborStoreMaxFillTime = 60.0 * 10.0;
//Whether to use labor to build asteroids in the background
bool backgroundBuildAsteroids = true;
//Whether to choose the best resource on an asteroid, instead of doing it randomly
bool chooseAsteroidResource = true;
//Whether to distribute labor to shipyards when planets are idle
bool distributeLaborExports = true;
//Whether to build a shipyard to consolidate multiple planets of labor where possible
bool consolidateLaborExports = true;
//Estimate amount of labor spent per point of support ship size
double estSizeSupportLabor = 0.25;
//Maximum combat fleets we can have in service at once (counts starting fleet(s))
uint maxActiveFleets = UINT_MAX;
//How much flagship size we try to make per available money
double shipSizePerMoney = 1.0 / 3.5;
//How much flagship size we try to make per available labor
double shipSizePerLabor = 1.0 / 0.33;
//How much maintenance we expect per ship size
double maintenancePerShipSize = 2.0;
//Minimum percentage increase in size before we decide to retrofit a flagship to be bigger
double shipRetrofitThreshold = 0.5;
//Whether to retrofit our free starting fleet if appropriate
bool retrofitFreeFleets = false;
//Minimum percentage of average current flagship size new fleets should be
double flagshipBuildMinAvgSize = 1.00;
//Minimum game time before we consider constructing new flagships
double flagshipBuildMinGameTime = 4.0 * 60.0;
//Whether to build factories when we need labor
bool buildFactoryForLabor = true;
//Whether to build warehouses when we're not using labor
bool buildLaborStorage = true;
//Whether factories can queue labor resource imports when needed
bool allowRequestLaborImports = true;
//Whether fleets with ghosted supports attempt to rebuild the ghosts or just clear them
bool fleetsRebuildGhosts = true;
//When trying to order supports on a fleet, wait for the planet to construct its supports so we can claim them
bool supportOrderWaitOnFactory = true;
//How much stronger we need to be than a remnant fleet to clear it
double remnantOverkillFactor = 1.5;
//Whether to allow idle fleets to be sent to clear remnants
// Modified by Relations
bool remnantAllowArbitraryClear = true;
//Whether we should aggressively try to take out enemies
bool aggressive = false;
//Whether to become aggressive after we get boxed in and can no longer expand anywhere
bool aggressiveWhenBoxedIn = false;
//Whether we should never declare war ourselves
bool passive = false;
//Whether to hate human players the most
bool biased = false;
//How much stronger we need to be than someone to declare war out of hatred
double hatredWarOverkill = 0.5;
//How much stronger we need to be than someone to try to take them out in an aggressive war
double aggressiveWarOverkill = 1.5;
//How much stronger we want to be before we attack a system
double attackStrengthOverkill = 1.5;
//How many battles we can be performing at once
uint maxBattles = UINT_MAX;
//How much we try to overkill while fighting
double battleStrengthOverkill = 1.5;
//How many fleets we don't commit to attacks when we're already currently fighting
uint battleReserveFleets = 1;
//How much extra supply we try to have before starting a capture, to make sure we can actually do it
double captureSupplyEstimate = 1.5;
//Maximum hop distance we use as staging areas for our attacks
int stagingMaxHops = 5;
//If our fleet fill is less than this, immediately move back to factory from staging
double stagingToFactoryFill = 0.6;
//How much ftl is reserved for critical applications
double ftlReservePctCritical = 0.25;
//How much ftl is reserved to not be used for background applications
double ftlReservePctNormal = 0.25;
//How many artifacts we consider where to use per focus turn
uint artifactFocusConsiderCount = 2;
//How long after trying to build a generically requested building we give up
double genericBuildExpire = 3.0 * 60.0;
//How much the hate in a relationship decays to every minute
double hateDecayRate = 0.9;
//How much weaker we need to be to even consider surrender
double surrenderMinStrength = 0.5;
//How many of our total war points have to be taken by an empire for us to surrender
double acceptSurrenderRatio = 0.75;
double offerSurrenderRatio = 0.5;
void setDifficulty(int diff, uint flags) {
//This changes the behavior values based on difficulty and flags
if(flags & AIF_Aggressive != 0)
aggressive = true;
if(flags & AIF_Passive != 0)
passive = true;
if(flags & AIF_Biased != 0)
biased = true;
//Low difficulties can't colonize as many things at once
if(diff <= 0) {
maxConcurrentColonizations = 1;
guaranteeColonizations = 1;
weightOutwardExpand = 0.5;
}
else if(diff <= 1) {
maxConcurrentColonizations = 2;
weightOutwardExpand = 1.0;
}
//Hard AI becomes aggressive when it gets boxed in
aggressiveWhenBoxedIn = diff >= 2;
//Easy difficulty can't attack and defend at the same time
if(diff <= 0)
maxBattles = 1;
//Low difficulties aren't as good at managing labor
if(diff <= 0) {
distributeLaborExports = false;
consolidateLaborExports = false;
buildLaborStorage = false;
}
else if(diff <= 1) {
consolidateLaborExports = false;
}
//Low difficulties aren't as good at managing fleets
if(diff <= 0) {
maxActiveFleets = 2;
retrofitFreeFleets = true;
}
//Low difficulties aren't as good at scouting
if(diff <= 1)
scoutAllTimer = 0.0;
//Low difficulties are worse at designing
if(diff <= 0)
designEvaluateCount = 3;
else if(diff <= 1)
designEvaluateCount = 8;
else
designEvaluateCount = 12;
//Easy is a bit slow
if(diff <= 0)
focusPerTick = 1;
else if(diff >= 2)
focusPerTick = 3;
}
};
final class AIDefs {
const BuildingType@ Factory;
const BuildingType@ LaborStorage;
const OrbitalModule@ Shipyard;
};
final class AI : AIController, Savable {
Empire@ empire;
AIBehavior behavior;
AIDefs defs;
int cycleId = -1;
uint componentCycle = 0;
uint addedComponents = 0;
uint majorMask = 0;
uint difficulty = 0;
uint flags = 0;
bool isLoading = false;
array<IAIComponent@> components;
array<ProfileData> profileData;
IAIComponent@ fleets;
IAIComponent@ budget;
IAIComponent@ colonization;
IAIComponent@ resources;
IAIComponent@ planets;
IAIComponent@ systems;
IAIComponent@ scouting;
IAIComponent@ development;
IAIComponent@ designs;
IAIComponent@ construction;
IAIComponent@ military;
IAIComponent@ movement;
IAIComponent@ creeping;
IAIComponent@ relations;
IAIComponent@ intelligence;
IAIComponent@ war;
IAIComponent@ research;
IAIComponent@ energy;
IAIComponent@ diplomacy;
IAIComponent@ consider;
IAIComponent@ orbitals;
IAIComponent@ ftl;
IAIComponent@ race;
IAIComponent@ invasion;
void createComponents() {
//NOTE: This is also save/load order, so
//make sure to add loading logic when changing this list
@budget = add(createBudget());
@planets = add(createPlanets());
@resources = add(createResources());
@colonization = add(createColonization());
@systems = add(createSystems());
@fleets = add(createFleets());
@scouting = add(createScouting());
@development = add(createDevelopment());
@designs = add(createDesigns());
@construction = add(createConstruction());
@military = add(createMilitary());
@movement = add(createMovement());
@creeping = add(createCreeping());
@relations = add(createRelations());
@intelligence = add(createIntelligence());
@war = add(createWar());
@research = add(createResearch());
@energy = add(createEnergy());
@diplomacy = add(createDiplomacy());
@consider = add(createConsider());
@orbitals = add(createOrbitals());
//Make FTL component
if(empire.hasTrait(getTraitID("Hyperdrive")))
@ftl = add(createHyperdrive());
else if(empire.hasTrait(getTraitID("Gate")))
@ftl = add(createGate());
else if(empire.hasTrait(getTraitID("Fling")))
@ftl = add(createFling());
else if(empire.hasTrait(getTraitID("Slipstream")))
@ftl = add(createSlipstream());
else if(empire.hasTrait(getTraitID("Jumpdrive")))
@ftl = add(createJumpdrive());
//Make racial component
if(empire.hasTrait(getTraitID("Verdant")))
@race = add(createVerdant());
else if(empire.hasTrait(getTraitID("Mechanoid")))
@race = add(createMechanoid());
else if(empire.hasTrait(getTraitID("StarChildren")))
@race = add(createStarChildren());
else if(empire.hasTrait(getTraitID("Extragalactic")))
@race = add(createExtragalactic());
else if(empire.hasTrait(getTraitID("Linked")))
@race = add(createLinked());
else if(empire.hasTrait(getTraitID("Devout")))
@race = add(createDevout());
else if(empire.hasTrait(getTraitID("Ancient")))
@race = add(createAncient());
//Misc components
if(hasInvasionMap() || addedComponents & AC_Invasion != 0) {
@invasion = add(createInvasion());
addedComponents |= AC_Invasion;
}
//if(empire is playerEmpire) {
//log(race);
// log(colonization);
// log(resources);
// log(construction);
//}
//log(intelligence);
//logAll();
logCritical();
profileData.length = components.length;
for(uint i = 0, cnt = components.length; i < cnt; ++i)
components[i].create();
}
void createGeneral() {
}
void init(Empire& emp, EmpireSettings& settings) {
@this.empire = emp;
flags = settings.aiFlags;
difficulty = settings.difficulty;
behavior.setDifficulty(difficulty, flags);
createComponents();
}
int getDifficultyLevel() {
return difficulty;
}
void load(SaveFile& file) {
file >> empire;
file >> cycleId;
file >> majorMask;
file >> difficulty;
file >> flags;
if(file >= SV_0153)
file >> addedComponents;
behavior.setDifficulty(difficulty, flags);
createComponents();
createGeneral();
uint loadCnt = 0;
file >> loadCnt;
loadCnt = loadCnt;
for(uint i = 0; i < loadCnt; ++i) {
double prevFocus = 0;
file >> prevFocus;
components[i].setPrevFocus(prevFocus);
file >> components[i];
uint check = 0;
file >> check;
if(check != GUARD)
error("ERROR: AI Load error detected in component "+addrstr(components[i])+" of "+empire.name);
}
for(uint i = 0, cnt = components.length; i < cnt; ++i)
components[i].postLoad(this);
isLoading = true;
}
void save(SaveFile& file) {
file << empire;
file << cycleId;
file << majorMask;
file << difficulty;
file << flags;
file << addedComponents;
uint saveCnt = components.length;
file << saveCnt;
for(uint i = 0; i < saveCnt; ++i) {
file << components[i].getPrevFocus();
file << components[i];
file << GUARD;
}
for(uint i = 0, cnt = components.length; i < cnt; ++i)
components[i].postSave(this);
}
void log(IAIComponent@ comp) {
if(comp is null)
return;
comp.setLog();
comp.setLogCritical();
}
void logCritical() {
for(uint i = 0, cnt = components.length; i < cnt; ++i)
components[i].setLogCritical();
}
void logAll() {
for(uint i = 0, cnt = components.length; i < cnt; ++i) {
components[i].setLog();
components[i].setLogCritical();
}
}
IAIComponent@ add(IAIComponent& component) {
component.set(this);
components.insertLast(component);
return component;
}
void init(Empire& emp) {
majorMask = 0;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(emp.major)
majorMask |= emp.mask;
}
createGeneral();
}
bool hasStarted = false;
void tick(Empire& emp, double time) {
if(isLoading) {
for(uint i = 0, cnt = components.length; i < cnt; ++i)
components[i].loadFinalize(this);
isLoading = false;
hasStarted = true;
}
else if(!hasStarted) {
for(uint i = 0, cnt = components.length; i < cnt; ++i)
components[i].start();
hasStarted = true;
}
else if(emp.Victory == -1) {
//Don't do anything when actually defeated
return;
}
//Manage gametime-specific behaviors
behavior.colonizeGenericExpand = gameTime >= 6.0 * 60.0;
//Find cycled turns
int curCycle = emp.BudgetCycleId;
if(curCycle != cycleId) {
for(uint i = 0, cnt = components.length; i < cnt; ++i)
components[i].turn();
cycleId = curCycle;
}
//Generic ticks
double startTime = getExactTime();
for(uint i = 0, cnt = components.length; i < cnt; ++i) {
auto@ comp = components[i];
comp.tick(time);
double endTime = getExactTime();
//double ms = 1000.0 * (endTime - startTime);
startTime = endTime;
//auto@ dat = profileData[i];
//dat.tickPeak = max(dat.tickPeak, ms);
//dat.tickAvg += ms;
//dat.tickCount += 1.0;
}
//Do focuseds tick on components
uint focusCount = behavior.focusPerTick;
if(ai_full_speed.value == 1.0)
focusCount = max(uint(round((time / 0.25) * behavior.focusPerTick)), behavior.focusPerTick);
double allocStart = startTime;
for(uint n = 0; n < focusCount; ++n) {
componentCycle = (componentCycle+1) % components.length;
auto@ focusComp = components[componentCycle];
focusComp.focusTick(gameTime - focusComp.getPrevFocus());
focusComp.setPrevFocus(gameTime);
double endTime = getExactTime();
//double ms = 1000.0 * (endTime - startTime);
startTime = endTime;
if(endTime - allocStart > 4000.0)
break;
//auto@ dat = profileData[componentCycle];
//dat.focusPeak = max(dat.focusPeak, ms);
//dat.focusAvg += ms;
//dat.focusCount += 1.0;
}
}
void dumpProfile() {
for(uint i = 0, cnt = components.length; i < cnt; ++i) {
auto@ c = profileData[i];
print(pad(addrstr(components[i]), 40)+" tick peak "+toString(c.tickPeak,2)+" tick avg "+toString(c.tickAvg/c.tickCount, 2)
+" focus peak "+toString(c.focusPeak,2)+" focus avg "+toString(c.focusAvg/c.focusCount, 2));
}
}
void resetProfile() {
for(uint i = 0, cnt = profileData.length; i < cnt; ++i) {
auto@ c = profileData[i];
c.tickPeak = 0.0;
c.tickAvg = 0.0;
c.tickCount = 0.0;
c.focusPeak = 0.0;
c.focusAvg = 0.0;
c.focusCount = 0.0;
}
}
uint get_mask() {
return empire.mask;
}
uint get_teamMask() {
//TODO
return empire.mask;
}
uint get_visionMask() {
return majorMask & empire.visionMask;
}
uint get_allyMask() {
return empire.mutualDefenseMask | empire.ForcedPeaceMask.value;
}
uint get_enemyMask() {
return empire.hostileMask & majorMask;
}
uint get_neutralMask() {
return majorMask & ~allyMask & ~mask & ~enemyMask;
}
uint get_otherMask() {
return majorMask & ~mask;
}
string pad(const string& input, uint width) {
string str = input;
while(str.length < width)
str += " ";
return str;
}
void print(const string& info, Object@ related = null, double value = INFINITY, bool flag = false, Empire@ emp = null) {
string str = info;
if(related !is null)
str = pad(related.name, 16)+" | "+str;
str = pad("["+empire.index+": "+empire.name+" AI] ", 20)+str;
str = formatGameTime(gameTime) + " " + str;
if(value != INFINITY)
str += " | Value = "+standardize(value, true);
if(flag)
str += " | FLAGGED On";
if(emp !is null)
str += " | Target = "+emp.name;
::print(str);
}
void debugAI() {}
void commandAI(string cmd) {}
void aiPing(Empire@ fromEmpire, vec3d position, uint type) {}
void pause(Empire& emp) {}
void resume(Empire& emp) {}
vec3d get_aiFocus() { return vec3d(); }
string getOpinionOf(Empire& emp, Empire@ other) { return ""; }
int getStandingTo(Empire& emp, Empire@ other) { return 0; }
};
AIController@ createWeaselAI() {
return AI();
}
+167
View File
@@ -0,0 +1,167 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Colonization;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Budget;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Development;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Military;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Scouting;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Creeping;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Relations;
import empire_ai.weasel.Intelligence;
import empire_ai.weasel.War;
import empire_ai.weasel.Research;
import empire_ai.weasel.Energy;
import empire_ai.weasel.Diplomacy;
import empire_ai.weasel.ftl.Gate;
import empire_ai.weasel.ftl.Hyperdrive;
import empire_ai.weasel.ftl.Fling;
import empire_ai.weasel.ftl.Slipstream;
import empire_ai.weasel.ftl.Jumpdrive;
import empire_ai.weasel.race.Verdant;
import empire_ai.weasel.race.Mechanoid;
import empire_ai.weasel.race.StarChildren;
import empire_ai.weasel.race.Extragalactic;
import empire_ai.weasel.race.Linked;
import empire_ai.weasel.race.Devout;
import empire_ai.weasel.race.Ancient;
import empire_ai.weasel.misc.Invasion;
import empire_ai.EmpireAI;
AI@ ai(uint index) {
Empire@ emp = getEmpire(index);
return cast<AI>(cast<EmpireAI>(emp.EmpireAI).ctrl);
}
Colonization@ colonization(uint index) {
return cast<Colonization>(ai(index).colonization);
}
Construction@ construction(uint index) {
return cast<Construction>(ai(index).construction);
}
Budget@ budget(uint index) {
return cast<Budget>(ai(index).budget);
}
Designs@ designs(uint index) {
return cast<Designs>(ai(index).designs);
}
Development@ development(uint index) {
return cast<Development>(ai(index).development);
}
Fleets@ fleets(uint index) {
return cast<Fleets>(ai(index).fleets);
}
Military@ military(uint index) {
return cast<Military>(ai(index).military);
}
Planets@ planets(uint index) {
return cast<Planets>(ai(index).planets);
}
Resources@ resources(uint index) {
return cast<Resources>(ai(index).resources);
}
Scouting@ scouting(uint index) {
return cast<Scouting>(ai(index).scouting);
}
Systems@ systems(uint index) {
return cast<Systems>(ai(index).systems);
}
Movement@ movement(uint index) {
return cast<Movement>(ai(index).movement);
}
Creeping@ creeping(uint index) {
return cast<Creeping>(ai(index).creeping);
}
Relations@ relations(uint index) {
return cast<Relations>(ai(index).relations);
}
Intelligence@ intelligence(uint index) {
return cast<Intelligence>(ai(index).intelligence);
}
War@ war(uint index) {
return cast<War>(ai(index).war);
}
Research@ research(uint index) {
return cast<Research>(ai(index).research);
}
Energy@ energy(uint index) {
return cast<Energy>(ai(index).energy);
}
Diplomacy@ diplomacy(uint index) {
return cast<Diplomacy>(ai(index).diplomacy);
}
Gate@ gate(uint index) {
return cast<Gate>(ai(index).ftl);
}
Hyperdrive@ hyperdrive(uint index) {
return cast<Hyperdrive>(ai(index).ftl);
}
Fling@ fling(uint index) {
return cast<Fling>(ai(index).ftl);
}
Slipstream@ slipstream(uint index) {
return cast<Slipstream>(ai(index).ftl);
}
Jumpdrive@ jumpdrive(uint index) {
return cast<Jumpdrive>(ai(index).ftl);
}
Mechanoid@ mechanoid(uint index) {
return cast<Mechanoid>(ai(index).race);
}
Verdant@ verdant(uint index) {
return cast<Verdant>(ai(index).race);
}
StarChildren@ starchildren(uint index) {
return cast<StarChildren>(ai(index).race);
}
Extragalactic@ extragalactic(uint index) {
return cast<Extragalactic>(ai(index).race);
}
Linked@ linked(uint index) {
return cast<Linked>(ai(index).race);
}
Devout@ devout(uint index) {
return cast<Devout>(ai(index).race);
}
Ancient@ ancient(uint index) {
return cast<Ancient>(ai(index).race);
}
Invasion@ invasion(uint index) {
return cast<Invasion>(ai(index).invasion);
}
@@ -0,0 +1,400 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Military;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Development;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Budget;
import empire_ai.weasel.Fleets;
import ftl;
from orbitals import getOrbitalModuleID;
const double FLING_MIN_DISTANCE_STAGE = 10000;
const double FLING_MIN_DISTANCE_DEVELOP = 20000;
const double FLING_MIN_TIMER = 3.0 * 60.0;
int flingModule = -1;
void init() {
flingModule = getOrbitalModuleID("FlingCore");
}
class FlingRegion : Savable {
Region@ region;
Object@ obj;
bool installed = false;
vec3d destination;
void save(SaveFile& file) {
file << region;
file << obj;
file << installed;
file << destination;
}
void load(SaveFile& file) {
file >> region;
file >> obj;
file >> installed;
file >> destination;
}
};
class Fling : FTL {
Military@ military;
Designs@ designs;
Construction@ construction;
Development@ development;
Systems@ systems;
Budget@ budget;
Fleets@ fleets;
array<FlingRegion@> tracked;
array<Object@> unused;
BuildOrbital@ buildFling;
double nextBuildTry = 15.0 * 60.0;
bool wantToBuild = false;
void create() override {
@military = cast<Military>(ai.military);
@designs = cast<Designs>(ai.designs);
@construction = cast<Construction>(ai.construction);
@development = cast<Development>(ai.development);
@systems = cast<Systems>(ai.systems);
@budget = cast<Budget>(ai.budget);
@fleets = cast<Fleets>(ai.fleets);
}
void save(SaveFile& file) override {
construction.saveConstruction(file, buildFling);
file << nextBuildTry;
file << wantToBuild;
uint cnt = tracked.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << tracked[i];
cnt = unused.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << unused[i];
}
void load(SaveFile& file) override {
@buildFling = cast<BuildOrbital>(construction.loadConstruction(file));
file >> nextBuildTry;
file >> wantToBuild;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
FlingRegion fr;
file >> fr;
tracked.insertLast(fr);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Object@ obj;
file >> obj;
if(obj !is null)
unused.insertLast(obj);
}
}
uint order(MoveOrder& ord) override {
if(!canFling(ord.obj))
return F_Pass;
//Find the position to fling to
vec3d toPosition;
if(!targetPosition(ord, toPosition))
return F_Pass;
//Don't fling if we're saving our FTL for a new beacon
double avail = usableFTL(ai, ord);
if((buildFling !is null && !buildFling.started) || wantToBuild)
avail = min(avail, ai.empire.FTLStored - 250.0);
//Make sure we have the ftl to fling
if(flingCost(ord.obj, toPosition) > avail)
return F_Pass;
//Make sure we're in range of a beacon
Object@ beacon = getClosest(ord.obj.position);
if(beacon is null || beacon.position.distanceTo(ord.obj.position) > FLING_BEACON_RANGE)
return F_Pass;
ord.obj.addFlingOrder(beacon, toPosition);
return F_Continue;
}
FlingRegion@ get(Region@ reg) {
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].region is reg)
return tracked[i];
}
return null;
}
void remove(FlingRegion@ gt) {
if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire)
unused.insertLast(gt.obj);
tracked.remove(gt);
}
Object@ getClosest(const vec3d& position) {
Object@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
Object@ obj = tracked[i].obj;
if(obj is null)
continue;
double d = obj.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = obj;
}
}
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
Object@ obj = unused[i];
if(obj is null)
continue;
double d = obj.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = obj;
}
}
return closest;
}
FlingRegion@ getClosestRegion(const vec3d& position) {
FlingRegion@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
double d = tracked[i].region.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = tracked[i];
}
}
return closest;
}
void assignTo(FlingRegion@ track, Object@ closest) {
unused.remove(closest);
@track.obj = closest;
}
bool trackingBeacon(Object@ obj) {
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
if(unused[i] is obj)
return true;
}
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].obj is obj)
return true;
}
return false;
}
bool shouldHaveBeacon(Region@ reg, bool always = false) {
if(military.getBase(reg) !is null)
return true;
if(development.isDevelopingIn(reg))
return true;
return false;
}
void focusTick(double time) override {
//Manage unused beacons list
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
Object@ obj = unused[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
unused.removeAt(i);
--i; --cnt;
}
}
//Detect new beacons
auto@ data = ai.empire.getFlingBeacons();
Object@ obj;
while(receive(data, obj)) {
if(obj is null)
continue;
if(!trackingBeacon(obj))
unused.insertLast(obj);
}
//Update existing beacons for staging bases
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ reg = tracked[i];
bool checkAlways = false;
if(reg.obj !is null) {
if(!reg.obj.valid || reg.obj.owner !is ai.empire || reg.obj.region !is reg.region) {
@reg.obj = null;
checkAlways = true;
}
}
if(!shouldHaveBeacon(reg.region, checkAlways)) {
remove(tracked[i]);
--i; --cnt;
}
}
//Detect new staging bases to build beacons at
for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) {
auto@ base = military.stagingBases[i];
if(base.occupiedTime < FLING_MIN_TIMER)
continue;
if(get(base.region) is null) {
FlingRegion@ closest = getClosestRegion(base.region.position);
if(closest !is null && closest.region.position.distanceTo(base.region.position) < FLING_MIN_DISTANCE_STAGE)
continue;
FlingRegion gt;
@gt.region = base.region;
tracked.insertLast(gt);
break;
}
}
//Detect new important planets to build beacons at
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
auto@ focus = development.focuses[i];
Region@ reg = focus.obj.region;
if(reg is null)
continue;
if(get(reg) is null) {
FlingRegion@ closest = getClosestRegion(reg.position);
if(closest !is null && closest.region.position.distanceTo(reg.position) < FLING_MIN_DISTANCE_DEVELOP)
continue;
FlingRegion gt;
@gt.region = reg;
tracked.insertLast(gt);
break;
}
}
//Destroy beacons if we're having ftl trouble
if(ai.empire.FTLShortage) {
Orbital@ leastImportant;
double leastWeight = INFINITY;
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
Orbital@ obj = cast<Orbital>(unused[i]);
if(obj is null || !obj.valid)
continue;
@leastImportant = obj;
leastWeight = 0.0;
break;
}
if(leastImportant !is null) {
if(log)
ai.print("Scuttle unused beacon for ftl", leastImportant.region);
leastImportant.scuttle();
}
else {
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
Orbital@ obj = cast<Orbital>(tracked[i].obj);
if(obj is null || !obj.valid)
continue;
double weight = 1.0;
auto@ base = military.getBase(tracked[i].region);
if(base is null) {
weight *= 5.0;
}
else if(base.idleTime >= 1) {
weight *= 1.0 + (base.idleTime / 60.0);
}
else {
weight /= 2.0;
}
if(weight < leastWeight) {
@leastImportant = obj;
leastWeight = weight;
}
}
if(leastImportant !is null) {
if(log)
ai.print("Scuttle unimportant beacon for ftl", leastImportant.region);
leastImportant.scuttle();
}
}
}
//See if we should build a new gate
if(buildFling !is null) {
if(buildFling.completed) {
@buildFling = null;
nextBuildTry = gameTime + 60.0;
}
}
wantToBuild = false;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ gt = tracked[i];
if(gt.obj is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) {
Object@ found;
for(uint n = 0, ncnt = unused.length; n < ncnt; ++n) {
Object@ obj = unused[n];
if(obj.region is gt.region) {
@found = obj;
break;
}
}
if(found !is null) {
if(log)
ai.print("Assign beacon to => "+gt.region.name, found.region);
assignTo(gt, found);
} else if(buildFling is null && gameTime > nextBuildTry && !ai.empire.isFTLShortage(0.15)) {
if(ai.empire.FTLStored >= 250) {
if(log)
ai.print("Build beacon for this system", gt.region);
@buildFling = construction.buildOrbital(getOrbitalModule(flingModule), military.getStationPosition(gt.region));
}
else {
wantToBuild = true;
}
}
}
}
//Scuttle anything unused if we don't need beacons in those regions
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
if(get(unused[i].region) is null && unused[i].isOrbital) {
cast<Orbital>(unused[i]).scuttle();
unused.removeAt(i);
--i; --cnt;
}
}
//Try to get enough ftl storage that we can fling our largest fleet and have some remaining
double highestCost = 0.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
highestCost = max(highestCost, double(flingCost(flAI.obj, vec3d())));
}
development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal);
}
};
AIComponent@ createFling() {
return Fling();
}
+444
View File
@@ -0,0 +1,444 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Military;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Development;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Budget;
from statuses import getStatusID;
from abilities import getAbilityID;
const double GATE_MIN_DISTANCE_STAGE = 10000;
const double GATE_MIN_DISTANCE_DEVELOP = 20000;
const double GATE_MIN_DISTANCE_BORDER = 30000;
const double GATE_MIN_TIMER = 3.0 * 60.0;
const int GATE_BUILD_MOVE_HOPS = 5;
int packAbility = -1;
int unpackAbility = -1;
int packedStatus = -1;
int unpackedStatus = -1;
void init() {
packAbility = getAbilityID("GatePack");
unpackAbility = getAbilityID("GateUnpack");
packedStatus = getAbilityID("GatePacked");
unpackedStatus = getAbilityID("GateUnpacked");
}
class GateRegion : Savable {
Region@ region;
Object@ gate;
bool installed = false;
vec3d destination;
void save(SaveFile& file) {
file << region;
file << gate;
file << installed;
file << destination;
}
void load(SaveFile& file) {
file >> region;
file >> gate;
file >> installed;
file >> destination;
}
};
class Gate : FTL {
Military@ military;
Designs@ designs;
Construction@ construction;
Development@ development;
Systems@ systems;
Budget@ budget;
DesignTarget@ gateDesign;
array<GateRegion@> tracked;
array<Object@> unassigned;
BuildStation@ buildGate;
double nextBuildTry = 15.0 * 60.0;
void create() override {
@military = cast<Military>(ai.military);
@designs = cast<Designs>(ai.designs);
@construction = cast<Construction>(ai.construction);
@development = cast<Development>(ai.development);
@systems = cast<Systems>(ai.systems);
@budget = cast<Budget>(ai.budget);
}
void save(SaveFile& file) override {
designs.saveDesign(file, gateDesign);
construction.saveConstruction(file, buildGate);
file << nextBuildTry;
uint cnt = tracked.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << tracked[i];
cnt = unassigned.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << unassigned[i];
}
void load(SaveFile& file) override {
@gateDesign = designs.loadDesign(file);
@buildGate = cast<BuildStation>(construction.loadConstruction(file));
file >> nextBuildTry;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
GateRegion gt;
file >> gt;
tracked.insertLast(gt);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Object@ obj;
file >> obj;
if(obj !is null)
unassigned.insertLast(obj);
}
}
GateRegion@ get(Region@ reg) {
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].region is reg)
return tracked[i];
}
return null;
}
void remove(GateRegion@ gt) {
if(gt.gate !is null && gt.gate.valid && gt.gate.owner is ai.empire)
unassigned.insertLast(gt.gate);
tracked.remove(gt);
}
Object@ getClosestGate(const vec3d& position) {
Object@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
Object@ gate = tracked[i].gate;
if(gate is null)
continue;
if(!tracked[i].installed)
continue;
double d = gate.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = gate;
}
}
return closest;
}
GateRegion@ getClosestGateRegion(const vec3d& position) {
GateRegion@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
double d = tracked[i].region.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = tracked[i];
}
}
return closest;
}
void assignTo(GateRegion@ gt, Object@ closest) {
unassigned.remove(closest);
@gt.gate = closest;
gt.installed = false;
if(closest.region is gt.region) {
if(closest.hasStatusEffect(unpackedStatus)) {
gt.installed = true;
}
}
if(!gt.installed) {
gt.destination = military.getStationPosition(gt.region);
closest.activateAbilityTypeFor(ai.empire, packAbility);
closest.addMoveOrder(gt.destination);
}
}
bool trackingGate(Object@ obj) {
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
if(unassigned[i] is obj)
return true;
}
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].gate is obj)
return true;
}
return false;
}
bool shouldHaveGate(Region@ reg, bool always = false) {
if(military.getBase(reg) !is null)
return true;
if(development.isDevelopingIn(reg))
return true;
if(!always) {
auto@ sys = systems.getAI(reg);
if(sys !is null) {
if(sys.border && sys.bordersEmpires)
return true;
}
}
return false;
}
void turn() override {
if(gateDesign !is null && gateDesign.active !is null) {
int newSize = round(double(budget.spendable(BT_Military)) * 0.5 * ai.behavior.shipSizePerMoney / 64.0) * 64;
if(newSize < 128)
newSize = 128;
if(newSize != gateDesign.targetSize) {
@gateDesign = designs.design(DP_Gate, newSize);
gateDesign.customName = "Gate";
}
}
}
void focusTick(double time) override {
//Design a gate
if(gateDesign is null) {
@gateDesign = designs.design(DP_Gate, 128);
gateDesign.customName = "Gate";
}
//Manage unassigned gates list
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
Object@ obj = unassigned[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
unassigned.removeAt(i);
--i; --cnt;
}
}
//Detect new gates
auto@ data = ai.empire.getStargates();
Object@ obj;
while(receive(data, obj)) {
if(obj is null)
continue;
if(!trackingGate(obj))
unassigned.insertLast(obj);
}
//Update existing gates for staging bases
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ gt = tracked[i];
bool checkAlways = false;
if(gt.gate !is null) {
if(!gt.gate.valid || gt.gate.owner !is ai.empire || (gt.installed && gt.gate.region !is gt.region)) {
@gt.gate = null;
gt.installed = false;
checkAlways = true;
}
else if(!gt.installed && !gt.gate.hasOrders) {
if(gt.destination.distanceTo(gt.gate.position) < 10.0) {
gt.gate.activateAbilityTypeFor(ai.empire, unpackAbility, gt.destination);
gt.installed = true;
}
else {
gt.gate.activateAbilityTypeFor(ai.empire, packAbility);
gt.gate.addMoveOrder(gt.destination);
}
}
}
if(!shouldHaveGate(gt.region, checkAlways)) {
remove(tracked[i]);
--i; --cnt;
}
}
//Detect new staging bases to build gates at
for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) {
auto@ base = military.stagingBases[i];
if(base.occupiedTime < GATE_MIN_TIMER)
continue;
if(get(base.region) is null) {
GateRegion@ closest = getClosestGateRegion(base.region.position);
if(closest !is null && closest.region.position.distanceTo(base.region.position) < GATE_MIN_DISTANCE_STAGE)
continue;
GateRegion gt;
@gt.region = base.region;
tracked.insertLast(gt);
break;
}
}
//Detect new important planets to build gates at
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
auto@ focus = development.focuses[i];
Region@ reg = focus.obj.region;
if(reg is null)
continue;
if(get(reg) is null) {
GateRegion@ closest = getClosestGateRegion(reg.position);
if(closest !is null && closest.region.position.distanceTo(reg.position) < GATE_MIN_DISTANCE_DEVELOP)
continue;
GateRegion gt;
@gt.region = reg;
tracked.insertLast(gt);
break;
}
}
//Detect new border systems to build gates at
uint offset = randomi(0, systems.border.length-1);
for(uint i = 0, cnt = systems.border.length; i < cnt; ++i) {
auto@ sys = systems.border[(i+offset)%cnt];
Region@ reg = sys.obj;
if(reg is null)
continue;
if(!sys.bordersEmpires)
continue;
if(get(reg) is null) {
GateRegion@ closest = getClosestGateRegion(reg.position);
if(closest !is null && closest.region.position.distanceTo(reg.position) < GATE_MIN_DISTANCE_DEVELOP)
continue;
GateRegion gt;
@gt.region = reg;
tracked.insertLast(gt);
break;
}
}
//Destroy gates if we're having ftl trouble
if(ai.empire.FTLShortage) {
Ship@ leastImportant;
double leastWeight = INFINITY;
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
Ship@ ship = cast<Ship>(unassigned[i]);
if(ship is null || !ship.valid)
continue;
double weight = ship.blueprint.design.size;
weight *= 10.0;
if(weight < leastWeight) {
@leastImportant = ship;
leastWeight = weight;
}
}
if(leastImportant !is null) {
if(log)
ai.print("Scuttle unassigned gate for ftl", leastImportant.region);
leastImportant.scuttle();
}
else {
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
Ship@ ship = cast<Ship>(tracked[i].gate);
if(ship is null || !ship.valid)
continue;
double weight = ship.blueprint.design.size;
auto@ base = military.getBase(tracked[i].region);
if(base is null) {
weight *= 5.0;
}
else if(base.idleTime >= 1) {
weight *= 1.0 + (base.idleTime / 60.0);
}
else {
weight /= 2.0;
}
if(weight < leastWeight) {
@leastImportant = ship;
leastWeight = weight;
}
}
if(leastImportant !is null) {
if(log)
ai.print("Scuttle unimportant gate for ftl", leastImportant.region);
leastImportant.scuttle();
}
}
}
//See if we should build a new gate
if(buildGate !is null) {
if(buildGate.completed) {
@buildGate = null;
nextBuildTry = gameTime + 60.0;
}
}
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ gt = tracked[i];
if(gt.gate is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) {
Object@ closest;
double closestDist = INFINITY;
for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) {
Object@ obj = unassigned[n];
if(obj.region is gt.region) {
@closest = obj;
break;
}
if(!obj.hasMover)
continue;
if(buildGate is null && gameTime > nextBuildTry) {
double d = obj.position.distanceTo(gt.region.position);
if(d < closestDist) {
closestDist = d;
@closest = obj;
}
}
}
if(closest !is null) {
if(log)
ai.print("Assign gate to => "+gt.region.name, closest.region);
assignTo(gt, closest);
} else if(buildGate is null && gameTime > nextBuildTry && !ai.empire.isFTLShortage(0.15)) {
if(log)
ai.print("Build gate for this system", gt.region);
bool buildLocal = true;
auto@ factory = construction.primaryFactory;
if(factory !is null) {
Region@ factRegion = factory.obj.region;
if(factRegion !is null && systems.hopDistance(gt.region, factRegion) < GATE_BUILD_MOVE_HOPS)
buildLocal = false;
}
if(buildLocal)
@buildGate = construction.buildLocalStation(gateDesign);
else
@buildGate = construction.buildStation(gateDesign, military.getStationPosition(gt.region));
}
}
}
}
};
AIComponent@ createGate() {
return Gate();
}
@@ -0,0 +1,99 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Development;
import empire_ai.weasel.Fleets;
import ftl;
from orders import OrderType;
const double REJUMP_MIN_DIST = 8000.0;
const double STORAGE_AIM_DISTANCE = 40000;
class Hyperdrive : FTL {
Development@ development;
Fleets@ fleets;
void create() override {
@development = cast<Development>(ai.development);
@fleets = cast<Fleets>(ai.fleets);
}
double hdETA(Object& obj, const vec3d& position) {
double charge = HYPERDRIVE_CHARGE_TIME;
if(obj.owner.HyperdriveNeedCharge == 0)
charge = 0.0;
double dist = position.distanceTo(obj.position);
double speed = hyperdriveMaxSpeed(obj);
return charge + dist / speed;
}
double subETA(Object& obj, const vec3d& position) {
return newtonArrivalTime(obj.maxAcceleration, position - obj.position, vec3d());
}
bool shouldHD(Object& obj, const vec3d& position, uint priority) {
//This makes me sad
if(position.distanceTo(obj.position) < 3000)
return false;
double pathDist = cast<Movement>(ai.movement).getPathDistance(obj.position, position, obj.maxAcceleration);
double straightDist = position.distanceTo(obj.position);
return pathDist >= straightDist * 0.6;
}
uint order(MoveOrder& ord) override {
if(!canHyperdrive(ord.obj))
return F_Pass;
double avail = usableFTL(ai, ord);
if(avail > 0) {
vec3d toPosition;
if(targetPosition(ord, toPosition)) {
if(shouldHD(ord.obj, toPosition, ord.priority)) {
double cost = hyperdriveCost(ord.obj, toPosition);
if(avail >= cost) {
ord.obj.addHyperdriveOrder(toPosition);
return F_Continue;
}
}
}
}
return F_Pass;
}
uint tick(MoveOrder& ord, double time) {
if(ord.priority == MP_Critical && canHyperdrive(ord.obj) && ord.obj.firstOrderType != OT_Hyperdrive) {
vec3d toPosition;
if(targetPosition(ord, toPosition)) {
double dist = ord.obj.position.distanceToSQ(toPosition);
if(dist > REJUMP_MIN_DIST * REJUMP_MIN_DIST) {
double avail = usableFTL(ai, ord);
double cost = hyperdriveCost(ord.obj, toPosition);
if(avail >= cost && shouldHD(ord.obj, toPosition, ord.priority)) {
cast<Movement>(ai.movement).order(ord);
return F_Continue;
}
}
}
}
return F_Pass;
}
void focusTick(double time) override {
//Try to get enough ftl storage that we can ftl our largest fleet a fair distance and have some remaining
double highestCost = 0.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
vec3d toPosition = flAI.obj.position + vec3d(0, 0, STORAGE_AIM_DISTANCE);
highestCost = max(highestCost, double(hyperdriveCost(flAI.obj, toPosition)));
}
development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal);
}
};
AIComponent@ createHyperdrive() {
return Hyperdrive();
}
@@ -0,0 +1,208 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Development;
import empire_ai.weasel.Fleets;
import ftl;
import system_flags;
import regions.regions;
import systems;
from orders import OrderType;
const double REJUMP_MIN_DIST = 8000.0;
class Jumpdrive : FTL {
Development@ development;
Fleets@ fleets;
int safetyFlag = -1;
array<Region@> safeRegions;
void create() override {
@development = cast<Development>(ai.development);
@fleets = cast<Fleets>(ai.fleets);
safetyFlag = getSystemFlag("JumpdriveSafety");
}
void save(SaveFile& file) {
uint cnt = safeRegions.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << safeRegions[i];
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
safeRegions.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> safeRegions[i];
}
double jdETA(Object& obj, const vec3d& position) {
double charge = JUMPDRIVE_CHARGE_TIME;
return charge;
}
double subETA(Object& obj, const vec3d& position) {
return newtonArrivalTime(obj.maxAcceleration, position - obj.position, vec3d());
}
bool shouldJD(Object& obj, const vec3d& position, uint priority) {
//This makes me sad
if(position.distanceTo(obj.position) < 3000)
return false;
return true;
/*double factor = 0.8;*/
/*if(priority == MP_Critical)*/
/* factor = 1.0;*/
/*return jdETA(obj, position) <= factor * subETA(obj, position);*/
}
uint order(MoveOrder& ord) override {
return order(ord, ord.obj.position, false);
}
uint order(MoveOrder& ord, const vec3d& fromPos, bool secondary) {
if(!canJumpdrive(ord.obj))
return F_Pass;
double avail = usableFTL(ai, ord);
if(avail > 0) {
vec3d toPosition;
if(targetPosition(ord, toPosition)) {
double maxRange = jumpdriveRange(ord.obj);
double dist = toPosition.distanceTo(fromPos);
bool isSafe = false;
Region@ reg = getRegion(toPosition);
if(reg !is null)
isSafe = reg.getSystemFlag(ai.empire, safetyFlag);
if(dist > maxRange && !isSafe) {
//See if we should jump to a safe region first
if(!secondary) {
double bestHop = INFINITY;
Region@ hopRegion;
vec3d bestPos;
for(uint i = 0, cnt = safeRegions.length; i < cnt; ++i) {
if(!safeRegions[i].getSystemFlag(ai.empire, safetyFlag))
continue;
vec3d hopPos = safeRegions[i].position;
hopPos = hopPos + (fromPos-hopPos).normalized(safeRegions[i].radius * 0.85);
double d = hopPos.distanceTo(toPosition);
if(d < bestHop) {
bestHop = d;
@hopRegion = safeRegions[i];
bestPos = hopPos;
}
}
if(bestHop < dist * 0.8) {
double cost = jumpdriveCost(ord.obj, fromPos, bestPos);
if(avail >= cost) {
ord.obj.addJumpdriveOrder(bestPos);
order(ord, bestPos, true);
return F_Continue;
}
}
}
//Shorten our jump
if(ord.priority < MP_Normal)
return F_Pass;
toPosition = fromPos + (toPosition - fromPos).normalized(maxRange);
}
if(shouldJD(ord.obj, toPosition, ord.priority)) {
double cost = jumpdriveCost(ord.obj, toPosition);
if(avail >= cost) {
ord.obj.addJumpdriveOrder(toPosition, append=secondary);
return F_Continue;
}
}
}
}
return F_Pass;
}
uint tick(MoveOrder& ord, double time) {
if(ord.priority == MP_Critical && canJumpdrive(ord.obj) && ord.obj.firstOrderType != OT_Jumpdrive) {
vec3d toPosition;
if(targetPosition(ord, toPosition)) {
double dist = ord.obj.position.distanceToSQ(toPosition);
if(dist > REJUMP_MIN_DIST * REJUMP_MIN_DIST) {
double maxRange = jumpdriveRange(ord.obj);
dist = sqrt(dist);
bool isSafe = false;
Region@ reg = getRegion(toPosition);
if(reg !is null)
isSafe = reg.getSystemFlag(ai.empire, safetyFlag);
if(dist > maxRange && !isSafe)
toPosition = ord.obj.position + (toPosition - ord.obj.position).normalized(maxRange);
if(shouldJD(ord.obj, toPosition, ord.priority)) {
double avail = usableFTL(ai, ord);
double cost = jumpdriveCost(ord.obj, toPosition);
if(avail >= cost) {
cast<Movement>(ai.movement).order(ord);
return F_Continue;
}
}
}
}
}
return F_Pass;
}
uint sysChk = 0;
void start() {
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
Region@ reg = getSystem(i).object;
if(reg.getSystemFlag(ai.empire, safetyFlag))
safeRegions.insertLast(reg);
}
}
void focusTick(double time) override {
//Try to get enough ftl storage that we can ftl our largest fleet a fair distance and have some remaining
double highestCost = 0.0;
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
double dist = jumpdriveRange(flAI.obj);
vec3d toPosition = flAI.obj.position + vec3d(0, 0, dist);
highestCost = max(highestCost, double(jumpdriveCost(flAI.obj, toPosition)));
}
development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal);
//Disable systems that are no longer safe
for(uint i = 0, cnt = safeRegions.length; i < cnt; ++i) {
if(!safeRegions[i].getSystemFlag(ai.empire, safetyFlag)) {
safeRegions.removeAt(i);
--i; --cnt;
}
}
//Try to find regions that are safe for us
{
sysChk = (sysChk+1) % systemCount;
auto@ reg = getSystem(sysChk).object;
if(reg.getSystemFlag(ai.empire, safetyFlag)) {
if(safeRegions.find(reg) == -1)
safeRegions.insertLast(reg);
}
}
}
};
AIComponent@ createJumpdrive() {
return Jumpdrive();
}
@@ -0,0 +1,382 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Military;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Development;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Budget;
import empire_ai.weasel.Fleets;
from statuses import getStatusID;
from abilities import getAbilityID;
from oddity_navigation import hasOddityLink;
const double SS_MIN_DISTANCE_STAGE = 0;
const double SS_MIN_DISTANCE_DEVELOP = 10000;
const double SS_MIN_TIMER = 3.0 * 60.0;
const double SS_MAX_DISTANCE = 3000.0;
class SSRegion : Savable {
Region@ region;
Object@ obj;
bool arrived = false;
vec3d destination;
void save(SaveFile& file) {
file << region;
file << obj;
file << arrived;
file << destination;
}
void load(SaveFile& file) {
file >> region;
file >> obj;
file >> arrived;
file >> destination;
}
};
class Slipstream : FTL {
Military@ military;
Designs@ designs;
Construction@ construction;
Development@ development;
Systems@ systems;
Budget@ budget;
Fleets@ fleets;
DesignTarget@ ssDesign;
array<SSRegion@> tracked;
array<Object@> unassigned;
BuildFlagship@ buildSS;
double nextBuildTry = 15.0 * 60.0;
void create() override {
@military = cast<Military>(ai.military);
@designs = cast<Designs>(ai.designs);
@construction = cast<Construction>(ai.construction);
@development = cast<Development>(ai.development);
@systems = cast<Systems>(ai.systems);
@budget = cast<Budget>(ai.budget);
@fleets = cast<Fleets>(ai.fleets);
}
void save(SaveFile& file) override {
designs.saveDesign(file, ssDesign);
construction.saveConstruction(file, buildSS);
file << nextBuildTry;
uint cnt = tracked.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << tracked[i];
cnt = unassigned.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << unassigned[i];
}
void load(SaveFile& file) override {
@ssDesign = designs.loadDesign(file);
@buildSS = cast<BuildFlagship>(construction.loadConstruction(file));
file >> nextBuildTry;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
SSRegion gt;
file >> gt;
tracked.insertLast(gt);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Object@ obj;
file >> obj;
if(obj !is null)
unassigned.insertLast(obj);
}
}
uint order(MoveOrder& ord) override {
//Find the position to fling to
vec3d toPosition;
if(!targetPosition(ord, toPosition))
return F_Pass;
//Check if we have a slipstream generator in this region
auto@ gt = get(ord.obj.region);
if(gt is null || gt.obj is null || !gt.arrived)
return F_Pass;
//Make sure our generator is usable
Object@ ssGen = gt.obj;
if(!canSlipstream(ssGen))
return F_Pass;
//Check if we already have a link
if(hasOddityLink(gt.region, toPosition, SS_MAX_DISTANCE, minDuration=60.0))
return F_Pass;
//See if we have the FTL to make a link
double avail = usableFTL(ai, ord);
if(!canSlipstreamTo(ssGen, toPosition))
return F_Pass;
if(slipstreamCost(ssGen, 0, toPosition.distanceTo(ssGen.position)) >= avail)
return F_Pass;
ssGen.addSlipstreamOrder(toPosition, append=true);
if(ssGen !is ord.obj) {
ord.obj.addWaitOrder(ssGen, moveTo=true);
ssGen.addSecondaryToSlipstream(ord.obj);
}
else {
ord.obj.addMoveOrder(toPosition, append=true);
}
return F_Continue;
}
SSRegion@ get(Region@ reg) {
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].region is reg)
return tracked[i];
}
return null;
}
void remove(SSRegion@ gt) {
if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire)
unassigned.insertLast(gt.obj);
tracked.remove(gt);
}
Object@ getClosest(const vec3d& position) {
Object@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
Object@ obj = tracked[i].obj;
if(obj is null)
continue;
if(!tracked[i].arrived)
continue;
double d = obj.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = obj;
}
}
return closest;
}
SSRegion@ getClosestRegion(const vec3d& position) {
SSRegion@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
double d = tracked[i].region.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = tracked[i];
}
}
return closest;
}
void assignTo(SSRegion@ gt, Object@ closest) {
unassigned.remove(closest);
@gt.obj = closest;
gt.arrived = false;
military.stationFleet(fleets.getAI(closest), gt.region);
if(closest.region is gt.region)
gt.arrived = true;
if(!gt.arrived) {
gt.destination = military.getStationPosition(gt.region);
closest.addMoveOrder(gt.destination);
}
}
bool trackingGen(Object@ obj) {
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
if(unassigned[i] is obj)
return true;
}
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].obj is obj)
return true;
}
return false;
}
bool shouldHaveGen(Region@ reg, bool always = false) {
if(military.getBase(reg) !is null)
return true;
if(development.isDevelopingIn(reg))
return true;
return false;
}
void turn() override {
if(ssDesign !is null && ssDesign.active !is null) {
int newSize = round(double(budget.spendable(BT_Military)) * 0.2 * ai.behavior.shipSizePerMoney / 64.0) * 64;
if(newSize < 128)
newSize = 128;
if(newSize != ssDesign.targetSize) {
@ssDesign = designs.design(DP_Slipstream, newSize);
ssDesign.customName = "Slipstream";
}
}
}
void focusTick(double time) override {
//Design a generator
if(ssDesign is null) {
@ssDesign = designs.design(DP_Slipstream, 128);
ssDesign.customName = "Slipstream";
}
//Manage unassigned gens list
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
Object@ obj = unassigned[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
unassigned.removeAt(i);
--i; --cnt;
}
}
//Detect new gens
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Slipstream)
continue;
if(!trackingGen(flAI.obj))
unassigned.insertLast(flAI.obj);
}
//Update existing gens for staging bases
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ gt = tracked[i];
bool checkAlways = false;
if(gt.obj !is null) {
if(!gt.obj.valid || gt.obj.owner !is ai.empire || (gt.arrived && gt.obj.region !is gt.region)) {
@gt.obj = null;
gt.arrived = false;
checkAlways = true;
}
else if(!gt.arrived && !gt.obj.hasOrders) {
if(gt.destination.distanceTo(gt.obj.position) < 10.0)
gt.arrived = true;
else
assignTo(gt, gt.obj);
}
}
if(!shouldHaveGen(gt.region, checkAlways)) {
remove(tracked[i]);
--i; --cnt;
}
}
//Detect new staging bases to build gens at
for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) {
auto@ base = military.stagingBases[i];
if(base.occupiedTime < SS_MIN_TIMER)
continue;
if(get(base.region) is null) {
SSRegion@ closest = getClosestRegion(base.region.position);
if(closest !is null && closest.region.position.distanceTo(base.region.position) < SS_MIN_DISTANCE_STAGE)
continue;
SSRegion gt;
@gt.region = base.region;
tracked.insertLast(gt);
break;
}
}
//Detect new important planets to build generator at
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
auto@ focus = development.focuses[i];
Region@ reg = focus.obj.region;
if(reg is null)
continue;
if(get(reg) is null) {
SSRegion@ closest = getClosestRegion(reg.position);
if(closest !is null && closest.region.position.distanceTo(reg.position) < SS_MIN_DISTANCE_DEVELOP)
continue;
SSRegion gt;
@gt.region = reg;
tracked.insertLast(gt);
break;
}
}
//See if we should build a new generator
if(buildSS !is null) {
if(buildSS.completed) {
@buildSS = null;
nextBuildTry = gameTime + 60.0;
}
}
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ gt = tracked[i];
if(gt.obj is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) {
Object@ closest;
double closestDist = INFINITY;
for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) {
Object@ obj = unassigned[n];
if(obj.region is gt.region) {
@closest = obj;
break;
}
if(!obj.hasMover)
continue;
if(buildSS is null && gameTime > nextBuildTry) {
double d = obj.position.distanceTo(gt.region.position);
if(d < closestDist) {
closestDist = d;
@closest = obj;
}
}
}
if(closest !is null) {
if(log)
ai.print("Assign slipstream gen to => "+gt.region.name, closest.region);
assignTo(gt, closest);
} else if(buildSS is null && gameTime > nextBuildTry) {
if(log)
ai.print("Build slipstream gen for this system", gt.region);
@buildSS = construction.buildFlagship(ssDesign);
}
}
}
//Try to get enough ftl storage that we can permanently open a slipstream with each of generators
double mostCost = 0.0;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
Ship@ obj = cast<Ship>(tracked[i].obj);
if(obj is null)
continue;
double baseCost = obj.blueprint.design.average(SV_SlipstreamCost);
double duration = obj.blueprint.design.average(SV_SlipstreamDuration);
mostCost += baseCost / duration;
}
development.aimFTLStorage = mostCost;
}
};
AIComponent@ createSlipstream() {
return Slipstream();
}
@@ -0,0 +1,349 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Movement;
import empire_ai.weasel.searches;
import systems;
from empire import Pirates;
class InvasionDefendMission : Mission {
FleetAI@ fleet;
Region@ targRegion;
MoveOrder@ move;
bool pending = false;
Object@ eliminate;
void save(Fleets& fleets, SaveFile& file) override {
fleets.saveAI(file, fleet);
file << targRegion;
fleets.movement.saveMoveOrder(file, move);
file << pending;
}
void load(Fleets& fleets, SaveFile& file) override {
@fleet = fleets.loadAI(file);
file >> targRegion;
@move = fleets.movement.loadMoveOrder(file);
file >> pending;
}
bool get_isActive() override {
return targRegion !is null;
}
void tick(AI& ai, FleetAI& fleet, double time) override {
if(targRegion is null)
return;
if(move !is null)
return;
//Find stuff to fight
if(eliminate is null)
@eliminate = findEnemy(targRegion, null, ai.empire.hostileMask);
if(eliminate !is null) {
if(!eliminate.valid) {
@eliminate = null;
}
else {
if(!fleet.obj.hasOrders)
fleet.obj.addAttackOrder(eliminate);
if((fleet.filled < 0.3 || fleet.supplies < 0.3 || fleet.flagshipHealth < 0.5)
&& eliminate.getFleetStrength() * 2.0 > fleet.strength
&& !pending) {
@targRegion = null;
@eliminate = null;
@move = cast<Fleets>(ai.fleets).returnToBase(fleet, MP_Critical);
}
}
}
}
void update(AI& ai, Invasion& invasion) {
//Manage movement
if(move !is null) {
if(move.failed || move.completed)
@move = null;
}
//Find new regions to go to
if(targRegion is null || (!pending && move is null && !invasion.isFighting(targRegion))) {
bool ready = fleet.actionableState && move is null;
DefendSystem@ bestDef;
double bestWeight = 0.0;
for(uint i = 0, cnt = invasion.defending.length; i < cnt; ++i) {
auto@ def = invasion.defending[i];
double w = randomd(0.9, 1.1);
if(!def.fighting) {
if(!ready)
continue;
else
w *= 0.1;
}
if(!def.winning) {
w *= 10.0;
}
else {
if(!ready)
continue;
}
if(def.obj is targRegion)
w *= 1.5;
if(w > bestWeight) {
bestWeight = w;
@bestDef = def;
}
}
if(bestDef !is null && fleet.supplies >= 0.25 && fleet.filled >= 0.2 && fleet.fleetHealth >= 0.2) {
@targRegion = bestDef.obj;
invasion.pend(targRegion, fleet);
pending = true;
}
}
//Move to the region we want to go to
if(targRegion !is null) {
if(move is null) {
if(fleet.obj.region !is targRegion) {
@eliminate = findEnemy(targRegion, null, ai.empire.hostileMask);
if(eliminate is null) {
vec3d targPos = targRegion.position;
targPos += (targRegion.position - ai.empire.HomeSystem.position).normalized(targRegion.radius * 0.85);
@move = invasion.movement.move(fleet.obj, targPos, MP_Critical);
}
else {
@move = invasion.movement.move(fleet.obj, eliminate, MP_Critical, nearOnly=true);
}
}
else {
//Remove from pending list
if(pending) {
invasion.unpend(targRegion, fleet);
pending = false;
}
//See if we should return to base
if(!invasion.isFighting(targRegion) && (fleet.supplies < 0.25 || fleet.filled < 0.5)) {
@targRegion = null;
@move = invasion.fleets.returnToBase(fleet, MP_Critical);
}
}
}
}
}
};
class DefendSystem {
Region@ obj;
array<FleetAI@> pending;
double enemyStrength = 0.0;
double ourStrength = 0.0;
double remnantStrength = 0.0;
double pendingStrength = 0.0;
void save(Invasion& invasion, SaveFile& file) {
file << obj;
uint cnt = pending.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
invasion.fleets.saveAI(file, pending[i]);
file << enemyStrength;
file << ourStrength;
file << remnantStrength;
file << pendingStrength;
}
void load(Invasion& invasion, SaveFile& file) {
file >> obj;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ fleet = invasion.fleets.loadAI(file);
if(fleet !is null && fleet.obj !is null)
pending.insertLast(fleet);
}
file >> enemyStrength;
file >> ourStrength;
file >> remnantStrength;
file >> pendingStrength;
}
void update(AI& ai, Invasion& invasion) {
enemyStrength = getTotalFleetStrength(obj, ai.empire.hostileMask);
ourStrength = getTotalFleetStrength(obj, ai.mask);
remnantStrength = getTotalFleetStrength(obj, Pirates.mask);
if(gameTime < 10.0 * 60.0)
ourStrength += remnantStrength;
else if(gameTime < 30.0 * 60.0)
ourStrength += remnantStrength * 0.5;
pendingStrength = 0.0;
for(uint i = 0, cnt = pending.length; i < cnt; ++i)
pendingStrength += sqrt(pending[i].strength);
pendingStrength *= pendingStrength;
if(obj.PlanetsMask & ai.empire.mask != 0)
ai.empire.setDefending(obj, true);
}
bool get_fighting() {
return enemyStrength > 0;
}
bool get_winning() {
return ourStrength + pendingStrength > enemyStrength;
}
};
class Invasion : AIComponent {
Fleets@ fleets;
Movement@ movement;
array<DefendSystem@> defending;
array<InvasionDefendMission@> tracked;
void create() {
@fleets = cast<Fleets>(ai.fleets);
@movement = cast<Movement>(ai.movement);
ai.behavior.maintenancePerShipSize = 0.0;
}
void save(SaveFile& file) {
uint cnt = defending.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
defending[i].save(this, file);
cnt = tracked.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets.saveMission(file, tracked[i]);
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
DefendSystem def;
def.load(this, file);
defending.insertLast(def);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
InvasionDefendMission@ miss = cast<InvasionDefendMission>(fleets.loadMission(file));
if(miss !is null)
tracked.insertLast(miss);
}
}
void start() {
//Find systems to defend
Region@ home = ai.empire.HomeSystem;
const SystemDesc@ sys = getSystem(home);
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
auto@ otherSys = getSystem(sys.adjacent[i]);
if(findEnemy(otherSys.object, null, Pirates.mask, fleets=false, stations=true) !is null) {
DefendSystem def;
@def.obj = otherSys.object;
defending.insertLast(def);
}
}
}
bool isManaging(FleetAI& fleet) {
if(fleet.mission is null)
return false;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i] is fleet.mission)
return true;
}
return false;
}
void manage(FleetAI& fleet) {
InvasionDefendMission miss;
@miss.fleet = fleet;
fleets.performMission(fleet, miss);
tracked.insertLast(miss);
}
void pend(Region@ region, FleetAI& fleet) {
for(uint i = 0, cnt = defending.length; i < cnt; ++i ){
if(defending[i].obj is region) {
defending[i].pending.insertLast(fleet);
break;
}
}
}
void unpend(Region@ region, FleetAI& fleet) {
for(uint i = 0, cnt = defending.length; i < cnt; ++i ){
if(defending[i].obj is region) {
defending[i].pending.remove(fleet);
break;
}
}
}
DefendSystem@ getDefending(Region@ region) {
for(uint i = 0, cnt = defending.length; i < cnt; ++i ){
if(defending[i].obj is region)
return defending[i];
}
return null;
}
bool isFighting(Region@ region) {
for(uint i = 0, cnt = defending.length; i < cnt; ++i ){
if(defending[i].obj is region)
return defending[i].fighting;
}
return false;
}
uint sysUpd = 0;
void focusTick(double time) {
//All your fleets are belong to us
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Combat)
continue;
if(!isManaging(flAI))
manage(flAI);
}
//Update systems we're defending
if(defending.length != 0) {
sysUpd = (sysUpd+1) % defending.length;
defending[sysUpd].update(ai, this);
}
//Make sure our fleets are in the right places
for(uint i = 0, cnt = tracked.length; i < cnt; ++i)
tracked[i].update(ai, this);
}
};
AIComponent@ createInvasion() {
return Invasion();
}
@@ -0,0 +1,518 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.race.Race;
import empire_ai.weasel.Colonization;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Development;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Orbitals;
from orbitals import getOrbitalModule, OrbitalModule;
from buildings import getBuildingType, BuildingType;
from resources import ResourceType, getResource, getResourceID;
from statuses import getStatusID;
from biomes import getBiomeID;
enum PlanetClass {
PC_Empty,
PC_Core,
PC_Mine,
PC_Transmute,
}
class TrackReplicator {
Object@ obj;
Planet@ target;
bool arrived = false;
MoveOrder@ move;
BuildingRequest@ build;
uint intention = PC_Empty;
bool get_busy() {
if(target is null)
return false;
if(!arrived || move !is null || build !is null)
return true;
return false;
}
void save(Ancient& ancient, SaveFile& file) {
file << obj;
file << target;
file << arrived;
ancient.movement.saveMoveOrder(file, move);
ancient.planets.saveBuildingRequest(file, build);
file << intention;
}
void load(Ancient& ancient, SaveFile& file) {
file >> obj;
file >> target;
file >> arrived;
@move = ancient.movement.loadMoveOrder(file);
@build = ancient.planets.loadBuildingRequest(file);
file >> intention;
}
};
class Ancient : Race, RaceResources, RaceColonization {
Colonization@ colonization;
Construction@ construction;
Resources@ resources;
Planets@ planets;
Development@ development;
Movement@ movement;
Orbitals@ orbitals;
array<TrackReplicator@> replicators;
const OrbitalModule@ replicatorMod;
const BuildingType@ core;
const BuildingType@ miner;
const BuildingType@ transmuter;
const BuildingType@ foundry;
const BuildingType@ depot;
const BuildingType@ refinery;
const BuildingType@ reinforcer;
const BuildingType@ developer;
const BuildingType@ compressor;
int claimStatus = -1;
int replicatorStatus = -1;
int mountainsBiome = -1;
int oreResource = -1;
int baseMatResource = -1;
bool foundFirstT2 = false;
void create() {
@colonization = cast<Colonization>(ai.colonization);
colonization.performColonization = false;
@resources = cast<Resources>(ai.resources);
@construction = cast<Construction>(ai.construction);
@movement = cast<Movement>(ai.movement);
@planets = cast<Planets>(ai.planets);
@orbitals = cast<Orbitals>(ai.orbitals);
@planets = cast<Planets>(ai.planets);
@development = cast<Development>(ai.development);
development.managePlanetPressure = false;
development.buildBuildings = false;
development.colonizeResources = false;
@replicatorMod = getOrbitalModule("AncientReplicator");
@transmuter = getBuildingType("AncientTransmuter");
@miner = getBuildingType("AncientMiner");
@core = getBuildingType("AncientCore");
@foundry = getBuildingType("AncientFoundry");
@depot = getBuildingType("AncientDepot");
@refinery = getBuildingType("AncientRefinery");
@reinforcer = getBuildingType("AncientReinforcer");
@developer = getBuildingType("AncientDeveloper");
@compressor = getBuildingType("Compressor");
claimStatus = getStatusID("AncientClaim");
replicatorStatus = getStatusID("AncientReplicator");
mountainsBiome = getBiomeID("Mountains");
oreResource = getResourceID("OreRate");
baseMatResource = getResourceID("BaseMaterial");
@ai.defs.Factory = null;
@ai.defs.LaborStorage = null;
}
void save(SaveFile& file) override {
file << foundFirstT2;
uint cnt = replicators.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
replicators[i].save(this, file);
}
void load(SaveFile& file) override {
file >> foundFirstT2;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
TrackReplicator t;
t.load(this, file);
if(t.obj !is null)
replicators.insertLast(t);
}
}
void levelRequirements(Object& obj, int targetLevel, array<ResourceSpec@>& specs) {
//YOLO
specs.length = 0;
}
bool orderColonization(ColonizeData& data, Planet@ sourcePlanet) {
return true;
}
double getGenericUsefulness(const ResourceType@ type) {
return 1.0;
}
bool hasReplicator(Planet& pl) {
for(uint i = 0, cnt = replicators.length; i < cnt; ++i) {
if(replicators[i].target is pl)
return true;
}
return false;
}
bool isTracking(Object& obj) {
for(uint i = 0, cnt = replicators.length; i < cnt; ++i) {
if(replicators[i].obj is obj)
return true;
}
return false;
}
void trackReplicator(Object& obj) {
TrackReplicator t;
@t.obj = obj;
replicators.insertLast(t);
}
void updateRequests(Planet& pl) {
//Handle requests for base materials
uint baseMatReqs = 0;
baseMatReqs += pl.getBuildingCount(depot.id);
baseMatReqs += pl.getBuildingCount(refinery.id);
baseMatReqs += pl.getBuildingCount(reinforcer.id);
baseMatReqs += pl.getBuildingCount(developer.id);
baseMatReqs += pl.getBuildingCount(compressor.id);
array<ImportData@> curBaseMat;
resources.getImportsOf(curBaseMat, baseMatResource, pl);
if(curBaseMat.length < baseMatReqs) {
for(uint i = curBaseMat.length, cnt = baseMatReqs; i < cnt; ++i) {
ResourceSpec spec;
spec.type = RST_Specific;
@spec.resource = getResource(baseMatResource);
resources.requestResource(pl, spec);
}
}
else if(curBaseMat.length > baseMatReqs) {
for(uint i = baseMatReqs, cnt = curBaseMat.length; i < cnt; ++i)
resources.cancelRequest(curBaseMat[i]);
}
//Handle requests for ore
uint oreReqs = 0;
oreReqs += pl.getBuildingCount(foundry.id);
array<ImportData@> curOre;
resources.getImportsOf(curOre, oreResource, pl);
if(curOre.length < oreReqs) {
for(uint i = curOre.length, cnt = oreReqs; i < cnt; ++i) {
ResourceSpec spec;
spec.type = RST_Specific;
@spec.resource = getResource(oreResource);
resources.requestResource(pl, spec);
}
}
else if(curOre.length > oreReqs) {
for(uint i = oreReqs, cnt = curOre.length; i < cnt; ++i)
resources.cancelRequest(curOre[i]);
}
}
uint plInd = 0;
void focusTick(double time) {
//Find new replicators
for(uint i = 0, cnt = orbitals.orbitals.length; i < cnt; ++i) {
auto@ orb = cast<Orbital>(orbitals.orbitals[i].obj);
if(orb.coreModule == replicatorMod.id) {
if(!isTracking(orb))
trackReplicator(orb);
}
}
//Update requests for planets
if(planets.planets.length != 0) {
for(uint n = 0, ncnt = min(planets.planets.length, 10); n < ncnt; ++n) {
plInd = (plInd+1) % planets.planets.length;
Planet@ pl = planets.planets[plInd].obj;
if(classify(pl) == PC_Core)
updateRequests(pl);
}
}
//Manage existing replicators
for(uint i = 0, cnt = replicators.length; i < cnt; ++i) {
auto@ t = replicators[i];
if(t.obj is null || !t.obj.valid || t.obj.owner !is ai.empire) {
replicators.removeAt(i);
--i; --cnt;
continue;
}
if(t.target !is null) {
if(!t.target.valid) {
@t.target = null;
if(!t.arrived)
t.obj.stopMoving();
t.arrived = false;
}
else if(t.target.owner !is ai.empire && t.target.owner.valid) {
@t.target = null;
if(!t.arrived)
t.obj.stopMoving();
t.arrived = false;
}
}
if(t.move !is null) {
if(t.move.failed) {
@t.move = null;
t.arrived = false;
}
else if(t.move.completed) {
if(t.obj.isOrbitingAround(t.target)) {
@t.move = null;
t.arrived = true;
}
else if(t.obj.inOrbit) {
@t.move = null;
t.arrived = false;
@t.target = null;
}
}
}
else if(t.target !is null && !t.arrived) {
@t.move = movement.move(t.obj, t.target);
}
if(t.build !is null) {
if(t.build.canceled) {
//A build failed, give up on this planet
if(log)
ai.print("Failed building build", t.target);
@t.target = null;
@t.build = null;
t.arrived = false;
}
else if(t.build.built) {
float progress = t.build.getProgress();
if(progress >= 1.f) {
if(log)
ai.print("Completed building build", t.target);
@t.build = null;
}
else if(progress < -0.5f) {
if(log)
ai.print("Failed building build location "+t.build.builtAt, t.target);
@t.build = null;
@t.target = null;
t.arrived = false;
}
}
}
if(t.arrived || t.target is null) {
if(!t.busy)
useReplicator(t);
}
}
}
uint classify(Planet& pl) {
int resType = pl.primaryResourceType;
if(resType == oreResource)
return PC_Mine;
if(resType == baseMatResource)
return PC_Transmute;
uint claims = pl.getStatusStackCountAny(claimStatus);
if(claims <= 1)
return PC_Empty;
if(pl.getBuildingCount(core.id) >= 1)
return PC_Core;
if(pl.getBuildingCount(transmuter.id) >= 1)
return PC_Transmute;
if(pl.getBuildingCount(miner.id) >= 1)
return PC_Mine;
return PC_Empty;
}
bool shouldBeCore(const ResourceType@ type) {
if(type.level >= 1)
return true;
if(type.totalPressure >= 8)
return true;
return false;
}
int openOreRequests(TrackReplicator@ discount = null) {
int reqs = 0;
for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) {
auto@ req = resources.requested[i];
if(req.beingMet)
continue;
if(req.spec.type != RST_Specific)
continue;
if(req.spec.resource.id != uint(oreResource))
continue;
reqs += 1;
}
for(uint i = 0, cnt = replicators.length; i < cnt; ++i) {
auto@ t = replicators[i];
if(t is discount)
continue;
if(t.target is null)
continue;
if(t.intention == PC_Mine && (t.build is null || t.build.type is miner))
reqs -= 1;
}
return reqs;
}
int openBaseMatRequests(TrackReplicator@ discount = null) {
int reqs = 0;
for(uint i = 0, cnt = resources.requested.length; i < cnt; ++i) {
auto@ req = resources.requested[i];
if(req.beingMet)
continue;
if(req.spec.type != RST_Specific)
continue;
if(req.spec.resource.id != uint(baseMatResource))
continue;
reqs += 1;
}
for(uint i = 0, cnt = replicators.length; i < cnt; ++i) {
auto@ t = replicators[i];
if(t is discount)
continue;
if(t.target is null)
continue;
if(t.intention == PC_Transmute && (t.build is null || t.build.type is transmuter))
reqs -= 1;
}
return reqs;
}
void build(TrackReplicator& t, const BuildingType@ building) {
auto@ plAI = planets.getAI(t.target);
if(plAI is null)
return;
if(!t.target.hasStatusEffect(replicatorStatus))
return;
//bool scatter = building is miner || building is transmuter;
bool scatter = false;
@t.build = planets.requestBuilding(plAI, building, scatter=scatter, moneyType=BT_Colonization);
if(log)
ai.print("Build "+building.name, t.target);
}
void useReplicator(TrackReplicator& t) {
if(t.target !is null) {
uint type = classify(t.target);
switch(type) {
case PC_Empty: {
const ResourceType@ res = getResource(t.target.primaryResourceType);
if(res is null) {
@t.target = null;
t.arrived = false;
return;
}
if(shouldBeCore(res)) {
build(t, core);
}
else if(openBaseMatRequests(t) >= openOreRequests(t) || gameTime < 6.0 * 60.0 || !t.target.hasBiome(mountainsBiome)) {
build(t, transmuter);
}
else {
build(t, miner);
}
return;
}
case PC_Transmute:
@t.target = null;
t.arrived = false;
break;
case PC_Mine:
@t.target = null;
t.arrived = false;
break;
case PC_Core:
build(t, refinery);
return;
}
}
//Find a new planet to colonize
PotentialColonize@ best;
double bestWeight = 0.0;
uint getType = PC_Core;
if(openBaseMatRequests() >= 1)
getType = PC_Transmute;
else if(openOreRequests() >= 1 && gameTime > 6.0 * 60.0)
getType = PC_Mine;
auto@ potentials = colonization.getPotentialColonize();
for(uint i = 0, cnt = potentials.length; i < cnt; ++i) {
PotentialColonize@ p = potentials[i];
if(hasReplicator(p.pl))
continue;
double w = p.weight;
if(!foundFirstT2 && p.resource.level >= 2)
w *= 100.0;
else if((getType == PC_Core) != shouldBeCore(p.resource))
w *= 0.6;
if(getType == PC_Core && p.resource.level >= 2)
w *= 4.0;
if(getType == PC_Core && p.resource.level >= 3)
w *= 6.0;
if(getType == PC_Mine && !p.pl.hasBiome(mountainsBiome))
w *= 0.1;
if(getType == PC_Core)
w *= double(p.pl.totalSurfaceTiles) / 100.0;
w /= p.pl.position.distanceTo(t.obj.position)/1000.0;
if(w > bestWeight) {
bestWeight = w;
@best = p;
}
}
if(best !is null) {
@t.target = best.pl;
t.intention = shouldBeCore(best.resource) ? uint(PC_Core) : getType;
t.arrived = false;
if(!foundFirstT2) {
if(best.resource.level == 2)
foundFirstT2 = true;
}
}
}
};
AIComponent@ createAncient() {
return Ancient();
}
@@ -0,0 +1,150 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.race.Race;
import empire_ai.weasel.Development;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Budget;
import resources;
import buildings;
import attributes;
class Devout : Race, RaceDevelopment {
Development@ development;
Planets@ planets;
Budget@ budget;
const ResourceType@ altarResource;
const BuildingType@ altar;
int coverAttrib = -1;
BuildingRequest@ altarBuild;
Planet@ focusAltar;
double considerTimer = 0.0;
void save(SaveFile& file) {
planets.saveBuildingRequest(file, altarBuild);
file << focusAltar;
file << considerTimer;
}
void load(SaveFile& file) {
@altarBuild = planets.loadBuildingRequest(file);
file >> focusAltar;
file >> considerTimer;
}
void create() {
@planets = cast<Planets>(ai.planets);
@development = cast<Development>(ai.development);
@budget = cast<Budget>(ai.budget);
@altarResource = getResource("Altar");
@altar = getBuildingType("Altar");
coverAttrib = getEmpAttribute("AltarSupportedPopulation");
}
void start() {
auto@ data = ai.empire.getPlanets();
Object@ obj;
while(receive(data, obj)) {
Planet@ pl = cast<Planet>(obj);
if(pl !is null){
if(pl.primaryResourceType == altarResource.id) {
@focusAltar = pl;
break;
}
}
}
}
bool shouldBeFocus(Planet& pl, const ResourceType@ resource) override {
if(resource is altarResource)
return true;
return false;
}
void focusTick(double time) override {
//Handle our current altar build
if(altarBuild !is null) {
if(altarBuild.built) {
@focusAltar = altarBuild.plAI.obj;
@altarBuild = null;
}
else if(altarBuild.canceled) {
@altarBuild = null;
}
}
//Handle our focused altar
if(focusAltar !is null) {
if(!focusAltar.valid || focusAltar.owner !is ai.empire || focusAltar.primaryResourceType != altarResource.id) {
@focusAltar = null;
}
}
//If we aren't covering our entire population, find new planets to make into altars
double coverage = ai.empire.getAttribute(coverAttrib);
double population = ai.empire.TotalPopulation;
if(coverage >= population || altarBuild !is null)
return;
bool makeNewAltar = true;
if(focusAltar !is null) {
auto@ foc = development.getFocus(focusAltar);
if(foc !is null && int(foc.obj.level) >= foc.targetLevel) {
foc.targetLevel += 1;
considerTimer = gameTime + 180.0;
makeNewAltar = false;
}
else {
makeNewAltar = gameTime > considerTimer;
}
}
if(makeNewAltar) {
if(budget.canSpend(BT_Development, 300)) {
//Turn our most suitable planet into an altar
PlanetAI@ bestBuild;
double bestWeight = 0.0;
for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) {
auto@ plAI = planets.planets[i];
double w = randomd(0.9, 1.1);
if(plAI.resources !is null && plAI.resources.length != 0) {
auto@ res = plAI.resources[0].resource;
if(res.level == 0 && !res.limitlessLevel)
w *= 5.0;
if(res.cls !is null)
w *= 0.5;
if(res.level > 0)
w /= pow(2.0, res.level);
}
else {
w *= 100.0;
}
if(w > bestWeight) {
bestWeight = w;
@bestBuild = plAI;
}
}
if(bestBuild !is null) {
@altarBuild = planets.requestBuilding(bestBuild, altar, expire=60.0);
considerTimer = gameTime + 120.0;
}
}
}
}
};
AIComponent@ createDevout() {
return Devout();
}
@@ -0,0 +1,199 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.race.Race;
import empire_ai.weasel.Colonization;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Scouting;
import empire_ai.weasel.Orbitals;
import empire_ai.weasel.Budget;
from orbitals import getOrbitalModuleID;
from constructions import ConstructionType, getConstructionType;
class Extragalactic : Race {
Colonization@ colonization;
Construction@ construction;
Scouting@ scouting;
Orbitals@ orbitals;
Resources@ resources;
Budget@ budget;
array<OrbitalAI@> beacons;
OrbitalAI@ masterBeacon;
int beaconMod = -1;
array<ImportData@> imports;
array<const ConstructionType@> beaconBuilds;
void create() {
@colonization = cast<Colonization>(ai.colonization);
colonization.performColonization = false;
colonization.queueColonization = false;
@scouting = cast<Scouting>(ai.scouting);
scouting.buildScouts = false;
@orbitals = cast<Orbitals>(ai.orbitals);
beaconMod = getOrbitalModuleID("Beacon");
@construction = cast<Construction>(ai.construction);
@resources = cast<Resources>(ai.resources);
@budget = cast<Budget>(ai.budget);
beaconBuilds.insertLast(getConstructionType("BeaconHealth"));
beaconBuilds.insertLast(getConstructionType("BeaconWeapons"));
beaconBuilds.insertLast(getConstructionType("BeaconLabor"));
}
void save(SaveFile& file) override {
uint cnt = beacons.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
orbitals.saveAI(file, beacons[i]);
orbitals.saveAI(file, masterBeacon);
cnt = imports.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
resources.saveImport(file, imports[i]);
}
void load(SaveFile& file) override {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ b = orbitals.loadAI(file);
if(b !is null && b.obj !is null)
beacons.insertLast(b);
}
@masterBeacon = orbitals.loadAI(file);
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ imp = resources.loadImport(file);
if(imp !is null)
imports.insertLast(imp);
}
}
uint prevBeacons = 0;
void focusTick(double time) {
//Find our beacons
for(uint i = 0, cnt = beacons.length; i < cnt; ++i) {
auto@ b = beacons[i];
if(b is null || b.obj is null || !b.obj.valid || b.obj.owner !is ai.empire) {
if(b.obj !is null)
resources.killImportsTo(b.obj);
beacons.removeAt(i);
--i; --cnt;
}
}
for(uint i = 0, cnt = orbitals.orbitals.length; i < cnt; ++i) {
auto@ orb = orbitals.orbitals[i];
Orbital@ obj = cast<Orbital>(orb.obj);
if(obj !is null && obj.coreModule == uint(beaconMod)) {
if(beacons.find(orb) == -1)
beacons.insertLast(orb);
}
}
//Find our master beacon
if(masterBeacon !is null) {
Orbital@ obj = cast<Orbital>(masterBeacon.obj);
if(obj is null || !obj.valid || obj.owner !is ai.empire || obj.hasMaster())
@masterBeacon = null;
}
else {
for(uint i = 0, cnt = beacons.length; i < cnt; ++i) {
auto@ b = beacons[i];
Orbital@ obj = cast<Orbital>(b.obj);
if(!obj.hasMaster()) {
@masterBeacon = b;
ai.empire.setDefending(obj, true);
break;
}
}
}
scouting.buildScouts = gameTime > 5.0 * 60.0;
if(prevBeacons < beacons.length && masterBeacon !is null && gameTime > 10.0) {
for(int i = beacons.length-1; i >= int(prevBeacons); --i) {
//Make sure we order a scout at each beacon
if(!scouting.buildScouts) {
BuildFlagshipSourced build(scouting.scoutDesign);
build.moneyType = BT_Military;
@build.buildAt = masterBeacon.obj;
if(beacons[i] !is masterBeacon)
@build.buildFrom = beacons[i].obj;
construction.build(build, force=true);
}
//Set the beacon to fill up other stuff
beacons[i].obj.allowFillFrom = true;
}
prevBeacons = beacons.length;
}
//Handle with importing labor and defense to our master beacon
if(masterBeacon !is null) {
if(imports.length == 0) {
//Request labor and defense at our beacon
{
ResourceSpec spec;
spec.type = RST_Pressure_Type;
spec.pressureType = TR_Labor;
imports.insertLast(resources.requestResource(masterBeacon.obj, spec));
}
{
ResourceSpec spec;
spec.type = RST_Pressure_Type;
spec.pressureType = TR_Defense;
imports.insertLast(resources.requestResource(masterBeacon.obj, spec));
}
{
ResourceSpec spec;
spec.type = RST_Pressure_Level0;
spec.pressureType = TR_Research;
imports.insertLast(resources.requestResource(masterBeacon.obj, spec));
}
}
else {
//When our requests are met, make more requests!
for(uint i = 0, cnt = imports.length; i < cnt; ++i) {
if(imports[i].beingMet || imports[i].obj !is masterBeacon.obj) {
ResourceSpec spec;
spec = imports[i].spec;
@imports[i] = resources.requestResource(masterBeacon.obj, spec);
}
}
}
//Build stuff on our beacon if we have enough stuff
if(budget.canSpend(BT_Development, 300)) {
uint offset = randomi(0, beaconBuilds.length-1);
for(uint i = 0, cnt = beaconBuilds.length; i < cnt; ++i) {
uint ind = (i+offset) % cnt;
auto@ type = beaconBuilds[ind];
if(type is null)
continue;
if(type.canBuild(masterBeacon.obj, ignoreCost=false)) {
masterBeacon.obj.buildConstruction(type.id);
break;
}
}
}
}
}
};
AIComponent@ createExtragalactic() {
return Extragalactic();
}
@@ -0,0 +1,321 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.race.Race;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Military;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Development;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Budget;
from orbitals import getOrbitalModuleID;
const double MAINFRAME_MIN_DISTANCE_STAGE = 15000;
const double MAINFRAME_MIN_DISTANCE_DEVELOP = 20000;
const double MAINFRAME_MIN_TIMER = 3.0 * 60.0;
const int MAINFRAME_BUILD_MOVE_HOPS = 5;
class LinkRegion : Savable {
Region@ region;
Object@ obj;
bool arrived = false;
vec3d destination;
void save(SaveFile& file) {
file << region;
file << obj;
file << arrived;
file << destination;
}
void load(SaveFile& file) {
file >> region;
file >> obj;
file >> arrived;
file >> destination;
}
};
class Linked : Race {
Military@ military;
Designs@ designs;
Construction@ construction;
Development@ development;
Systems@ systems;
Budget@ budget;
array<LinkRegion@> tracked;
array<Object@> unassigned;
BuildOrbital@ buildMainframe;
int mainframeId = -1;
double nextBuildTry = 15.0 * 60.0;
void create() override {
@military = cast<Military>(ai.military);
@designs = cast<Designs>(ai.designs);
@construction = cast<Construction>(ai.construction);
@development = cast<Development>(ai.development);
@systems = cast<Systems>(ai.systems);
@budget = cast<Budget>(ai.budget);
mainframeId = getOrbitalModuleID("Mainframe");
}
void save(SaveFile& file) override {
construction.saveConstruction(file, buildMainframe);
file << nextBuildTry;
uint cnt = tracked.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << tracked[i];
cnt = unassigned.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << unassigned[i];
}
void load(SaveFile& file) override {
@buildMainframe = cast<BuildOrbital>(construction.loadConstruction(file));
file >> nextBuildTry;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
LinkRegion gt;
file >> gt;
tracked.insertLast(gt);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Object@ obj;
file >> obj;
if(obj !is null)
unassigned.insertLast(obj);
}
}
LinkRegion@ get(Region@ reg) {
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].region is reg)
return tracked[i];
}
return null;
}
void remove(LinkRegion@ gt) {
if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire)
unassigned.insertLast(gt.obj);
tracked.remove(gt);
}
Object@ getClosestMainframe(const vec3d& position) {
Object@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
Object@ obj = tracked[i].obj;
if(obj is null)
continue;
if(!tracked[i].arrived)
continue;
double d = obj.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = obj;
}
}
return closest;
}
LinkRegion@ getClosestLinkRegion(const vec3d& position) {
LinkRegion@ closest;
double minDist = INFINITY;
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
double d = tracked[i].region.position.distanceTo(position);
if(d < minDist) {
minDist = d;
@closest = tracked[i];
}
}
return closest;
}
void assignTo(LinkRegion@ gt, Object@ closest) {
unassigned.remove(closest);
@gt.obj = closest;
gt.arrived = false;
if(closest.region is gt.region)
gt.arrived = true;
if(!gt.arrived) {
gt.destination = military.getStationPosition(gt.region);
closest.addMoveOrder(gt.destination);
}
}
bool trackingMainframe(Object@ obj) {
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
if(unassigned[i] is obj)
return true;
}
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
if(tracked[i].obj is obj)
return true;
}
return false;
}
bool shouldHaveMainframe(Region@ reg, bool always = false) {
if(military.getBase(reg) !is null)
return true;
if(development.isDevelopingIn(reg))
return true;
return false;
}
void focusTick(double time) override {
//Manage unassigned mainframes list
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
Object@ obj = unassigned[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
unassigned.removeAt(i);
--i; --cnt;
}
}
//Detect new gates
auto@ data = ai.empire.getOrbitals();
Object@ obj;
while(receive(data, obj)) {
if(obj is null)
continue;
Orbital@ orb = cast<Orbital>(obj);
if(orb is null || orb.coreModule != uint(mainframeId))
continue;
if(!trackingMainframe(obj))
unassigned.insertLast(obj);
}
//Update existing gates for staging bases
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ gt = tracked[i];
bool checkAlways = false;
if(gt.obj !is null) {
if(!gt.obj.valid || gt.obj.owner !is ai.empire || (gt.arrived && gt.obj.region !is gt.region)) {
@gt.obj = null;
gt.arrived = false;
checkAlways = true;
}
else if(!gt.arrived && !gt.obj.hasOrders) {
if(gt.destination.distanceTo(gt.obj.position) < 10.0)
gt.arrived = true;
else
gt.obj.addMoveOrder(gt.destination);
}
}
if(!shouldHaveMainframe(gt.region, checkAlways)) {
remove(tracked[i]);
--i; --cnt;
}
}
//Detect new staging bases to build mainframes at
for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) {
auto@ base = military.stagingBases[i];
if(base.occupiedTime < MAINFRAME_MIN_TIMER)
continue;
if(get(base.region) is null) {
LinkRegion@ closest = getClosestLinkRegion(base.region.position);
if(closest !is null && closest.region.position.distanceTo(base.region.position) < MAINFRAME_MIN_DISTANCE_STAGE)
continue;
LinkRegion gt;
@gt.region = base.region;
tracked.insertLast(gt);
break;
}
}
//Detect new important planets to build mainframes at
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
auto@ focus = development.focuses[i];
Region@ reg = focus.obj.region;
if(reg is null)
continue;
if(get(reg) is null) {
LinkRegion@ closest = getClosestLinkRegion(reg.position);
if(closest !is null && closest.region.position.distanceTo(reg.position) < MAINFRAME_MIN_DISTANCE_DEVELOP)
continue;
LinkRegion gt;
@gt.region = reg;
tracked.insertLast(gt);
break;
}
}
//See if we should build a new mainframe
if(buildMainframe !is null) {
if(buildMainframe.completed) {
@buildMainframe = null;
nextBuildTry = gameTime + 60.0;
}
}
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
auto@ gt = tracked[i];
if(gt.obj is null) {
Object@ closest;
double closestDist = INFINITY;
for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) {
Object@ obj = unassigned[n];
if(obj.region is gt.region) {
@closest = obj;
break;
}
if(!obj.hasMover)
continue;
if(buildMainframe is null && gameTime > nextBuildTry) {
double d = obj.position.distanceTo(gt.region.position);
if(d < closestDist) {
closestDist = d;
@closest = obj;
}
}
}
if(closest !is null) {
if(log)
ai.print("Assign mainframe to => "+gt.region.name, closest.region);
assignTo(gt, closest);
} else if(buildMainframe is null && gameTime > nextBuildTry) {
if(log)
ai.print("Build mainframe for this system", gt.region);
bool buildLocal = true;
auto@ factory = construction.primaryFactory;
if(factory !is null) {
Region@ factRegion = factory.obj.region;
if(factRegion !is null && systems.hopDistance(gt.region, factRegion) < MAINFRAME_BUILD_MOVE_HOPS)
buildLocal = false;
}
if(buildLocal)
@buildMainframe = construction.buildLocalOrbital(getOrbitalModule(mainframeId));
else
@buildMainframe = construction.buildOrbital(getOrbitalModule(mainframeId), military.getStationPosition(gt.region));
}
}
}
}
};
AIComponent@ createLinked() {
return Linked();
}
@@ -0,0 +1,348 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.race.Race;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Colonization;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Budget;
import resources;
import abilities;
import planet_levels;
from constructions import getConstructionType, ConstructionType;
from abilities import getAbilityID;
import oddity_navigation;
const double MAX_POP_BUILDTIME = 3.0 * 60.0;
class Mechanoid : Race, RaceResources, RaceColonization {
Colonization@ colonization;
Construction@ construction;
Movement@ movement;
Budget@ budget;
Planets@ planets;
const ResourceType@ unobtanium;
const ResourceType@ crystals;
int unobtaniumAbl = -1;
const ResourceClass@ foodClass;
const ResourceClass@ waterClass;
const ResourceClass@ scalableClass;
const ConstructionType@ buildPop;
int colonizeAbl = -1;
array<Planet@> popRequests;
array<Planet@> popSources;
array<Planet@> popFactories;
void create() {
@colonization = cast<Colonization>(ai.colonization);
@construction = cast<Construction>(ai.construction);
@movement = cast<Movement>(ai.movement);
@planets = cast<Planets>(ai.planets);
@budget = cast<Budget>(ai.budget);
@ai.defs.Shipyard = null;
@crystals = getResource("FTL");
@unobtanium = getResource("Unobtanium");
unobtaniumAbl = getAbilityID("UnobtaniumMorph");
@foodClass = getResourceClass("Food");
@waterClass = getResourceClass("WaterType");
@scalableClass = getResourceClass("Scalable");
colonizeAbl = getAbilityID("MechanoidColonize");
colonization.performColonization = false;
@buildPop = getConstructionType("MechanoidPopulation");
}
void start() {
//Oh yes please can we have some ftl crystals sir
if(crystals !is null) {
ResourceSpec spec;
spec.type = RST_Specific;
@spec.resource = crystals;
spec.isLevelRequirement = false;
spec.isForImport = false;
colonization.queueColonize(spec);
}
}
void levelRequirements(Object& obj, int targetLevel, array<ResourceSpec@>& specs) override {
//Remove all food and water resources
if(obj.levelChain != baseLevelChain.id)
return;
for(int i = specs.length-1; i >= 0; --i) {
auto@ spec = specs[i];
if(spec.type == RST_Class && (spec.cls is foodClass || spec.cls is waterClass))
specs.removeAt(i);
}
}
double transferCost(double dist) {
return 20 + dist * 0.002;
}
bool orderColonization(ColonizeData& data, Planet@ sourcePlanet) {
return false;
}
double getGenericUsefulness(const ResourceType@ type) override {
if(type.cls is foodClass || type.cls is waterClass)
return 0.00001;
if(type.level == 1)
return 100.0;
return 1.0;
}
bool canBuildPopulation(Planet& pl, double factor=1.0) {
if(buildPop is null)
return false;
if(!buildPop.canBuild(pl, ignoreCost=true))
return false;
auto@ primFact = construction.primaryFactory;
if(primFact !is null && pl is primFact.obj)
return true;
double laborCost = buildPop.getLaborCost(pl);
double laborIncome = pl.laborIncome;
return laborCost < laborIncome * MAX_POP_BUILDTIME * factor;
}
bool requiresPopulation(Planet& pl, double mod = 0.0) {
double curPop = pl.population + mod;
double maxPop = pl.maxPopulation;
return curPop < maxPop;
}
bool canSendPopulation(Planet& pl, double mod = 0.0) {
double curPop = pl.population + mod;
double maxPop = pl.maxPopulation;
if(curPop >= maxPop + 1)
return true;
//auto@ primFact = construction.primaryFactory;
//if(primFact !is null && pl is primFact.obj) {
// uint minFacts = 2;
// if(popFactories.find(pl) == -1)
// minFacts -= 1;
// if(popFactories.length >= minFacts)
// return false;
//}
//if(canBuildPopulation(pl)) {
// if(curPop >= maxPop)
// return true;
//}
return false;
}
uint chkInd = 0;
array<Planet@> availSources;
void focusTick(double time) override {
//Check existing lists
for(uint i = 0, cnt = popFactories.length; i < cnt; ++i) {
auto@ obj = popFactories[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
popFactories.removeAt(i);
--i; --cnt;
continue;
}
if(!canBuildPopulation(popFactories[i])) {
popFactories.removeAt(i);
--i; --cnt;
continue;
}
}
for(uint i = 0, cnt = popSources.length; i < cnt; ++i) {
auto@ obj = popSources[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
popSources.removeAt(i);
--i; --cnt;
continue;
}
if(!canSendPopulation(popSources[i])) {
popSources.removeAt(i);
--i; --cnt;
continue;
}
}
for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) {
auto@ obj = popRequests[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
popRequests.removeAt(i);
--i; --cnt;
continue;
}
if(!requiresPopulation(popRequests[i])) {
popRequests.removeAt(i);
--i; --cnt;
continue;
}
}
//Find new planets to add to our lists
bool checkMorph = false;
Planet@ hw = ai.empire.Homeworld;
if(hw !is null && hw.valid && hw.owner is ai.empire && unobtanium !is null) {
if(hw.primaryResourceType == unobtanium.id)
checkMorph = true;
}
uint plCnt = planets.planets.length;
for(uint n = 0, cnt = min(15, plCnt); n < cnt; ++n) {
chkInd = (chkInd+1) % plCnt;
auto@ plAI = planets.planets[chkInd];
//Find planets that can build population reliably
if(canBuildPopulation(plAI.obj)) {
if(popFactories.find(plAI.obj) == -1)
popFactories.insertLast(plAI.obj);
}
//Find planets that need population
if(requiresPopulation(plAI.obj)) {
if(popRequests.find(plAI.obj) == -1)
popRequests.insertLast(plAI.obj);
}
//Find planets that have extra population
if(canSendPopulation(plAI.obj)) {
if(popSources.find(plAI.obj) == -1)
popSources.insertLast(plAI.obj);
}
if(plAI.resources !is null && plAI.resources.length != 0) {
auto@ res = plAI.resources[0];
//Get rid of food and water we don't need
if(res.resource.cls is foodClass || res.resource.cls is waterClass) {
if(res.request is null) {
Region@ reg = res.obj.region;
if(reg !is null && reg.getPlanetCount(ai.empire) >= 2) {
plAI.obj.abandon();
}
}
}
//See if we have anything useful to morph our homeworld too
if(checkMorph) {
bool morph = false;
if(res.resource is crystals)
morph = true;
else if(res.resource.level >= 2 && res.resource.tilePressure[TR_Labor] >= 5)
morph = true;
else if(res.resource.level >= 3 && res.resource.totalPressure > 10)
morph = true;
else if(res.resource.cls is scalableClass && gameTime > 30.0 * 60.0)
morph = true;
else if(res.resource.level >= 2 && res.resource.totalPressure >= 5 && gameTime > 60.0 * 60.0)
morph = true;
if(morph) {
if(log)
ai.print("Morph homeworld to "+res.resource.name+" from "+res.obj.name, hw);
hw.activateAbilityTypeFor(ai.empire, unobtaniumAbl, plAI.obj);
}
}
}
}
//See if we can find something to send population to
availSources = popSources;
for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) {
Planet@ dest = popRequests[i];
if(canBuildPopulation(dest, factor=(availSources.length == 0 ? 2.5 : 1.5))) {
Factory@ f = construction.get(dest);
if(f !is null) {
if(f.active is null) {
auto@ build = construction.buildConstruction(buildPop);
construction.buildNow(build, f);
if(log)
ai.print("Build population", f.obj);
continue;
}
else {
auto@ cons = cast<BuildConstruction>(f.active);
if(cons !is null && cons.consType is buildPop) {
if(double(dest.maxPopulation) <= dest.population + 0.0)
continue;
}
}
}
}
transferBest(dest, availSources);
}
if(availSources.length != 0) {
//If we have any population left, do stuff from our colonization queue
for(uint i = 0, cnt = colonization.awaitingSource.length; i < cnt && availSources.length != 0; ++i) {
Planet@ dest = colonization.awaitingSource[i].target;
Planet@ source = transferBest(dest, availSources);
if(source !is null) {
@colonization.awaitingSource[i].colonizeFrom = source;
colonization.awaitingSource.removeAt(i);
--i; --cnt;
}
}
}
//Build population on idle planets
if(budget.canSpend(BT_Development, 100)) {
for(int i = popFactories.length-1; i >= 0; --i) {
Planet@ dest = popFactories[i];
Factory@ f = construction.get(dest);
if(f is null || f.active !is null)
continue;
if(dest.population >= double(dest.maxPopulation) + 1.0)
continue;
auto@ build = construction.buildConstruction(buildPop);
construction.buildNow(build, f);
if(log)
ai.print("Build population for idle", f.obj);
break;
}
}
}
Planet@ transferBest(Planet& dest, array<Planet@>& availSources) {
//Find closest source
Planet@ bestSource;
double bestDist = INFINITY;
for(uint j = 0, jcnt = availSources.length; j < jcnt; ++j) {
double d = movement.getPathDistance(availSources[j].position, dest.position);
if(d < bestDist) {
bestDist = d;
@bestSource = availSources[j];
}
}
if(bestSource !is null) {
double cost = transferCost(bestDist);
if(cost <= ai.empire.FTLStored) {
if(log)
ai.print("Transfering population to "+dest.name, bestSource);
availSources.remove(bestSource);
bestSource.activateAbilityTypeFor(ai.empire, colonizeAbl, dest);
return bestSource;
}
}
return null;
}
void tick(double time) override {
}
};
AIComponent@ createMechanoid() {
return Mechanoid();
}
@@ -0,0 +1,4 @@
import empire_ai.weasel.WeaselAI;
class Race : AIComponent {
};
@@ -0,0 +1,538 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.race.Race;
import empire_ai.weasel.Colonization;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Construction;
import empire_ai.weasel.Development;
import empire_ai.weasel.Fleets;
import empire_ai.weasel.Movement;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Designs;
import oddity_navigation;
from abilities import getAbilityID;
from statuses import getStatusID;
class HabitatMission : Mission {
Planet@ target;
MoveOrder@ move;
double timer = 0.0;
void save(Fleets& fleets, SaveFile& file) override {
file << target;
file << timer;
fleets.movement.saveMoveOrder(file, move);
}
void load(Fleets& fleets, SaveFile& file) override {
file >> target;
file >> timer;
@move = fleets.movement.loadMoveOrder(file);
}
void start(AI& ai, FleetAI& fleet) override {
uint prior = MP_Normal;
if(gameTime < 30.0 * 60.0)
prior = MP_Critical;
@move = cast<Movement>(ai.movement).move(fleet.obj, target, prior);
}
void tick(AI& ai, FleetAI& fleet, double time) override {
if(move !is null) {
if(move.failed) {
canceled = true;
return;
}
if(move.completed) {
int ablId = cast<StarChildren>(ai.race).habitatAbl;
fleet.obj.activateAbilityTypeFor(ai.empire, ablId, target);
@move = null;
timer = gameTime + 60.0;
}
}
else {
if(target is null || !target.valid || target.quarantined
|| (target.owner !is ai.empire && target.owner.valid)
|| target.inCombat) {
canceled = true;
return;
}
double maxPop = max(double(target.maxPopulation), double(getPlanetLevel(target, target.primaryResourceLevel).population));
double curPop = target.population;
if(curPop >= maxPop) {
completed = true;
return;
}
if(gameTime >= timer) {
int popStatus = cast<StarChildren>(ai.race).popStatus;
if(target.getStatusStackCountAny(popStatus) >= 5) {
canceled = true;
return;
}
}
}
}
};
class LaborMission : Mission {
Planet@ target;
MoveOrder@ move;
double timer = 0.0;
void save(Fleets& fleets, SaveFile& file) override {
file << target;
file << timer;
fleets.movement.saveMoveOrder(file, move);
}
void load(Fleets& fleets, SaveFile& file) override {
file >> target;
file >> timer;
@move = fleets.movement.loadMoveOrder(file);
}
void start(AI& ai, FleetAI& fleet) override {
@move = cast<Movement>(ai.movement).move(fleet.obj, target);
}
void tick(AI& ai, FleetAI& fleet, double time) override {
if(move !is null) {
if(move.failed) {
canceled = true;
return;
}
if(move.completed) {
@move = null;
timer = gameTime + 10.0;
}
}
else {
if(target is null || !target.valid || target.quarantined
|| target.owner !is ai.empire) {
canceled = true;
return;
}
if(gameTime >= timer) {
int popStatus = cast<StarChildren>(ai.race).popStatus;
timer = gameTime + 10.0;
if(target.getStatusStackCountAny(popStatus) >= 10) {
completed = true;
return;
}
}
}
}
};
class StarChildren : Race {
Colonization@ colonization;
Construction@ construction;
Development@ development;
Movement@ movement;
Planets@ planets;
Fleets@ fleets;
Designs@ designs;
DesignTarget@ mothershipDesign;
double idleSince = 0;
array<FleetAI@> motherships;
int habitatAbl = -1;
int popStatus = -1;
array<Planet@> popRequests;
array<Planet@> laborPlanets;
BuildFlagship@ mcBuild;
BuildOrbital@ yardBuild;
void save(SaveFile& file) override {
designs.saveDesign(file, mothershipDesign);
file << idleSince;
construction.saveConstruction(file, mcBuild);
construction.saveConstruction(file, yardBuild);
uint cnt = motherships.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
fleets.saveAI(file, motherships[i]);
}
void load(SaveFile& file) override {
@mothershipDesign = designs.loadDesign(file);
file >> idleSince;
@mcBuild = cast<BuildFlagship>(construction.loadConstruction(file));
@yardBuild = cast<BuildOrbital>(construction.loadConstruction(file));
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ flAI = fleets.loadAI(file);
if(flAI !is null)
motherships.insertLast(flAI);
}
}
void create() override {
@colonization = cast<Colonization>(ai.colonization);
colonization.performColonization = false;
@development = cast<Development>(ai.development);
development.managePlanetPressure = false;
development.buildBuildings = false;
@fleets = cast<Fleets>(ai.fleets);
@construction = cast<Construction>(ai.construction);
@planets = cast<Planets>(ai.planets);
@designs = cast<Designs>(ai.designs);
@movement = cast<Movement>(ai.movement);
@ai.defs.Factory = null;
@ai.defs.LaborStorage = null;
habitatAbl = getAbilityID("MothershipColonize");
popStatus = getAbilityID("MothershipPopulation");
}
void start() override {
//Get the Tier 1 in our home system
{
ResourceSpec spec;
spec.type = RST_Level_Specific;
spec.level = 1;
spec.isForImport = false;
spec.isLevelRequirement = false;
colonization.queueColonize(spec);
}
//Then find a Tier 2 to get
{
ResourceSpec spec;
spec.type = RST_Level_Specific;
spec.level = 2;
spec.isForImport = false;
spec.isLevelRequirement = false;
colonization.queueColonize(spec);
}
//Design a mothership
@mothershipDesign = designs.design(DP_Mothership, 500);
mothershipDesign.targetMaintenance = 300;
mothershipDesign.targetLaborCost = 110;
mothershipDesign.customName = "Mothership";
}
bool requiresPopulation(Planet& target) {
double maxPop = max(double(target.maxPopulation), double(getPlanetLevel(target, target.primaryResourceLevel).population));
double curPop = target.population;
return curPop < maxPop;
}
uint chkInd = 0;
void focusTick(double time) override {
//Detect motherships
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
auto@ flAI = fleets.fleets[i];
if(flAI.fleetClass != FC_Mothership)
continue;
if(motherships.find(flAI) == -1) {
//Add to our tracking list
flAI.obj.autoFillSupports = false;
flAI.obj.allowFillFrom = true;
motherships.insertLast(flAI);
//Add as a factory
construction.registerFactory(flAI.obj);
}
}
for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {
Object@ obj = motherships[i].obj;
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
motherships.removeAt(i);
--i; --cnt;
}
}
//Detect planets that require more population
for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) {
auto@ obj = popRequests[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
popRequests.removeAt(i);
--i; --cnt;
continue;
}
if(!requiresPopulation(obj)) {
popRequests.removeAt(i);
--i; --cnt;
continue;
}
}
for(uint i = 0, cnt = laborPlanets.length; i < cnt; ++i) {
auto@ obj = laborPlanets[i];
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
laborPlanets.removeAt(i);
--i; --cnt;
continue;
}
if(obj.laborIncome < 3.0/60.0) {
laborPlanets.removeAt(i);
--i; --cnt;
continue;
}
}
uint plCnt = planets.planets.length;
for(uint n = 0, cnt = min(15, plCnt); n < cnt; ++n) {
chkInd = (chkInd+1) % plCnt;
auto@ plAI = planets.planets[chkInd];
//Find planets that need population
if(requiresPopulation(plAI.obj)) {
if(popRequests.find(plAI.obj) == -1)
popRequests.insertLast(plAI.obj);
}
//Find planets that have labor
if(plAI.obj.laborIncome >= 3.0/60.0) {
if(laborPlanets.find(plAI.obj) == -1)
laborPlanets.insertLast(plAI.obj);
}
}
//Send motherships to do colonization
uint totalCount = popRequests.length + colonization.awaitingSource.length;
uint motherCount = idleMothershipCount();
/*if(motherCount > totalCount) {*/
for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) {
Planet@ dest = popRequests[i];
if(isColonizing(dest))
continue;
if(dest.inCombat)
continue;
colonizeBest(dest);
}
for(uint i = 0, cnt = colonization.awaitingSource.length; i < cnt; ++i) {
Planet@ dest = colonization.awaitingSource[i].target;
if(isColonizing(dest))
continue;
colonizeBest(dest);
}
/*}*/
/*else {*/
/* for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {*/
/* auto@ flAI = motherships[i];*/
/* if(flAI.mission !is null)*/
/* continue;*/
/* if(isBuildingWithLabor(flAI))*/
/* continue;*/
/* colonizeBest(flAI);*/
/* }*/
/*}*/
if(totalCount != 0)
idleSince = gameTime;
//See if we should build new motherships
uint haveMC = motherships.length;
uint wantMC = 1;
if(gameTime > 20.0 * 60.0)
wantMC += 1;
wantMC = max(wantMC, min(laborPlanets.length, uint(gameTime/(30.0*60.0))));
if(mcBuild !is null && mcBuild.completed)
@mcBuild = null;
if(wantMC > haveMC && mcBuild is null)
@mcBuild = construction.buildFlagship(mothershipDesign, force=true);
if(yardBuild is null && haveMC > 0 && gameTime > 60 && gameTime < 180 && ai.defs.Shipyard !is null) {
Region@ reg = motherships[0].obj.region;
if(reg !is null) {
vec3d pos = reg.position;
vec2d offset = random2d(reg.radius * 0.4, reg.radius * 0.8);
pos.x += offset.x;
pos.z += offset.y;
@yardBuild = construction.buildOrbital(ai.defs.Shipyard, pos);
}
}
if(motherships.length == 1)
@colonization.colonizeWeightObj = motherships[0].obj;
else
@colonization.colonizeWeightObj = null;
//Idle motherships should be sent to go collect labor from labor planets
if(laborPlanets.length != 0) {
for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {
auto@ flAI = motherships[i];
if(flAI.mission !is null)
continue;
if(isAtLaborPlanet(flAI))
continue;
if(i == 0 && idleSince < gameTime-60.0)
continue;
double bestDist = INFINITY;
Planet@ best;
for(uint n = 0, ncnt = laborPlanets.length; n < ncnt; ++n) {
Planet@ check = laborPlanets[n];
if(hasMothershipAt(check))
continue;
double d = movement.getPathDistance(flAI.obj.position, check.position);
if(d < bestDist) {
@best = check;
bestDist = d;
}
}
if(best !is null) {
LaborMission miss;
@miss.target = best;
fleets.performMission(flAI, miss);
}
}
}
}
bool isAtLaborPlanet(FleetAI& flAI) {
auto@ miss = cast<LaborMission>(flAI);
if(miss !is null)
return true;
for(uint i = 0, cnt = laborPlanets.length; i < cnt; ++i) {
if(flAI.obj.isLockedOrbit(laborPlanets[i]))
return true;
}
return false;
}
bool isBuildingWithLabor(FleetAI& flAI) {
auto@ f = construction.get(flAI.obj);
if(f !is null && f.active !is null)
return false;
if(isAtLaborPlanet(flAI))
return true;
return false;
}
bool hasMothershipAt(Planet& pl) {
for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {
auto@ flAI = motherships[i];
auto@ miss = cast<LaborMission>(flAI);
if(miss !is null && miss.target is pl)
return true;
if(flAI.obj.isLockedOrbit(pl))
return true;
}
return false;
}
uint idleMothershipCount() {
uint count = 0;
for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {
if(motherships[i].mission is null)
count += 1;
}
return count;
}
bool isColonizing(Planet& dest) {
for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {
auto@ flAI = motherships[i];
auto@ miss = cast<HabitatMission>(flAI.mission);
if(miss !is null && miss.target is dest)
return true;
}
return false;
}
Planet@ colonizeBest(FleetAI& flAI) {
Planet@ best;
double bestDist = INFINITY;
for(uint i = 0, cnt = popRequests.length; i < cnt; ++i) {
Planet@ dest = popRequests[i];
if(isColonizing(dest))
continue;
if(dest.inCombat)
continue;
double d = movement.getPathDistance(flAI.obj.position, dest.position);
if(d < bestDist) {
@best = dest;
bestDist = d;
}
}
if(best is null) {
for(uint i = 0, cnt = colonization.awaitingSource.length; i < cnt; ++i) {
Planet@ dest = colonization.awaitingSource[i].target;
if(isColonizing(dest))
continue;
double d = movement.getPathDistance(flAI.obj.position, dest.position);
if(d < bestDist) {
@best = dest;
bestDist = d;
}
}
}
if(best !is null) {
HabitatMission miss;
@miss.target = best;
fleets.performMission(flAI, miss);
}
return best;
}
FleetAI@ colonizeBest(Planet& dest) {
FleetAI@ best;
double bestDist = INFINITY;
for(uint i = 0, cnt = motherships.length; i < cnt; ++i) {
auto@ flAI = motherships[i];
if(flAI.mission !is null)
continue;
if(isBuildingWithLabor(flAI))
continue;
double d = movement.getPathDistance(flAI.obj.position, dest.position);
if(d < bestDist) {
@best = flAI;
bestDist = d;
}
}
if(best !is null) {
HabitatMission miss;
@miss.target = dest;
fleets.performMission(best, miss);
}
return best;
}
};
AIComponent@ createStarChildren() {
return StarChildren();
}
@@ -0,0 +1,155 @@
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.race.Race;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Development;
import empire_ai.weasel.Planets;
import buildings;
class Verdant : Race, RaceDesigns {
Designs@ designs;
Development@ development;
Planets@ planets;
array<const Design@> defaultDesigns;
array<uint> defaultGoals;
const SubsystemDef@ sinewSubsystem;
const SubsystemDef@ supportSinewSubsystem;
const BuildingType@ stalk;
void create() override {
@designs = cast<Designs>(ai.designs);
@development = cast<Development>(ai.development);
@planets = cast<Planets>(ai.planets);
@sinewSubsystem = getSubsystemDef("VerdantSinew");
@supportSinewSubsystem = getSubsystemDef("VerdantSupportSinew");
@stalk = getBuildingType("Stalk");
}
void start() override {
ReadLock lock(ai.empire.designMutex);
for(uint i = 0, cnt = ai.empire.designCount; i < cnt; ++i) {
const Design@ dsg = ai.empire.getDesign(i);
if(dsg.newer !is null)
continue;
if(dsg.updated !is null)
continue;
uint goal = designs.classify(dsg, DP_Unknown);
if(goal == DP_Unknown)
continue;
defaultDesigns.insertLast(dsg);
defaultGoals.insertLast(goal);
}
}
void save(SaveFile& file) override {
uint cnt = defaultDesigns.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
file << defaultDesigns[i];
file << defaultGoals[i];
}
}
void load(SaveFile& file) override {
uint cnt = 0;
file >> cnt;
defaultDesigns.length = cnt;
defaultGoals.length = cnt;
for(uint i = 0; i < cnt; ++i) {
file >> defaultDesigns[i];
file >> defaultGoals[i];
}
}
uint plCheck = 0;
void focusTick(double time) override {
//Check if we need to build stalks anywhere
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i)
checkForStalk(development.focuses[i].plAI);
uint plCnt = planets.planets.length;
if(plCnt != 0) {
for(uint n = 0; n < min(plCnt, 5); ++n) {
plCheck = (plCheck+1) % plCnt;
auto@ plAI = planets.planets[plCheck];
checkForStalk(plAI);
}
}
}
void checkForStalk(PlanetAI@ plAI) {
if(plAI is null)
return;
Planet@ pl = plAI.obj;
if(pl.pressureCap <= 0 && pl.totalPressure >= 1) {
if(planets.isBuilding(plAI.obj, stalk))
return;
if(pl.getBuildingCount(stalk.id) != 0)
return;
planets.requestBuilding(plAI, stalk, expire=180.0);
}
}
bool preCompose(DesignTarget@ target) override {
return false;
}
bool postCompose(DesignTarget@ target) override {
// auto@ d = target.designer;
// //Add an extra engine
// if(target.purpose == DP_Combat)
// d.composition.insertAt(0, Exhaust(tag("Engine") & tag("GivesThrust"), 0.25, 0.35));
// //Remove armor layers we don't need
// for(uint i = 0, cnt = d.composition.length; i < cnt; ++i) {
// if(cast<ArmorLayer>(d.composition[i]) !is null) {
// d.composition.removeAt(i);
// --i; --cnt;
// }
// }
return false;
}
bool design(DesignTarget@ target, int size, const Design@& output) {
//All designs are rescales of default designs
const Design@ baseDesign;
uint possible = 0;
for(uint i = 0, cnt = defaultDesigns.length; i < cnt; ++i) {
if(defaultGoals[i] == target.purpose) {
possible += 1;
if(randomd() < 1.0 / double(possible))
@baseDesign = defaultDesigns[i];
}
}
if(baseDesign is null)
return false;
//if(target.designer !is null) {
// @target.designer.baseOffDesign = baseDesign;
// if(target.purpose != DP_Support)
// @target.designer.baseOffSubsystem = sinewSubsystem;
// else
// @target.designer.baseOffSubsystem = supportSinewSubsystem;
// @output = target.designer.design();
//}
if(output is null)
@output = scaleDesign(baseDesign, size);
return true;
}
};
AIComponent@ createVerdant() {
return Verdant();
}
+228
View File
@@ -0,0 +1,228 @@
Object@ findEnemy(Region@ region, Empire@ emp, uint empireMask, bool fleets = true, bool stations = true, bool planets = false) {
array<Object@>@ objs = findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask);
uint offset = randomi(0, objs.length-1);
uint cnt = objs.length;
for(uint i = 0; i < cnt; ++i) {
Object@ obj = objs[(i+offset)%cnt];
Empire@ owner = obj.owner;
if(!obj.valid) {
continue;
}
else if(owner is null || owner.mask & empireMask == 0) {
continue;
}
else if(emp !is null && !obj.isVisibleTo(emp)) {
continue;
}
else if(obj.region !is region) {
continue;
}
else {
uint type = obj.type;
switch(type) {
case OT_Ship:
if(!obj.hasLeaderAI)
continue;
if(cast<Ship>(obj).isStation) {
if(!stations)
continue;
}
else {
if(!fleets)
continue;
}
if(obj.getFleetStrength() < 100.0)
continue;
break;
case OT_Orbital:
if(!stations)
continue;
break;
case OT_Planet:
if(!planets)
continue;
break;
default:
continue;
}
}
return obj;
}
return null;
}
array<Object@>@ findEnemies(Region@ region, Empire@ emp, uint empireMask, bool fleets = true, bool stations = true, bool planets = false) {
array<Object@>@ objs = findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask);
array<Object@> outObjs;
for(int i = objs.length-1; i >= 0; --i) {
Object@ obj = objs[i];
Empire@ owner = obj.owner;
bool remove = false;
if(!obj.valid) {
remove = true;
}
else if(owner is null || owner.mask & empireMask == 0) {
remove = true;
}
else if(emp !is null && !obj.isVisibleTo(emp)) {
remove = true;
}
else if(obj.region !is region) {
remove = true;
}
else {
uint type = obj.type;
switch(type) {
case OT_Ship:
if(!obj.hasLeaderAI)
remove = true;
if(cast<Ship>(obj).isStation) {
if(!stations)
remove = true;
}
else {
if(!fleets)
remove = true;
}
if(obj.getFleetStrength() < 100.0)
remove = true;
break;
case OT_Orbital:
if(!stations)
remove = true;
break;
case OT_Planet:
if(!planets)
remove = true;
break;
default:
remove = true;
}
}
if(!remove)
outObjs.insertLast(obj);
}
return outObjs;
}
array<Object@>@ findType(Region@ region, Empire@ emp, uint objectType, uint empireMask = ~0) {
// Specialized for safe object buckets
array<Object@>@ objs;
DataList@ data;
switch(objectType)
{
case OT_Planet:
@data = region.getPlanets();
break;
case OT_Pickup:
@data = region.getPickups();
break;
case OT_Anomaly:
@data = region.getAnomalies();
break;
case OT_Artifact:
@data = region.getArtifacts();
break;
case OT_Asteroid:
@data = region.getAsteroids();
break;
}
if(data !is null)
{
@objs = array<Object@>();
Object@ obj;
while(receive(data, obj)) {
if(obj !is null)
objs.insertLast(obj);
}
}
else {
// No object bucket retrieval mechanism, do a full physics search
@objs = findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask);
}
// Generic search using physics system
array<Object@> outObjs;
for(int i = objs.length-1; i >= 0; --i) {
Object@ obj = objs[i];
Empire@ owner = obj.owner;
bool remove = false;
if(!obj.valid) {
remove = true;
}
else if(owner is null || owner.mask & empireMask == 0) {
remove = true;
}
else if(emp !is null && !obj.isVisibleTo(emp)) {
remove = true;
}
else if(obj.region !is region) {
remove = true;
}
else {
uint type = obj.type;
if(type != objectType)
remove = true;
}
if(!remove)
outObjs.insertLast(obj);
}
return outObjs;
}
array<Object@>@ findAll(Region@ region, uint empireMask = ~0) {
return findInBox(region.position - vec3d(region.radius), region.position + vec3d(region.radius), empireMask);
}
double getTotalFleetStrength(Region@ region, uint empireMask, bool fleets = true, bool stations = true, bool planets = true) {
auto@ objs = findAll(region, empireMask);
double str = 0.0;
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
Object@ obj = objs[i];
Empire@ owner = obj.owner;
if(!obj.valid)
continue;
if(owner is null || owner.mask & empireMask == 0)
continue;
if(obj.region !is region)
continue;
uint type = obj.type;
switch(type) {
case OT_Ship:
if(!obj.hasLeaderAI)
continue;
if(cast<Ship>(obj).isStation) {
if(!stations)
continue;
}
else {
if(!fleets)
continue;
}
if(obj.getFleetStrength() < 100.0)
continue;
break;
case OT_Orbital:
if(!stations)
continue;
break;
case OT_Planet:
if(!planets)
continue;
break;
default:
continue;
}
str += sqrt(obj.getFleetStrength());
}
return str * str;
}