Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
@@ -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();
}