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 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(ai.colonization); colonization.performColonization = false; @resources = cast(ai.resources); @construction = cast(ai.construction); @movement = cast(ai.movement); @planets = cast(ai.planets); @orbitals = cast(ai.orbitals); @planets = cast(ai.planets); @development = cast(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& 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 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 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(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(); }