1154 lines
35 KiB
ActionScript
1154 lines
35 KiB
ActionScript
import resources;
|
|
import research;
|
|
import influence_global;
|
|
import buildings;
|
|
import void declareWar(Empire@ from, Empire& to) from "warandpeace";
|
|
|
|
string[] planetNames = {
|
|
"Nist",
|
|
"Tullio",
|
|
"Centris",
|
|
"Paquil",
|
|
"Standev"
|
|
};
|
|
|
|
int64 expendHash() {
|
|
return int64(ACT_Expend) << ACT_BIT_OFFSET;
|
|
}
|
|
|
|
bool isTargetOf(BasicAI@ ai, const InfluenceVoteStub@ vote, Empire@ emp) {
|
|
if(vote.targets is null)
|
|
return false;
|
|
|
|
for(uint i = 0, cnt = vote.targets.targets.length; i < cnt; ++i) {
|
|
auto@ targ = vote.targets.targets[i];
|
|
switch(targ.type) {
|
|
case TT_Empire:
|
|
if(targ.emp is emp) {
|
|
return true;
|
|
}
|
|
break;
|
|
case TT_Object:
|
|
{
|
|
if(vote.type is getInfluenceVoteType("AnnexSystem")) {
|
|
auto@ reg = ai.getPlanRegion(targ.obj);
|
|
if(reg !is null)
|
|
if(reg.planetMask & emp.mask != 0)
|
|
return true;
|
|
}
|
|
else {
|
|
if(targ.obj !is null && targ.obj.owner is emp)
|
|
return true;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class ExpendResources : Action {
|
|
bool idleColony = true;
|
|
Action@ queued = null;
|
|
Action@ expandQueue = null;
|
|
int64 qhash = 0, eqhash = 0;
|
|
uint nextStep = randomi(0,100);
|
|
|
|
ExpendResources() {
|
|
}
|
|
|
|
ExpendResources(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> idleColony;
|
|
msg >> qhash;
|
|
if(msg >= SV_0019)
|
|
msg >> eqhash;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
if(qhash != 0)
|
|
@queued = ai.locateAction(qhash);
|
|
if(eqhash != 0)
|
|
@expandQueue = ai.locateAction(eqhash);
|
|
@expandQueue = null;
|
|
//TODO: Actually don't save/load this
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
msg << idleColony;
|
|
|
|
if(queued !is null)
|
|
qhash = queued.hash;
|
|
else
|
|
qhash = 0;
|
|
msg << qhash;
|
|
|
|
if(expandQueue !is null)
|
|
eqhash = expandQueue.hash;
|
|
else
|
|
eqhash = 0;
|
|
msg << eqhash;
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return expendHash();
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Expend;
|
|
}
|
|
|
|
string get_state() const {
|
|
return "Spending spare resources";
|
|
}
|
|
|
|
uint totalSupport = 0;
|
|
const InfluenceCardType@ cardNamePlanet, cardEnhance, cardInvestigate, cardSpy, cardLeverage, cardAnnex, cardRider,
|
|
cardProtect, cardUprising;
|
|
const InfluenceVoteType@ annexVote, annexSysVote, investigate;
|
|
|
|
array<const InfluenceCardType@> sites, supports, hardSupport;
|
|
array<const InfluenceVoteType@> siteVotes;
|
|
|
|
void fillCardTypes() {
|
|
@cardNamePlanet = getInfluenceCardType("NamePlanet");
|
|
@cardEnhance = getInfluenceCardType("Enhance");
|
|
@cardInvestigate = getInfluenceCardType("Investigate");
|
|
@cardSpy = getInfluenceCardType("Spy");
|
|
@cardLeverage = getInfluenceCardType("Leverage");
|
|
@cardAnnex = getInfluenceCardType("AnnexPlanet");
|
|
@cardRider = getInfluenceCardType("Rider");
|
|
@cardProtect = getInfluenceCardType("ProtectSystem");
|
|
@cardUprising = getInfluenceCardType("Uprising");
|
|
|
|
supports.insertLast(getInfluenceCardType("Negotiate"));
|
|
supports.insertLast(getInfluenceCardType("EnergyClash"));
|
|
supports.insertLast(getInfluenceCardType("AncientIntuition"));
|
|
supports.insertLast(getInfluenceCardType("Intelligence"));
|
|
|
|
hardSupport.insertLast(getInfluenceCardType("Promises"));
|
|
hardSupport.insertLast(getInfluenceCardType("Assurances"));
|
|
hardSupport.insertLast(getInfluenceCardType("Rhetoric"));
|
|
|
|
sites.insertLast(getInfluenceCardType("UnitedResearch"));
|
|
sites.insertLast(getInfluenceCardType("SenateBuilding"));
|
|
sites.insertLast(getInfluenceCardType("GalaxyMall"));
|
|
|
|
siteVotes.insertLast(getInfluenceVoteType("UnitedResearch"));
|
|
siteVotes.insertLast(getInfluenceVoteType("SenateBuilding"));
|
|
|
|
@annexVote = getInfluenceVoteType("AnnexPlanet");
|
|
@annexSysVote = getInfluenceVoteType("AnnexSystem");
|
|
@investigate = getInfluenceVoteType("Investigate");
|
|
}
|
|
|
|
bool opposeVote(BasicAI@ ai, const InfluenceVoteStub@ vote, array<InfluenceCard>& cards, bool useHard = false) {
|
|
InfluenceCard@ fallback;
|
|
|
|
uint off = randomi(0,cards.length-1);
|
|
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
|
|
auto@ card = cards[(i+off) % cnt];
|
|
auto@ type = card.type;
|
|
if(supports.find(type) >= 0) {
|
|
Targets targs = card.targets;
|
|
targs[0].filled = true;
|
|
targs[0].side = false;
|
|
playInfluenceCard_server(ai.empire, card.id, targs, vote.id);
|
|
return true;
|
|
}
|
|
else if(type is cardLeverage) {
|
|
//Will probably usually fail, but we hate the server
|
|
Targets targs = card.targets;
|
|
targs[0].filled = true;
|
|
targs[0].side = false;
|
|
playInfluenceCard_server(ai.empire, card.id, targs, vote.id);
|
|
return true;
|
|
}
|
|
else if(fallback is null && useHard && hardSupport.find(type) >= 0) {
|
|
@fallback = card;
|
|
}
|
|
}
|
|
|
|
if(fallback !is null) {
|
|
Targets targs = fallback.targets;
|
|
targs[0].filled = true;
|
|
targs[0].side = false;
|
|
playInfluenceCard_server(ai.empire, fallback.id, targs, vote.id);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool supportVote(BasicAI@ ai, const InfluenceVoteStub@ vote, array<InfluenceCard>& cards, bool useHard = false) {
|
|
InfluenceCard@ fallback;
|
|
int influence = ai.empire.Influence;
|
|
|
|
uint off = randomi(0,cards.length-1);
|
|
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
|
|
auto@ card = cards[(i+off) % cnt];
|
|
|
|
auto@ type = card.type;
|
|
if(supports.find(type) >= 0) {
|
|
Targets targs = card.targets;
|
|
targs[0].filled = true;
|
|
targs[0].side = true;
|
|
if(card.getPlayCost(null, targs) > influence)
|
|
continue;
|
|
playInfluenceCard_server(ai.empire, card.id, targs, vote.id);
|
|
return true;
|
|
}
|
|
else if(type is cardLeverage) {
|
|
//Will probably usually fail, but we hate the server
|
|
Targets targs = card.targets;
|
|
targs[0].filled = true;
|
|
targs[0].side = true;
|
|
if(card.getPlayCost(null, targs) > influence)
|
|
continue;
|
|
playInfluenceCard_server(ai.empire, card.id, targs, vote.id);
|
|
return true;
|
|
}
|
|
else if(fallback is null && useHard && hardSupport.find(type) >= 0) {
|
|
@fallback = card;
|
|
}
|
|
}
|
|
|
|
if(fallback !is null) {
|
|
Targets targs = fallback.targets;
|
|
targs[0].filled = true;
|
|
targs[0].side = true;
|
|
playInfluenceCard_server(ai.empire, fallback.id, targs, vote.id);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool playRider(BasicAI@ ai, const InfluenceVoteStub@ vote, array<InfluenceCard>& cards) {
|
|
uint off = randomi(0,cards.length-1);
|
|
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
|
|
auto@ card = cards[(i+off) % cnt];
|
|
auto@ type = card.type;
|
|
if(type is cardRider) {
|
|
Targets targs = card.targets;
|
|
playInfluenceCard_server(ai.empire, card.id, targs, vote.id);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool playRandomAction(BasicAI@ ai, array<InfluenceCard>& cards) {
|
|
if(cards.length == 0)
|
|
return false;
|
|
int influence = ai.empire.Influence;
|
|
auto@ card = cards[randomi(0,cards.length-1)];
|
|
for(uint i = 0; (card.type.cls == ICC_Support || card.getPlayCost(null, null) > influence) && i < 5; ++i)
|
|
@card = cards[randomi(0,cards.length-1)];
|
|
Targets targs = card.targets;
|
|
|
|
if(card.type is cardNamePlanet) {
|
|
if(ai.usesMotherships)
|
|
return false;
|
|
Planet@ pl = ai.homeworld;
|
|
if(pl is null || pl.owner !is ai.empire || pl.named)
|
|
if(ai.factories.length > 0)
|
|
@pl = cast<Planet>(ai.factories[randomi(0, ai.factories.length-1)]);
|
|
else
|
|
@pl = null;
|
|
|
|
if(pl !is null && pl.owner is ai.empire && !pl.named) {
|
|
auto@ targPlanet = targs[0];
|
|
targPlanet.filled = true;
|
|
@targPlanet.obj = @pl;
|
|
|
|
auto@ targName = targs[1];
|
|
targName.filled = true;
|
|
if(pl is ai.homeworld)
|
|
targName.str = ai.empire.name + " Prime";
|
|
else
|
|
targName.str = planetNames[randomi(0,planetNames.length-1)];
|
|
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
return true;
|
|
}
|
|
}
|
|
else if(card.type is cardAnnex) {
|
|
SysSearch search;
|
|
auto@ sys = search.random(ai.exploredSystems);
|
|
if(sys !is null) {
|
|
Planet@ pl;
|
|
uint ownerMask = 0xffffffff & ~(ai.empire.mask | ai.allyMask | 1);
|
|
if(sys.planetMask & ownerMask != 0) {
|
|
for(uint i = 0, cnt = sys.planets.length; i < cnt; ++i) {
|
|
Planet@ p = sys.planets[i];
|
|
if(p.owner.mask & ownerMask != 0) {
|
|
@pl = p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(pl !is null) {
|
|
auto@ targPlanet = targs[0];
|
|
targPlanet.filled = true;
|
|
@targPlanet.obj = pl;
|
|
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if(sites.find(card.type) >= 0) {
|
|
if(totalSupport < 3)
|
|
return false;
|
|
|
|
auto@ targPlanet = targs[0];
|
|
targPlanet.filled = true;
|
|
@targPlanet.obj = @ai.homeworld;
|
|
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
return true;
|
|
}
|
|
else if(card.type is cardInvestigate) {
|
|
if(totalSupport < 2)
|
|
return false;
|
|
|
|
auto@ targEmp = targs[0];
|
|
if(!targEmp.filled || targEmp is null) {
|
|
@targEmp.emp = getEmpire(randomi(0,getEmpireCount()-1));
|
|
targEmp.filled = true;
|
|
}
|
|
|
|
if(targEmp.emp !is ai.empire && targEmp.emp.valid && !ai.empire.isHostile(targEmp.emp) && targEmp.emp.mask & ai.allyMask == 0) {
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
return true;
|
|
}
|
|
}
|
|
else if(card.type is cardEnhance && ai.skillDiplo >= DIFF_Hard) {
|
|
auto@ targCard = targs[0];
|
|
targCard.filled = true;
|
|
targCard.id = cards[randomi(0,cards.length-1)].id;
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
return true;
|
|
}
|
|
else if(card.type is cardSpy && ai.skillDiplo >= DIFF_Medium) {
|
|
auto@ targEmp = targs[0];
|
|
if(!targEmp.filled && ai.enemies.length > 0) {
|
|
targEmp.filled = true;
|
|
@targEmp.emp = ai.enemies[randomi(0,ai.enemies.length-1)];
|
|
}
|
|
|
|
if(targEmp.emp !is null && targEmp.emp.valid && ai.empire.isHostile(targEmp.emp)) {
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
return true;
|
|
}
|
|
}
|
|
else if(ai.protect !is null) {
|
|
auto@ sys = ai.protect;
|
|
if(card.type is cardProtect) {
|
|
if(sys.region.ProtectedMask.value & ai.empire.mask == 0 && (sys.planetMask & sys.region.ContestedMask) & ai.empire.mask != 0) {
|
|
auto@ targSys = targs[0];
|
|
targSys.filled = true;
|
|
@targSys.obj = sys.region;
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
@ai.protect = null;
|
|
return true;
|
|
}
|
|
}
|
|
else if(card.type is cardUprising) {
|
|
uint friendMask = ai.empire.mask | ai.allyMask;
|
|
if((sys.planetMask & sys.region.ContestedMask) & friendMask != 0) {
|
|
for(uint p = 0, pcnt = sys.planets.length; p < pcnt; ++p) {
|
|
auto@ pl = sys.planets[p];
|
|
if(pl.owner.mask & friendMask != 0 && pl.isContested && !pl.isProtected(ai.empire)) {
|
|
auto@ targSys = targs[0];
|
|
targSys.filled = true;
|
|
@targSys.obj = pl;
|
|
playInfluenceCard_server(ai.empire, card.id, targs);
|
|
@ai.protect = null;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
array<InfluenceCard> cards;
|
|
|
|
bool spendInfluence(BasicAI@ ai) {
|
|
if(supports.length == 0)
|
|
fillCardTypes();
|
|
|
|
if(ai.empire.getInfluenceCardCount() != cards.length) {
|
|
cards.length = 0;
|
|
cards.syncFrom(ai.empire.getInfluenceCards());
|
|
|
|
totalSupport = 0;
|
|
for(uint i = 0, cnt = cards.length; i < cnt; ++i) {
|
|
auto@ card = cards[i];
|
|
if(supports.find(card.type) >= 0) {
|
|
auto amt = card.getWeight();
|
|
if(amt != INDETERMINATE)
|
|
totalSupport += amt;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(cards.length != 0) {
|
|
auto@ votes = getActiveInfluenceVotes_server();
|
|
uint cnt = votes.length;
|
|
uint off = randomi(0,cnt-1);
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ vote = votes[(i+off) % cnt];
|
|
int stance = 0;
|
|
|
|
auto@ startRelation = ai.getRelation(vote.startedBy);
|
|
|
|
if(vote.startedBy is ai.empire)
|
|
stance = 2;
|
|
else if(ai.empire.hostileMask & vote.startedBy.mask != 0)
|
|
stance = -1;
|
|
else if(isTargetOf(ai, vote, ai.empire))
|
|
stance = -2;
|
|
else if(vote.startedBy.mask & ai.allyMask != 0 || startRelation.standing > 50)
|
|
stance = startRelation.standing > 75 ? 2 : 1;
|
|
else if(siteVotes.find(vote.type) >= 0)
|
|
stance = -1;
|
|
else if(ai.getRelation(vote.startedBy).standing < -10)
|
|
stance = -1;
|
|
else if(ai.callOuts.contains(vote.id)) {
|
|
stance = randomi(0,1) == 0 ? 1 : -1;
|
|
ai.callOuts.erase(vote.id);
|
|
}
|
|
|
|
if(stance <= -1) {
|
|
auto@ trueVote = getInfluenceVoteByID(vote.id);
|
|
if(trueVote.totalFor > trueVote.totalAgainst) {
|
|
if(trueVote.totalAgainst + totalSupport >= trueVote.totalFor) {
|
|
if(opposeVote(ai, vote, cards, useHard = (stance == -2)))
|
|
return true;
|
|
}
|
|
else {
|
|
if(playRider(ai, vote, cards))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if(stance >= 1) {
|
|
auto@ trueVote = getInfluenceVoteByID(vote.id);
|
|
if(trueVote.totalFor <= trueVote.totalAgainst && trueVote.totalAgainst < trueVote.totalFor + totalSupport)
|
|
if(supportVote(ai, vote, cards, useHard = (stance == 2)))
|
|
return true;
|
|
}
|
|
else {
|
|
if(playRider(ai, vote, cards))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ai.empire.Influence > max(1, int(ai.empire.InfluenceCap * 0.5))) {
|
|
if(randomi(0,3) != 0)
|
|
if(playRandomAction(ai, cards))
|
|
return true;
|
|
|
|
auto@ cards = getInfluenceStack();
|
|
if(cards.length > 0) {
|
|
buyCardFromInfluenceStack(ai.empire, cards[min(randomi(0, cards.length-1), randomi(0, cards.length-1))].id, pay=true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
array<TechnologyNode@> techs;
|
|
|
|
bool spendResearch(BasicAI@ ai) {
|
|
if(techs.length == 0) {
|
|
auto@ dat = ai.empire.getTechnologyNodes();
|
|
TechnologyNode@ n = TechnologyNode();
|
|
while(receive(dat, n)) {
|
|
techs.insertLast(n);
|
|
@n = TechnologyNode();
|
|
}
|
|
}
|
|
|
|
double pts = ai.empire.ResearchPoints;
|
|
|
|
uint offset = randomi(0,techs.length-1);
|
|
for(uint i = 0, cnt = techs.length / (7 - ai.skillEconomy) + 1; i < cnt; ++i) {
|
|
uint index = (i + offset) % techs.length;
|
|
auto@ tech = techs[index];
|
|
if(!tech.available || tech.bought)
|
|
continue;
|
|
double pointCost = tech.getPointCost(ai.empire);
|
|
if(pointCost > 0 && pts > pointCost) {
|
|
ai.empire.research(index, false);
|
|
techs.length = 0;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
switch(nextStep++ % 7) {
|
|
case 0:
|
|
if(ai.empire.ResearchPoints >= 100.0)
|
|
if(spendResearch(ai))
|
|
return false;
|
|
//Fall through if there was nothing to spend
|
|
case 1:
|
|
//Export idle waters to idle foods
|
|
//Otherwise, export idle foods to idle level 1s
|
|
if(ai.skillEconomy >= DIFF_Medium && !ai.usesMotherships && !ai.isMachineRace) {
|
|
auto@ foods = ai.getResourceList(RT_Food, onlyExportable=true);
|
|
int water = ai.getResourceList(RT_Water, onlyExportable=true)[0];
|
|
|
|
bool found = false;
|
|
{
|
|
auto@ waterWorlds = ai.planets[water].idle;
|
|
if(waterWorlds.length > 0) {
|
|
auto@ waterPl = waterWorlds[randomi(0,waterWorlds.length-1)];
|
|
if(waterPl.Population > 0.9999 && waterPl.isPrimaryDestination(null)) {
|
|
//Look for an idle level 0 food world in the same territory
|
|
auto@ terr = waterPl.region.getTerritory(ai.empire);
|
|
|
|
for(uint i = 0; i < foods.length; ++i) {
|
|
auto@ foodWorlds = ai.planets[foods[i]].idle;
|
|
if(foodWorlds.length == 0)
|
|
continue;
|
|
auto@ foodPl = foodWorlds[randomi(0,foodWorlds.length-1)];
|
|
auto@ reg = foodPl.region;
|
|
if(reg !is null && reg.getTerritory(ai.empire) is terr && foodPl.resourceLevel == 0 && foodPl.Population > 0.9999 && foodPl.isPrimaryDestination(null)) {
|
|
found = true;
|
|
waterPl.exportResource(0, foodPl);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!found && ai.skillEconomy >= DIFF_Hard) {
|
|
Planet@ foodPl;
|
|
for(uint i = 0; i < foods.length; ++i) {
|
|
auto@ foodWorlds = ai.planets[foods[i]].idle;
|
|
if(foodWorlds.length == 0)
|
|
continue;
|
|
auto@ pl = foodWorlds[randomi(0,foodWorlds.length-1)];
|
|
if(pl.level == 0 && pl.Population > 0.9999 && pl.isPrimaryDestination(null)) {
|
|
@foodPl = pl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(foodPl !is null) {
|
|
//Look for an idle level 1 resource world in the same territory
|
|
auto@ terr = foodPl.region.getTerritory(ai.empire);
|
|
auto@ level1s = ai.getResourceList(RT_LevelOne);
|
|
|
|
for(uint i = 0, l1cnt = level1s.length; i < 3 && i < l1cnt; ++i) {
|
|
auto@ level1Worlds = ai.planets[level1s[randomi(0,l1cnt-1)]].idle;
|
|
if(level1Worlds.length == 0)
|
|
continue;
|
|
auto@ level1Pl = level1Worlds[randomi(0,level1Worlds.length-1)];
|
|
if(level1Pl.region.getTerritory(ai.empire) is terr && level1Pl.resourceLevel == 1 && level1Pl.isPrimaryDestination(null)) {
|
|
found = true;
|
|
foodPl.exportResource(0, level1Pl);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(found)
|
|
break;
|
|
}
|
|
case 2:
|
|
//Colonize food and water worlds (Without unbalancing the ratio too much)
|
|
// We delay this behavior for 9 minutes, which is about when the homeworld can reach level 2 (otherwise we may colonize a useless water first)
|
|
if(ai.skillEconomy >= DIFF_Medium && gameTime > 9.0 * 60.0 && !ai.isMachineRace) {
|
|
auto@ foods = ai.getResourceList(RT_Food, onlyExportable=true);
|
|
int water = ai.getResourceList(RT_Water, onlyExportable=true)[0];
|
|
auto@ level1s = ai.getResourceList(RT_LevelOne);
|
|
|
|
uint freeWaters = ai.planets[water].idle.length;
|
|
uint freeFoods = 0;
|
|
for(uint i = 0, cnt = foods.length; i < cnt; ++i)
|
|
freeFoods += ai.planets[foods[i]].idle.length;
|
|
|
|
bool busy = false;
|
|
if(ai.empire.EstNextBudget > 300) {
|
|
if(freeFoods <= freeWaters && freeFoods < 2) {
|
|
ai.addIdle(ai.colonizeByResource(foods, execute=false));
|
|
busy = true;
|
|
}
|
|
|
|
if(freeWaters <= freeFoods && freeWaters < 2) {
|
|
ai.addIdle(ai.colonizeByResource(ai.getResourceList(RT_Water, onlyExportable=true), execute=false));
|
|
busy = true;
|
|
}
|
|
}
|
|
|
|
if(((freeFoods > 0 && freeWaters > 0) || ai.skillEconomy >= DIFF_Hard) && gameTime > 6.0 * 60.0) {
|
|
//Improve level 1 resource planets to level 1
|
|
//Otherwise, capture a new level 1
|
|
for(uint i = 0, cnt = level1s.length; i < cnt; ++i) {
|
|
PlanetList@ planets = ai.planets[level1s[i]];
|
|
for(uint j = 0, jcnt = planets.idle.length; j < jcnt; ++j) {
|
|
Planet@ planet = planets.idle[j];
|
|
if(planet.resourceLevel < 1) {
|
|
ai.addIdle(ai.requestPlanetImprovement(planet, 1));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ai.empire.EstNextBudget > 300 || (freeFoods > 0 && freeWaters > 0)) {
|
|
ai.addIdle(ai.colonizeByResource(level1s, execute=false));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(busy)
|
|
return false;
|
|
}
|
|
case 3:
|
|
if(queued !is null) {
|
|
if(ai.performAction(queued))
|
|
@queued = null;
|
|
return false;
|
|
}
|
|
|
|
{
|
|
int freeBudget = min(ai.empire.RemainingBudget, ai.empire.EstNextBudget);
|
|
|
|
if(freeBudget > 240 && ai.fleets[FT_Scout].length < 1) {
|
|
@queued = ai.requestFleetBuild(FT_Scout);
|
|
return false;
|
|
}
|
|
else if(!ai.usesMotherships && ai.skillEconomy >= DIFF_Medium && freeBudget > 450 && gameTime > 24.0 * 60.0 && ai.empire.FTLCapacity < 500.0) {
|
|
//TODO: Mothership race can build an orbital instead
|
|
Planet@ pl = ai.homeworld;
|
|
if(pl !is null && pl.owner is ai.empire)
|
|
ai.requestBuilding(pl, getBuildingType("FTLStorage"));
|
|
}
|
|
else if(ai.needsAltars && ai.empire.TotalPopulation > ai.empire.getAttribute(getEmpAttribute("AltarSupportedPopulation"))) {
|
|
|
|
auto@ res = getResource("Altar");
|
|
if(res is null)
|
|
return false;
|
|
|
|
auto@ altars = ai.planetsByResource[res.id];
|
|
uint index = randomi(0, altars.length);
|
|
if(index != 0) {
|
|
Planet@ pl = altars.planets[index-1];
|
|
if(pl !is null && pl.level < 5) {
|
|
//We can't stick these in the strategy's @queued, because we may not always have the resources to level up a planet unilaterally.
|
|
ai.addIdle(ai.requestPlanetImprovement(pl, pl.level+1));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Planet@ pl = ai.empire.planetList[randomi(0, ai.empire.planetCount - 1)];
|
|
if(pl !is null)
|
|
@queued = ai.requestBuilding(pl, getBuildingType("Altar"), force=true);
|
|
}
|
|
else if(ai.usesMotherships && freeBudget > 4800 && ai.fleets[FT_Mothership].length < 2) {
|
|
@queued = ai.requestFleetBuild(FT_Mothership);
|
|
}
|
|
else if(ai.needsMainframes && ai.mainframeMod !is null && freeBudget >= 500) {
|
|
//Make sure all factories have an available fling beacon
|
|
if(ai.factories.length > 0) {
|
|
auto@ factory = ai.factories[randomi(0, ai.factories.length - 1)];
|
|
auto@ reg = factory.region;
|
|
if(reg !is null) {
|
|
Orbital@ orb = ai.empire.getClosestOrbital(ai.mainframeMod.id, reg.position);
|
|
if(orb is null || orb.position.distanceTo(reg.position) > reg.radius) {
|
|
@queued = ai.requestOrbital(reg, OT_Mainframe);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if(ai.ftl == FTL_Fling && freeBudget > 550) {
|
|
//Make sure all factories have an available fling beacon
|
|
if(ai.factories.length > 0) {
|
|
auto@ factory = ai.factories[randomi(0, ai.factories.length - 1)];
|
|
auto@ reg = factory.region;
|
|
if(reg !is null && (!ai.empire.hasFlingBeacons || ai.empire.getFlingBeacon(factory.position) is null)) {
|
|
@queued = ai.requestOrbital(reg, OT_FlingBeacon);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(ai.ftl == FTL_Gate && freeBudget > 600 && ai.empire.FTLIncome > 1.0/3.0) {
|
|
//Make sure all factories have an available fling beacon
|
|
if(ai.factories.length > 0) {
|
|
auto@ factory = ai.factories[randomi(0, ai.factories.length - 1)];
|
|
auto@ reg = factory.region;
|
|
if(reg !is null) {
|
|
auto@ gate = ai.empire.getStargate(factory.position);
|
|
if(gate is null || gate.position.distanceTo(factory.position) > 10000.0) {
|
|
@queued = ai.requestOrbital(reg, OT_Gate);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int buildCost = 450;
|
|
auto@ dsg = ai.dsgFlagships[FST_Combat];
|
|
if(dsg !is null)
|
|
buildCost = dsg.total(HV_BuildCost);
|
|
|
|
int buildThres = buildCost * 2;
|
|
if(ai.skillCombat >= DIFF_Medium)
|
|
buildThres = min(buildThres, buildCost + 500);
|
|
|
|
if(freeBudget > buildCost + 250 && ai.fleets[FT_Combat].length == 0) {
|
|
@queued = ai.requestFleetBuild(FT_Combat);
|
|
return false;
|
|
}
|
|
else if(freeBudget > buildThres) {
|
|
@queued = ai.requestFleetBuild(FT_Combat);
|
|
return false;
|
|
}
|
|
else if(freeBudget > 250 && ai.skillEconomy > DIFF_Medium) {
|
|
if(ai.factories.length > 0) {
|
|
auto@ leader = ai.factories[randomi(0,ai.factories.length-1)];
|
|
if(leader.valid && leader.owner is ai.empire)
|
|
ai.fillFleet(leader, freeBudget - 150);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case 4:
|
|
if(ai.empire.EnergyStored > 1500) {
|
|
Artifact@ artifact;
|
|
switch(randomi(0,3)) {
|
|
case 0:
|
|
//Arcology
|
|
if(ai.homeworld !is null && ai.homeworld.owner is ai.empire) {
|
|
@artifact = ai.getArtifact(getArtifactType("NatureDevice"), ai.homeworld.region);
|
|
if(artifact !is null) {
|
|
artifact.activateAbilityFor(ai.empire, 0, ai.homeworld);
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
//Ancient Embassy
|
|
@artifact = ai.getArtifact(getArtifactType("AncientEmbassy"));
|
|
if(artifact !is null && ai.exploredSystems.length > 0) {
|
|
//Choose a random system in another player's territory
|
|
Region@ target;
|
|
for(uint i = 0; i < 15; ++i) {
|
|
auto@ sys = ai.exploredSystems[randomi(0,ai.exploredSystems.length-1)];
|
|
if(sys.planetMask & ~(ai.empire.mask | 0x1) != 0) {
|
|
@target = sys.region;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(target !is null) {
|
|
vec2d off = random2d(target.radius * 0.5, target.radius * 0.8);
|
|
artifact.activateAbilityFor(ai.empire, 0, target.position + vec3d(off.x, 0.0, off.y));
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
//Espionage Probe
|
|
@artifact = ai.getArtifact(getArtifactType("SpyProbe"));
|
|
if(artifact !is null && ai.exploredSystems.length > 0) {
|
|
//Choose a random system in another player's territory
|
|
Region@ target;
|
|
for(uint i = 0; i < 15; ++i) {
|
|
auto@ sys = ai.exploredSystems[randomi(0,ai.exploredSystems.length-1)];
|
|
if(sys.planetMask & ~(ai.empire.mask | 0x1) != 0 && sys.region.VisionMask & ai.empire.visionMask != 0) {
|
|
@target = sys.region;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(target !is null) {
|
|
artifact.activateAbilityFor(ai.empire, 0, target);
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case 3:
|
|
//Revenant part
|
|
switch(randomi(0,3)) {
|
|
case 0: @artifact = ai.getArtifact(getArtifactType("RevenantCannon")); break;
|
|
case 1: @artifact = ai.getArtifact(getArtifactType("RevenantCore")); break;
|
|
case 2: @artifact = ai.getArtifact(getArtifactType("RevenantChassis")); break;
|
|
case 3: @artifact = ai.getArtifact(getArtifactType("RevenantEngine")); break;
|
|
}
|
|
|
|
if(artifact !is null)
|
|
artifact.activateAbilityFor(ai.empire, 0);
|
|
}
|
|
}
|
|
case 5: {
|
|
double forceExpand;
|
|
if(ai.skillEconomy < DIFF_Medium)
|
|
forceExpand = 10.0 * 60.0;
|
|
else if(ai.skillEconomy < DIFF_Hard)
|
|
forceExpand = 5.0 * 60.0;
|
|
else
|
|
forceExpand = 1.5 * 60.0;
|
|
|
|
if(ai.timeSinceLastExpand > forceExpand && gameTime > 7.0 * 60.0) {
|
|
ai.requestExpansion();
|
|
return false;
|
|
}
|
|
}
|
|
case 6:
|
|
ai.requestExploration();
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//
|
|
class MilitaryVictory : Action {
|
|
Action@ awaiting;
|
|
int64 ahash = 0;
|
|
|
|
MilitaryVictory() {
|
|
}
|
|
|
|
MilitaryVictory(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> ahash;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
if(ahash != 0)
|
|
@awaiting = ai.locateAction(ahash);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
if(awaiting !is null) {
|
|
msg << awaiting.hash;
|
|
}
|
|
else {
|
|
int64 tmp = 0;
|
|
msg << tmp;
|
|
}
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return int64(STRAT_Military) << ACT_BIT_OFFSET;
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return STRAT_Military;
|
|
}
|
|
|
|
string get_state() const {
|
|
return "Winning by military";
|
|
}
|
|
|
|
double startupTime = gameTime;
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
switch(randomi(0,3)) {
|
|
case 0: case 1:
|
|
ai.requestBudget();
|
|
break;
|
|
case 2:
|
|
ai.requestDefense();
|
|
break;
|
|
case 3:
|
|
{
|
|
set_int prevEnemies;
|
|
for(uint i = 0, cnt = ai.enemies.length; i < cnt; ++i)
|
|
prevEnemies.insert(ai.enemies[i].id);
|
|
ai.enemies.length = 0;
|
|
ai.allyMask = 0;
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
Empire@ emp = getEmpire(i);
|
|
if(emp.valid && emp.major && emp !is ai.empire) {
|
|
if(emp.isHostile(ai.empire))
|
|
ai.enemies.insertLast(emp);
|
|
else if(emp.team == ai.empire.team && emp.team != -1)
|
|
ai.allyMask |= emp.mask;
|
|
else if(emp.SubjugatedBy is ai.empire || ai.empire.SubjugatedBy is emp)
|
|
ai.allyMask |= emp.mask;
|
|
}
|
|
}
|
|
|
|
bool warred = false;
|
|
uint borderMask = 0;
|
|
bool checkedBorder = false;
|
|
|
|
//Only start wars if we aren't in any, or we're insane.
|
|
// Delay this logic for a while after the game loads, as it takes a while for empire strengths to update
|
|
if((ai.enemies.length == 0 || ai.behaviorFlags & AIB_QuickToWar != 0) && gameTime > startupTime + 60.0 && ai.empire.SubjugatedBy is null) {
|
|
int strength = ai.empire.MilitaryStrength;
|
|
Empire@ target;
|
|
|
|
auto@ border = ai.getBorder();
|
|
for(uint i = 0, cnt = border.length; i < cnt; ++i)
|
|
borderMask |= border[i].planetMask;
|
|
auto@ inner = ai.ourSystems;
|
|
for(uint i = 0, cnt = inner.length; i < cnt; ++i)
|
|
borderMask |= inner[i].planetMask;
|
|
checkedBorder = true;
|
|
|
|
uint base = randomi(0, getEmpireCount() - 1);
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
Empire@ other = getEmpire((i + base) % cnt);
|
|
if(other is ai.empire || !other.major || ai.empire.ContactMask.value & other.mask == 0)
|
|
continue;
|
|
if(ai.ignoreEmpire(other) || ai.allyMask & other.mask != 0)
|
|
continue;
|
|
|
|
if(other.SubjugatedBy is null && gameTime > ai.treatyWaits[other.index]) {
|
|
if(ai.empire.isHostile(other)) {
|
|
if((ai.empire.points.value + 200) / (other.points.value + 200) >= 5) {
|
|
demandSurrender(ai.empire, other);
|
|
ai.treatyWaits[other.index] = gameTime + randomd(120.0,240.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ai.enemies.find(other) >= 0)
|
|
continue;
|
|
|
|
if(ai.empire.isHostile(other)) {
|
|
@target = other;
|
|
break;
|
|
}
|
|
|
|
if(ai.behaviorFlags & AIB_QuickToWar != 0 && ai.empire.getTotalFleetStrength() > 100.0) {
|
|
@target = other;
|
|
break;
|
|
}
|
|
|
|
//Go to war depending on how we're doing against our opponents, and if we've been able to expand recently
|
|
int pointStr = ai.empire.points.value - other.points.value;
|
|
int otherStr = other.MilitaryStrength;
|
|
|
|
uint revCount = 0, unownedRev = 4 - ai.revenantParts.length;
|
|
for(uint j = 0, jcnt = ai.revenantParts.length; j < jcnt; ++j) {
|
|
auto@ part = ai.revenantParts[j];
|
|
if(part.valid && part.owner is other)
|
|
++revCount;
|
|
}
|
|
|
|
bool casusBelli = false;
|
|
|
|
double relation = pow(2.0, double(ai.getRelation(other).standing) / 100.0);
|
|
|
|
if(borderMask & other.mask != 0) {
|
|
if(strength - otherStr > 0 && (ai.timeSinceLastExpand > 9.0 * 60.0 * relation))
|
|
casusBelli = true;
|
|
else if(strength - otherStr >= 0 && (ai.timeSinceLastExpand > 12.0 * 60.0 * relation || pointStr < -320 * relation || revCount >= 2))
|
|
casusBelli = true;
|
|
}
|
|
|
|
if(!casusBelli) {
|
|
if(strength - otherStr >= 0 && pointStr < -2250 * relation)
|
|
casusBelli = true;
|
|
else if(strength - otherStr >= 0 && ai.timeSinceLastExpand > 20.0 * 60.0 * relation)
|
|
casusBelli = true;
|
|
else if(revCount >= 3 && (revCount + unownedRev) == 4)//That's a nice giant evil ship you've got there. Would be a shame if something happened to it.
|
|
casusBelli = true;
|
|
}
|
|
|
|
if(casusBelli)
|
|
@target = other;
|
|
}
|
|
|
|
if(target !is null) {
|
|
if(target.SubjugatedBy is null && ai.empire.SubjugatedBy is null) {
|
|
if(ai.empire.ForcedPeaceMask.value & target.mask != 0)
|
|
leaveTreatiesWith(ai.empire, target.mask);
|
|
declareWar(ai.empire, target);
|
|
}
|
|
ai.enemies.insertLast(target);
|
|
ai.requestWar(target);
|
|
auto@ relation = ai.getRelation(target);
|
|
relation.standing = min(relation.standing - 35, 0);
|
|
prevEnemies.insert(target.id);
|
|
warred = true;
|
|
}
|
|
else {
|
|
ai.requestBudget();
|
|
}
|
|
}
|
|
|
|
if(ai.enemies.length != 0) {
|
|
for(uint i = 0, cnt = ai.enemies.length; i < cnt; ++i) {
|
|
if(!prevEnemies.contains(ai.enemies[i].id)) {
|
|
auto@ enemy = ai.getRelation(ai.enemies[i]);
|
|
if(enemy.standing >= 15)
|
|
enemy.brokeAlliance = true;
|
|
enemy.standing = min(enemy.standing - 35, 0);
|
|
enemy.offense = RA_War;
|
|
}
|
|
}
|
|
//Consider offering surrender if we're losing badly, or offer peace to an opponent
|
|
//Otherwise, demand that our opponents surrender if we're winning
|
|
Empire@ other = ai.enemies[randomi(0,ai.enemies.length-1)];
|
|
if(ai.treatyWaits[other.index] < gameTime) {
|
|
bool treated = false;
|
|
int relation = ai.getRelation(other).standing * max(ai.empire.points.value / 400, 1);
|
|
|
|
if((ai.empire.points.value + 200) / max(other.points.value + 200 + relation, 1) >= 5) {
|
|
demandSurrender(ai.empire, other);
|
|
treated = true;
|
|
}
|
|
else if((other.points.value + 250 + relation) / max(ai.empire.points.value + 250, 1) >= 5 && ai.empire.MilitaryStrength < other.MilitaryStrength) {
|
|
offerSurrender(ai.empire, other);
|
|
treated = true;
|
|
}
|
|
else if(ai.enemies.length > 1 && ai.behaviorFlags & AIB_QuickToWar == 0) {
|
|
//Consider peace with a weak opponent
|
|
Empire@ weakest;
|
|
int weakestPoints = 0, totalPoints = 0;
|
|
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
auto@ emp = getEmpire(i);
|
|
if(emp.valid && emp.isHostile(ai.empire)) {
|
|
int pts = emp.points.value;
|
|
if(pts <= 0)
|
|
continue;
|
|
totalPoints += pts;
|
|
|
|
//Consider opponents that have offered peace as 'weak'
|
|
if(emp.PeaceMask.value & ai.empire.mask != 0)
|
|
pts /= 3;
|
|
|
|
pts -= relation;
|
|
|
|
if(weakest is null || pts < weakestPoints) {
|
|
weakestPoints = pts;
|
|
@weakest = emp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(weakest is other && (totalPoints + 150) / max(ai.empire.points.value + 150, 1) >= 2) {
|
|
sendPeaceOffer(ai.empire, other);
|
|
treated = true;
|
|
}
|
|
}
|
|
|
|
if(treated)
|
|
ai.treatyWaits[other.index] = gameTime + randomd(120.0,240.0);
|
|
}
|
|
|
|
if(!warred)
|
|
ai.requestWar(ai.enemies[randomi(0, ai.enemies.length-1)]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
class InfluenceVictory : Action, ObjectReceiver {
|
|
Action@ awaiting;
|
|
Planet@ improve;
|
|
int64 ahash = 0;
|
|
|
|
InfluenceVictory() {
|
|
}
|
|
|
|
InfluenceVictory(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> ahash;
|
|
msg >> improve;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
if(ahash != 0)
|
|
@awaiting = ai.locateAction(ahash);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
if(awaiting !is null) {
|
|
msg << awaiting.hash;
|
|
}
|
|
else {
|
|
int64 tmp = 0;
|
|
msg << tmp;
|
|
}
|
|
msg << improve;
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return int64(STRAT_Influence) << ACT_BIT_OFFSET;
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return STRAT_Influence;
|
|
}
|
|
|
|
bool giveObject(BasicAI@ ai, Object@ obj) {
|
|
Planet@ pl = cast<Planet>(obj);
|
|
if(pl !is null) {
|
|
const ResourceType@ native = getResource(pl.primaryResourceType);
|
|
if(native.tilePressure[TR_Influence] > 0 && pl.level < native.level) {
|
|
@improve = pl;
|
|
@awaiting = null;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
string get_state() const {
|
|
return "Winning by influence";
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
switch(randomi(0,5)) {
|
|
case 0: case 1:
|
|
ai.requestBudget();
|
|
return false;
|
|
case 2:
|
|
ai.requestDefense();
|
|
return false;
|
|
}
|
|
|
|
if(awaiting !is null) {
|
|
if(ai.performAction(awaiting))
|
|
@awaiting = null;
|
|
}
|
|
else if(improve !is null) {
|
|
const ResourceType@ native = getResource(improve.primaryResourceType);
|
|
if(native.tilePressure[TR_Influence] > 0 && improve.level < native.level)
|
|
@awaiting = ai.requestPlanetImprovement(improve, native.level);
|
|
@improve = null;
|
|
}
|
|
else {
|
|
int[] influenceRes;
|
|
auto@ res = getResource("Plastics");
|
|
if(res !is null)
|
|
influenceRes.insertLast(res.id);
|
|
@res = getResource("Spice");
|
|
if(res !is null)
|
|
influenceRes.insertLast(res.id);
|
|
if(influenceRes.length != 0)
|
|
ai.colonizeByResource(influenceRes, this);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|