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(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(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(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(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(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 motherships; int habitatAbl = -1; int popStatus = -1; array popRequests; array 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(construction.loadConstruction(file)); @yardBuild = cast(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(ai.colonization); colonization.performColonization = false; @development = cast(ai.development); development.managePlanetPressure = false; development.buildBuildings = false; @fleets = cast(ai.fleets); @construction = cast(ai.construction); @planets = cast(ai.planets); @designs = cast(ai.designs); @movement = cast(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(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(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(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(); }