Open source Star Ruler 2 source code!
This commit is contained in:
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user