Files
2018-07-17 14:15:37 +02:00

1551 lines
37 KiB
ActionScript

// Construction
// ------------
// Manages factories and allows build requests for flagships, orbitals, and
// anything else that requires labor.
//
import empire_ai.weasel.WeaselAI;
import empire_ai.weasel.Budget;
import empire_ai.weasel.Planets;
import empire_ai.weasel.Designs;
import empire_ai.weasel.Resources;
import empire_ai.weasel.Systems;
import empire_ai.weasel.Orbitals;
import orbitals;
import saving;
import systems;
import regions.regions;
from constructible import ConstructibleType;
from constructions import ConstructionType, getConstructionType;
class AllocateConstruction {
int id = -1;
uint moneyType = BT_Development;
Factory@ tryFactory;
double maxTime = INFINITY;
bool completed = false;
bool started = false;
double completedAt = 0;
AllocateBudget@ alloc;
int cost = 0;
int maintenance = 0;
double priority = 1.0;
AllocateConstruction() {
}
void _save(Construction& construction, SaveFile& file) {
file << moneyType;
construction.saveFactory(file, tryFactory);
file << maxTime;
file << completed;
file << started;
file << completedAt;
construction.budget.saveAlloc(file, alloc);
file << cost;
file << maintenance;
file << priority;
save(construction, file);
}
void save(Construction& construction, SaveFile& file) {
}
void _load(Construction& construction, SaveFile& file) {
file >> moneyType;
@tryFactory = construction.loadFactory(file);
file >> maxTime;
file >> completed;
file >> started;
file >> completedAt;
@alloc = construction.budget.loadAlloc(file);
file >> cost;
file >> maintenance;
file >> priority;
load(construction, file);
}
void load(Construction& construction, SaveFile& file) {
}
bool tick(AI& ai, Construction& construction, double time) {
if(tryFactory !is null && alloc.allocated) {
construction.start(tryFactory, this);
return false;
}
return true;
}
void update(AI& ai, Factory@ f) {
@alloc = cast<Budget>(ai.budget).allocate(moneyType, cost, maintenance, priority);
}
double laborCost(AI& ai, Object@ obj) {
return 0.0;
}
bool canBuild(AI& ai, Factory@ f) {
return true;
}
void construct(AI& ai, Factory@ f) {
started = true;
}
string toString() {
return "construction";
}
};
class BuildFlagship : AllocateConstruction {
double baseLabor = 0.0;
const Design@ design;
DesignTarget@ target;
BuildFlagship() {
}
BuildFlagship(const Design@ dsg) {
set(dsg);
}
BuildFlagship(DesignTarget@ target) {
@this.target = target;
}
void save(Construction& construction, SaveFile& file) {
file << baseLabor;
if(design !is null) {
file.write1();
file << design;
}
else {
file.write0();
}
construction.designs.saveDesign(file, target);
}
void load(Construction& construction, SaveFile& file) {
file >> baseLabor;
if(file.readBit())
file >> design;
@target = construction.designs.loadDesign(file);
}
void set(const Design& dsg) {
@design = dsg.mostUpdated();
baseLabor = design.total(HV_LaborCost);
}
double laborCost(AI& ai, Object@ obj) {
return baseLabor;
}
bool tick(AI& ai, Construction& construction, double time) override {
if(target !is null) {
if(target.active !is null) {
set(target.active);
@target = null;
}
}
return AllocateConstruction::tick(ai, construction, time);
}
bool canBuild(AI& ai, Factory@ f) override {
if(!f.obj.canBuildShips)
return false;
return design !is null;
}
void update(AI& ai, Factory@ f) {
double c = design.total(HV_BuildCost);
c *= double(f.obj.shipBuildCost) / 100.0;
c *= f.obj.constructionCostMod;
cost = ceil(c);
maintenance = ceil(design.total(HV_MaintainCost));
AllocateConstruction::update(ai, f);
}
void construct(AI& ai, Factory@ f) {
f.obj.buildFlagship(design);
AllocateConstruction::construct(ai, f);
}
string toString() {
if(design is null)
return "flagship (design in progress)";
return "flagship "+design.name;
}
};
class BuildFlagshipSourced : BuildFlagship {
Object@ buildAt;
Object@ buildFrom;
BuildFlagshipSourced() {
}
BuildFlagshipSourced(const Design@ dsg) {
set(dsg);
}
BuildFlagshipSourced(DesignTarget@ target) {
@this.target = target;
}
void save(Construction& construction, SaveFile& file) override {
BuildFlagship::save(construction, file);
file << buildAt;
file << buildFrom;
}
void load(Construction& construction, SaveFile& file) override {
BuildFlagship::load(construction, file);
file >> buildAt;
file >> buildFrom;
}
bool canBuild(AI& ai, Factory@ f) override {
if(buildAt !is null && f.obj !is buildAt)
return false;
return BuildFlagship::canBuild(ai, f);
}
void construct(AI& ai, Factory@ f) override {
f.obj.buildFlagship(design, constructFrom=buildFrom);
AllocateConstruction::construct(ai, f);
}
};
class BuildStation : AllocateConstruction {
double baseLabor = 0.0;
const Design@ design;
DesignTarget@ target;
vec3d position;
bool local = false;
BuildStation() {
}
BuildStation(const Design@ dsg, const vec3d& position) {
this.position = position;
set(dsg);
}
BuildStation(DesignTarget@ target, const vec3d& position) {
this.position = position;
@this.target = target;
}
BuildStation(const Design@ dsg, bool local) {
this.local = true;
set(dsg);
}
BuildStation(DesignTarget@ target, bool local) {
@this.target = target;
this.local = true;
}
void save(Construction& construction, SaveFile& file) {
file << baseLabor;
file << position;
file << local;
if(design !is null) {
file.write1();
file << design;
}
else {
file.write0();
}
construction.designs.saveDesign(file, target);
}
void load(Construction& construction, SaveFile& file) {
file >> baseLabor;
file >> position;
file >> local;
if(file.readBit())
file >> design;
@target = construction.designs.loadDesign(file);
}
void set(const Design& dsg) {
@design = dsg.mostUpdated();
baseLabor = design.total(HV_LaborCost);
}
double laborCost(AI& ai, Object@ obj) {
double labor = baseLabor;
labor *= obj.owner.OrbitalLaborCostFactor;
if(!local) {
Region@ reg = getRegion(position);
Region@ targReg = obj.region;
if(reg !is null && targReg !is null) {
int hops = cast<Systems>(ai.systems).tradeDistance(targReg, reg);
if(hops > 0) {
double penalty = 1.0 + config::ORBITAL_LABOR_COST_STEP * double(hops);
baseLabor *= penalty;
}
}
}
return labor;
}
bool tick(AI& ai, Construction& construction, double time) override {
if(target !is null) {
if(target.active !is null) {
set(target.active);
@target = null;
}
}
return AllocateConstruction::tick(ai, construction, time);
}
bool canBuild(AI& ai, Factory@ f) override {
if(design is null)
return false;
if(!f.obj.canBuildOrbitals)
return false;
Region@ targReg = f.obj.region;
if(targReg is null)
return false;
if(!local) {
Region@ reg = getRegion(position);
if(reg is null)
return false;
if(!cast<Systems>(ai.systems).canTrade(targReg, reg))
return false;
}
return true;
}
void update(AI& ai, Factory@ f) {
double c = design.total(HV_BuildCost);
c *= f.obj.owner.OrbitalBuildCostFactor;
c *= f.obj.constructionCostMod;
cost = ceil(c);
maintenance = ceil(design.total(HV_MaintainCost));
AllocateConstruction::update(ai, f);
}
void construct(AI& ai, Factory@ f) {
if(local) {
position = f.obj.position;
vec2d offset = random2d(f.obj.radius + 10.0, f.obj.radius + 100.0);
position.x += offset.x;
position.z += offset.y;
}
f.obj.buildStation(design, position);
AllocateConstruction::construct(ai, f);
}
string toString() {
if(design is null)
return "station (design in progress)";
return "station "+design.name;
}
};
class BuildOrbital : AllocateConstruction {
double baseLabor = 0.0;
const OrbitalModule@ module;
bool local = false;
vec3d position;
BuildOrbital() {
}
BuildOrbital(const OrbitalModule@ module, const vec3d& position) {
this.position = position;
@this.module = module;
baseLabor = module.laborCost;
}
BuildOrbital(const OrbitalModule@ module, bool local) {
this.local = true;
@this.module = module;
baseLabor = module.laborCost;
}
void save(Construction& construction, SaveFile& file) {
file << baseLabor;
file << position;
file << local;
file.writeIdentifier(SI_Orbital, module.id);
}
void load(Construction& construction, SaveFile& file) {
file >> baseLabor;
file >> position;
file >> local;
@module = getOrbitalModule(file.readIdentifier(SI_Orbital));
}
double laborCost(AI& ai, Object@ obj) {
double labor = baseLabor;
labor *= obj.owner.OrbitalLaborCostFactor;
if(!local) {
Region@ reg = getRegion(position);
Region@ targReg = obj.region;
if(reg !is null && targReg !is null) {
int hops = cast<Systems>(ai.systems).tradeDistance(targReg, reg);
if(hops > 0) {
double penalty = 1.0 + config::ORBITAL_LABOR_COST_STEP * double(hops);
baseLabor *= penalty;
}
}
}
return labor;
}
bool tick(AI& ai, Construction& construction, double time) override {
return AllocateConstruction::tick(ai, construction, time);
}
bool canBuild(AI& ai, Factory@ f) override {
if(module is null)
return false;
if(!f.obj.canBuildOrbitals)
return false;
Region@ targReg = f.obj.region;
if(targReg is null)
return false;
if(!local) {
Region@ reg = getRegion(position);
if(reg is null)
return false;
if(!cast<Systems>(ai.systems).canTrade(targReg, reg))
return false;
}
return true;
}
void update(AI& ai, Factory@ f) {
double c = module.buildCost;
c *= f.obj.owner.OrbitalBuildCostFactor;
c *= f.obj.constructionCostMod;
cost = ceil(c);
maintenance = module.maintenance;
AllocateConstruction::update(ai, f);
}
void construct(AI& ai, Factory@ f) {
if(local) {
position = f.obj.position;
vec2d offset = random2d(f.obj.radius + 10.0, f.obj.radius + 100.0);
position.x += offset.x;
position.z += offset.y;
}
f.obj.buildOrbital(module.id, position);
AllocateConstruction::construct(ai, f);
}
string toString() {
return "orbital "+module.name;
}
};
class RetrofitShip : AllocateConstruction {
Ship@ ship;
double labor;
RetrofitShip() {
}
RetrofitShip(Ship@ ship) {
@this.ship = ship;
labor = ship.getRetrofitLabor();
cost = ship.getRetrofitCost();
}
void save(Construction& construction, SaveFile& file) {
file << ship;
file << labor;
}
void load(Construction& construction, SaveFile& file) {
file >> ship;
file >> labor;
}
double laborCost(AI& ai, Object@ obj) {
return labor;
}
bool canBuild(AI& ai, Factory@ f) override {
if(!f.obj.canBuildShips)
return false;
Region@ reg = ship.region;
return reg !is null && reg is f.obj.region;
}
void construct(AI& ai, Factory@ f) {
ship.retrofitFleetAt(f.obj);
AllocateConstruction::construct(ai, f);
}
string toString() {
return "retrofit "+ship.name;
}
};
class BuildConstruction : AllocateConstruction {
const ConstructionType@ consType;
BuildConstruction() {
}
BuildConstruction(const ConstructionType@ consType) {
@this.consType = consType;
}
void save(Construction& construction, SaveFile& file) {
file.writeIdentifier(SI_ConstructionType, consType.id);
}
void load(Construction& construction, SaveFile& file) {
@consType = getConstructionType(file.readIdentifier(SI_ConstructionType));
}
double laborCost(AI& ai, Object@ obj) {
if(obj is null)
return consType.laborCost;
return consType.getLaborCost(obj);
}
bool canBuild(AI& ai, Factory@ f) override {
return consType.canBuild(f.obj, ignoreCost=true);
}
void update(AI& ai, Factory@ f) {
cost = consType.getBuildCost(f.obj);
maintenance = consType.getMaintainCost(f.obj);
AllocateConstruction::update(ai, f);
}
void construct(AI& ai, Factory@ f) {
f.obj.buildConstruction(consType.id);
AllocateConstruction::construct(ai, f);
}
string toString() {
return "construction "+consType.name;
}
};
class Factory {
Object@ obj;
PlanetAI@ plAI;
Factory@ exportingTo;
AllocateConstruction@ active;
double laborAim = 0.0;
double laborIncome = 0.0;
double idleSince = 0.0;
double storedLabor = 0.0;
double laborMaxStorage = 0.0;
double buildingPenalty = 0.0;
bool needsSupportLabor = false;
double waitingSupportLabor = 0.0;
uint curConstructionType = 0;
bool valid = true;
bool significantLabor = true;
uint backgrounded = 0;
Asteroid@ bgAsteroid;
BuildingRequest@ curBuilding;
ImportData@ curImport;
void save(Construction& construction, SaveFile& file) {
construction.planets.saveAI(file, plAI);
construction.saveConstruction(file, active);
file << laborAim;
file << laborIncome;
file << idleSince;
file << storedLabor;
file << laborMaxStorage;
file << buildingPenalty;
construction.planets.saveBuildingRequest(file, curBuilding);
construction.resources.saveImport(file, curImport);
file << backgrounded;
file << bgAsteroid;
file << curConstructionType;
file << valid;
file << needsSupportLabor;
file << waitingSupportLabor;
construction.saveFactory(file, exportingTo);
}
void load(Construction& construction, SaveFile& file) {
@plAI = construction.planets.loadAI(file);
@active = construction.loadConstruction(file);
file >> laborAim;
file >> laborIncome;
file >> idleSince;
file >> storedLabor;
file >> laborMaxStorage;
file >> buildingPenalty;
@curBuilding = construction.planets.loadBuildingRequest(file);
@curImport = construction.resources.loadImport(file);
file >> backgrounded;
file >> bgAsteroid;
file >> curConstructionType;
file >> valid;
file >> needsSupportLabor;
file >> waitingSupportLabor;
@exportingTo = construction.loadFactory(file);
}
bool get_busy() {
return active !is null;
}
bool get_needsLabor() {
if(!valid)
return false;
if(obj.hasOrderedSupports)
return true;
if(active !is null)
return true;
if(needsSupportLabor)
return true;
if(obj.constructionCount > 0 && curConstructionType != CT_Export)
return true;
return false;
}
double laborToBear(AI& ai) {
return laborIncome * ai.behavior.constructionMaxTime + storedLabor;
}
bool viable(AI& ai, AllocateConstruction@ alloc) {
double labor = obj.laborIncome;
double estTime = (alloc.laborCost(ai, obj) - storedLabor) / labor;
if(estTime > alloc.maxTime)
return false;
return true;
}
bool tick(AI& ai, Construction& construction, double time) {
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
valid = false;
return false;
}
uint curCount = obj.constructionCount;
curConstructionType = 0;
bool isBackground = false;
if(curCount != 0) {
curConstructionType = obj.constructionType;
isBackground = curConstructionType == CT_Asteroid || curConstructionType == CT_Export;
}
if(active !is null) {
if(curCount <= backgrounded || (curCount == 1 && isBackground)) {
if(construction.log)
ai.print("Completed construction of "+active.toString()+" "+backgrounded+" / "+curCount, obj);
active.completed = true;
active.completedAt = gameTime;
@active = null;
idleSince = gameTime;
backgrounded = 0;
}
}
else {
if(curCount < backgrounded) {
backgrounded = curCount;
}
}
//Background constructibles we don't need to do right now
if(curCount > 1 && curConstructionType == CT_Asteroid && bgAsteroid !is null) {
obj.moveConstruction(obj.constructionID[0], -1);
backgrounded += 1;
}
if(curCount > 1 && curConstructionType == CT_Export && exportingTo !is null) {
obj.cancelConstruction(obj.constructionID[0]);
@exportingTo = null;
}
if(bgAsteroid !is null && (bgAsteroid.owner.valid || curCount == 0)) {
if(bgAsteroid.owner is ai.empire)
construction.planets.register(bgAsteroid);
@bgAsteroid = null;
}
//Build warehouse(s) if we've been idle
laborIncome = obj.laborIncome;
storedLabor = obj.currentLaborStored;
laborMaxStorage = obj.laborStorageCapacity;
significantLabor = laborIncome >= 0.4 * construction.bestLabor && obj.baseLaborIncome > 4.0/60.0;
if(storedLabor < laborMaxStorage)
idleSince = gameTime;
if(active is null && curBuilding is null && plAI !is null && gameTime - idleSince > ai.behavior.laborStoreIdleTimer && ai.behavior.buildLaborStorage && (laborMaxStorage+50) < ai.behavior.laborStoreMaxFillTime * max(obj.baseLaborIncome, laborAim) && significantLabor) {
auto@ bld = ai.defs.LaborStorage;
if(bld !is null && buildingPenalty < gameTime) {
if(construction.log)
ai.print("Build building "+bld.name+" for labor storage", obj);
@curBuilding = construction.planets.requestBuilding(plAI, bld);
}
}
//Remove waits on completed labor gains
if(curBuilding !is null) {
if(curBuilding.canceled || (curBuilding.built && curBuilding.getProgress() >= 1.0)) {
if(construction.log)
ai.print("Building construction for labor finished", obj);
if(curBuilding.canceled)
buildingPenalty = gameTime + 60.0;
@curBuilding = null;
}
}
if(curImport !is null) {
if(curImport.beingMet) {
if(construction.log)
ai.print("Resource import for labor finished", obj);
@curImport = null;
}
}
//See if we need a new labor gain
if(laborIncome < laborAim) {
if(curImport is null && plAI !is null && obj.isPressureSaturated(TR_Labor) && obj.pressureCap < uint(obj.totalPressure) && gameTime > 6.0 * 60.0 && ai.behavior.buildFactoryForLabor) {
ResourceSpec spec;
spec.type = RST_Pressure_Level0;
spec.pressureType = TR_Labor;
if(construction.log)
ai.print("Queue resource import for labor", obj);
@curImport = construction.resources.requestResource(obj, spec, prioritize=true);
}
if(curBuilding is null && plAI !is null && ai.behavior.buildLaborStorage) {
auto@ bld = ai.defs.Factory;
if(bld !is null && buildingPenalty < gameTime) {
if(construction.log)
ai.print("Build building "+bld.name+" for labor", obj);
@curBuilding = construction.planets.requestBuilding(plAI, bld);
}
}
}
//See if we should spend our labor on a labor export somewhere else
if(exportingTo !is null && curConstructionType == CT_Export) {
if(!exportingTo.valid || (!exportingTo.needsLabor && exportingTo !is construction.primaryFactory)) {
obj.cancelConstruction(obj.constructionID[0]);
@exportingTo = null;
}
}
if(ai.behavior.distributeLaborExports) {
if(curCount == 0 && obj.canExportLabor) {
uint offset = randomi(0, construction.factories.length-1);
for(uint i = 0, cnt = construction.factories.length; i < cnt; ++i) {
auto@ other = construction.factories[(i+offset) % cnt];
if(other is this)
continue;
if(!other.obj.canImportLabor)
continue;
//Check if this is currently busy
if(other !is construction.primaryFactory) {
if(!other.needsLabor)
continue;
}
obj.exportLaborTo(other.obj);
@exportingTo = other;
}
}
}
//See if we should spend our labor trying to build an asteroid
if(ai.behavior.backgroundBuildAsteroids) {
if((curCount == 0 || (curConstructionType == CT_Export && curCount == 1)) && storedLabor >= laborMaxStorage * 0.5 && obj.canBuildAsteroids) {
Asteroid@ roid = construction.getBackgroundAsteroid(this);
if(roid !is null) {
uint resCount = roid.getAvailableCount();
if(resCount != 0) {
uint bestIndex = 0;
int bestId = -1;
double bestWeight = 0.0;
if(ai.behavior.chooseAsteroidResource) {
for(uint i = 0; i < resCount; ++i) {
int resourceId = roid.getAvailable(i);
double w = asteroidResourceValue(getResource(resourceId));
if(w > bestWeight) {
bestWeight = w;
bestId = resourceId;
bestIndex = i;
}
}
}
else {
bestIndex = randomi(0, resCount-1);
bestId = roid.getAvailable(bestIndex);
}
double laborCost = roid.getAvailableCost(bestIndex);
Region@ fromReg = obj.region;
Region@ toReg = roid.region;
if(fromReg !is null && toReg !is null)
laborCost *= 1.0 + config::ASTEROID_COST_STEP * double(construction.systems.hopDistance(fromReg, toReg));
double timeTaken = laborIncome / laborCost;
if(timeTaken < ai.behavior.constructionMaxTime || storedLabor >= laborMaxStorage * 0.95) {
@bgAsteroid = roid;
obj.buildAsteroid(roid, bestId);
if(construction.log)
ai.print("Use background labor to mine "+roid.name+" in "+roid.region.name, obj);
}
}
}
}
}
return true;
}
void aimForLabor(double labor) {
if(labor > laborAim)
laborAim = labor;
}
};
class Construction : AIComponent {
array<Factory@> factories;
Factory@ primaryFactory;
double noFactoryTimer = 0.0;
int nextAllocId = 0;
array<AllocateConstruction@> allocations;
double totalLabor = 0.0;
double bestLabor = 0.0;
BuildOrbital@ buildConsolidate;
Budget@ budget;
Planets@ planets;
Orbitals@ orbitals;
Resources@ resources;
Designs@ designs;
Systems@ systems;
void create() {
@budget = cast<Budget>(ai.budget);
@planets = cast<Planets>(ai.planets);
@resources = cast<Resources>(ai.resources);
@designs = cast<Designs>(ai.designs);
@systems = cast<Systems>(ai.systems);
@orbitals = cast<Orbitals>(ai.orbitals);
}
void save(SaveFile& file) {
file << nextAllocId;
uint cnt = allocations.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
saveConstruction(file, allocations[i]);
cnt = factories.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
saveFactory(file, factories[i]);
factories[i].save(this, file);
}
saveFactory(file, primaryFactory);
file << noFactoryTimer;
saveConstruction(file, buildConsolidate);
}
void load(SaveFile& file) {
file >> nextAllocId;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ alloc = loadConstruction(file);
if(alloc !is null)
allocations.insertLast(alloc);
}
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
Factory@ f = loadFactory(file);
if(f !is null)
f.load(this, file);
else
Factory().load(this, file);
}
@primaryFactory = loadFactory(file);
file >> noFactoryTimer;
@buildConsolidate = cast<BuildOrbital>(loadConstruction(file));
}
void saveFactory(SaveFile& file, Factory@ f) {
if(f !is null) {
file.write1();
file << f.obj;
}
else {
file.write0();
}
}
Factory@ loadFactory(SaveFile& file) {
if(!file.readBit())
return null;
Object@ obj;
file >> obj;
if(obj is null)
return null;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
if(factories[i].obj is obj)
return factories[i];
}
Factory f;
@f.obj = obj;
factories.insertLast(f);
return f;
}
array<AllocateConstruction@> savedConstructions;
array<AllocateConstruction@> loadedConstructions;
void postSave(AI& ai) {
savedConstructions.length = 0;
}
void postLoad(AI& ai) {
loadedConstructions.length = 0;
}
void saveConstruction(SaveFile& file, AllocateConstruction@ alloc) {
if(alloc is null) {
file.write0();
return;
}
file.write1();
file << alloc.id;
if(alloc.id == -1) {
storeConstruction(file, alloc);
}
else {
bool found = false;
for(uint i = 0, cnt = savedConstructions.length; i < cnt; ++i) {
if(savedConstructions[i] is alloc) {
found = true;
break;
}
}
if(!found) {
storeConstruction(file, alloc);
savedConstructions.insertLast(alloc);
}
}
}
AllocateConstruction@ loadConstruction(SaveFile& file) {
if(!file.readBit())
return null;
int id = 0;
file >> id;
if(id == -1) {
AllocateConstruction@ alloc = createConstruction(file);
alloc.id = id;
return alloc;
}
else {
for(uint i = 0, cnt = loadedConstructions.length; i < cnt; ++i) {
if(loadedConstructions[i].id == id)
return loadedConstructions[i];
}
AllocateConstruction@ alloc = createConstruction(file);
alloc.id = id;
loadedConstructions.insertLast(alloc);
return alloc;
}
}
void storeConstruction(SaveFile& file, AllocateConstruction@ alloc) {
auto@ cls = getClass(alloc);
auto@ mod = cls.module;
file << mod.name;
file << cls.name;
alloc._save(this, file);
}
AllocateConstruction@ createConstruction(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 alloc "+modName+"::"+clsName);
return null;
}
auto@ cls = mod.getClass(clsName);
if(cls is null) {
error("ERROR: AI Load could not find class for alloc "+modName+"::"+clsName);
return null;
}
auto@ alloc = cast<AllocateConstruction>(cls.create());
if(alloc is null) {
error("ERROR: AI Load could not create class instance for alloc "+modName+"::"+clsName);
return null;
}
alloc._load(this, file);
return alloc;
}
void start() {
Object@ hw = ai.empire.Homeworld;
if(hw !is null) {
Factory f;
@f.obj = hw;
@f.plAI = planets.getAI(cast<Planet>(hw));
factories.insertLast(f);
}
}
Factory@ get(Object@ obj) {
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
if(factories[i].obj is obj)
return factories[i];
}
return null;
}
Factory@ registerFactory(Object@ obj) {
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
if(factories[i].obj is obj)
return factories[i];
}
Factory f;
@f.obj = obj;
factories.insertLast(f);
return f;
}
Factory@ getFactory(Region@ region) {
Factory@ best;
double bestLabor = 0;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
if(factories[i].obj.region !is region)
continue;
double l = factories[i].obj.laborIncome;
if(l > bestLabor) {
bestLabor = l;
@best = factories[i];
}
}
return best;
}
BuildConstruction@ buildConstruction(const ConstructionType@ type, double priority = 1.0, bool force = false, uint moneyType = BT_Development) {
//Potentially build a flagship
BuildConstruction f(type);
f.moneyType = moneyType;
f.priority = priority;
build(f, force=force);
return f;
}
BuildFlagship@ buildFlagship(const Design@ dsg, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildFlagship f(dsg);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
BuildFlagship@ buildFlagship(DesignTarget@ target, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildFlagship f(target);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
BuildStation@ buildStation(const Design@ dsg, const vec3d& position, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildStation f(dsg, position);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
BuildStation@ buildStation(DesignTarget@ target, const vec3d& position, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildStation f(target, position);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
BuildOrbital@ buildOrbital(const OrbitalModule@ module, const vec3d& position, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildOrbital f(module, position);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
BuildStation@ buildLocalStation(const Design@ dsg, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildStation f(dsg, local=true);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
BuildStation@ buildLocalStation(DesignTarget@ target, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildStation f(target, local=true);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
BuildOrbital@ buildLocalOrbital(const OrbitalModule@ module, double priority = 1.0, bool force = false) {
//Potentially build a flagship
BuildOrbital f(module, local=true);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
RetrofitShip@ retrofit(Ship@ ship, double priority = 1.0, bool force = false) {
//Potentially build a flagship
RetrofitShip f(ship);
f.moneyType = BT_Military;
f.priority = priority;
build(f, force=force);
return f;
}
AllocateConstruction@ build(AllocateConstruction@ alloc, bool force = false) {
//Add a construction into the potential constructions queue
if(!force)
alloc.maxTime = ai.behavior.constructionMaxTime;
alloc.id = nextAllocId++;
allocations.insertLast(alloc);
if(log)
ai.print("Queue construction: "+alloc.toString());
return alloc;
}
AllocateConstruction@ buildNow(AllocateConstruction@ alloc, Factory@ f) {
if(f.busy)
return null;
if(alloc.alloc !is null)
budget.applyNow(alloc.alloc);
start(f, alloc);
allocations.remove(alloc);
return alloc;
}
void cancel(AllocateConstruction@ alloc) {
if(alloc.started || (alloc.alloc !is null && alloc.alloc.allocated))
return; //TODO
allocations.remove(alloc);
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
if(factories[i].active is alloc)
@factories[i].active = null;
}
if(alloc.alloc !is null)
budget.remove(alloc.alloc);
}
uint factInd = 0;
void tick(double time) {
//Manage factories
if(factories.length != 0) {
factInd = (factInd+1) % factories.length;
auto@ f = factories[factInd];
if(!f.tick(ai, this, time))
factories.removeAt(factInd);
}
}
void start(Factory@ f, AllocateConstruction@ c) {
//Actually construct something we've allocated budget for
@f.active = c;
@c.tryFactory = null;
c.construct(ai, f);
if(log)
ai.print("Construct: "+c.toString(), f.obj);
for(uint i = 0, cnt = allocations.length; i < cnt; ++i) {
if(allocations[i].tryFactory is f)
@allocations[i].tryFactory = null;
}
}
uint plCheck = 0;
uint orbCheck = 0;
double consTimer = 0.0;
void focusTick(double time) {
//Progress the allocations
for(uint n = 0, ncnt = allocations.length; n < ncnt; ++n) {
if(!allocations[n].tick(ai, this, time)) {
allocations.removeAt(n);
--n; --ncnt;
}
}
//See if anything we can potentially construct is constructible
totalLabor = 0.0;
bestLabor = 0.0;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
auto@ f = factories[i];
totalLabor += f.laborIncome;
if(f.laborIncome > bestLabor)
bestLabor = f.laborIncome;
}
for(uint n = 0, ncnt = allocations.length; n < ncnt; ++n) {
auto@ alloc = allocations[n];
if(alloc.tryFactory !is null)
continue;
Factory@ bestFact;
double bestCur = 0.0;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
auto@ f = factories[i];
if(f.busy)
continue;
if(!alloc.canBuild(ai, f))
continue;
if(!f.viable(ai, alloc))
continue;
double w = f.laborIncome;
if(f is primaryFactory)
w *= 1.5;
if(f.exportingTo !is null)
w /= 0.75;
if(w > bestCur) {
bestCur = w;
@bestFact = f;
}
}
if(bestFact !is null) {
@alloc.tryFactory = bestFact;
alloc.update(ai, bestFact);
}
}
//Classify our primary factory
if(primaryFactory is null) {
//Find our best factory
Factory@ best;
double bestWeight = 0.0;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
auto@ f = factories[i];
double w = f.laborIncome;
w += 0.1 * f.laborAim;
if(f.obj.isPlanet)
w *= 100.0;
if(f.obj.isShip)
w *= 0.1;
if(w > bestWeight) {
bestWeight = w;
@best = f;
}
}
if(best !is null) {
@primaryFactory = best;
}
else {
noFactoryTimer += time;
if(noFactoryTimer > 3.0 * 60.0 && ai.defs.Factory !is null) {
//Just pick our highest level planet and hope for the best
PlanetAI@ best;
double bestWeight = 0.0;
for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) {
auto@ plAI = planets.planets[i];
double w = plAI.obj.level;
w += 0.5 * plAI.obj.resourceLevel;
if(w > bestWeight) {
bestWeight = w;
@best = plAI;
}
}
if(best !is null) {
Factory f;
@f.obj = best.obj;
@f.plAI = best;
factories.insertLast(f);
@primaryFactory = f;
}
noFactoryTimer = 0.0;
}
}
}
else {
noFactoryTimer = 0.0;
}
//Find new factories
if(planets.planets.length != 0) {
plCheck = (plCheck+1) % planets.planets.length;
PlanetAI@ plAI = planets.planets[plCheck];
if(plAI.obj.laborIncome > 0 && plAI.obj.canBuildShips) {
if(get(plAI.obj) is null) {
Factory f;
@f.obj = plAI.obj;
@f.plAI = plAI;
factories.insertLast(f);
}
}
}
if(orbitals.orbitals.length != 0) {
orbCheck = (orbCheck+1) % orbitals.orbitals.length;
OrbitalAI@ orbAI = orbitals.orbitals[orbCheck];
if(orbAI.obj.hasConstruction && orbAI.obj.laborIncome > 0
&& !cast<Orbital>(orbAI.obj).hasMaster()) {
if(get(orbAI.obj) is null) {
Factory f;
@f.obj = orbAI.obj;
factories.insertLast(f);
}
}
}
//See if we should switch our primary factory
if(primaryFactory !is null) {
if(!primaryFactory.valid) {
@primaryFactory = null;
}
else {
Factory@ best;
double bestLabor = 0.0;
double primaryLabor = primaryFactory.laborIncome;
bool canImport = primaryFactory.obj.canImportLabor;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
auto@ f = factories[i];
double checkLabor = f.laborIncome;
if(f.obj.isShip)
checkLabor *= 0.1;
if(f.exportingTo !is primaryFactory && canImport)
primaryLabor += checkLabor * 0.75;
if(checkLabor > bestLabor) {
bestLabor = checkLabor;
@best = f;
}
}
if(best !is null && bestLabor > 1.5 * primaryLabor)
@primaryFactory = best;
}
}
//See if we should consolidate at a shipyard
if(buildConsolidate !is null && buildConsolidate.completed) {
@buildConsolidate = null;
consTimer = gameTime + 60.0;
}
else if(ai.behavior.consolidateLaborExports && primaryFactory !is null && ai.defs.Shipyard !is null && buildConsolidate is null && !primaryFactory.obj.canImportLabor && consTimer < gameTime) {
double totalLabor = 0.0, bestLabor = 0.0;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
double inc = factories[i].obj.baseLaborIncome;
if(factories[i].obj.canExportLabor)
totalLabor += inc;
if(factories[i].laborIncome > bestLabor)
bestLabor = factories[i].laborIncome;
}
if(bestLabor < totalLabor * 0.6) {
Factory@ bestConsolidate;
double bestWeight = 0.0;
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
auto@ f = factories[i];
if(!f.obj.canImportLabor)
continue;
double w = f.obj.baseLaborIncome;
w /= f.obj.position.distanceTo(primaryFactory.obj.position);
if(w > bestWeight) {
bestWeight = w;
@bestConsolidate = f;
}
}
if(bestConsolidate !is null) {
if(log)
ai.print("Set shipyard for consolidate.", bestConsolidate.obj.region);
@primaryFactory = bestConsolidate;
}
else {
Region@ reg = primaryFactory.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;
if(log)
ai.print("Build shipyard for consolidate.", reg);
@buildConsolidate = buildOrbital(ai.defs.Shipyard, pos);
}
}
}
}
}
bool isGettingAsteroid(Asteroid@ asteroid) {
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
if(factories[i].bgAsteroid is asteroid)
return true;
}
return false;
}
Asteroid@ getBackgroundAsteroid(Factory& f) {
double closest = INFINITY;
Asteroid@ best;
Region@ reg = f.obj.region;
if(reg is null)
return null;
uint cnt = systems.owned.length;
uint offset = randomi(0, cnt-1);
for(uint i = 0, check = min(3, cnt); i < check; ++i) {
auto@ sys = systems.owned[(i+offset)%cnt];
double dist = sys.obj.position.distanceToSQ(f.obj.position);
if(dist > closest)
continue;
if(!sys.obj.sharesTerritory(ai.empire, reg))
continue;
for(uint n = 0, ncnt = sys.asteroids.length; n < ncnt; ++n) {
Asteroid@ roid = sys.asteroids[n];
if(roid.owner.valid)
continue;
if(roid.getAvailableCount() == 0)
continue;
if(isGettingAsteroid(roid))
continue;
closest = dist;
@best = roid;
break;
}
}
cnt = systems.outsideBorder.length;
offset = randomi(0, cnt-1);
for(uint i = 0, check = min(3, cnt); i < check; ++i) {
auto@ sys = systems.outsideBorder[(i+offset)%cnt];
double dist = sys.obj.position.distanceToSQ(f.obj.position);
if(dist > closest)
continue;
if(!sys.obj.sharesTerritory(ai.empire, reg))
continue;
for(uint n = 0, ncnt = sys.asteroids.length; n < ncnt; ++n) {
Asteroid@ roid = sys.asteroids[n];
if(roid.owner.valid)
continue;
if(roid.getAvailableCount() == 0)
continue;
if(isGettingAsteroid(roid))
continue;
closest = dist;
@best = roid;
break;
}
}
return best;
}
};
double asteroidResourceValue(const ResourceType@ type) {
if(type is null)
return 0.0;
double w = 1.0;
w += type.level * 10.0;
w += type.totalPressure;
if(type.cls !is null)
w += 5.0;
return w;
}
AIComponent@ createConstruction() {
return Construction();
}