Files
starruler-linux/scripts/server/empire_ai/include/bai_act_resources.as
T
2018-07-17 14:15:37 +02:00

1332 lines
34 KiB
ActionScript

import resources;
import constructions;
import artifacts;
import int getOrbitalModuleID(const string&) from "orbitals";
//TODO: This doesn't represent the import request well
// Relies on how a given planet will tend to request imports
int64 importResHash(Planet@ pl, array<int>@ res) {
int64 Hash = (int64(ACT_Trade) << ACT_BIT_OFFSET) | int64(pl.id);
Hash |= int64(res[0]) << 32;
Hash |= int64(res.length) << 48;
return Hash;
}
class ImportResource : Action {
Planet@ dest; Object@ source;
uint nextType = 0;
int foundRes = -1;
int[]@ resources;
bool attemptExpand = false;
int64 Hash;
const ResourceType@ water = getResource("Water");
double attemptStart = gameTime;
ImportResource(Planet@ to, int[]@ Resources) {
@dest = to;
@resources = Resources;
Hash = importResHash(to, Resources);
}
ImportResource(BasicAI@ ai, SaveFile& msg) {
msg >> dest >> source;
msg >> foundRes >> attemptExpand >> nextType;
uint cnt = 0;
msg >> cnt;
@resources = array<int>(cnt);
for(uint i = 0; i < cnt; ++i)
resources[i] = msg.readIdentifier(SI_Resource);
Hash = importResHash(dest, resources);
}
~ImportResource() {
if(source !is null)
error("ImportResource ended without proper resolution");
}
double get_age() const {
return gameTime - attemptStart;
}
void postLoad(BasicAI@ ai) {
}
void save(BasicAI@ ai, SaveFile& msg) {
msg << dest << source;
msg << foundRes << attemptExpand << nextType;
uint cnt = resources.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg.writeIdentifier(SI_Resource, resources[i]);
}
int64 get_hash() const {
return Hash;
}
ActionType get_actionType() const {
return ACT_Trade;
}
string get_state() const {
if(source is null)
return format("Looking for $1 (+$2 others) for $3", getResource(resources[0]).name, resources.length-1, dest.name);
else
return format("Importing $1 from $2 to $3", getResource(foundRes).name, source.name, dest.name);
}
void clear(BasicAI@ ai) {
if(source !is null) {
ai.markIdleGeneric(source);
@source = null;
}
ai.removeIdle(this);
}
bool hasWater(BasicAI@ ai) {
if(ai.planets[water.id].idle.length == 0 && ai.skillEconomy >= DIFF_Medium) {
auto@ hydro = getBuildingType("Hydrogenator");
if((ai.usesMotherships ||ai.empire.RemainingBudget < hydro.baseBuildCost) || gameTime < 3.0 * 60.0) {
const auto@ comet = getArtifactType("Comet");
if(ai.empire.EnergyStored < comet.abilities[0].energyCost || ai.getArtifact(comet, dest.region) is null) {
return false;
}
}
}
return true;
}
bool hasFood(BasicAI@ ai) {
const ResourceClass@ cls = getResourceClass("Food");
for(uint i = 0, cnt = cls.types.length; i < cnt; ++i)
if(ai.planets[cls.types[i].id].idle.length != 0)
return true;
if(!ai.usesMotherships && gameTime > 3.0 * 60.0) {
auto@ farm = getBuildingType("Farm");
if(ai.empire.RemainingBudget >= farm.baseBuildCost)
return true;
}
return false;
}
bool canLevelPlanet(BasicAI@ ai) const {
if(ai.isMachineRace)
return true;
return hasWater(ai) && hasFood(ai);
}
Planet@ findViablePlanet(BasicAI@ ai) {
uint tryCount = 1;
if(ai.skillEconomy >= DIFF_Hard)
tryCount = resources.length;
else if(ai.skillEconomy >= DIFF_Medium)
tryCount = min(2, resources.length);
uint checkType = randomi(0, resources.length-1);
Empire@ owner = ai.empire;
auto@ reg = dest.region;
auto@ destTerr = reg !is null ? dest.region.getTerritory(owner) : null;
if(destTerr is null)
return null;
//Look through a few resources for idle planets of the resources we need
for(uint tries = 0; tries < tryCount; ++tries) {
uint resID = resources[checkType % resources.length];
PlanetList@ planets = ai.planets[resID];
const ResourceType@ type = getResource(resID);
uint cnt = planets.idle.length;
for(uint i = 0; i < cnt; ++i) {
Planet@ pl = planets.idle[i];
if(pl.owner !is owner || !pl.valid)
continue;
auto@ r = pl.region;
if(r is null || r.getTerritory(owner) !is destTerr)
continue;
uint plLevel = pl.level;
if(pl.level < type.level)
continue;
return pl;
}
checkType += 1;
if(cnt != 0)
break;
}
return null;
}
bool perform(BasicAI@ ai) {
if(source !is null) {
uint resLevel = getResource(foundRes).level;
if(source.owner !is ai.empire) {
foundRes = -1;
@source = null;
ai.removeIdle(this);
}
else if(!source.isPlanet || source.resourceLevel >= resLevel) {
//Attempt to export
if(source is dest) {
//When the homeworld moves, it can end up using its own resource - this looks a bit weird but works for this purpose
source.exportResource(0, null);
ai.markUsedGeneric(source, source);
@source = null;
return true;
}
else {
source.exportResource(0, dest);
source.wait();
if(!source.isPrimaryDestination(dest)) {
ai.markIdleGeneric(source);
@source = null;
foundRes = -1;
ai.removeIdle(this);
}
else {
ai.markUsedGeneric(source, dest);
@source = null;
return true;
}
}
}
else {
//See if any other planets are available instead
Planet@ other = findViablePlanet(ai);
if(other !is null) {
ai.markIdleGeneric(source);
@source = other;
foundRes = other.primaryResourceType;
ai.markUsedGeneric(source, source);
}
else if(source.isPlanet && source.population >= 1.0) {
//Request the planet be upgraded
ai.requestPlanetImprovement(cast<Planet>(source), resLevel);
}
}
}
else {
//Already found the resource and ordered the export
if(foundRes != -1)
return true;
if(nextType >= resources.length)
attemptExpand = true;
Empire@ owner = ai.empire;
Territory@ destTerr = null;
{
auto@ reg = dest.region;
if(reg !is null)
@destTerr = reg.getTerritory(owner);
if(destTerr is null)
return true;
}
Object@ farMatch;
Object@ best = null;
double bestScore = 0;
uint resID;
bool tryBuildWater = false, tryBuildFood = false;
int aiHasBaseNeeds = -1;
uint tryCount = 1;
if(ai.skillEconomy >= DIFF_Hard)
tryCount = resources.length;
else if(ai.skillEconomy >= DIFF_Medium)
tryCount = min(2, resources.length);
ai.focus = dest.position;
//Look through a few resources for idle planets of the resources we need
for(uint tries = 0; tries < tryCount; ++tries) {
resID = resources[nextType % resources.length];
if(resID == water.id) {
const auto@ comet = getArtifactType("Comet");
if(owner.EnergyStored >= comet.abilities[0].energyCost) {
auto@ artifact = ai.getArtifact(comet, dest.region);
if(artifact !is null) {
artifact.activateAbilityFor(owner, 0, dest);
dest.wait();
return true;
}
}
if(!ai.usesMotherships && ai.skillEconomy >= DIFF_Medium && age > 180.0 && ai.empire.RemainingBudget >= 300)
tryBuildWater = true;
}
PlanetList@ planets = ai.planets[resID];
const ResourceType@ type = getResource(resID);
uint cnt = planets.idle.length;
for(uint i = 0; i < cnt; ++i) {
Planet@ pl = planets.idle[i];
if(pl.owner !is owner || !pl.valid)
continue;
auto@ reg = pl.region;
if(reg is null)
continue;
if(reg.getTerritory(owner) !is destTerr) {
@farMatch = pl;
continue;
}
uint plLevel = pl.level;
if(type.level > 0 && plLevel == 0 && aiHasBaseNeeds == 0)
continue;
double score = 1.0;
if(plLevel >= type.level)
score = 2.0;
else if(pl.resourceLevel >= type.level)
score = 1.5;
if(score > bestScore) {
@best = pl;
bestScore = score;
}
}
//TODO: This could probably be sorted into resources as well
if(best is null) {
for(uint i = 0, cnt = ai.idleAsteroids.length; i < cnt; ++i) {
Asteroid@ roid = ai.idleAsteroids[i];
if(roid.primaryResourceType != resID)
continue;
auto@ reg = roid.region;
if(reg is null)
continue;
if(reg.getTerritory(owner) !is destTerr) {
@farMatch = roid;
continue;
}
@best = roid;
bestScore = 3.0;
break;
}
}
//Check that the AI has enough spare planets to bother aquiring the planet
// Only check for Medium or harder AI early in the game
// TODO: Skip check based on colonization capacity, rather than a time heuristic
if(type.level >= 1 && gameTime < 30.0 * 60.0 && ai.skillEconomy >= DIFF_Medium) {
if(aiHasBaseNeeds == -1)
aiHasBaseNeeds = canLevelPlanet(ai) ? 1 : 0;
}
else if(type.level == 0 && ai.skillEconomy >= DIFF_Medium && (best is null || cnt <= 2) && age > 180.0 && type.cls is getResourceClass("Food")) {
if(!ai.usesMotherships && ai.empire.RemainingBudget >= 300)
tryBuildFood = true;
}
else if(resID == water.id && best !is null && cnt > 2) {
tryBuildWater = false;
}
nextType += 1;
if(cnt != 0)
break;
}
if(best !is null) {
foundRes = resID;
@source = best;
ai.markUsedGeneric(source, source);
ai.addIdle(this);
//We found a useful planet.
//Hard AIs will go on to try to use their free money to build up food and water
if(ai.skillEconomy < DIFF_Hard)
return false;
}
else if(farMatch !is null) {
//TODO: Request the connection of two territories
}
if(tryBuildFood) {
ai.requestBuilding(dest, getBuildingType("Farm"));
if(source is null)
return true;
}
else if(tryBuildWater) {
ai.requestBuilding(dest, getBuildingType("Hydrogenator"));
if(source is null)
return true;
}
else if(attemptExpand && source is null && aiHasBaseNeeds != 0) {
//Request colonization of planet with a resource we need
ai.colonizeByResource(resources);
}
else if(aiHasBaseNeeds == 0 && !ai.isMachineRace) {
if(!hasWater(ai))
ai.addIdle(ai.colonizeByResource(ai.getResourceList(RT_Water, onlyExportable=true), execute=false));
if(!hasFood(ai))
ai.addIdle(ai.colonizeByResource(ai.getResourceList(RT_Food, onlyExportable=true), execute=false));
}
}
return false;
}
}
int64 buildBuildingHash(Planet@ pl, const BuildingType@ bldg) {
return (int64(ACT_Building) << ACT_BIT_OFFSET) | (int64(bldg.id) << 32) | int64(pl.id);
}
class BuildBuilding : Action {
Planet@ planet;
const BuildingType@ type;
int64 Hash;
uint tries = 0;
vec2i inProgress = vec2i(-1,-1);
bool force = false;
BuildBuilding(Planet@ pl, const BuildingType@ bldg, bool force = false) {
@planet = pl;
@type = bldg;
this.force = force;
Hash = buildBuildingHash(pl, bldg);
}
int64 get_hash() const {
return Hash;
}
ActionType get_actionType() const {
return ACT_Building;
}
string get_state() const {
return "Building " + type.name + " on " + planet.name;
}
BuildBuilding(BasicAI@ ai, SaveFile& msg) {
uint typeID = 0;
msg >> planet >> typeID >> inProgress;
@type = getBuildingType(typeID);
Hash = buildBuildingHash(planet, type);
if(msg >= SV_0130)
msg >> force;
}
void postLoad(BasicAI@ ai) {
}
void save(BasicAI@ ai, SaveFile& msg) {
msg << planet << type.id << inProgress;
msg << force;
}
bool perform(BasicAI@ ai) {
if(planet is null || !planet.valid || planet.owner !is ai.empire)
return true;
if(ai.usesMotherships)
return true;
ai.focus = planet.position;
if(inProgress.x >= 0) {
int at = planet.getBuildingAt(inProgress.x, inProgress.y);
if(at != int(type.id)) {
//error(type.name + " on " + planet.name + " expected at " + inProgress + " got " + at);
inProgress = vec2i(-1,-1);
}
else if(planet.getBuildingProgressAt(inProgress.x, inProgress.y) >= 0.999f) {
@planet = null;
return true;
}
else {
//Await completion
return false;
}
}
if(force) {
if(!ai.empire.canPay(type.baseBuildCost)) {
if(tries++ > 20)
return true;
return false;
}
}
else {
if(ai.empire.RemainingBudget < type.baseBuildCost)
return true;
}
//Try to plop it on a random tile
if(tries++ < 20) {
vec2i off = vec2i(type.getCenter());
bool validPos = true;
vec2i pos = vec2i(randomi(0,10), randomi(0,8));
for(int x = pos.x, endx = pos.x + type.size.width; x < endx; ++x) {
for(int y = pos.y, endy = pos.y + type.size.height; y < endy; ++y) {
if(x < 0 || y < 0) {
validPos = false;
break;
}
int bType = planet.getBuildingAt(x - off.x,y - off.y);
if(bType >= 0) {
auto@ b = getBuildingType(bType);
if(b !is null && !b.civilian) {
validPos = false;
break;
}
}
}
if(!validPos)
break;
}
if(validPos) {
planet.buildBuilding(type.id, pos);
planet.wait();
inProgress = pos;
}
return false;
}
else {
return true;
}
}
}
int64 improvePlanetHash(Planet@ pl, uint toLevel) {
return (int64(ACT_Improve) << ACT_BIT_OFFSET) | (int64(toLevel) << 48) | int64(pl.id);
}
class ImprovePlanet : Action {
Planet@ planet;
uint level = 1;
bool requested = false;
int64 Hash;
Action@[] requests;
int64[]@ reqHashes;
uint lastResState = 0xffffffff;
ImprovePlanet(Planet@ pl, uint toLevel) {
@planet = pl;
level = toLevel;
Hash = improvePlanetHash(pl, toLevel);
}
ImprovePlanet(BasicAI@ ai, SaveFile& msg) {
msg >> planet >> level;
msg >> requested >> Hash;
uint cnt = 0;
msg >> cnt;
@reqHashes = int64[](cnt);
for(uint i = 0; i < cnt; ++i)
msg >> reqHashes[i];
}
void postLoad(BasicAI@ ai) {
for(uint i = 0, cnt = reqHashes.length; i < cnt; ++i) {
Action@ act = ai.locateAction(reqHashes[i]);
if(act !is null)
requests.insertLast(act);
}
@reqHashes = null;
}
void save(BasicAI@ ai, SaveFile& msg) {
msg << planet << level;
msg << requested << Hash;
uint cnt = requests.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << requests[i].hash;
}
int64 get_hash() const {
return Hash;
}
ActionType get_actionType() const {
return ACT_Improve;
}
string get_state() const {
return "Improving " + planet.name + " to level " + level;
}
bool hasFoods(array<Resource>& avail, uint count) {
if(count == 0)
return true;
const ResourceClass@ cls = getResourceClass("Food");
uint levelCount = count;
for(uint i = 0, cnt = avail.length; i < cnt; ++i) {
auto@ type = avail[i].type;
if(type.cls is cls)
if(--count == 0)
return true;
}
return false;
}
bool hasRequirement(array<Resource>& avail, uint level, uint count) {
if(count == 0)
return true;
uint levelCount = count;
for(uint i = 0, cnt = avail.length; i < cnt; ++i) {
auto@ type = avail[i].type;
if(type.level == level)
if(--count == 0)
return true;
}
return false;
}
bool perform(BasicAI@ ai) {
//TODO: Doesn't this leak into the action map?
// Looks like it should be killing the actions in requests
// if it exits prematurely from here.
if(!planet.valid || planet.owner !is ai.empire)
return true;
if(ai.usesMotherships && planet.Population < double(planet.maxPopulation)) {
ai.requestPopulate(planet);
return false;
}
uint plLevel = planet.resourceLevel;
if(plLevel >= level)
return true;
ai.focus = planet.position;
if(requests.length > 0 && lastResState == planet.resourceModID) {
int index = randomi(0, int(requests.length)-1);
if(ai.performAction(requests[index]))
requests.length = 0;
}
else {
lastResState = planet.resourceModID;
//We only check against the needs for the next level, not our ultimate goal
uint goingTo = plLevel + 1;
requests.length = 0;
auto@ reg = planet.region;
auto@ destTerr = reg is null ? null : reg.getTerritory(ai.empire);
array<Resource> avail;
avail.syncFrom(planet.getAllResources());
//Remove all resources that aren't usable or won't be soon
for(int i = int(avail.length) - 1; i > 0; --i) {
auto@ res = avail[i];
if(!res.usable) {
auto@ src = res.origin;
auto@ srcReg = src.region;
if(src !is planet && (src.owner !is ai.empire || srcReg is null || srcReg.getTerritory(ai.empire) !is destTerr || (src.isPlanet && src.resourceLevel < res.type.level))) {
Planet@ source = cast<Planet>(src);
if(source !is null) {
source.exportResource(ai.empire, 0, null);
ai.markPlanetIdle(source);
}
avail.removeAt(i);
}
}
}
//Look for all the resources we still need, and queue up a series of requests that can be completed simultaneously
bool needWater = true;
if(!ai.isMachineRace) {
array<int>@ waterNeed = ai.getResourceList(RT_Water);
for(uint i = 0, cnt = avail.length; i < cnt; ++i) {
if(waterNeed.find(avail[i].type.id) > 0) {
needWater = false;
break;
}
}
}
if(goingTo > 2) {
uint need = 0;
switch(goingTo) {
case 3: need = 1; break;
case 4: need = 2; break;
case 5: need = 4; break;
}
if(!hasRequirement(avail, 2, need)) {
requests.insertLast(ai.requestImport(planet, ai.getResourceList(RT_LevelTwo, true), execute=false));
return false;
}
}
bool requestLowerResources = gameTime > 15.0 * 60.0;
if(goingTo > 1) {
uint need = 0;
switch(goingTo) {
case 2: need = 1; break;
case 3: need = ai.isFrugal ? 1 : 2; break;
case 4: need = ai.isFrugal ? 2 : 4; break;
case 5: need = ai.isFrugal ? 4 : 6; break;
}
if(!hasRequirement(avail, 1, need)) {
requests.insertLast(ai.requestImport(planet, ai.getResourceList(RT_LevelOne, true), execute=false));
if(!requestLowerResources)
return false;
}
}
if(!ai.isMachineRace) {
if(needWater)
requests.insertLast(ai.requestImport(planet, ai.getResourceList(RT_Water, true), execute=false));
if(!hasFoods(avail, goingTo))
requests.insertLast(ai.requestImport(planet, ai.getResourceList(RT_Food, true), execute=false));
}
}
return false;
}
}
int64 budgetHash() {
return int64(ACT_Budget) << ACT_BIT_OFFSET;
}
class GatherBudget : Action {
Planet@ hw;
GatherBudget() {
}
GatherBudget(BasicAI@ ai, SaveFile& msg) {
if(msg < SV_0024) {
int64 dummy = 0;
msg >> dummy;
}
if(msg >= SV_0114) {
msg >> hw;
}
}
void postLoad(BasicAI@ ai) {
}
void save(BasicAI@ ai, SaveFile& msg) {
msg << hw;
}
int64 get_hash() const {
return budgetHash();
}
ActionType get_actionType() const {
return ACT_Budget;
}
string get_state() const {
return "Increasing available budget";
}
Planet@ getHomeworld(BasicAI@ ai) {
Planet@ hw = ai.homeworld;
if(hw !is null && hw.owner is ai.empire && hw.valid)
return hw;
for(int t = 3; t >= 0; --t) {
AIResourceType type;
switch(t) {
case 3: type = RT_LevelThree; break;
case 2: type = RT_LevelTwo; break;
case 1: type = RT_LevelOne; break;
case 0: type = RT_Food; break;
}
auto@ resources = ai.getResourceList(type, onlyExportable = false);
uint base = randomi(0, 250);
for(uint r = 0, rcnt = resources.length; r < rcnt; ++r) {
auto@ planets = ai.planets[resources[(r+base) % rcnt]].idle;
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
auto@ pl = planets[i];
if(pl.valid && pl.owner is ai.empire)
return pl;
}
}
}
return null;
}
bool perform(BasicAI@ ai) {
if(hw is null)
@hw = getHomeworld(ai);
if(hw !is null) {
uint plLevel = hw.level;
if(plLevel < 5) {
ai.requestPlanetImprovement(hw, plLevel+1);
}
else {
ai.requestImport(hw, ai.getResourceList(RT_LevelThree, true));
}
}
else {
ai.colonizeByResource(ai.getResourceList(RT_LevelOne, onlyExportable = false));
}
return false;
}
}
enum OrbitalType {
OT_Shipyard = 0,
OT_TradeOutpost = 1,
OT_FlingBeacon = 2,
OT_Mainframe = 3,
OT_Gate,
};
int64 buildOrbitalHash(Region@ at, OrbitalType type) {
return (int64(ACT_BuildOrbital) << ACT_BIT_OFFSET) | (int64(type) << 32) | at.id;
}
class BuildOrbital : Action {
uint type;
Region@ sys;
Object@ builder;
int64 Hash;
BuildOrbital(Region& at, OrbitalType Type) {
type = Type;
@sys = at;
Hash = buildOrbitalHash(sys, Type);
}
BuildOrbital(BasicAI@ ai, SaveFile& msg) {
msg >> type;
msg >> sys;
msg >> builder;
Hash = buildOrbitalHash(sys, OrbitalType(type));
}
void postLoad(BasicAI@ ai) {
}
void save(BasicAI@ ai, SaveFile& msg) {
msg << type;
msg << sys;
msg << builder;
}
int64 get_hash() const {
return Hash;
}
ActionType get_actionType() const {
return ACT_BuildOrbital;
}
string get_state() const {
if(builder is null)
return "Planning orbital construction in " + sys.name;
else
return "Building Orbital in " + sys.name;
}
bool isTradeable(Region@ region, Empire@ emp, const Territory@ territory) {
if(territory is region.getTerritory(emp))
return true;
const SystemDesc@ system = getSystem(region);
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
Region@ other = getSystem(system.adjacent[i]).object;
if(territory is other.getTerritory(emp))
return true;
}
return false;
}
bool perform(BasicAI@ ai) {
if(builder !is null) {
//Look for the orbital
auto@ reg = ai.findSystem(sys);
if(reg is null)
return true;
if(type == OT_Gate) {
auto@ gate = ai.empire.getStargate(sys.position);
if(gate !is null && gate.region is sys)
return true;
}
else {
reg.scout(ai);
for(uint i = 0, cnt = reg.orbitals.length; i < cnt; ++i) {
auto@ orb = reg.orbitals[i];
if(orb.owner is ai.empire && ai.orbitals.find(orb) < 0) {
int orbID = -1;
switch(type) {
case OT_Shipyard:
orbID = getOrbitalModuleID("Shipyard");
break;
case OT_TradeOutpost:
orbID = getOrbitalModuleID("TradeOutpost");
break;
case OT_FlingBeacon:
orbID = getOrbitalModuleID("FlingCore");
break;
case OT_Mainframe:
orbID = getOrbitalModuleID("Mainframe");
break;
}
if(orbID < -1)
return true;
if(orb.hasModule(orbID)) {
ai.orbitals.insertLast(reg.orbitals[i]);
return true;
}
}
}
}
if(!builder.valid || builder.owner !is ai.empire || ai.isBuildIdle(builder))
@builder = null;
}
else {
for(uint i = 0, cnt = ai.factories.length; i < cnt; ++i) {
Object@ buildAt = ai.factories[i];
if(buildAt.owner is ai.empire && buildAt.hasConstruction && buildAt.canBuildOrbitals &&
ai.isBuildIdle(buildAt) && buildAt.laborIncome > 0.05)
{
auto@ reg = buildAt.region;
if(reg is null || !isTradeable(sys, ai.empire, reg.getTerritory(ai.empire)))
continue;
int orbID = -1;
const Design@ station;
switch(type) {
case OT_Shipyard:
orbID = getOrbitalModuleID("Shipyard");
break;
case OT_TradeOutpost:
orbID = getOrbitalModuleID("TradeOutpost");
break;
case OT_FlingBeacon:
orbID = getOrbitalModuleID("FlingCore");
break;
case OT_Mainframe:
orbID = getOrbitalModuleID("Mainframe");
break;
case OT_Gate:
@station = ai.empire.getDesign("Gate");
break;
}
if(orbID < 0 && station is null)
return true;
vec2d off = random2d(200.0, sys.radius - 200.0);
if(orbID >= 0)
buildAt.buildOrbital(orbID, sys.position + vec3d(off.x, 0.0, off.y));
else
buildAt.buildStation(station, sys.position + vec3d(off.x, 0.0, off.y));
@builder = buildAt;
break;
}
}
}
return false;
}
}
int64 manageFactoriesHash() {
return int64(ACT_ManageFactories) << ACT_BIT_OFFSET;
}
class ManageFactories : Action {
ManageFactories(BasicAI@ ai, SaveFile& file) {
}
ManageFactories() {
}
int64 get_hash() const {
return manageFactoriesHash();
}
ActionType get_actionType() const {
return ACT_ManageFactories;
}
string get_state() const {
return "Managing factories";
}
void save(BasicAI@, SaveFile& msg) {}
void postLoad(BasicAI@) {}
set_int checked;
AsteroidCache@ findAsteroid(BasicAI@ ai, PlanRegion@ region, bool recurse = true, bool original = true, int maxHops = -1) {
if(original)
checked.clear();
if(checked.contains(region.region.id))
return null;
checked.insert(region.region.id);
if(region.resourceAsteroids.length != 0)
return region.resourceAsteroids[randomi(0, region.resourceAsteroids.length-1)];
if(recurse && region.region.TradeMask & ai.empire.TradeMask.value != 0 && maxHops != 0) {
uint cnt = region.system.adjacent.length;
uint off = randomi(0, cnt-1);
for(uint i = 0; i < cnt; ++i) {
auto@ other = getSystem(region.system.adjacent[(i+off)%cnt]);
if(other !is null) {
PlanRegion@ otherRegion = ai.findSystem(other.object);
if(otherRegion !is null) {
AsteroidCache@ cache = findAsteroid(ai, otherRegion, recurse=false, original=false, maxHops=maxHops-1);
if(cache !is null)
return cache;
}
}
}
for(uint i = 0; i < cnt; ++i) {
auto@ other = getSystem(region.system.adjacent[(i+off)%cnt]);
if(other !is null) {
PlanRegion@ otherRegion = ai.findSystem(other.object);
if(otherRegion !is null) {
AsteroidCache@ cache = findAsteroid(ai, otherRegion, recurse=true, original=false, maxHops=maxHops-1);
if(cache !is null)
return cache;
}
}
}
}
return null;
}
bool perform(BasicAI@ ai) {
//Check for idle factories, and make them build nearby asteroids
if(ai.factories.length == 0)
return false;
Object@ fact = ai.factories[randomi(0, ai.factories.length-1)];
uint consCount = fact.constructionCount;
uint consType = fact.constructionType;
//Deal with mechanoid population construction
if(ai.isMachineRace && fact.isPlanet) {
bool buildPop = false;
if(fact.population < int(fact.maxPopulation + 1 + fact.owner.autoColonizeCount)) {
buildPop = true;
}
if(consCount != 0) {
if(consType == CT_Construction)
buildPop = false;
if(consType == CT_Flagship && ai.factories.length > 1 && int(fact.population) >= int(fact.maxPopulation))
buildPop = false;
}
if(buildPop) {
auto@ constr = getConstructionType("MechanoidPopulation");
if(constr !is null) {
fact.buildConstruction(constr.id);
if(consCount != 0)
fact.moveConstruction(fact.constructionID[0], -1);
consCount += 1;
if(constr.getLaborCost(fact) > fact.laborIncome * 60.0) {
ai.addIdle( ai.requestImport(cast<Planet>(fact), ai.getResourceList(RT_LaborZero, onlyExportable=true), execute=false) );
}
return false;
}
}
}
//Move asteroid constructibles to the end
if(consCount != 0) {
if(consCount > 1 && consType == CT_Asteroid)
fact.moveConstruction(fact.constructionID[0], -1);
return false;
}
//Find a nearby asteroid to build
if(fact.canBuildAsteroids) {
Region@ regObj = fact.region;
if(regObj is null)
return false;
int hops = randomi(0,ai.skillEconomy * 2);
auto@ region = ai.findSystem(regObj);
AsteroidCache@ roid = findAsteroid(ai, region, maxHops=hops);
if(roid is null || roid.asteroid.owner !is defaultEmpire || !roid.asteroid.valid)
return false;
roid.cache();
if(roid.resources.length == 0)
return false;
int resourceId = roid.resources[randomi(0, roid.resources.length-1)];
fact.buildAsteroid(roid.asteroid, resourceId);
return false;
}
return false;
}
};
int64 managePressureResourcesHash() {
return int64(ACT_ManagePressureResources) << ACT_BIT_OFFSET;
}
class ManagePressureResources : Action {
Object@ destination;
int destFree = 0;
float destPct = 1.f;
Object@ secondary;
int secondaryFree = 0;
const ResourceType@ asteroidLabor = getResource("AsteroidLabor");
const ResourceType@ morphics = getResource("AsteroidAffinity");
const ResourceType@ growth = getResource("AsteroidGrowth");
ManagePressureResources(BasicAI@ ai, SaveFile& file) {
file >> destination;
file >> destFree;
file >> destPct;
file >> secondary;
file >> secondaryFree;
}
ManagePressureResources() {
}
int64 get_hash() const {
return managePressureResourcesHash();
}
ActionType get_actionType() const {
return ACT_ManagePressureResources;
}
string get_state() const {
return "Managing pressure resources.";
}
void save(BasicAI@, SaveFile& file) {
file << destination;
file << destFree;
file << destPct;
file << secondary;
file << secondaryFree;
}
void postLoad(BasicAI@) {}
void checkDestination(Object@ dest) {
int pTotal = dest.pressureCap;
int pUsed = dest.totalPressure;
int free = max(pTotal - pUsed, 0);
float pct = float(pUsed) / float(pTotal);
if(free > destFree) {
@destination = dest;
destFree = free;
destPct = pct;
}
if(free > secondaryFree) {
auto@ res = getResource(dest.primaryResourceType);
if(res.totalPressure > 0 && res.tilePressure[TR_Labor] == 0) {
secondaryFree = free;
@secondary = dest;
}
}
}
bool checkResource(BasicAI@ ai, Object@ obj, Object@ curDest = null) {
const ResourceType@ res = getResource(obj.primaryResourceType);
if(res !is null) {
uint resCls = ai.classifyResource(res);
if(resCls == RT_PressureResource) {
if(destination !is null && destPct < 1.5f) {
if(!obj.primaryResourceExported) {
obj.exportResource(0, destination);
ai.markUsedGeneric(obj, destination);
return true;
}
else if(ai.skillEconomy >= DIFF_Medium) {
if(curDest is null)
@curDest = obj.nativeResourceDestination[0];
if(curDest !is null && curDest.isPlanet) {
if(destFree >= res.totalPressure - 0.001f && uint(curDest.totalPressure) > curDest.pressureCap) {
obj.exportResource(0, destination);
ai.markUsedGeneric(obj, destination);
return true;
}
}
}
}
}
else if(res is asteroidLabor) {
//Send this to one of our factories
if(ai.factories.length != 0 && !obj.primaryResourceExported) {
Object@ fact = ai.factories[randomi(0, ai.factories.length-1)];
if(fact.isPlanet) {
obj.exportResource(0, fact);
ai.markUsedGeneric(obj, fact);
return true;
}
}
}
else if(res is morphics) {
//Send this to a planet that can use pressure and has a native resource with non-labor pressure
if(secondary !is null) {
if(!obj.primaryResourceExported) {
obj.exportResource(0, secondary);
ai.markUsedGeneric(obj, secondary);
return true;
}
else if(secondaryFree >= 2) {
if(curDest is null)
@curDest = obj.nativeResourceDestination[0];
if(curDest !is null && curDest.isPlanet) {
if(uint(curDest.totalPressure) > curDest.pressureCap) {
obj.exportResource(0, secondary);
ai.markUsedGeneric(obj, secondary);
return true;
}
}
}
}
}
else if(res is growth) {
//Send this whereever
if(destination !is null && !obj.primaryResourceExported) {
obj.exportResource(0, destination);
ai.markUsedGeneric(obj, destination);
return true;
}
}
else if(ai.isMachineRace && (resCls == RT_Food || resCls == RT_Water) && obj.isPlanet && !obj.isContested) {
//Abandon food and water planets we captured or no longer need to have to keep the system.
Region@ reg = obj.region;
if(reg !is null && reg.getPlanetCount(ai.empire) >= 2) {
obj.abandon();
return true;
}
}
}
if(ai.needsStalks && obj.isPlanet && obj.resourceLevel >= 2 && obj.pressureCap < 1 && obj.totalPressure > 0)
ai.addIdle(ai.requestBuilding(cast<Planet>(obj), getBuildingType("Stalk")));
return false;
}
uint nextCheckRes = 0;
bool perform(BasicAI@ ai) {
//check our cached destinations
if(destination is null)
@destination = ai.homeworld;
if(destination !is null && (!destination.valid || destination.owner !is ai.empire))
@destination = null;
if(secondary !is null && (!secondary.valid || secondary.owner !is ai.empire))
@secondary = null;
if(destination !is null) {
int pTotal = destination.pressureCap;
int pUsed = destination.totalPressure;
destFree = max(pTotal - pUsed, 0);
destPct = float(pUsed) / float(pTotal);
}
if(secondary !is null) {
secondaryFree = secondary.pressureCap - secondary.totalPressure;
if(secondaryFree <= 0) {
@secondary = null;
secondaryFree = 0;
}
}
//Find a planet we have that has significant pressure cap free
PlanetList@ list;
for(uint n = 0; n < 30; ++n) {
@list = ai.planetsByResource[nextCheckRes++ % ai.planetsByResource.length];
if(list.idle.length == 0 && list.used.length == 0)
continue;
for(uint j = 0; j < 2; ++j) {
auto@ planets = j == 0 ? list.idle : list.used;
uint plCnt = planets.length;
if(plCnt == 0)
continue;
uint check = min(ai.skillEconomy * 2, plCnt);
for(uint i = 0, cnt = check, base = randomi(0,plCnt-1); i < cnt; ++i) {
Planet@ pl = planets[(i+base) % plCnt];
if(pl.valid && pl.owner is ai.empire)
checkDestination(pl);
}
}
break;
}
if(destination is null || destPct > 1.5f)
return false;
//Handle pressure-providing resources from asteroids
if(ai.idleAsteroids.length > 0) {
uint index = randomi(0, ai.idleAsteroids.length-1);
Asteroid@ roid = ai.idleAsteroids[index];
if(checkResource(ai, roid))
return false;
}
if(ai.usedAsteroids.length > 0) {
uint index = randomi(0, ai.usedAsteroids.length-1);
Asteroid@ roid = ai.usedAsteroids[index];
if(checkResource(ai, roid))
return false;
}
//Handle pressure-providing resources on planets
for(uint j = 0; j < 2; ++j) {
auto@ planets = j == 0 ? list.idle : list.used;
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
Planet@ pl = planets[i];
Object@ curDest;
if(j == 1)
@curDest = list.purpose[i];
if(checkResource(ai, pl, curDest))
return false;
}
}
return false;
}
};