704 lines
16 KiB
ActionScript 3
704 lines
16 KiB
ActionScript 3
int64 colonyHash(Planet@ target) {
|
|
return (int64(ACT_Colonize) << ACT_BIT_OFFSET) | int64(target.id);
|
|
}
|
|
|
|
class Colonize : Action {
|
|
bool requested = false;
|
|
int64 Hash;
|
|
Planet@ dest;
|
|
Object@ source;
|
|
float best = 0;
|
|
SysSearch search;
|
|
uint enroute = 0;
|
|
bool colonyAttempted = false;
|
|
|
|
Colonize(BasicAI@ ai, Planet@ target) {
|
|
@dest = target;
|
|
Hash = colonyHash(target);
|
|
}
|
|
|
|
Colonize(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> requested;
|
|
msg >> Hash;
|
|
msg >> source;
|
|
msg >> dest;
|
|
msg >> best;
|
|
search.load(msg);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
msg << requested;
|
|
msg << Hash;
|
|
msg << source;
|
|
msg << dest;
|
|
msg << best;
|
|
search.save(msg);
|
|
|
|
if(source !is null)
|
|
enroute = 1;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return Hash;
|
|
}
|
|
|
|
void reset(BasicAI@ ai) {
|
|
if(source !is null && source.isShip)
|
|
ai.freeFleet(cast<Ship>(source), FT_Mothership);
|
|
@source = null;
|
|
requested = false;
|
|
best = 0;
|
|
enroute = 0;
|
|
search.reset();
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Colonize;
|
|
}
|
|
|
|
string get_state() const {
|
|
if(dest is null)
|
|
return "Nothing";
|
|
else if(source is null)
|
|
return "Colonizing " + dest.name;
|
|
else
|
|
return "Colonizing " + dest.name + " from " + source.name;
|
|
}
|
|
|
|
Planet@ findSourcePlanet(BasicAI@ ai) {
|
|
const uint tries = 8;
|
|
for(uint try = 0; try < tries; ++try) {
|
|
PlanRegion@ reg = search.next(ai.ourSystems);
|
|
|
|
if(reg is null) {
|
|
//The search is over, dispatch colony ships if possible
|
|
if(source !is null) {
|
|
return cast<Planet>(source);
|
|
}
|
|
else {
|
|
reset(ai);
|
|
}
|
|
break;
|
|
}
|
|
else {
|
|
for(uint i = 0, cnt = reg.planets.length; i < cnt; ++i) {
|
|
Planet@ pl = reg.planets[i];
|
|
if(pl is null || pl.owner !is ai.empire)
|
|
continue;
|
|
|
|
double pop = pl.population;
|
|
if(pop < 1.5 || pl.isSendingColonyShips || pl.quarantined)
|
|
continue;
|
|
|
|
double maxPop = double(pl.maxPopulation);
|
|
if(pop > maxPop * 0.5) {
|
|
//Weight primarily by time (square root of distance due to newtonian motion)
|
|
double weight = 1000.0 / pow(pl.position.distanceToSQ(dest.position), 0.25);
|
|
|
|
//Strongly prefer planets near max population
|
|
if(pop >= maxPop - 1.0)
|
|
weight *= 8.0;
|
|
//Prefer full planets
|
|
weight *= pop / maxPop;
|
|
|
|
if(weight > best) {
|
|
@source = pl;
|
|
best = float(weight);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void sendColonizers(BasicAI@ ai, bool allowAuto = true) {
|
|
if(ai.usesMotherships) {
|
|
//Find an idle mothership and bring it over
|
|
auto@ mother = ai.getAvailableFleet(FT_Mothership, build=false);
|
|
if(mother !is null && !mother.hasOrders) {
|
|
auto ablID = mother.findAbilityOfType(getAbilityID("MothershipColonize"));
|
|
double range = mother.getAbilityRange(ablID, dest) * 0.99;
|
|
moveToFastest(mother, dest.position + (mother.position - dest.position).normalize(range), ai.ftl);
|
|
mother.addAbilityOrder(ablID, dest, range, append=true);
|
|
@source = mother;
|
|
ai.addIdle(this);
|
|
}
|
|
else if(mother !is null) {
|
|
ai.freeFleet(mother, FT_Mothership);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Easy AIs use auto-colonize
|
|
if(ai.skillEconomy < DIFF_Medium || allowAuto || ai.isMachineRace) {
|
|
ai.empire.autoColonize(dest);
|
|
}
|
|
else {
|
|
//Multiple colony orders may try to request colonization from the same planet
|
|
if(source !is null && source.isSendingColonyShips) {
|
|
@source = null;
|
|
best = 0;
|
|
}
|
|
|
|
findSourcePlanet(ai);
|
|
if(source !is null) {
|
|
ai.focus = source.position;
|
|
source.colonize(dest, 1.0);
|
|
requested = true;
|
|
enroute += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
//Keep trying until the planet can't be colonized
|
|
if(dest is null || !dest.valid) {
|
|
reset(ai);
|
|
return true;
|
|
}
|
|
|
|
Empire@ owner = dest.visibleOwnerToEmp(ai.empire);
|
|
if(owner is null) {
|
|
reset(ai);
|
|
@dest = null;
|
|
return true;
|
|
}
|
|
else if(owner.valid && owner !is ai.empire) {
|
|
reset(ai);
|
|
@dest = null;
|
|
return true;
|
|
}
|
|
|
|
if(owner.valid && dest.Population >= 1.0) {
|
|
ai.addPlanet(dest);
|
|
ai.markAsColony(dest.region).scout(ai);
|
|
@dest = null;
|
|
reset(ai);
|
|
return true;
|
|
}
|
|
|
|
ai.focus = dest.position;
|
|
|
|
ai.addIdle(this);
|
|
|
|
if(requested) {
|
|
if(source is null || !source.valid || source.owner !is ai.empire || source.level < 1 || (dest.IncomingPop + dest.Population) >= 1.0)
|
|
reset(ai);
|
|
else if(source !is null && source.isShip && !source.hasOrders)
|
|
reset(ai);
|
|
}
|
|
else if(!dest.isEmpireColonizing(ai.empire)) {
|
|
sendColonizers(ai, allowAuto=true);
|
|
}
|
|
else if(enroute < 2 && ai.skillEconomy >= DIFF_Medium) {
|
|
if(dest.IncomingPop + dest.Population > 0.99)
|
|
colonyAttempted = true;
|
|
|
|
//We may encounter some form of resistance during colonization
|
|
// If so, brute force by sending ships from a second colony
|
|
if(colonyAttempted && dest.IncomingPop + dest.Population < 1.0) {
|
|
PlanRegion@ pr = ai.getPlanRegion(dest.region);
|
|
if(pr !is null)
|
|
sendColonizers(ai);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
int64 expandHash() {
|
|
return (int64(ACT_Expand) << ACT_BIT_OFFSET);
|
|
}
|
|
|
|
class Expand : Action {
|
|
SysSearch search;
|
|
Planet@ target;
|
|
double started = 0.0;
|
|
|
|
Expand() {
|
|
}
|
|
|
|
Expand(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> target;
|
|
msg >> started;
|
|
search.load(msg);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
msg << target;
|
|
msg << started;
|
|
search.save(msg);
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return expandHash();
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Expand;
|
|
}
|
|
|
|
string get_state() const {
|
|
if(target is null)
|
|
return "Expanding";
|
|
else
|
|
return "Expanding to " + target.name;
|
|
}
|
|
|
|
bool expandable(BasicAI@ ai, Region@ region) {
|
|
bool tradeable = false, bridge = false;
|
|
uint mask = ai.empire.mask;
|
|
const SystemDesc@ system = getSystem(region);
|
|
Territory@ prevTerr;
|
|
|
|
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
|
|
Region@ other = getSystem(system.adjacent[i]).object;
|
|
auto@ terr = other.getTerritory(ai.empire);
|
|
|
|
if(other.TradeMask & mask != 0) {
|
|
tradeable = true;
|
|
if(prevTerr is null)
|
|
@prevTerr = terr;
|
|
else
|
|
bridge = true;
|
|
}
|
|
else {
|
|
bridge = true;
|
|
}
|
|
}
|
|
|
|
return tradeable && bridge;
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
if(target !is null) {
|
|
if(!target.valid)
|
|
@target = null;
|
|
else if(target.owner is ai.empire)
|
|
return true;
|
|
else if(target.visibleOwnerToEmp(ai.empire).valid)
|
|
@target = null;
|
|
else if(gameTime - started > 300.0)
|
|
@target = null;
|
|
}
|
|
|
|
PlanRegion@ reg = search.random(ai.exploredSystems);
|
|
if(reg is null) {
|
|
ai.requestExploration();
|
|
return false;
|
|
}
|
|
|
|
if(!expandable(ai, reg.region))
|
|
return false;
|
|
|
|
Planet@ bestExp;
|
|
double expScore = 0;
|
|
|
|
for(uint i = 0, cnt = reg.planets.length; i < cnt; ++i) {
|
|
Planet@ pl = reg.planets[i];
|
|
auto@ owner = pl.visibleOwnerToEmp(ai.empire);
|
|
if(owner !is null && !owner.valid && !pl.quarantined) {
|
|
double score = 1.0;
|
|
|
|
const ResourceType@ res = getResource(pl.primaryResourceType);
|
|
if(res is null) {
|
|
score /= 1000.0;
|
|
}
|
|
else {
|
|
uint resCls = ai.classifyResource(res);
|
|
bool isFoodWater = resCls == RT_Food || resCls == RT_Water;
|
|
if(isFoodWater) {
|
|
if(ai.isMachineRace)
|
|
score /= 10.0;
|
|
else
|
|
score *= 10.0;
|
|
}
|
|
score /= double(res.level+1);
|
|
score *= double(res.rarity)+1.0;
|
|
}
|
|
|
|
if(score > expScore) {
|
|
expScore = score;
|
|
@bestExp = pl;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bestExp !is null) {
|
|
@target = bestExp;
|
|
ai.requestColony(bestExp);
|
|
started = gameTime;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
int64 colonyByResHash(array<int>@ res) {
|
|
int64 Hash = (int64(ACT_ColonizeRes) << ACT_BIT_OFFSET) | int64(res.length);
|
|
for(uint i = 0, cnt = res.length; i < cnt; ++i)
|
|
Hash ^= int64(res[i]) << i;
|
|
return Hash;
|
|
}
|
|
|
|
class ColonizeByResource : Action {
|
|
int64 Hash;
|
|
Planet@ target;
|
|
uint nextOurIndex = 0, nextOtherIndex = 0;
|
|
ObjectReceiver@ dest;
|
|
int64 reqHash = 0, waitHash = 0;
|
|
array<int>@ resources;
|
|
|
|
PlanRegion@ expansion;
|
|
Action@ waitingOn;
|
|
|
|
ColonizeByResource(array<int>@ Resources, ObjectReceiver@ requester) {
|
|
@dest = requester;
|
|
@resources = Resources;
|
|
|
|
Hash = colonyByResHash(Resources);
|
|
}
|
|
|
|
ColonizeByResource(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> Hash;
|
|
msg >> target;
|
|
msg >> nextOurIndex;
|
|
msg >> nextOtherIndex;
|
|
msg >> reqHash;
|
|
|
|
uint cnt = 0;
|
|
msg >> cnt;
|
|
@resources = array<int>(cnt);
|
|
for(uint i = 0; i < cnt; ++i)
|
|
resources[i] = msg.readIdentifier(SI_Resource);
|
|
|
|
Region@ reg;
|
|
msg >> reg;
|
|
if(reg !is null)
|
|
@expansion = ai.getPlanRegion(reg);
|
|
msg >> waitHash;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
if(reqHash != 0)
|
|
@dest = cast<ObjectReceiver>(ai.locateAction(reqHash));
|
|
if(waitHash != 0)
|
|
@waitingOn = ai.locateAction(waitHash);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
msg << Hash;
|
|
msg << target;
|
|
msg << nextOurIndex;
|
|
msg << nextOtherIndex;
|
|
|
|
if(dest !is null) {
|
|
msg << cast<Action>(dest).hash;
|
|
}
|
|
else {
|
|
int64 tmp = 0;
|
|
msg << tmp;
|
|
}
|
|
|
|
uint cnt = resources.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg.writeIdentifier(SI_Resource, resources[i]);
|
|
|
|
msg << (expansion is null ? null : expansion.region);
|
|
msg << (waitingOn !is null ? waitingOn.hash : int64(0));
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return Hash;
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_ColonizeRes;
|
|
}
|
|
|
|
string get_state() const {
|
|
if(target is null)
|
|
return "Looking for source of "+getResource(resources[0]).name+" (+" + (resources.length-1) + " others) resources";
|
|
else
|
|
return "Awaiting ownership of " + target.name;
|
|
}
|
|
|
|
void reset() {
|
|
nextOtherIndex = 0;
|
|
nextOurIndex = 0;
|
|
@target = null;
|
|
}
|
|
|
|
//TODO: Determine trade access based on territories instead
|
|
bool isTradeable(BasicAI@ ai, Region@ region) {
|
|
uint mask = ai.empire.mask;
|
|
if(region.TradeMask & mask != 0)
|
|
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(other.TradeMask & mask != 0)
|
|
return true;
|
|
}
|
|
|
|
if(ai.usesMotherships && ai.ourSystems.length == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
PlanRegion@ getBridgeSystem(BasicAI@ ai, PlanRegion@ to) {
|
|
uint mask = ai.empire.mask;
|
|
|
|
const SystemDesc@ system = to.system;
|
|
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
|
|
Region@ other = getSystem(system.adjacent[i]).object;
|
|
if(isTradeable(ai, other))
|
|
return ai.getPlanRegion(other);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool searchForPlanets(BasicAI@ ai) {
|
|
array<PlanRegion@>@ regions;
|
|
uint index = 0;
|
|
if(nextOurIndex < ai.ourSystems.length) {
|
|
@regions = ai.ourSystems;
|
|
index = nextOurIndex++;
|
|
nextOtherIndex = 0;
|
|
}
|
|
else if(nextOtherIndex < ai.exploredSystems.length) {
|
|
@regions = ai.exploredSystems;
|
|
index = nextOtherIndex++;
|
|
}
|
|
else {
|
|
nextOurIndex = 0;
|
|
index = 0;
|
|
}
|
|
|
|
if(regions !is null) {
|
|
PlanRegion@ reg = regions[index];
|
|
Planet@ wanted;
|
|
for(uint i = 0, cnt = reg.planets.length; i < cnt; ++i) {
|
|
ai.focus = reg.region.position;
|
|
|
|
Planet@ pl = reg.planets[i];
|
|
if(!pl.valid || pl.visibleOwnerToEmp(ai.empire).valid || pl.isEmpireColonizing(ai.empire) || pl.quarantined)
|
|
continue;
|
|
|
|
int native = reg.planetResources[i];
|
|
if(resources.find(native) < 0)
|
|
continue;
|
|
|
|
@wanted = pl;
|
|
break;
|
|
}
|
|
|
|
if(wanted !is null) {
|
|
if(isTradeable(ai, reg.region)) {
|
|
@target = wanted;
|
|
@waitingOn = ai.requestColony(wanted);
|
|
return true;
|
|
}
|
|
else {
|
|
//Choose closer expansions that we can expand to
|
|
PlanRegion@ bridge = getBridgeSystem(ai, reg);
|
|
if(bridge !is null && (expansion is null || ai.homeworld is null || bridge.center.distanceToSQ(ai.homeworld.position) < expansion.center.distanceToSQ(ai.homeworld.position))) {
|
|
for(uint i = 0, cnt = bridge.planets.length; i < cnt; ++i) {
|
|
auto@ pl = bridge.planets[i];
|
|
if(!pl.valid)
|
|
continue;
|
|
auto@ owner = pl.visibleOwnerToEmp(ai.empire);
|
|
if(owner !is null && !owner.valid) {
|
|
@expansion = bridge;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@expansion = bridge;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else if(expansion !is null) {
|
|
Planet@ bestExp;
|
|
double expScore = 0;
|
|
|
|
for(uint i = 0, cnt = expansion.planets.length; i < cnt; ++i) {
|
|
Planet@ pl = expansion.planets[i];
|
|
if(!pl.visibleOwnerToEmp(ai.empire).valid) {
|
|
double score = 1.0;
|
|
|
|
const ResourceType@ res = getResource(pl.primaryResourceType);
|
|
if(res is null) {
|
|
score /= 1000.0;
|
|
}
|
|
else {
|
|
uint resCls = ai.classifyResource(res);
|
|
bool isFoodWater = resCls == RT_Food || resCls == RT_Water;
|
|
if(isFoodWater) {
|
|
if(ai.isMachineRace)
|
|
score /= 10.0;
|
|
else
|
|
score *= 10.0;
|
|
}
|
|
score /= double(res.level+1);
|
|
score *= double(res.rarity)+1.0;
|
|
}
|
|
|
|
if(score > expScore) {
|
|
expScore = score;
|
|
@bestExp = pl;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bestExp !is null) {
|
|
@waitingOn = ai.requestColony(bestExp);
|
|
@expansion = null;
|
|
}
|
|
|
|
if(waitingOn is null) {
|
|
@waitingOn = ai.requestOrbital(expansion.region, OT_TradeOutpost);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else {
|
|
//We can't find the resource we need anywhere, explore some more
|
|
ai.requestExploration();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
if(waitingOn !is null) {
|
|
if(ai.performAction(waitingOn))
|
|
@waitingOn = null;
|
|
return false;
|
|
}
|
|
|
|
if(target !is null) {
|
|
if(target.owner is ai.empire) {
|
|
if(dest is null || dest.giveObject(ai, target))
|
|
return true;
|
|
else
|
|
reset();
|
|
}
|
|
else if(target.owner.valid) {
|
|
reset();
|
|
}
|
|
}
|
|
else {
|
|
const uint tries = 8;
|
|
for(uint i = 0; i < tries; ++i)
|
|
if(searchForPlanets(ai))
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
int64 populateHash(Planet@ target) {
|
|
return (int64(ACT_Populate) << ACT_BIT_OFFSET) | int64(target.id);
|
|
}
|
|
|
|
class Populate : Action {
|
|
int64 Hash;
|
|
Planet@ dest;
|
|
Object@ source;
|
|
|
|
Populate(BasicAI@ ai, Planet@ target) {
|
|
@dest = target;
|
|
Hash = populateHash(target);
|
|
}
|
|
|
|
Populate(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> source;
|
|
msg >> dest;
|
|
Hash = populateHash(dest);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
msg << source;
|
|
msg << dest;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return Hash;
|
|
}
|
|
|
|
void reset(BasicAI@ ai) {
|
|
if(source !is null && source.isShip)
|
|
ai.freeFleet(cast<Ship>(source), FT_Mothership);
|
|
@source = null;
|
|
ai.removeIdle(this);
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Populate;
|
|
}
|
|
|
|
string get_state() const {
|
|
if(dest is null)
|
|
return "Nothing";
|
|
else if(source is null)
|
|
return "Populating " + dest.name;
|
|
else
|
|
return "Populating " + dest.name + " from " + source.name;
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
//Keep trying until the planet can't be colonized
|
|
if(dest is null || !dest.valid) {
|
|
reset(ai);
|
|
return true;
|
|
}
|
|
|
|
if(dest.Population >= dest.maxPopulation - 0.001) {
|
|
reset(ai);
|
|
return true;
|
|
}
|
|
|
|
if(!ai.usesMotherships)
|
|
return true;
|
|
|
|
if(source is null) {
|
|
auto@ mother = ai.getAvailableFleet(FT_Mothership, build=false);
|
|
if(mother !is null && !mother.hasOrders) {
|
|
auto ablID = mother.findAbilityOfType(getAbilityID("MothershipColonize"));
|
|
double range = mother.getAbilityRange(ablID, dest) * 0.99;
|
|
moveToFastest(mother, dest.position + (mother.position - dest.position).normalize(range), ai.ftl);
|
|
mother.addAbilityOrder(ablID, dest, range, append=true);
|
|
@source = mother;
|
|
ai.addIdle(this);
|
|
}
|
|
else if(mother !is null) {
|
|
ai.freeFleet(mother, FT_Mothership);
|
|
}
|
|
}
|
|
else if(!source.valid || source.owner !is ai.empire || !source.hasOrders) {
|
|
@source = null;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|