866 lines
22 KiB
ActionScript
866 lines
22 KiB
ActionScript
// Relations
|
|
// ---------
|
|
// Manages the relationships we have with other empires, including treaties, hatred, and wars.
|
|
//
|
|
|
|
import empire_ai.weasel.WeaselAI;
|
|
|
|
import empire_ai.weasel.Intelligence;
|
|
import empire_ai.weasel.Fleets;
|
|
import empire_ai.weasel.Systems;
|
|
import empire_ai.weasel.Planets;
|
|
|
|
import warandpeace;
|
|
import influence;
|
|
from influence_global import activeTreaties, influenceLock, joinTreaty, leaveTreaty, declineTreaty, Treaty, sendPeaceOffer, createTreaty, offerSurrender, demandSurrender, leaveTreatiesWith;
|
|
|
|
enum HateType {
|
|
HT_SystemPresence,
|
|
HT_FleetPresence,
|
|
HT_COUNT
|
|
};
|
|
|
|
class Hate {
|
|
uint type;
|
|
double amount = 0.0;
|
|
Object@ obj;
|
|
SystemAI@ sys;
|
|
|
|
void save(Relations& relations, SaveFile& file) {
|
|
file << type;
|
|
file << amount;
|
|
file << obj;
|
|
relations.systems.saveAI(file, sys);
|
|
}
|
|
|
|
void load(Relations& relations, SaveFile& file) {
|
|
file >> type;
|
|
file >> amount;
|
|
file >> obj;
|
|
@sys = relations.systems.loadAI(file);
|
|
}
|
|
|
|
bool get_valid() {
|
|
if(type == HT_SystemPresence)
|
|
return sys !is null;
|
|
if(type == HT_FleetPresence)
|
|
return obj !is null && sys !is null;
|
|
return true;
|
|
}
|
|
|
|
bool update(AI& ai, Relations& relations, Relation& rel, double time) {
|
|
if(type == HT_SystemPresence) {
|
|
amount = 0.25;
|
|
if(sys.seenPresent & rel.empire.mask == 0)
|
|
return false;
|
|
if(sys.seenPresent & ai.empire.mask == 0)
|
|
return false;
|
|
}
|
|
else if(type == HT_FleetPresence) {
|
|
if(!obj.valid || obj.owner !is rel.empire)
|
|
return false;
|
|
if(sys.seenPresent & ai.empire.mask == 0)
|
|
return false;
|
|
if(obj.region !is sys.obj)
|
|
return false;
|
|
if(obj.getFleetStrength() < 1000.0)
|
|
amount = 0.1;
|
|
else
|
|
amount = 0.5;
|
|
}
|
|
|
|
rel.hate += amount * time;
|
|
return true;
|
|
}
|
|
|
|
string dump() {
|
|
switch(type) {
|
|
case HT_SystemPresence:
|
|
return "system presence in "+sys.obj.name;
|
|
case HT_FleetPresence:
|
|
return "fleet presence "+obj.name+" in "+sys.obj.name;
|
|
}
|
|
return "unknown";
|
|
}
|
|
};
|
|
|
|
final class Relation {
|
|
Empire@ empire;
|
|
|
|
//Whether we've met this empire
|
|
bool contacted = false;
|
|
|
|
//Whether we're currently at war
|
|
bool atWar = false;
|
|
|
|
//Last time we tried to make peace
|
|
double lastPeaceTry = 0;
|
|
|
|
//Whether this is our war of aggression
|
|
bool aggressive = false;
|
|
|
|
//Whether this is our ally
|
|
bool allied = false;
|
|
|
|
//Our relationship data
|
|
double hate = 0.0;
|
|
array<Hate@> hates;
|
|
|
|
//Masks
|
|
uint borderedTo = 0;
|
|
uint alliedTo = 0;
|
|
|
|
//Whether we consider this empire a threat to us
|
|
bool isThreat = false;
|
|
|
|
//How much we would value having this empire as an ally
|
|
double allyValue = 0.0;
|
|
//How much we think we can beat this empire and all its allies
|
|
double defeatable = 0.0;
|
|
//Relative strength of this empire to us in a vacuum
|
|
double relStrength = 0.0;
|
|
|
|
//How much we've lost to them in this recent war
|
|
double warLost = 0.0;
|
|
//How much we've taken from them in this recent war
|
|
double warTaken = 0.0;
|
|
|
|
void save(Relations& relations, SaveFile& file) {
|
|
file << contacted;
|
|
file << atWar;
|
|
file << aggressive;
|
|
file << allied;
|
|
|
|
file << hate;
|
|
uint cnt = hates.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
hates[i].save(relations, file);
|
|
|
|
file << borderedTo;
|
|
file << alliedTo;
|
|
file << isThreat;
|
|
file << allyValue;
|
|
file << defeatable;
|
|
file << relStrength;
|
|
file << warTaken;
|
|
file << warLost;
|
|
file << lastPeaceTry;
|
|
}
|
|
|
|
void load(Relations& relations, SaveFile& file) {
|
|
file >> contacted;
|
|
file >> atWar;
|
|
file >> aggressive;
|
|
file >> allied;
|
|
|
|
file >> hate;
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Hate ht;
|
|
ht.load(relations, file);
|
|
if(ht.valid)
|
|
hates.insertLast(ht);
|
|
}
|
|
|
|
file >> borderedTo;
|
|
file >> alliedTo;
|
|
file >> isThreat;
|
|
file >> allyValue;
|
|
file >> defeatable;
|
|
file >> relStrength;
|
|
file >> warTaken;
|
|
file >> warLost;
|
|
file >> lastPeaceTry;
|
|
}
|
|
|
|
void trackSystem(AI& ai, Relations& relations, SystemAI@ sys) {
|
|
for(uint i = 0, cnt = hates.length; i < cnt; ++i) {
|
|
auto@ ht = hates[i];
|
|
if(ht.type != HT_SystemPresence)
|
|
continue;
|
|
if(ht.sys is sys)
|
|
return;
|
|
}
|
|
|
|
Hate ht;
|
|
ht.type = HT_SystemPresence;
|
|
@ht.sys = sys;
|
|
hates.insertLast(ht);
|
|
|
|
if(relations.log)
|
|
ai.print("Gain hate of "+empire.name+": "+ht.dump());
|
|
}
|
|
|
|
void trackFleet(AI& ai, Relations& relations, FleetIntel@ intel, SystemAI@ sys) {
|
|
for(uint i = 0, cnt = hates.length; i < cnt; ++i) {
|
|
auto@ ht = hates[i];
|
|
if(ht.type != HT_FleetPresence)
|
|
continue;
|
|
if(ht.obj is intel.obj && ht.sys is sys)
|
|
return;
|
|
}
|
|
|
|
Hate ht;
|
|
ht.type = HT_FleetPresence;
|
|
@ht.sys = sys;
|
|
@ht.obj = intel.obj;
|
|
hates.insertLast(ht);
|
|
|
|
if(relations.log)
|
|
ai.print("Gain hate of "+empire.name+": "+ht.dump());
|
|
}
|
|
|
|
void tick(AI& ai, Relations& relations, double time) {
|
|
if(!contacted) {
|
|
if(ai.empire.ContactMask & empire.mask != 0)
|
|
contacted = true;
|
|
}
|
|
|
|
bool curWar = ai.empire.isHostile(empire);
|
|
if(curWar != atWar)
|
|
atWar = curWar;
|
|
if(!atWar) {
|
|
aggressive = false;
|
|
warLost = 0.0;
|
|
warTaken = 0.0;
|
|
lastPeaceTry = 0.0;
|
|
}
|
|
|
|
borderedTo = relations.intelligence.get(empire).borderedTo;
|
|
alliedTo = empire.mask | empire.mutualDefenseMask | empire.ForcedPeaceMask.value;
|
|
|
|
defeatable = relations.intelligence.defeatability(alliedTo, ai.mask | ai.allyMask);
|
|
relStrength = relations.intelligence.defeatability(ai.mask, empire.mask);
|
|
isThreat = defeatable < 0.8 && (borderedTo & ai.empire.mask) != 0;
|
|
|
|
//Check how valuable of an ally this empire would make
|
|
allyValue = 1.0;
|
|
for(uint i = 0, cnt = relations.relations.length; i < cnt; ++i) {
|
|
auto@ other = relations.relations[i];
|
|
if(other is null || other is this || other.empire is null)
|
|
continue;
|
|
if(other.borderedTo & empire.mask == 0)
|
|
continue;
|
|
if(alliedTo & empire.mask != 0)
|
|
continue;
|
|
|
|
if(other.atWar)
|
|
allyValue *= 3.0;
|
|
else if(other.isThreat)
|
|
allyValue *= 1.5;
|
|
}
|
|
|
|
//Become aggressive here if we're aggressive against one of its allies
|
|
if(atWar && !aggressive) {
|
|
for(uint i = 0, cnt = relations.relations.length; i < cnt; ++i) {
|
|
auto@ other = relations.relations[i];
|
|
if(other is null || other is this || other.empire is null)
|
|
continue;
|
|
if(other.aggressive && this.alliedTo & other.empire.mask != 0) {
|
|
aggressive = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Update our hatred of them
|
|
for(uint i = 0, cnt = hates.length; i < cnt; ++i) {
|
|
if(!hates[i].update(ai, relations, this, time)) {
|
|
if(relations.log)
|
|
ai.print("Hate with "+empire.name+" expired: "+hates[i].dump());
|
|
hates.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
|
|
hate *= pow(ai.behavior.hateDecayRate, time / 60.0);
|
|
if(ai.behavior.biased && !empire.isAI)
|
|
hate += 1.0;
|
|
|
|
//If we really really hate them, declare war
|
|
if(!atWar || !aggressive) {
|
|
double reqHate = 100.0;
|
|
if(defeatable < 1.0)
|
|
reqHate *= sqr(1.0 / defeatable);
|
|
reqHate *= pow(2.0, relations.warCount());
|
|
|
|
if(hate > reqHate && (!ai.behavior.passive || atWar) && defeatable >= ai.behavior.hatredWarOverkill) {
|
|
//Make sure our other requirements for war are met
|
|
if(relations.fleets.haveCombatReadyFleets()) {
|
|
if(canDeclareWar(ai)) {
|
|
if(relations.log)
|
|
ai.print("Declaring hatred war on "+empire.name+": "+hate+" / "+reqHate);
|
|
if(atWar)
|
|
aggressive = true;
|
|
else
|
|
relations.declareWar(empire, aggressive=true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isAllied(AI& ai) {
|
|
return alliedTo & ai.empire.mask != 0;
|
|
}
|
|
|
|
bool canDeclareWar(AI& ai) {
|
|
if(empire.SubjugatedBy !is null)
|
|
return false;
|
|
if(ai.empire.SubjugatedBy !is null)
|
|
return false;
|
|
if(!contacted)
|
|
return false;
|
|
if(ai.empire.ForcedPeaceMask & empire.mask != 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class Relations : AIComponent {
|
|
Intelligence@ intelligence;
|
|
Systems@ systems;
|
|
Fleets@ fleets;
|
|
Planets@ planets;
|
|
|
|
array<Relation@> relations;
|
|
|
|
bool expansionLocked = false;
|
|
double treatyRespond = 0;
|
|
double treatyConsider = 0;
|
|
|
|
double warPoints = 0.0;
|
|
|
|
void create() {
|
|
@intelligence = cast<Intelligence>(ai.intelligence);
|
|
@fleets = cast<Fleets>(ai.fleets);
|
|
@systems = cast<Systems>(ai.systems);
|
|
@planets = cast<Planets>(ai.planets);
|
|
}
|
|
|
|
void start() {
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
Empire@ emp = getEmpire(i);
|
|
if(emp is ai.empire)
|
|
continue;
|
|
if(!emp.major)
|
|
continue;
|
|
|
|
Relation r;
|
|
@r.empire = emp;
|
|
|
|
if(relations.length <= uint(emp.index))
|
|
relations.length = uint(emp.index)+1;
|
|
@relations[emp.index] = r;
|
|
}
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
uint cnt = relations.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
if(relations[i] is null) {
|
|
file.write0();
|
|
continue;
|
|
}
|
|
|
|
file.write1();
|
|
relations[i].save(this, file);
|
|
}
|
|
|
|
file << expansionLocked;
|
|
file << treatyRespond;
|
|
file << treatyConsider;
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
relations.length = cnt;
|
|
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
if(!file.readBit())
|
|
continue;
|
|
|
|
@relations[i] = Relation();
|
|
@relations[i].empire = getEmpire(i);
|
|
relations[i].load(this, file);
|
|
}
|
|
|
|
file >> expansionLocked;
|
|
file >> treatyRespond;
|
|
file >> treatyConsider;
|
|
}
|
|
|
|
double getPointValue(Object@ obj) {
|
|
if(obj is null)
|
|
return 0.0;
|
|
if(obj.isShip) {
|
|
auto@ dsg = cast<Ship>(obj).blueprint.design;
|
|
if(dsg !is null)
|
|
return dsg.size;
|
|
}
|
|
else if(obj.isPlanet) {
|
|
return 10.0 * pow(3.0, double(obj.level));
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
void recordTakenFrom(Empire& emp, double amount) {
|
|
if(!emp.valid)
|
|
return;
|
|
if(log)
|
|
ai.print("Taken value "+amount+" from "+emp.name);
|
|
auto@ rel = get(emp);
|
|
if(rel !is null)
|
|
rel.warTaken += amount;
|
|
}
|
|
|
|
void recordLostTo(Empire& emp, double amount) {
|
|
if(!emp.valid)
|
|
return;
|
|
if(log)
|
|
ai.print("Lost value "+amount+" to "+emp.name);
|
|
auto@ rel = get(emp);
|
|
if(rel !is null)
|
|
rel.warLost += amount;
|
|
}
|
|
|
|
void recordLostTo(Empire& emp, Object@ obj) {
|
|
recordLostTo(emp, getPointValue(obj));
|
|
}
|
|
|
|
void recordTakenFrom(Empire& emp, Object@ obj) {
|
|
recordTakenFrom(emp, getPointValue(obj));
|
|
}
|
|
|
|
Relation@ get(Empire@ emp) {
|
|
if(emp is null)
|
|
return null;
|
|
if(!emp.major)
|
|
return null;
|
|
if(uint(emp.index) >= relations.length)
|
|
return null;
|
|
return relations[emp.index];
|
|
}
|
|
|
|
bool isFightingWar(bool aggressive = false) {
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
if(relations[i] is null)
|
|
continue;
|
|
if(relations[i].atWar) {
|
|
if(!aggressive || relations[i].aggressive)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint warCount() {
|
|
uint count = 0;
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
if(relations[i] is null)
|
|
continue;
|
|
if(relations[i].atWar)
|
|
count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void declareWar(Empire@ onEmpire, bool aggressive = true) {
|
|
//Break all treaties
|
|
leaveTreatiesWith(ai.empire, onEmpire.mask);
|
|
|
|
//Declare actual war
|
|
auto@ rel = get(onEmpire);
|
|
rel.aggressive = aggressive;
|
|
::declareWar(ai.empire, onEmpire);
|
|
}
|
|
|
|
uint sysIdx = 0;
|
|
uint relIdx = 0;
|
|
void tick(double time) override {
|
|
//Find new ways to hate other empires
|
|
if(systems.all.length != 0) {
|
|
sysIdx = (sysIdx+1) % systems.all.length;
|
|
auto@ sys = systems.all[sysIdx];
|
|
if(sys.owned && sys.seenPresent & ~ai.mask != 0) {
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ rel = relations[i];
|
|
if(rel is null)
|
|
continue;
|
|
if(sys.seenPresent & rel.empire.mask != 0)
|
|
rel.trackSystem(ai, this, sys);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(relations.length != 0) {
|
|
relIdx = (relIdx+1) % relations.length;
|
|
auto@ rel = relations[relIdx];
|
|
auto@ itl = intelligence.intel[relIdx];
|
|
if(rel !is null && itl !is null) {
|
|
for(uint i = 0, cnt = itl.fleets.length; i < cnt; ++i) {
|
|
if(!itl.fleets[i].visible)
|
|
continue;
|
|
|
|
auto@ inSys = systems.getAI(itl.fleets[i].obj.region);
|
|
if(inSys !is null && inSys.owned)
|
|
rel.trackFleet(ai, this, itl.fleets[i], inSys);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint relInd = 0;
|
|
void focusTick(double time) override {
|
|
//Update our current relations
|
|
if(relations.length != 0) {
|
|
relInd = (relInd+1) % relations.length;
|
|
if(relations[relInd] !is null)
|
|
relations[relInd].tick(ai, this, time);
|
|
}
|
|
|
|
//Compute how many points we have in total that can be taken
|
|
warPoints = 0.0;
|
|
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
|
|
Ship@ ship = cast<Ship>(fleets.fleets[i].obj);
|
|
if(ship !is null && ship.valid && ship.owner is ai.empire)
|
|
warPoints += getPointValue(ship);
|
|
}
|
|
for(uint i = 0, cnt = planets.planets.length; i < cnt; ++i) {
|
|
Planet@ pl = cast<Planet>(planets.planets[i].obj);
|
|
if(pl !is null && pl.valid && pl.owner is ai.empire)
|
|
warPoints += getPointValue(pl);
|
|
}
|
|
|
|
//Become aggressive if we cannot expand anywhere anymore
|
|
expansionLocked = true;
|
|
for(uint i = 0, cnt = systems.outsideBorder.length; i < cnt; ++i) {
|
|
auto@ sys = systems.outsideBorder[i];
|
|
if(sys.seenPresent == 0) {
|
|
bool havePlanets = false;
|
|
for(uint n = 0, ncnt = sys.planets.length; n < ncnt; ++n) {
|
|
if(sys.planets[n].quarantined)
|
|
continue;
|
|
havePlanets = true;
|
|
break;
|
|
}
|
|
if(havePlanets) {
|
|
expansionLocked = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Deal with our AI's aggressive behavior
|
|
if(ai.behavior.aggressive || (expansionLocked && ai.behavior.aggressiveWhenBoxedIn && !ai.behavior.passive)) {
|
|
//Try to make sure we're always fighting at least one aggressive war
|
|
bool atWar = false, aggro = false;
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
if(relations[i] is null)
|
|
continue;
|
|
if(relations[i].atWar) {
|
|
atWar = true;
|
|
if(relations[i].aggressive)
|
|
aggro = true;
|
|
}
|
|
}
|
|
|
|
if(!atWar) {
|
|
if(fleets.haveCombatReadyFleets()) {
|
|
//Declare war on people who share our border and are defeatable
|
|
Empire@ best;
|
|
double bestWeight = 0;
|
|
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ rel = relations[i];
|
|
if(rel is null)
|
|
continue;
|
|
|
|
auto@ intel = intelligence.get(rel.empire);
|
|
if(intel.shared.length == 0 && intel.theirBorder.length == 0)
|
|
continue;
|
|
if(!rel.canDeclareWar(ai))
|
|
continue;
|
|
if(!ai.behavior.biased || rel.empire.isAI) {
|
|
if(rel.defeatable < ai.behavior.aggressiveWarOverkill)
|
|
continue;
|
|
}
|
|
|
|
double w = rel.defeatable * rel.hate;
|
|
if(rel.isAllied(ai))
|
|
w *= 0.01;
|
|
if(w > bestWeight) {
|
|
bestWeight = w;
|
|
@best = rel.empire;
|
|
}
|
|
}
|
|
|
|
if(best !is null) {
|
|
if(log)
|
|
ai.print("Declare aggressive war against "+best.name);
|
|
declareWar(best, aggressive=true);
|
|
}
|
|
}
|
|
}
|
|
else if(!aggro) {
|
|
//Start going aggressive on someone defeatable we are already at war with
|
|
Empire@ best;
|
|
double bestWeight = 0;
|
|
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ rel = relations[i];
|
|
if(rel is null)
|
|
continue;
|
|
if(!rel.atWar)
|
|
continue;
|
|
if(rel.defeatable < ai.behavior.aggressiveWarOverkill)
|
|
continue;
|
|
|
|
double w = rel.defeatable * rel.hate;
|
|
if(w > bestWeight) {
|
|
bestWeight = w;
|
|
@best = rel.empire;
|
|
}
|
|
}
|
|
|
|
if(best !is null) {
|
|
//Go aggressive then!
|
|
if(log)
|
|
ai.print("Become aggressive against "+best.name);
|
|
get(best).aggressive = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Respond to treaties
|
|
if(gameTime > treatyRespond) {
|
|
treatyRespond = gameTime + randomd(8.0, 20.0);
|
|
|
|
Treaty@ respondTreaty;
|
|
|
|
{
|
|
Lock lck(influenceLock);
|
|
for(uint i = 0, cnt = activeTreaties.length; i < cnt; ++i) {
|
|
auto@ trty = activeTreaties[i];
|
|
if(trty.inviteMask & ai.mask != 0 && trty.presentMask & ai.mask == 0) {
|
|
Message msg;
|
|
trty.write(msg);
|
|
|
|
@respondTreaty = Treaty();
|
|
respondTreaty.read(msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(respondTreaty !is null) {
|
|
bool accept = false;
|
|
Empire@ invitedBy = respondTreaty.leader;
|
|
if(invitedBy is null)
|
|
@invitedBy = respondTreaty.joinedEmpires[0];
|
|
Relation@ other = get(invitedBy);
|
|
|
|
if(respondTreaty.hasClause("SubjugateClause")) {
|
|
//This is a surrender offer or demand
|
|
if(respondTreaty.leader is null) {
|
|
//An offer
|
|
accept = true;
|
|
}
|
|
else if(respondTreaty.joinedEmpires.length != 0) {
|
|
//A demand
|
|
auto@ other = get(respondTreaty.joinedEmpires[0]);
|
|
if(other.defeatable < ai.behavior.surrenderMinStrength) {
|
|
if(warPoints / (other.warLost + warPoints) < ai.behavior.acceptSurrenderRatio) {
|
|
accept = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(respondTreaty.hasClause("MutualDefenseClause")
|
|
|| respondTreaty.hasClause("AllianceClause")) {
|
|
//This is an alliance treaty
|
|
if(other.atWar) {
|
|
//Need to be at peace first
|
|
accept = false;
|
|
}
|
|
else {
|
|
//See if this empire can help us defeat someone
|
|
if(other.allyValue >= 3.0 && other.relStrength >= 0.5)
|
|
accept = true;
|
|
}
|
|
}
|
|
else if(respondTreaty.hasClause("PeaceClause")) {
|
|
//This is a peace offering
|
|
accept = shouldPeace(other);
|
|
}
|
|
else if(respondTreaty.hasClause("VisionClause")) {
|
|
//This is a vision sharing treaty
|
|
if(other !is null)
|
|
accept = !other.isThreat && !other.atWar && other.hate <= 50.0;
|
|
}
|
|
else if(respondTreaty.hasClause("TradeClause")) {
|
|
//This is a trade sharing treaty
|
|
if(other !is null)
|
|
accept = !other.isThreat && !other.atWar && other.hate <= 10.0;
|
|
}
|
|
|
|
if(accept) {
|
|
if(log)
|
|
ai.print("Accept treaty: "+respondTreaty.name, emp=invitedBy);
|
|
joinTreaty(ai.empire, respondTreaty.id);
|
|
}
|
|
else {
|
|
if(log)
|
|
ai.print("Reject treaty: "+respondTreaty.name, emp=invitedBy);
|
|
declineTreaty(ai.empire, respondTreaty.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
//See if we should send a treaty over to someone
|
|
if(gameTime > treatyConsider) {
|
|
treatyConsider = gameTime + randomd(100.0, 300.0);
|
|
|
|
uint offset = randomi(0, relations.length-1);
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ other = relations[(i+offset) % cnt];
|
|
if(other is null)
|
|
continue;
|
|
|
|
//Check if we should make peace with them
|
|
if(other.atWar) {
|
|
if(other.lastPeaceTry < gameTime - 600.0 && shouldPeace(other, isOffer=true)) {
|
|
if(other.aggressive)
|
|
other.aggressive = false;
|
|
if(log)
|
|
ai.print("Send peace offer.", emp=other.empire);
|
|
other.lastPeaceTry = gameTime;
|
|
sendPeaceOffer(ai.empire, other.empire);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(other.atWar) {
|
|
//Check if we should try to surrender to them
|
|
if(other.defeatable < ai.behavior.surrenderMinStrength) {
|
|
if(warPoints / (other.warLost + warPoints) < ai.behavior.offerSurrenderRatio) {
|
|
if(log)
|
|
ai.print("Send surrender offer.", emp=other.empire);
|
|
offerSurrender(ai.empire, other.empire);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Check if we should try to demand their surrender
|
|
if(other.defeatable >= 1.0 / ai.behavior.surrenderMinStrength && other.warTaken >= warPoints * 0.1) {
|
|
if(log)
|
|
ai.print("Demand surrender.", emp=other.empire);
|
|
demandSurrender(ai.empire, other.empire);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Check if we should try to ally with them
|
|
if(!other.atWar && !other.isThreat && other.allyValue >= 3.0) {
|
|
Treaty treaty;
|
|
treaty.addClause(getInfluenceClauseType("AllianceClause"));
|
|
treaty.addClause(getInfluenceClauseType("VisionClause"));
|
|
treaty.addClause(getInfluenceClauseType("MutualDefenseClause"));
|
|
|
|
if(treaty.canInvite(ai.empire, other.empire)) {
|
|
treaty.inviteMask = other.empire.mask;
|
|
|
|
//Generate treaty name
|
|
string genName;
|
|
uint genCount = 0;
|
|
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
|
|
auto@ reg = getSystem(i).object;
|
|
if(reg.TradeMask & (ai.mask | other.empire.mask) != 0) {
|
|
genCount += 1;
|
|
if(randomd() < 1.0 / double(genCount))
|
|
genName = reg.name;
|
|
}
|
|
}
|
|
treaty.name = format(locale::TREATY_NAME_GEN, genName);
|
|
|
|
if(log)
|
|
ai.print("Send alliance offer.", emp=other.empire);
|
|
createTreaty(ai.empire, treaty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool shouldPeace(Relation@ other, bool isOffer = false) {
|
|
bool accept = false;
|
|
if(other.aggressive) {
|
|
//We're trying to conquer these people, don't accept peace unless
|
|
//we're fighting someone scarier or we're losing
|
|
double otherWar = 0.0;
|
|
uint otherInd = uint(-1);
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ rel = relations[i];
|
|
if(rel is null || rel is other)
|
|
continue;
|
|
if(rel.empire.mask & other.alliedTo != 0)
|
|
continue;
|
|
if(!rel.atWar)
|
|
continue;
|
|
otherWar = max(otherWar, rel.defeatable);
|
|
otherInd = i;
|
|
}
|
|
|
|
if(otherInd != uint(-1) && otherWar < other.defeatable) {
|
|
accept = true;
|
|
if(!relations[otherInd].aggressive)
|
|
relations[otherInd].aggressive = otherWar >= ai.behavior.aggressiveWarOverkill;
|
|
}
|
|
else if(other.defeatable < 0.25) {
|
|
accept = true;
|
|
}
|
|
}
|
|
else {
|
|
//We don't have any ~particular qualms with these people, peace should be good
|
|
if(!isOffer) {
|
|
if(other.defeatable < 0.5 || other.hate < 50.0)
|
|
accept = true;
|
|
}
|
|
}
|
|
return accept;
|
|
}
|
|
|
|
void turn() override {
|
|
if(log) {
|
|
ai.print("Relations Report on Empires:");
|
|
ai.print(" war points: "+warPoints);
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ rel = relations[i];
|
|
if(rel is null)
|
|
continue;
|
|
ai.print(" "+ai.pad(rel.empire.name, 15)
|
|
+" war: "+ai.pad(rel.atWar+" / "+rel.aggressive, 15)
|
|
+" threat: "+ai.pad(""+rel.isThreat, 8)
|
|
+" defeatable: "+ai.pad(toString(rel.defeatable,2), 8)
|
|
+" hate: "+ai.pad(toString(rel.hate,0), 8)
|
|
+" ally value: "+ai.pad(toString(rel.allyValue,1), 8)
|
|
+" taken: "+ai.pad(toString(rel.warTaken,1), 8)
|
|
+" lost: "+ai.pad(toString(rel.warLost,1), 8)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
AIComponent@ createRelations() {
|
|
return Relations();
|
|
}
|
|
|
|
void relationRecordLost(AI& ai, Empire& emp, Object@ obj) {
|
|
cast<Relations>(ai.relations).recordLostTo(emp, obj);
|
|
}
|