3389 lines
82 KiB
ActionScript
3389 lines
82 KiB
ActionScript
import settings.game_settings;
|
|
from empire_ai.EmpireAI import AIController;
|
|
from saving import SaveVersion;
|
|
import biomes;
|
|
import orbitals;
|
|
import attributes;
|
|
import util.convar;
|
|
import notifications;
|
|
import regions.regions;
|
|
import constructible;
|
|
import designs;
|
|
from util.design_export import getBestHull;
|
|
import util.random_designs;
|
|
import int getAbilityID(const string& ident) from "abilities";
|
|
import int getTraitID(const string&) from "traits";
|
|
|
|
const double maxAIFrame = 0.001;
|
|
|
|
const float gotPlanetWill = 1.25f;
|
|
const float lostPlanetWill = 1.f;
|
|
//Per size of flagship
|
|
const float gotFleetWill = 0.05f;
|
|
const float lostFleetWill = 0.02f;
|
|
|
|
//Percent of willpower remaining after 3 minute cycle
|
|
const double willDecayPerBudget = 0.55;
|
|
|
|
ConVar profile_ai("profile_ai", 0.0);
|
|
|
|
#include "empire_ai/include/bai_act_colonize.as"
|
|
#include "empire_ai/include/bai_act_resources.as"
|
|
#include "empire_ai/include/bai_act_fleets.as"
|
|
#include "empire_ai/include/bai_act_strategy.as"
|
|
#include "empire_ai/include/bai_act_influence.as"
|
|
#include "empire_ai/include/bai_act_diplomacy.as"
|
|
#include "include/resource_constants.as"
|
|
|
|
from influence_global import sendPeaceOffer, createTreaty;
|
|
|
|
const uint unownedMask = 1;
|
|
|
|
enum ActionType {
|
|
ACT_Plan,
|
|
ACT_FindIdle,
|
|
ACT_Colonize,
|
|
ACT_ColonizeRes,
|
|
ACT_Explore,
|
|
ACT_Build,
|
|
ACT_Improve,
|
|
ACT_Trade,
|
|
ACT_Expend,
|
|
ACT_Budget,
|
|
ACT_Vote,
|
|
ACT_Combat,
|
|
ACT_War,
|
|
ACT_Defend,
|
|
ACT_Expand,
|
|
ACT_Building,
|
|
ACT_BuildOrbital,
|
|
ACT_Design,
|
|
ACT_Populate,
|
|
ACT_ManageFactories,
|
|
ACT_ManagePressureResources,
|
|
ACT_Diplomacy, //21
|
|
|
|
STRAT_Military = 32,
|
|
STRAT_Influence,
|
|
|
|
ACT_BIT_OFFSET = 58,
|
|
};
|
|
|
|
enum AIDifficulty {
|
|
DIFF_Trivial = 0,
|
|
DIFF_Easy = 1,
|
|
DIFF_Medium = 2,
|
|
DIFF_Hard = 3,
|
|
DIFF_Max = 4,
|
|
};
|
|
|
|
enum AIBehavior {
|
|
AIB_IgnorePlayer = 0x1,
|
|
AIB_IgnoreAI = 0x2,
|
|
AIB_QuickToWar = 0x4,
|
|
};
|
|
|
|
enum AICheats {
|
|
AIC_Vision = 0x1,
|
|
AIC_Resources = 0x2,
|
|
};
|
|
|
|
enum FleetType {
|
|
FT_Scout,
|
|
FT_Combat,
|
|
FT_Carrier,
|
|
FT_Titan,
|
|
|
|
FT_Mothership,
|
|
FT_Slipstream,
|
|
|
|
FT_INVALID
|
|
};
|
|
|
|
enum SpendFlags {
|
|
SF_Borrow = 1
|
|
};
|
|
|
|
interface Action {
|
|
int64 get_hash() const;
|
|
ActionType get_actionType() const;
|
|
string get_state() const;
|
|
//Returns true if the action is finished
|
|
bool perform(BasicAI@);
|
|
void save(BasicAI@, SaveFile& msg);
|
|
void postLoad(BasicAI@);
|
|
}
|
|
|
|
interface ObjectReceiver : Action {
|
|
bool giveObject(BasicAI@ ai, Object@);
|
|
}
|
|
|
|
final class AsteroidCache {
|
|
Asteroid@ asteroid;
|
|
array<int> resources;
|
|
bool cached = false;
|
|
|
|
void cache() {
|
|
if(cached)
|
|
return;
|
|
cached = true;
|
|
resources.length = asteroid.getAvailableCount();
|
|
for(uint i = 0, cnt = resources.length; i < cnt; ++i)
|
|
resources[i] = asteroid.getAvailable(i);
|
|
}
|
|
}
|
|
|
|
final class PlanRegion {
|
|
Region@ region;
|
|
vec3d center;
|
|
double radius = 900.0;
|
|
|
|
double lastSeen = -1e3;
|
|
uint planetMask = 0;
|
|
|
|
array<Planet@> planets, plRecord;
|
|
array<int> planetResources;
|
|
array<AsteroidCache@> resourceAsteroids;
|
|
array<Artifact@> artifacts;
|
|
array<Orbital@> orbitals;
|
|
array<Anomaly@> anomalies;
|
|
//Ship strength per-empire (sqrt space)
|
|
array<double> strengths(getEmpireCount());
|
|
|
|
const SystemDesc@ cachedSystem;
|
|
|
|
const SystemDesc@ get_system() {
|
|
if(cachedSystem !is null)
|
|
return cachedSystem;
|
|
if(region is null)
|
|
return null;
|
|
@cachedSystem = getSystem(region);
|
|
return cachedSystem;
|
|
}
|
|
|
|
PlanRegion(Object@ Focus) {
|
|
while(Focus.region !is null)
|
|
@Focus = Focus.region;
|
|
@region = cast<Region>(Focus);
|
|
center = region.position;
|
|
radius = region.radius;
|
|
}
|
|
|
|
PlanRegion(SaveFile& file) {
|
|
file >> region;
|
|
file >> center;
|
|
if(file >= SV_0037)
|
|
file >> lastSeen;
|
|
|
|
file >> planetMask;
|
|
|
|
uint count = 0;
|
|
|
|
file >> count;
|
|
planets.length = count;
|
|
planetResources.length = count;
|
|
for(uint i = 0; i < count; ++i) {
|
|
file >> planets[i];
|
|
planetResources[i] = planets[i].primaryResourceType;
|
|
}
|
|
|
|
plRecord = planets;
|
|
|
|
file >> count;
|
|
resourceAsteroids.length = count;
|
|
for(uint i = 0; i < count; ++i) {
|
|
AsteroidCache cache;
|
|
file >> cache.asteroid;
|
|
@resourceAsteroids[i] = cache;
|
|
}
|
|
|
|
if(file >= SV_0030) {
|
|
file >> count;
|
|
artifacts.length = count;
|
|
for(uint i = 0; i < count; ++i)
|
|
file >> artifacts[i];
|
|
}
|
|
|
|
for(uint i = 0; i < strengths.length; ++i)
|
|
file >> strengths[i];
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
file << region;
|
|
file << center;
|
|
file << lastSeen;
|
|
|
|
file << planetMask;
|
|
file << uint(planets.length);
|
|
for(uint i = 0; i < planets.length; ++i)
|
|
file << planets[i];
|
|
|
|
file << uint(resourceAsteroids.length);
|
|
for(uint i = 0; i < resourceAsteroids.length; ++i)
|
|
file << resourceAsteroids[i].asteroid;
|
|
|
|
file << uint(artifacts.length);
|
|
for(uint i = 0; i < artifacts.length; ++i)
|
|
file << artifacts[i];
|
|
|
|
for(uint i = 0; i < strengths.length; ++i)
|
|
file << strengths[i];
|
|
}
|
|
|
|
double get_age() const {
|
|
return gameTime - lastSeen;
|
|
}
|
|
|
|
//Checks for various changes to remembered planet states
|
|
// Returns true if all planets were in memory
|
|
bool useMemory(BasicAI@ ai) {
|
|
bool anyMemory = false, allInMemory = true;
|
|
uint plOwnerMask = 0;
|
|
|
|
//TODO: Have regions track when their planet list changes
|
|
uint plCount = region.planetCount;
|
|
if(plRecord.length != plCount) {
|
|
plRecord.length = 0;
|
|
for(uint i = 0, cnt = plCount; i < cnt; ++i) {
|
|
Planet@ pl = region.planets[i];
|
|
if(pl !is null && pl.valid)
|
|
plRecord.insertLast(pl);
|
|
}
|
|
}
|
|
|
|
for(uint i = 0, cnt = plRecord.length; i < cnt; ++i) {
|
|
Planet@ pl = plRecord[i];
|
|
if(pl is null || !pl.valid)
|
|
break;
|
|
|
|
if(!pl.isKnownTo(ai.empire)) {
|
|
allInMemory = false;
|
|
continue;
|
|
}
|
|
|
|
Empire@ owner = pl.visibleOwnerToEmp(ai.empire);
|
|
if(owner is null)
|
|
continue;
|
|
|
|
if(!anyMemory) {
|
|
planets.length = 0;
|
|
planetResources.length = 0;
|
|
}
|
|
|
|
anyMemory = true;
|
|
plOwnerMask |= owner.mask;
|
|
planets.insertLast(pl);
|
|
planetResources.insertLast(pl.primaryResourceType);
|
|
}
|
|
|
|
if(anyMemory)
|
|
planetMask = plOwnerMask;
|
|
return allInMemory;
|
|
}
|
|
|
|
//Search the system for all objects we can track and remember, and update strength ratings
|
|
array<Object@>@ scout(BasicAI@ ai, bool verbose = false) {
|
|
ai.didTickScan = true;
|
|
auto@ empire = ai.empire;
|
|
|
|
array<Object@>@ objs = search();
|
|
lastSeen = gameTime;
|
|
|
|
plRecord.length = 0;
|
|
planets.length = 0;
|
|
planetResources.length = 0;
|
|
resourceAsteroids.length = 0;
|
|
artifacts.length = 0;
|
|
orbitals.length = 0;
|
|
anomalies.length = 0;
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i)
|
|
strengths[i] = 0.0;
|
|
|
|
planetMask = 0;
|
|
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Object@ obj = objs[i];
|
|
if(obj.region !is region)
|
|
continue;
|
|
if(!obj.isKnownTo(empire) && !obj.isVisibleTo(empire)) {
|
|
if(obj.isPlanet)
|
|
plRecord.insertLast(cast<Planet>(obj));
|
|
continue;
|
|
}
|
|
|
|
//if(verbose)
|
|
// error(obj.name);
|
|
|
|
switch(obj.type) {
|
|
case OT_Planet:
|
|
{
|
|
Planet@ pl = cast<Planet>(obj);
|
|
plRecord.insertLast(pl);
|
|
planets.insertLast(pl);
|
|
planetResources.insertLast(pl.primaryResourceType);
|
|
Empire@ owner = pl.visibleOwnerToEmp(empire);
|
|
planetMask |= owner.mask;
|
|
if(owner.valid)
|
|
strengths[owner.index] += sqrt(pl.getFleetStrength());
|
|
if(owner is empire && !ai.knownPlanets.contains(pl.id)) {
|
|
ai.addPlanet(pl);
|
|
ai.markAsColony(region);
|
|
}
|
|
}
|
|
break;
|
|
case OT_Asteroid: {
|
|
Empire@ owner = obj.owner;
|
|
if(owner is defaultEmpire && obj.valid && cast<Asteroid>(obj).getAvailableCount() != 0) {
|
|
AsteroidCache cache;
|
|
@cache.asteroid = cast<Asteroid>(obj);
|
|
cache.cache();
|
|
|
|
resourceAsteroids.insertLast(cache);
|
|
}
|
|
if(owner is empire && !ai.knownAsteroids.contains(obj.id))
|
|
ai.addAsteroid(cast<Asteroid>(obj));
|
|
} break;
|
|
case OT_Ship:
|
|
{
|
|
Empire@ owner = obj.owner;
|
|
if(owner.valid && obj.hasLeaderAI)
|
|
strengths[owner.index] += sqrt(obj.getFleetStrength());
|
|
} break;
|
|
case OT_Artifact:
|
|
artifacts.insertLast(cast<Artifact>(obj));
|
|
break;
|
|
case OT_Anomaly:
|
|
anomalies.insertLast(cast<Anomaly>(obj));
|
|
break;
|
|
case OT_Orbital:
|
|
{
|
|
orbitals.insertLast(cast<Orbital>(obj));
|
|
Empire@ owner = obj.owner;
|
|
if(owner.valid) {
|
|
Orbital@ orb = cast<Orbital>(obj);
|
|
double hp = orb.maxHealth + orb.maxArmor;
|
|
double dps = orb.dps * max(orb.efficiency, 0.75);
|
|
if(orb.hasLeaderAI) {
|
|
hp += obj.getFleetHP();
|
|
dps += orb.getFleetDPS();
|
|
}
|
|
strengths[owner.index] += sqrt(hp * dps);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
return objs;
|
|
}
|
|
|
|
array<Object@>@ search(uint mask = 0) {
|
|
vec3d bound(radius);
|
|
return findInBox(center - bound, center + bound, mask);
|
|
}
|
|
|
|
bool hasEnemies(Empire& against) {
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
auto@ emp = getEmpire(i);
|
|
if(!emp.valid)
|
|
continue;
|
|
if(against.isHostile(emp) && strengths[emp.index] > 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
final class SysSearch {
|
|
int start = -1;
|
|
int index = 0;
|
|
|
|
void reset() {
|
|
start = -1;
|
|
index = 0;
|
|
}
|
|
|
|
void save(SaveFile& msg) {
|
|
msg << start;
|
|
if(start != -1)
|
|
msg << index;
|
|
}
|
|
|
|
void load(SaveFile& msg) {
|
|
msg >> start;
|
|
if(start != -1)
|
|
msg >> index;
|
|
}
|
|
|
|
PlanRegion@ next(array<PlanRegion@>& sysList) {
|
|
int count = sysList.length;
|
|
if(start == -1)
|
|
start = randomi(0,count-1);
|
|
|
|
if(index >= count)
|
|
return null;
|
|
|
|
int ind = (start + index) % count;
|
|
index += 1;
|
|
|
|
return sysList[ind];
|
|
}
|
|
|
|
PlanRegion@ random(array<PlanRegion@>& sysList) {
|
|
int count = sysList.length;
|
|
if(count == 0)
|
|
return null;
|
|
else
|
|
return sysList[randomi(0,count-1)];
|
|
}
|
|
|
|
//Searches the next region, returning null if none are remaining
|
|
array<Object@>@ search(array<PlanRegion@>& sysList, uint mask = 0) {
|
|
PlanRegion@ region = next(sysList);
|
|
if(region !is null)
|
|
return region.search(mask);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
//Searches a random region
|
|
array<Object@>@ searchRandom(array<PlanRegion@>& sysList, uint mask = 0) {
|
|
PlanRegion@ region = random(sysList);
|
|
if(region !is null)
|
|
return region.search(mask);
|
|
else
|
|
return array<Object@>();
|
|
}
|
|
};
|
|
|
|
final class PlanetList {
|
|
array<Planet@> idle, used;
|
|
array<Object@> purpose;
|
|
|
|
uint get_length() {
|
|
return idle.length + used.length;
|
|
}
|
|
|
|
Planet@ get_planets(uint index) {
|
|
if(index < idle.length)
|
|
return idle[index];
|
|
index -= idle.length;
|
|
if(index >= used.length)
|
|
return null;
|
|
return used[index];
|
|
}
|
|
|
|
Object@ get_planetPurpose(uint index) {
|
|
if(index < idle.length)
|
|
return null;
|
|
index -= idle.length;
|
|
if(index >= used.length)
|
|
return null;
|
|
return purpose[index];
|
|
}
|
|
|
|
bool markIdle(Planet@ pl, bool onlyIfMissing = false) {
|
|
bool wasNew = true;
|
|
if(idle.find(pl) < 0) {
|
|
int ind = used.find(pl);
|
|
if(ind >= 0) {
|
|
if(onlyIfMissing)
|
|
return false;
|
|
|
|
wasNew = false;
|
|
used.removeAt(ind);
|
|
purpose.removeAt(ind);
|
|
}
|
|
idle.insertLast(pl);
|
|
}
|
|
|
|
return wasNew;
|
|
}
|
|
|
|
void markUsed(Planet@ pl, Object@ goal) {
|
|
int ind = used.find(pl);
|
|
if(ind < 0) {
|
|
idle.remove(pl);
|
|
used.insertLast(pl);
|
|
purpose.insertLast(goal);
|
|
}
|
|
else {
|
|
@purpose[ind] = goal;
|
|
}
|
|
}
|
|
|
|
Object@ getPurpose(Planet@ pl) {
|
|
int ind = used.find(pl);
|
|
if(ind >= 0)
|
|
return purpose[ind];
|
|
else
|
|
return null;
|
|
}
|
|
|
|
void remove(Planet@ pl) {
|
|
int index = idle.find(pl);
|
|
if(index >= 0) {
|
|
idle.removeAt(index);
|
|
return;
|
|
}
|
|
|
|
index = used.find(pl);
|
|
if(index >= 0) {
|
|
used.removeAt(index);
|
|
purpose.removeAt(index);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void validate(BasicAI@ ai, Empire@ owner, const ResourceType@ type) {
|
|
uint resType = uint(-1);
|
|
uint usedLevel = 0;
|
|
if(type !is null) {
|
|
usedLevel = type.level;
|
|
resType = type.id;
|
|
}
|
|
|
|
for(int i = idle.length - 1; i >= 0; --i) {
|
|
Planet@ pl = idle[i];
|
|
if(pl.owner !is owner || !pl.valid) {
|
|
ai.willpower -= lostPlanetWill;
|
|
idle.removeAt(i);
|
|
ai.knownPlanets.erase(pl.id);
|
|
if(pl.valid && pl.owner.valid) {
|
|
auto@ relation = ai.getRelation(pl.owner);
|
|
relation.offense = RA_Planet;
|
|
relation.standing -= 15;
|
|
}
|
|
}
|
|
else if(pl.Population < 1.0) {
|
|
ai.addIdle(ai.requestColony(pl, execute=false));
|
|
}
|
|
else if(pl.primaryResourceType != resType) {
|
|
idle.removeAt(i);
|
|
ai.addPlanet(pl);
|
|
}
|
|
}
|
|
|
|
for(int i = used.length - 1; i >= 0; --i) {
|
|
Planet@ pl = used[i];
|
|
if(pl.owner !is owner || !pl.valid) {
|
|
ai.willpower -= lostPlanetWill;
|
|
used.removeAt(i);
|
|
purpose.removeAt(i);
|
|
ai.knownPlanets.erase(pl.id);
|
|
}
|
|
else if(pl.resourceLevel < usedLevel) {
|
|
if(purpose[i] !is pl) {
|
|
pl.exportResource(owner, 0, null);
|
|
used.removeAt(i);
|
|
purpose.removeAt(i);
|
|
idle.insertLast(pl);
|
|
ai.freePlanetImports(pl);
|
|
}
|
|
}
|
|
else if(pl.primaryResourceType != resType) {
|
|
used.removeAt(i);
|
|
auto@ use = purpose[i];
|
|
purpose.removeAt(i);
|
|
ai.addPlanet(pl, use);
|
|
}
|
|
else {
|
|
Object@ goal = purpose[i];
|
|
if(!goal.valid || goal.owner !is owner || goal.region.getTerritory(owner) !is pl.region.getTerritory(owner)) {
|
|
pl.exportResource(owner, 0, null);
|
|
used.removeAt(i);
|
|
purpose.removeAt(i);
|
|
idle.insertLast(pl);
|
|
ai.freePlanetImports(pl);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(used.length > 0) {
|
|
uint index = randomi(0,used.length - 1);
|
|
Planet@ pl = used[index];
|
|
Object@ dest = purpose[index];
|
|
|
|
if(pl !is dest && pl.level >= usedLevel && !pl.isPrimaryDestination(dest)) {
|
|
pl.exportResource(owner, 0, null);
|
|
used.removeAt(index);
|
|
purpose.removeAt(index);
|
|
idle.insertLast(pl);
|
|
ai.freePlanetImports(pl);
|
|
}
|
|
}
|
|
}
|
|
|
|
void save(SaveFile& msg) {
|
|
uint count = idle.length;
|
|
msg << count;
|
|
for(uint i = 0; i < count; ++i)
|
|
msg << idle[i];
|
|
|
|
count = used.length;
|
|
msg << count;
|
|
for(uint i = 0; i < count; ++i) {
|
|
msg << used[i];
|
|
msg << purpose[i];
|
|
}
|
|
}
|
|
|
|
void load(BasicAI@ ai, SaveFile& msg) {
|
|
Planet@ pl;
|
|
uint count = 0;
|
|
msg >> count;
|
|
idle.reserve(count);
|
|
for(uint i = 0; i < count; ++i) {
|
|
msg >> pl;
|
|
if(pl is null)
|
|
continue;
|
|
idle.insertLast(pl);
|
|
ai.knownPlanets.insert(pl.id);
|
|
}
|
|
|
|
Object@ other;
|
|
count = 0;
|
|
msg >> count;
|
|
used.reserve(count);
|
|
purpose.reserve(count);
|
|
for(uint i = 0; i < count; ++i) {
|
|
msg >> pl; msg >> other;
|
|
if(pl is null)
|
|
continue;
|
|
used.insertLast(pl);
|
|
ai.knownPlanets.insert(pl.id);
|
|
purpose.insertLast(other);
|
|
}
|
|
}
|
|
};
|
|
|
|
enum AIResourceType {
|
|
RT_Water,
|
|
RT_Food,
|
|
RT_LevelZero,
|
|
RT_LevelOne,
|
|
RT_LevelTwo,
|
|
RT_LevelThree,
|
|
RT_LaborZero,
|
|
RT_PressureResource,
|
|
|
|
RT_COUNT
|
|
};
|
|
|
|
final class Request {
|
|
Region@ region;
|
|
double time;
|
|
};
|
|
|
|
enum RecentAction {
|
|
RA_None,
|
|
RA_War,
|
|
RA_Influence,
|
|
RA_Planet,
|
|
RA_Donation,
|
|
};
|
|
|
|
final class Relationship {
|
|
Empire@ emp;
|
|
int standing = 0;
|
|
bool bordered = false, allied = false, war = false, brokeAlliance = false;
|
|
int relStrength = 0;
|
|
RecentAction lastOffense = RA_None;
|
|
double offenseTime = 0;
|
|
|
|
void set_offense(RecentAction act) {
|
|
lastOffense = act;
|
|
offenseTime = gameTime;
|
|
}
|
|
};
|
|
|
|
enum SupportTask {
|
|
ST_Filler,
|
|
ST_AntiSupport,
|
|
ST_AntiFlagship,
|
|
ST_Tank,
|
|
ST_Supplies,
|
|
|
|
ST_COUNT
|
|
};
|
|
|
|
enum FlagshipTask {
|
|
FST_Scout,
|
|
FST_Combat,
|
|
FST_SuperHeavy,
|
|
FST_Mothership,
|
|
FST_Slipstream,
|
|
|
|
FST_COUNT,
|
|
|
|
FST_COUNT_OLD1 = FST_Mothership,
|
|
FST_COUNT_OLD2 = FST_Slipstream
|
|
};
|
|
|
|
enum StationTask {
|
|
STT_LightDefense,
|
|
STT_HeavyDefense,
|
|
|
|
STT_COUNT
|
|
};
|
|
|
|
enum TreatyClauses {
|
|
TC_Alliance = 0x1,
|
|
TC_Trade = 0x2,
|
|
TC_MutualDefense = 0x4,
|
|
TC_Vision = 0x8,
|
|
};
|
|
|
|
enum FTLType {
|
|
FTL_Hyperdrive,
|
|
FTL_Jumpdrive,
|
|
FTL_Fling,
|
|
FTL_Gate,
|
|
FTL_Slipstream
|
|
};
|
|
|
|
final class BasicAI : AIController {
|
|
Empire@ empire;
|
|
Planet@ homeworld;
|
|
|
|
bool isMachineRace = false, usesMotherships = false, needsStalks = false, needsAltars = false;
|
|
bool isFrugal = false;
|
|
bool cannotDesign = false, needsMainframes = false;
|
|
FTLType ftl = FTL_Hyperdrive;
|
|
const OrbitalModule@ mainframeMod;
|
|
|
|
PlanRegion@ protect;
|
|
|
|
bool debug = false, profile = false, logWar = false;
|
|
uint printDepth = 0;
|
|
string dbgMsg;
|
|
vec3d focus;
|
|
WriteFile@ log;
|
|
|
|
array<Empire@> enemies;
|
|
array<Relationship> relations(32);
|
|
Relationship pirateRelation;
|
|
uint allyMask = 0;
|
|
|
|
//Start at a reasonable willpower
|
|
float willpower = gotPlanetWill * 3.f;
|
|
|
|
array<double> treatyWaits(getEmpireCount(), gameTime + randomd(20.0,60.0));
|
|
|
|
double lastPing = gameTime - randomd(65.0,90.0);
|
|
Mutex reqLock;
|
|
array<Request> requests, queuedRequests;
|
|
|
|
array<PlanRegion@> ourSystems, exploredSystems, otherSystems;
|
|
array<PlanetList> planetsByResource(getResourceCount());
|
|
set_int knownPlanets;
|
|
array<Asteroid@> idleAsteroids;
|
|
array<Asteroid@> usedAsteroids;
|
|
set_int knownAsteroids;
|
|
|
|
array<Artifact@> artifacts(getArtifactTypeCount());
|
|
array<Ship@> scoutFleets, combatFleets, motherships, slipstreamers, untrackedFleets;
|
|
set_int knownLeaders;
|
|
map systems;
|
|
set_int knownSystems;
|
|
|
|
double lastSlipstream = -300.0;
|
|
|
|
array<Orbital@> orbitals;
|
|
array<Object@> factories;
|
|
set_int knownFactories;
|
|
|
|
array<const Design@> dsgSupports(ST_COUNT), dsgFlagships(FST_COUNT), dsgStations(STT_COUNT);
|
|
|
|
array<Object@> revenantParts;
|
|
|
|
//AI Skill at various mechanics
|
|
int skillEconomy = DIFF_Medium;
|
|
int skillCombat = DIFF_Medium;
|
|
int skillDiplo = DIFF_Medium;
|
|
int skillTech = DIFF_Medium;
|
|
int skillScout = DIFF_Medium;
|
|
|
|
uint behaviorFlags = 0;
|
|
uint cheatFlags = 0;
|
|
int cheatLevel = 0;
|
|
|
|
int getDifficultyLevel() {
|
|
if(skillEconomy <= DIFF_Easy)
|
|
return behaviorFlags & AIB_IgnorePlayer != 0 ? 0 : 1;
|
|
else if(skillEconomy == DIFF_Medium)
|
|
return 2;
|
|
else if(cheatLevel > 0) {
|
|
if(cheatLevel >= 12)
|
|
return 7;
|
|
else if(cheatLevel >= 11)
|
|
return 6;
|
|
return 5;
|
|
}
|
|
else if(behaviorFlags & AIB_IgnoreAI != 0)
|
|
return 4;
|
|
return 3;
|
|
}
|
|
|
|
map actions;
|
|
Action@ head;
|
|
array<Action@> idle;
|
|
set_int idles;
|
|
int thoughtCycle = 0;
|
|
|
|
//Track when the AI has performed a scout of a system, to avoid too much per-tick load
|
|
bool didTickScan = false;
|
|
|
|
double timeSinceLastExpand = 0.0;
|
|
|
|
uint nextNotification = 0;
|
|
|
|
array<array<int>> resLists(RT_COUNT), resListsExportable(RT_COUNT);
|
|
const ResourceClass@ foods = getResourceClass("Food");
|
|
const ResourceClass@ waters = getResourceClass("WaterType");
|
|
|
|
void buildCommonLists() {
|
|
pirateRelation.standing = -100;
|
|
|
|
isMachineRace = empire.hasTrait(getTraitID("Mechanoid"));
|
|
usesMotherships = empire.hasTrait(getTraitID("StarChildren"));
|
|
needsStalks = empire.hasTrait(getTraitID("Verdant"));
|
|
needsMainframes = empire.hasTrait(getTraitID("Linked"));
|
|
cannotDesign = empire.hasTrait(getTraitID("Verdant"));
|
|
needsAltars = empire.hasTrait(getTraitID("Devout"));
|
|
isFrugal = empire.hasTrait(getTraitID("Frugal"));
|
|
@mainframeMod = getOrbitalModule("Mainframe");
|
|
|
|
if(empire.hasTrait(getTraitID("Hyperdrive")))
|
|
ftl = FTL_Hyperdrive;
|
|
else if(empire.hasTrait(getTraitID("Slipstream")))
|
|
ftl = FTL_Slipstream;
|
|
else if(empire.hasTrait(getTraitID("Gate")))
|
|
ftl = FTL_Gate;
|
|
else if(empire.hasTrait(getTraitID("Fling")))
|
|
ftl = FTL_Fling;
|
|
else if(empire.hasTrait(getTraitID("Jumpdrive")))
|
|
ftl = FTL_Jumpdrive;
|
|
|
|
auto@ r = resLists, e = resListsExportable;
|
|
|
|
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
|
|
auto@ res = getResource(i);
|
|
uint type = classifyResource(res);
|
|
if(type == RT_COUNT)
|
|
continue;
|
|
r[type].insertLast(res.id);
|
|
if(res.exportable && !(res.artificial && type == RT_Water))
|
|
e[type].insertLast(res.id);
|
|
}
|
|
}
|
|
|
|
uint classifyResource(const ResourceType@ res) {
|
|
if(res.mode == RM_NonRequirement)
|
|
return RT_COUNT;
|
|
|
|
switch(res.level) {
|
|
case 0:
|
|
if(res.cls is waters) {
|
|
return RT_Water;
|
|
}
|
|
else if(res.cls is foods) {
|
|
return RT_Food;
|
|
}
|
|
else {
|
|
bool isLabor = res.tilePressure[TR_Labor] > 0;
|
|
if(isLabor)
|
|
return RT_LaborZero;
|
|
else if(res.totalPressure > 0)
|
|
return RT_PressureResource;
|
|
else
|
|
return RT_LevelZero;
|
|
}
|
|
case 1:
|
|
return RT_LevelOne;
|
|
case 2:
|
|
return RT_LevelTwo;
|
|
case 3:
|
|
return RT_LevelThree;
|
|
}
|
|
|
|
return RT_COUNT;
|
|
}
|
|
|
|
string getOpinionOf(Empire& emp, Empire@ other) {
|
|
auto@ relation = getRelation(other);
|
|
string response;
|
|
|
|
if(relation.standing > 10) {
|
|
if(relation.standing > 50)
|
|
response = locale::AI_STANDING_VERY_POSITIVE;
|
|
else
|
|
response = locale::AI_STANDING_POSITIVE;
|
|
}
|
|
else if(relation.standing < -10) {
|
|
if(relation.standing < -50)
|
|
response = locale::AI_STANDING_VERY_NEGATIVE;
|
|
else
|
|
response = locale::AI_STANDING_NEGATIVE;
|
|
}
|
|
else {
|
|
if(randomi(0,100) == 0)
|
|
response = locale::AI_STANDING_TRUE_NEUTRAL;
|
|
else
|
|
response = locale::AI_STANDING_NEUTRAL;
|
|
}
|
|
|
|
if(relation.offenseTime > gameTime - 180.0) {
|
|
switch(relation.lastOffense) {
|
|
case RA_War: response += " " + locale::AI_STANDING_OFFENSE_WAR; break;
|
|
case RA_Influence: response += " " + locale::AI_STANDING_OFFENSE_INFLUENCE; break;
|
|
case RA_Planet: response += " " + locale::AI_STANDING_OFFENSE_PLANET; break;
|
|
case RA_Donation: response += " " + locale::AI_STANDING_GLAD_DONATION; break;
|
|
}
|
|
}
|
|
|
|
if(relation.war) {
|
|
if(relation.relStrength > 0)
|
|
response += " " + locale::AI_STANDING_WAR_FEAR;
|
|
else
|
|
response += " " + locale::AI_STANDING_WAR;
|
|
}
|
|
else if(relation.allied) {
|
|
response += " " + locale::AI_STANDING_ALLIES;
|
|
}
|
|
|
|
if(relation.bordered && !relation.allied) {
|
|
if(!relation.war) {
|
|
if(timeSinceLastExpand > 7.5 * 60.0)
|
|
response += " " + locale::AI_STANDING_EXPANDING;
|
|
else
|
|
response += " " + locale::AI_STANDING_BORDER;
|
|
}
|
|
else {
|
|
response += " " + locale::AI_STANDING_BORDER_WAR;
|
|
}
|
|
}
|
|
|
|
if(ignoreEmpire(other) && !relation.war) {
|
|
if(relation.war || relation.allied)
|
|
response += " " + locale::AI_STANDING_IGNORE;
|
|
}
|
|
else if(behaviorFlags & locale::AIB_QuickToWar != 0) {
|
|
response += " " + locale::AI_STANDING_WAAGH;
|
|
}
|
|
else if(treatyWaits[other.index] > gameTime) {
|
|
response += " " + locale::AI_STANDING_WAIT;
|
|
}
|
|
else if((emp.points.value + 150) * 5 < other.points.value + 150) {
|
|
response += " " + locale::AI_STANDING_GIVE_UP;
|
|
}
|
|
|
|
if(cheatFlags != 0) {
|
|
response += " " + locale::AI_STANDING_CHEATS;
|
|
}
|
|
|
|
if(skillDiplo < DIFF_Medium) {
|
|
response += " " + locale::AI_STANDING_EASY;
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
int getStandingTo(Empire& emp, Empire@ other) {
|
|
return getRelation(other).standing / 10;
|
|
}
|
|
|
|
void proposeTreaty(Empire@ toEmpire, uint clauseMask) {
|
|
Treaty treaty;
|
|
|
|
if(clauseMask & TC_Trade != 0)
|
|
treaty.addClause(getInfluenceClauseType("TradeClause"));
|
|
if(clauseMask & TC_Alliance != 0)
|
|
treaty.addClause(getInfluenceClauseType("AllianceClause"));
|
|
if(clauseMask & TC_MutualDefense != 0)
|
|
treaty.addClause(getInfluenceClauseType("MutualDefenseClause"));
|
|
if(clauseMask & TC_Vision != 0)
|
|
treaty.addClause(getInfluenceClauseType("VisionClause"));
|
|
|
|
//Make sure we can invite to this treaty
|
|
if(!treaty.canInvite(empire, toEmpire))
|
|
return;
|
|
treaty.inviteMask = toEmpire.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 & (playerEmpire.mask | toEmpire.mask) != 0) {
|
|
genCount += 1;
|
|
if(randomd() < 1.0 / double(genCount))
|
|
genName = reg.name;
|
|
}
|
|
}
|
|
treaty.name = format(locale::TREATY_NAME_GEN, genName);
|
|
|
|
//Create the treaty globally
|
|
createTreaty(empire, treaty);
|
|
}
|
|
|
|
array<int>@ getResourceList(AIResourceType type, bool onlyExportable = false) {
|
|
if(uint(type) >= resLists.length)
|
|
return null;
|
|
|
|
if(onlyExportable)
|
|
return resListsExportable[type];
|
|
else
|
|
return resLists[type];
|
|
}
|
|
|
|
array<PlanRegion@>@ getBorder() {
|
|
array<PlanRegion@> border;
|
|
set_int found;
|
|
uint mask = empire.mask;
|
|
|
|
auto@ ours = ourSystems;
|
|
for(uint i = 0, cnt = ours.length; i < cnt; ++i) {
|
|
auto@ sys = ours[i].system;
|
|
for(uint j = 0, jcnt = sys.adjacent.length; j < jcnt; ++j) {
|
|
auto@ r = findSystem(getSystem(sys.adjacent[j]).object);
|
|
if(r !is null && r.planetMask & mask == 0 && !found.contains(r.region.id)) {
|
|
found.insert(r.region.id);
|
|
border.insertLast(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
return border;
|
|
}
|
|
|
|
PlanRegion@ getBorderSystem(PlanRegion& region) {
|
|
auto@ sys = region.system;
|
|
uint mask = empire.mask;
|
|
uint off = randomi();
|
|
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
|
|
auto@ r = findSystem(getSystem(sys.adjacent[(off+i) % cnt]).object);
|
|
if(r !is null && r.planetMask & mask == 0)
|
|
return r;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool isBorderSystem(PlanRegion& region) {
|
|
auto@ sys = region.system;
|
|
uint mask = empire.mask;
|
|
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
|
|
auto@ r = findSystem(getSystem(sys.adjacent[i]).object);
|
|
if(r !is null && r.planetMask & mask != 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
vec3d get_aiFocus() {
|
|
return focus;
|
|
}
|
|
|
|
Relationship@ getRelation(Empire@ emp) {
|
|
if(emp is null || !emp.valid || !emp.major)
|
|
return pirateRelation;
|
|
return relations[emp.index];
|
|
}
|
|
|
|
bool ignoreEmpire(Empire@ emp) const {
|
|
if(emp is null)
|
|
return true;
|
|
auto aiType = emp.getAIType();
|
|
if(behaviorFlags & AIB_IgnorePlayer != 0 && aiType == ET_Player)
|
|
return true;
|
|
else if(behaviorFlags & AIB_IgnoreAI != 0 && aiType != ET_Player)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
PlanetList@ get_planets(uint resourceType) {
|
|
return planetsByResource[resourceType];
|
|
}
|
|
|
|
void addAsteroid(Asteroid@ roid) {
|
|
knownAsteroids.insert(roid.id);
|
|
idleAsteroids.insertLast(roid);
|
|
}
|
|
|
|
void markAsteroidUsed(Asteroid@ roid) {
|
|
idleAsteroids.remove(roid);
|
|
usedAsteroids.insertLast(roid);
|
|
}
|
|
|
|
void markAsteroidIdle(Asteroid@ roid) {
|
|
usedAsteroids.remove(roid);
|
|
idleAsteroids.insertLast(roid);
|
|
}
|
|
|
|
void markUsedGeneric(Object@ obj, Object@ purpose) {
|
|
if(obj.isPlanet)
|
|
markPlanetUsed(cast<Planet>(obj), purpose);
|
|
else if(obj.isAsteroid)
|
|
markAsteroidUsed(cast<Asteroid>(obj));
|
|
}
|
|
|
|
void markIdleGeneric(Object@ obj) {
|
|
if(obj.isPlanet)
|
|
markPlanetIdle(cast<Planet>(obj));
|
|
else if(obj.isAsteroid)
|
|
markAsteroidIdle(cast<Asteroid>(obj));
|
|
}
|
|
|
|
void removeAsteroid(Asteroid@ roid) {
|
|
usedAsteroids.remove(roid);
|
|
idleAsteroids.remove(roid);
|
|
knownAsteroids.erase(roid.id);
|
|
}
|
|
|
|
void markPlanetIdle(Planet@ pl, bool onlyIfMissing = false) {
|
|
knownPlanets.insert(pl.id);
|
|
int resType = pl.primaryResourceType;
|
|
if(resType < 0)
|
|
return;
|
|
bool wasNew = planetsByResource[resType].markIdle(pl, onlyIfMissing);
|
|
if(wasNew)
|
|
willpower += gotPlanetWill;
|
|
}
|
|
|
|
void markPlanetUsed(Planet@ pl, Object@ purpose) {
|
|
knownPlanets.insert(pl.id);
|
|
int typeID = pl.primaryResourceType;
|
|
if(typeID < 0)
|
|
return;
|
|
planetsByResource[typeID].markUsed(pl, purpose);
|
|
optimizePlanetImports(pl, getResource(typeID).level);
|
|
}
|
|
|
|
bool isKnownPlanet(Planet@ pl) {
|
|
return knownPlanets.contains(pl.id);
|
|
}
|
|
|
|
void optimizePlanetImports(Planet@ pl, uint forLevel) {
|
|
if(pl is null)
|
|
return;
|
|
array<Resource> avail;
|
|
avail.syncFrom(pl.getAllResources());
|
|
|
|
bool needWater = forLevel > 0;
|
|
uint needFood = forLevel;
|
|
if(isMachineRace) {
|
|
needFood = 0;
|
|
needWater = false;
|
|
}
|
|
uint needLevel1 = 0;
|
|
uint needLevel2 = 0;
|
|
switch(forLevel) {
|
|
case 2:
|
|
needLevel1 = 1;
|
|
break;
|
|
case 3:
|
|
needLevel1 = isFrugal ? 1 : 2;
|
|
needLevel2 = 1;
|
|
break;
|
|
case 4:
|
|
needLevel1 = isFrugal ? 2 : 4;
|
|
needLevel2 = 2;
|
|
break;
|
|
case 5:
|
|
needLevel1 = isFrugal ? 4 : 6;
|
|
needLevel2 = 4;
|
|
break;
|
|
}
|
|
|
|
auto@ resFood = getResourceList(RT_Food);
|
|
auto@ resWater = getResourceList(RT_Water);
|
|
auto@ resLevelOne = getResourceList(RT_LevelOne);
|
|
auto@ resLevelTwo = getResourceList(RT_LevelTwo);
|
|
|
|
for(uint i = 0, cnt = avail.length; i < cnt; ++i) {
|
|
auto@ res = avail[i];
|
|
uint rid = res.type.id, rlevel = res.type.level;
|
|
Object@ source = res.origin;
|
|
if(!res.usable || (source !is null && source.owner !is empire))
|
|
continue;
|
|
|
|
bool needed = true;
|
|
|
|
if(rlevel == 0 && resFood.find(rid) >= 0) {
|
|
if(needFood == 0)
|
|
needed = false;
|
|
else
|
|
needFood -= 1;
|
|
}
|
|
else if(rlevel == 0 && resWater.find(rid) >= 0) {
|
|
if(!needWater)
|
|
needed = false;
|
|
else
|
|
needWater = false;
|
|
}
|
|
else if(rlevel == 1 && resLevelOne.find(rid) >= 0) {
|
|
if(needLevel1 == 0)
|
|
needed = false;
|
|
else
|
|
needLevel1 -= 1;
|
|
}
|
|
else if(rlevel == 2 && resLevelTwo.find(rid) >= 0) {
|
|
if(needLevel2 == 0)
|
|
needed = false;
|
|
else
|
|
needLevel2 -= 1;
|
|
}
|
|
|
|
if(source !is null && source !is pl) {
|
|
if(source.isPlanet) {
|
|
auto@ src = cast<Planet>(source);
|
|
if(needed)
|
|
markPlanetUsed(src, pl);
|
|
else {
|
|
markPlanetIdle(src);
|
|
src.exportResource(empire, 0, null);
|
|
}
|
|
}
|
|
else if(source.isAsteroid) {
|
|
auto@ src = cast<Asteroid>(source);
|
|
if(needed)
|
|
markAsteroidUsed(src);
|
|
else {
|
|
markAsteroidIdle(src);
|
|
src.exportResource(empire, 0, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void usePlanetImports(Planet@ pl, int targetLevel = -1) {
|
|
if(pl is null)
|
|
return;
|
|
array<Resource> avail;
|
|
avail.syncFrom(pl.getAllResources());
|
|
|
|
uint plLevel = targetLevel == -1 ? pl.level : uint(targetLevel);
|
|
|
|
bool needWater = plLevel > 0;
|
|
uint needFood = plLevel;
|
|
if(isMachineRace) {
|
|
needWater = false;
|
|
needFood = 0;
|
|
}
|
|
uint needLevel1 = 0;
|
|
uint needLevel2 = 0;
|
|
switch(plLevel) {
|
|
case 2:
|
|
needLevel1 = 1;
|
|
break;
|
|
case 3:
|
|
needLevel1 = 2;
|
|
needLevel2 = 1;
|
|
break;
|
|
case 4:
|
|
needLevel1 = 4;
|
|
needLevel2 = 2;
|
|
break;
|
|
case 5:
|
|
needLevel1 = 6;
|
|
needLevel2 = 4;
|
|
break;
|
|
}
|
|
|
|
auto@ resFood = getResourceList(RT_Food);
|
|
auto@ resWater = getResourceList(RT_Water);
|
|
auto@ resLevelOne = getResourceList(RT_LevelOne);
|
|
auto@ resLevelTwo = getResourceList(RT_LevelTwo);
|
|
|
|
for(uint i = 0, cnt = avail.length; i < cnt; ++i) {
|
|
auto@ res = avail[i];
|
|
uint rid = res.type.id, rlevel = res.type.level;
|
|
Object@ source = res.origin;
|
|
if(!res.usable || (source !is null && source.owner !is empire))
|
|
continue;
|
|
|
|
if(rlevel == 0 && resFood.find(rid) >= 0) {
|
|
if(needFood == 0)
|
|
continue;
|
|
needFood -= 1;
|
|
}
|
|
else if(rlevel == 0 && resWater.find(rid) >= 0) {
|
|
if(!needWater)
|
|
continue;
|
|
needWater = false;
|
|
}
|
|
else if(rlevel == 1 && resLevelOne.find(rid) >= 0) {
|
|
if(needLevel1 == 0)
|
|
continue;
|
|
needLevel1 -= 1;
|
|
}
|
|
else if(rlevel == 2 && resLevelTwo.find(rid) >= 0) {
|
|
if(needLevel2 == 0)
|
|
continue;
|
|
needLevel2 -= 1;
|
|
}
|
|
|
|
if(source !is null && source !is pl) {
|
|
if(source.isPlanet) {
|
|
auto@ src = cast<Planet>(source);
|
|
markPlanetUsed(src, pl);
|
|
}
|
|
else if(source.isAsteroid) {
|
|
auto@ src = cast<Asteroid>(source);
|
|
markAsteroidUsed(src);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void freePlanetImports(Planet@ pl) {
|
|
array<Resource> avail;
|
|
avail.syncFrom(pl.getAllResources());
|
|
|
|
for(uint i = 0, cnt = avail.length; i < cnt; ++i) {
|
|
Planet@ source = cast<Planet>(avail[i].origin);
|
|
if(source !is null && source !is pl)
|
|
markPlanetIdle(source);
|
|
}
|
|
}
|
|
|
|
void addPlanet(Planet@ pl, Object@ purpose = null) {
|
|
if(purpose is null)
|
|
markPlanetIdle(pl, true);
|
|
else
|
|
markPlanetUsed(pl, purpose);
|
|
}
|
|
|
|
void removePlanet(Planet@ pl) {
|
|
knownPlanets.erase(pl.id);
|
|
int resType = pl.primaryResourceType;
|
|
if(resType < 0)
|
|
return;
|
|
planetsByResource[resType].remove(pl);
|
|
}
|
|
|
|
uint nextFactoryRes = 0;
|
|
void updateFactories() {
|
|
auto@ list = planetsByResource[nextFactoryRes++ % planetsByResource.length];
|
|
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];
|
|
if(pl.valid && pl.owner is empire && pl.laborIncome > 0 && !knownFactories.contains(pl.id)) {
|
|
if(!isMachineRace || (pl.laborIncome * 60.0 > double(pl.population) + 0.05)) {
|
|
knownFactories.insert(pl.id);
|
|
factories.insertLast(pl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(uint i = 0, cnt = orbitals.length; i < cnt; ++i) {
|
|
Orbital@ orb = orbitals[i];
|
|
if(orb.valid && orb.owner is empire && orb.hasConstruction && orb.laborIncome > 0 && !knownFactories.contains(orb.id)) {
|
|
knownFactories.insert(orb.id);
|
|
factories.insertLast(orb);
|
|
}
|
|
}
|
|
}
|
|
|
|
double getMaxFactoryLabor() {
|
|
double labor = 0;
|
|
for(uint i = 0, cnt = factories.length; i < cnt; ++i)
|
|
labor = max(factories[i].laborIncome, labor);
|
|
return labor;
|
|
}
|
|
|
|
void validateFactories() {
|
|
for(uint i = 0, cnt = factories.length; i < cnt; ++i) {
|
|
Object@ factory = factories[i];
|
|
if(!factory.valid || !factory.hasConstruction || factory.laborIncome <= 0.000001 || factory.owner !is empire) {
|
|
knownFactories.erase(factory.id);
|
|
if(i + 1 < cnt)
|
|
@factories[i] = factories[cnt-1];
|
|
--cnt;
|
|
factories.length = cnt;
|
|
}
|
|
else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Check if the factory should be considered idle and we can give it build orders
|
|
// - Handles asteroid construction being automatically shuffled to the end to prevent blocking things.
|
|
bool isBuildIdle(Object@ factory) {
|
|
uint count = factory.constructionCount;
|
|
if(count == 0)
|
|
return true;
|
|
if(count == 1 && factory.constructionType == CT_Asteroid)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool factoriesInRegion(Region@ reg) const {
|
|
for(uint i = 0, cnt = factories.length; i < cnt; ++i)
|
|
if(factories[i].region is reg)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void validateOrbitals() {
|
|
for(uint i = 0, cnt = orbitals.length; i < cnt;) {
|
|
Orbital@ factory = orbitals[i];
|
|
if(!factory.valid || factory.owner !is empire) {
|
|
if(i + 1 < cnt)
|
|
@orbitals[i] = orbitals[cnt-1];
|
|
--cnt;
|
|
orbitals.length = cnt;
|
|
}
|
|
else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
PlanRegion@ pickRandomSys(array<PlanRegion@>& systemList) {
|
|
uint sysCount = systemList.length;
|
|
if(sysCount == 0)
|
|
return null;
|
|
else
|
|
return systemList[randomi(0,sysCount-1)];
|
|
}
|
|
|
|
void aiPing(Empire@ fromEmpire, vec3d position, uint type) {
|
|
if(empire is fromEmpire || (fromEmpire.team != empire.team && empire.team != -1))
|
|
return;
|
|
if(fromEmpire.SubjugatedBy !is empire || empire.SubjugatedBy !is fromEmpire)
|
|
return;
|
|
|
|
Request req;
|
|
@req.region = findNearestRegion(position);
|
|
req.time = gameTime;
|
|
|
|
Lock lock(reqLock);
|
|
queuedRequests.insertLast(req);
|
|
}
|
|
|
|
void commandAI(string cmd) {
|
|
if(cmd.substr(0,7) == "planet ") {
|
|
string name = cmd.substr(7);
|
|
for(uint i = 0, cnt = planetsByResource.length(); i < cnt; ++i) {
|
|
auto@ list = planetsByResource[i].idle;
|
|
for(uint j = 0, jcnt = list.length; j < jcnt; ++j) {
|
|
if(list[j].name == name) {
|
|
auto@ export = list[j].nativeResourceDestination[0];
|
|
if(export is null)
|
|
error(name + " is idle");
|
|
else
|
|
error(name + " is idle, but exporting to " + export.name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
@list = planetsByResource[i].used;
|
|
auto@ purpose = planetsByResource[i].purpose;
|
|
for(uint j = 0, jcnt = list.length; j < jcnt; ++j) {
|
|
if(list[j].name == name) {
|
|
auto@ export = list[j].nativeResourceDestination[0];
|
|
if(export is purpose[j])
|
|
error(name + " is used for " + purpose[j].name);
|
|
else
|
|
error(name + " is used for " + purpose[j].name + " but is exporting to " + (export is null ? "nowhere" : export.name));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
error("Could not locate planet");
|
|
}
|
|
else if(cmd == "planets") {
|
|
for(uint i = 0, cnt = planetsByResource.length(); i < cnt; ++i) {
|
|
auto@ list = planetsByResource[i].idle;
|
|
for(uint j = 0, jcnt = list.length; j < jcnt; ++j) {
|
|
auto@ pl = list[j];
|
|
auto@ export = pl.nativeResourceDestination[0];
|
|
if(export is null)
|
|
error(pl.name + " is idle");
|
|
else
|
|
error(pl.name + " is idle, but exporting to " + export.name);
|
|
}
|
|
|
|
@list = planetsByResource[i].used;
|
|
auto@ purpose = planetsByResource[i].purpose;
|
|
for(uint j = 0, jcnt = list.length; j < jcnt; ++j) {
|
|
auto@ pl = list[j];
|
|
auto@ export = pl.nativeResourceDestination[0];
|
|
if(export is purpose[j])
|
|
error(pl.name + " is used for " + purpose[j].name);
|
|
else
|
|
error(pl.name + " is used for " + purpose[j].name + " but is exporting to " + (export is null ? "nowhere" : export.name));
|
|
}
|
|
}
|
|
}
|
|
else if(cmd == "idle") {
|
|
error("Current idle actions:");
|
|
for(uint i = 0; i < idle.length; ++i) {
|
|
auto@ act = idle[i];
|
|
error(act.hash + ": " + act.state);
|
|
}
|
|
}
|
|
else if(cmd == "artifacts") {
|
|
error("Known artifacts:");
|
|
for(uint i = 0, cnt = artifacts.length; i < cnt; ++i) {
|
|
auto@ type = getArtifactType(i);
|
|
if(artifacts[i] !is null)
|
|
error(" " + type.name);
|
|
}
|
|
}
|
|
else if(cmd == "fleets") {
|
|
for(uint j = 0; j < 4; ++j) {
|
|
FleetType type = FleetType(j);
|
|
if(j == 2)
|
|
type = FT_Mothership;
|
|
else if(j == 3)
|
|
type = FT_Slipstream;
|
|
auto@ f = fleets[type];
|
|
error("Fleets of type " + j + ":");
|
|
for(uint i = 0, cnt = f.length; i < cnt; ++i) {
|
|
auto@ ship = f[i];
|
|
error("\t" + ship.name + " in " + (ship.region is null ? "empty space" : ship.region.name));
|
|
}
|
|
}
|
|
}
|
|
else if(cmd == "fix war") {
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i)
|
|
requestWar(getEmpire(i));
|
|
}
|
|
else if(cmd.substr(0,6) == "scout ") {
|
|
auto@ sys = getSystem(cmd.substr(6));
|
|
if(sys !is null) {
|
|
for(uint i = 0, cnt = ourSystems.length; i < cnt; ++i) {
|
|
if(ourSystems[i].region is sys.object) {
|
|
ourSystems[i].scout(this, verbose=true);
|
|
error("Scouted system");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
error("Couldn't find system");
|
|
}
|
|
}
|
|
else if(cmd.substr(0,9) == "artifact ") {
|
|
string id = cmd.substr(9);
|
|
auto@ art = getArtifact(getArtifactType(id));
|
|
if(art !is null)
|
|
error("Found artifact");
|
|
else
|
|
error("Found no artifact");
|
|
}
|
|
else if(cmd.substr(0,9) == "resource ") {
|
|
string id = cmd.substr(9);
|
|
auto@ res = getResource(id);
|
|
if(res is null) {
|
|
error("Invalid resource");
|
|
return;
|
|
}
|
|
|
|
auto@ list = planetsByResource[res.id].idle;
|
|
for(uint j = 0, jcnt = list.length; j < jcnt; ++j) {
|
|
auto@ pl = list[j];
|
|
auto@ export = pl.nativeResourceDestination[0];
|
|
if(export is null)
|
|
error(pl.name + " is idle");
|
|
else
|
|
error(pl.name + " is idle, but exporting to " + export.name);
|
|
}
|
|
|
|
@list = planetsByResource[res.id].used;
|
|
auto@ purpose = planetsByResource[res.id].purpose;
|
|
for(uint j = 0, jcnt = list.length; j < jcnt; ++j) {
|
|
auto@ pl = list[j];
|
|
auto@ export = pl.nativeResourceDestination[0];
|
|
if(export is purpose[j])
|
|
error(pl.name + " is used for " + purpose[j].name);
|
|
else
|
|
error(pl.name + " is used for " + purpose[j].name + " but is exporting to " + (export is null ? "nowhere" : export.name));
|
|
}
|
|
}
|
|
else if(cmd.substr(0,5) == "diff ") {
|
|
string newDiff = cmd.substr(5);
|
|
toLowercase(newDiff);
|
|
uint diff = 3;
|
|
if(newDiff == "passive")
|
|
diff = 0;
|
|
else if(newDiff == "easy")
|
|
diff = 1;
|
|
else if(newDiff == "medium")
|
|
diff = 2;
|
|
else if(newDiff == "hard")
|
|
diff = 3;
|
|
else if(newDiff == "murderous")
|
|
diff = 4;
|
|
else if(newDiff == "cheating")
|
|
diff = 5;
|
|
else if(newDiff == "savage")
|
|
diff = 6;
|
|
else if(newDiff == "barbaric")
|
|
diff = 7;
|
|
else
|
|
diff = toUInt(newDiff);
|
|
|
|
changeDifficulty(diff);
|
|
}
|
|
else if(cmd == "log war") {
|
|
if(!logWar) {
|
|
makeDirectory(profileRoot + "/war_logs/");
|
|
@log = WriteFile(profileRoot + "/war_logs/" + empire.name + ".txt");
|
|
logWar = true;
|
|
}
|
|
}
|
|
else if(cmd == "relations") {
|
|
for(uint i = 0; i < relations.length; ++i) {
|
|
auto@ rel = relations[i];
|
|
auto@ emp = getEmpire(i);
|
|
if(emp is null || emp is empire || !emp.valid)
|
|
continue;
|
|
error(emp.name + " = " + rel.standing);
|
|
}
|
|
}
|
|
else {
|
|
error("Unknown command");
|
|
}
|
|
}
|
|
|
|
void debugAI() {
|
|
debug = !debug;
|
|
}
|
|
|
|
PlanRegion@ markAsColony(Region@ region) {
|
|
for(uint i = 0, cnt = exploredSystems.length; i < cnt; ++i) {
|
|
Region@ reg = exploredSystems[i].region;
|
|
if(reg is region) {
|
|
auto@ pr = exploredSystems[i];
|
|
ourSystems.insertLast(pr);
|
|
exploredSystems.removeAt(i);
|
|
for(uint i = 0, cnt = pr.artifacts.length; i < cnt; ++i)
|
|
logArtifact(pr.artifacts[i]);
|
|
timeSinceLastExpand = 0.0;
|
|
return ourSystems.last;
|
|
}
|
|
}
|
|
|
|
//Check for duplicates
|
|
for(uint i = 0, cnt = ourSystems.length; i < cnt; ++i) {
|
|
Region@ reg = ourSystems[i].region;
|
|
if(reg is region)
|
|
return ourSystems[i];
|
|
}
|
|
|
|
PlanRegion@ newRegion = findSystem(region);
|
|
if(newRegion is null) {
|
|
@newRegion = PlanRegion(region);
|
|
systems.set(region.id, @newRegion);
|
|
}
|
|
|
|
for(uint i = 0, cnt = newRegion.artifacts.length; i < cnt; ++i)
|
|
logArtifact(newRegion.artifacts[i]);
|
|
|
|
knownSystems.insert(region.id);
|
|
ourSystems.insertLast(newRegion);
|
|
timeSinceLastExpand = 0.0;
|
|
return newRegion;
|
|
}
|
|
|
|
PlanRegion@ getPlanRegion(Object@ focus) {
|
|
Region@ area = focus.region;
|
|
if(area is null) {
|
|
@area = cast<Region>(focus);
|
|
if(area is null)
|
|
return null;
|
|
}
|
|
|
|
PlanRegion@ pr;
|
|
systems.get(area.id, @pr);
|
|
return pr;
|
|
}
|
|
|
|
bool knownSystem(Region@ region) const {
|
|
return knownSystems.contains(region.id);
|
|
}
|
|
|
|
PlanRegion@ findSystem(Region@ region) const {
|
|
PlanRegion@ pr;
|
|
systems.get(region.id, @pr);
|
|
return pr;
|
|
}
|
|
|
|
PlanRegion@ addExploredSystem(Region@ focus) {
|
|
PlanRegion@ region = PlanRegion(focus);
|
|
exploredSystems.insertLast(region);
|
|
systems.set(focus.id, @region);
|
|
knownSystems.insert(focus.id);
|
|
return region;
|
|
}
|
|
|
|
void addExploredSystem(PlanRegion@ region) {
|
|
exploredSystems.insertLast(region);
|
|
systems.set(region.region.id, @region);
|
|
knownSystems.insert(region.region.id);
|
|
}
|
|
|
|
uint nextVision = randomi(0,10000);
|
|
void updateRandomVision() {
|
|
for(uint i = 0; i < 5; ++i) {
|
|
Region@ region = getSystem(nextVision++ % systemCount).object;
|
|
if(region.VisionMask & empire.visionMask != 0) {
|
|
auto@ plan = findSystem(region);
|
|
if(plan is null)
|
|
@plan = addExploredSystem(region);
|
|
plan.scout(this);
|
|
break;
|
|
}
|
|
else if(region.SeenMask & empire.mask != 0) {
|
|
//We may have gained memory of the planets through various means
|
|
auto@ plan = findSystem(region);
|
|
if(plan is null) {
|
|
@plan = PlanRegion(region);
|
|
systems.set(region.id, @plan);
|
|
}
|
|
|
|
if(plan.useMemory(this) && !knownSystem(region))
|
|
addExploredSystem(plan);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
array<Ship@>@ get_fleets(FleetType type) {
|
|
if(type == FT_Scout)
|
|
return scoutFleets;
|
|
else if(type == FT_Mothership)
|
|
return motherships;
|
|
else if(type == FT_Slipstream)
|
|
return slipstreamers;
|
|
else
|
|
return combatFleets;
|
|
}
|
|
|
|
uint get_fleetTypeCount() const {
|
|
return 4;
|
|
}
|
|
|
|
void freeFleet(Ship@ leader, FleetType type) {
|
|
if(leader !is null && leader.valid && leader.owner is empire)
|
|
fleets[type].insertLast(leader);
|
|
}
|
|
|
|
void removeInvalidFleets() {
|
|
for(uint f = 0, fCnt = fleetTypeCount; f < fCnt; ++f) {
|
|
FleetType type;
|
|
switch(f) {
|
|
case 0: type = FT_Scout; break;
|
|
case 1: type = FT_Combat; break;
|
|
case 2: type = FT_Mothership; break;
|
|
case 3: type = FT_Slipstream; break;
|
|
}
|
|
|
|
array<Ship@>@ Fleets = fleets[type];
|
|
for(int i = int(Fleets.length) - 1; i >= 0; --i) {
|
|
Ship@ leader = Fleets[i];
|
|
if(!leader.valid || leader.owner !is empire) {
|
|
Fleets.removeAt(i);
|
|
knownLeaders.erase(leader.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ship@ getAvailableFleet(FleetType type, bool build = true) {
|
|
array<Ship@>@ Fleets = fleets[type];
|
|
|
|
if(Fleets.length != 0) {
|
|
Ship@ leader = Fleets.last;
|
|
Fleets.removeLast();
|
|
|
|
if(leader.valid && leader.owner is empire)
|
|
return leader;
|
|
else
|
|
return getAvailableFleet(type);
|
|
}
|
|
else {
|
|
if(build)
|
|
requestFleetBuild(type);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
void logArtifact(Artifact@ artifact) {
|
|
int type = artifact.ArtifactType;
|
|
if(type >= 0 && artifacts[type] is null)
|
|
@artifacts[type] = artifact;
|
|
}
|
|
|
|
Artifact@ getArtifact(const ArtifactType@ Type, Region@ availableTo = null) {
|
|
if(Type is null)
|
|
return null;
|
|
|
|
Artifact@ artifact = artifacts[Type.id];
|
|
if(artifact !is null && (!artifact.valid || artifact.region is null)) {
|
|
@artifacts[Type.id] = null;
|
|
@artifact = null;
|
|
}
|
|
|
|
if(artifact is null && ourSystems.length != 0) {
|
|
SysSearch search;
|
|
for(uint i = 0; i < 8; ++i) {
|
|
auto@ region = ourSystems[randomi(0,ourSystems.length-1)];
|
|
for(uint j = 0, cnt = region.artifacts.length; j < cnt; ++j) {
|
|
if(region.artifacts[j].ArtifactType == int(Type.id)) {
|
|
@artifact = region.artifacts[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@artifacts[Type.id] = artifact;
|
|
}
|
|
|
|
if(artifact !is null) {
|
|
Region@ from = artifact.region;
|
|
Territory@ fromTerr = from !is null ? from.getTerritory(empire) : null;
|
|
if(fromTerr is null) {
|
|
@artifacts[Type.id] = null;
|
|
return null;
|
|
}
|
|
|
|
if(availableTo !is null && availableTo.getTerritory(empire) !is fromTerr)
|
|
return null;
|
|
}
|
|
|
|
return artifact;
|
|
}
|
|
|
|
bool performAction(Action@ act) {
|
|
if(act is null) {
|
|
error("BasicAI Error: Unexpected null action.");
|
|
::debug();
|
|
return true;
|
|
}
|
|
|
|
//TODO: This leaks a reference? (if exist is not null)
|
|
Action@ exist;
|
|
if(actions.get(act.hash, @exist))
|
|
@act = exist;
|
|
if(act !is null) {
|
|
if(debug) {
|
|
dbgMsg += "\n ";
|
|
for(int i = printDepth; i > 0; --i)
|
|
dbgMsg += " ";
|
|
dbgMsg += act.state;
|
|
}
|
|
++printDepth;
|
|
|
|
if(printDepth >= 25) {
|
|
error("AI Recursed too deep.");
|
|
::debug();
|
|
actions.delete(act.hash);
|
|
removeIdle(act);
|
|
return true;
|
|
}
|
|
|
|
double t = 0.0;
|
|
if(profile)
|
|
t = getExactTime();
|
|
|
|
if(act.perform(this)) {
|
|
--printDepth;
|
|
if(profile && debug) {
|
|
double e = getExactTime();
|
|
dbgMsg += "\n ";
|
|
for(int i = printDepth; i > 0; --i)
|
|
dbgMsg += " ";
|
|
dbgMsg += "Took " + int((e-t) * 1.0e6) + " us";
|
|
}
|
|
|
|
actions.delete(act.hash);
|
|
removeIdle(act);
|
|
return true;
|
|
}
|
|
else {
|
|
--printDepth;
|
|
if(profile && debug) {
|
|
double e = getExactTime();
|
|
dbgMsg += "\n ";
|
|
for(int i = printDepth; i > 0; --i)
|
|
dbgMsg += " ";
|
|
dbgMsg += "Took " + int((e-t) * 1.0e6) + " us";
|
|
}
|
|
|
|
actions.set(act.hash, @act);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
Action@ locateAction(int64 hash) {
|
|
Action@ act;
|
|
actions.get(hash, @act);
|
|
if(act is null) {
|
|
int64 replace = 0;
|
|
if(newHashes.get(hash, replace))
|
|
return locateAction(replace);
|
|
}
|
|
return act;
|
|
}
|
|
|
|
void insertAction(Action@ act) {
|
|
actions.set(act.hash, @act);
|
|
}
|
|
|
|
Action@ performKnownAction(Action@ act) {
|
|
if(act is null)
|
|
return null;
|
|
|
|
if(debug) {
|
|
dbgMsg += "\n ";
|
|
for(int i = printDepth; i > 0; --i)
|
|
dbgMsg += " ";
|
|
dbgMsg += act.state;
|
|
}
|
|
++printDepth;
|
|
|
|
if(printDepth >= 25) {
|
|
error("AI Recursed too deep.");
|
|
::debug();
|
|
actions.delete(act.hash);
|
|
removeIdle(act);
|
|
return null;
|
|
}
|
|
|
|
double t = 0.0;
|
|
if(profile)
|
|
t = getExactTime();
|
|
|
|
if(act.perform(this)) {
|
|
--printDepth;
|
|
if(profile && debug) {
|
|
double e = getExactTime();
|
|
dbgMsg += "\n ";
|
|
for(int i = printDepth; i > 0; --i)
|
|
dbgMsg += " ";
|
|
dbgMsg += "Took " + int((e-t) * 1.0e6) + " us";
|
|
}
|
|
|
|
actions.delete(act.hash);
|
|
removeIdle(act);
|
|
return null;
|
|
}
|
|
else {
|
|
--printDepth;
|
|
if(profile && debug) {
|
|
double e = getExactTime();
|
|
dbgMsg += "\n ";
|
|
for(int i = printDepth; i > 0; --i)
|
|
dbgMsg += " ";
|
|
dbgMsg += "Took " + int((e-t) * 1.0e6) + " us";
|
|
}
|
|
actions.set(act.hash, @act);
|
|
return act;
|
|
}
|
|
}
|
|
|
|
const Design@ getDesign(DesignType type, uint task, bool create = true) {
|
|
if(type == DT_Flagship) {
|
|
if(task == FST_Mothership)
|
|
return empire.getDesign("Mothership");
|
|
else if(task == FST_Slipstream)
|
|
return empire.getDesign("Slipstream Generator");
|
|
}
|
|
|
|
if(create) {
|
|
Action@ act = locateAction( designHash(type, task) );
|
|
if(act is null)
|
|
@act = MakeDesign(type, task);
|
|
performKnownAction( act );
|
|
}
|
|
|
|
switch(type) {
|
|
case DT_Support:
|
|
if(task < ST_COUNT)
|
|
return dsgSupports[task];
|
|
break;
|
|
case DT_Flagship:
|
|
if(task < FST_COUNT)
|
|
return dsgFlagships[task];
|
|
break;
|
|
case DT_Station:
|
|
if(task < STT_COUNT)
|
|
return dsgStations[task];
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool fillFleet(Object@ fleet, int maxSpend = -1) {
|
|
if(!fleet.hasLeaderAI)
|
|
return true;
|
|
uint supports = fleet.SupplyAvailable;
|
|
if(supports == 0)
|
|
return true;
|
|
fleet.clearAllGhosts();
|
|
|
|
//Can't afford anything, probably not any time soon either
|
|
if(empire.RemainingBudget < -250 && empire.TotalBudget < -150)
|
|
return true;
|
|
|
|
const Design@ hvy, lit, tnk, fill;
|
|
@hvy = getDesign(DT_Support, ST_AntiFlagship);
|
|
if(hvy is null)
|
|
@hvy = empire.getDesign("Heavy Gunship");
|
|
@lit = getDesign(DT_Support, ST_AntiSupport);
|
|
if(lit is null)
|
|
@lit = empire.getDesign("Beamship");
|
|
@tnk = getDesign(DT_Support, ST_Tank);
|
|
if(tnk is null)
|
|
@tnk = empire.getDesign("Missile Boat");
|
|
@fill = getDesign(DT_Support, ST_Filler);
|
|
if(fill is null)
|
|
@fill = empire.getDesign("Gunship");
|
|
|
|
uint heavy = (supports / 3) / uint(hvy.size);
|
|
uint light = (supports / 2) / uint(lit.size);
|
|
uint tank = (supports / 8) / uint(tnk.size);
|
|
uint filler = (supports - (heavy * uint(hvy.size) + light * uint(lit.size) + tank * uint(tnk.size))) / uint(fill.size);
|
|
|
|
if(heavy + light + tank + filler == 0)
|
|
return true;
|
|
|
|
if(maxSpend >= 0) {
|
|
int cost = hvy.total(HV_BuildCost);
|
|
if(cost < maxSpend) {
|
|
heavy = min(heavy, uint(maxSpend/cost));
|
|
maxSpend -= int(heavy * cost);
|
|
}
|
|
|
|
cost = lit.total(HV_BuildCost);
|
|
if(cost < maxSpend) {
|
|
light = min(light, uint(maxSpend/cost));
|
|
maxSpend -= int(light * cost);
|
|
}
|
|
|
|
cost = tnk.total(HV_BuildCost);
|
|
if(cost < maxSpend) {
|
|
tank = min(tank, uint(maxSpend/cost));
|
|
maxSpend -= int(tank * cost);
|
|
}
|
|
|
|
cost = fill.total(HV_BuildCost);
|
|
if(cost < maxSpend) {
|
|
filler = min(filler, uint(maxSpend/cost));
|
|
maxSpend -= int(filler * cost);
|
|
}
|
|
}
|
|
|
|
if(heavy > 0)
|
|
fleet.orderSupports(hvy, heavy);
|
|
if(light > 0)
|
|
fleet.orderSupports(lit, light);
|
|
if(tank > 0)
|
|
fleet.orderSupports(tnk, tank);
|
|
if(filler > 0)
|
|
fleet.orderSupports(fill, filler);
|
|
return false;
|
|
}
|
|
|
|
Action@ requestFleetBuild(FleetType type) {
|
|
Action@ act = locateAction( buildFleetHash(type) );
|
|
if(act is null)
|
|
@act = BuildFleet(type);
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestOrbital(Region& where, OrbitalType type) {
|
|
Action@ act = locateAction( buildOrbitalHash(where, type) );
|
|
if(act is null)
|
|
@act = BuildOrbital(where, type);
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestCombatAt(Object@ system) {
|
|
Action@ act = locateAction( buildCombatHash(system.id) );
|
|
if(act is null)
|
|
@act = Combat(system);
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestWar(Empire@ target) {
|
|
Action@ act = locateAction( buildWarHash(target) );
|
|
if(act is null)
|
|
@act = War(target);
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestImport(Planet@ pl, array<int>@ resources, bool execute = true) {
|
|
Action@ act = locateAction( importResHash(pl, resources) );
|
|
if(act is null)
|
|
@act = ImportResource( pl, resources);
|
|
if(execute)
|
|
return performKnownAction( act );
|
|
else {
|
|
insertAction( act );
|
|
return act;
|
|
}
|
|
}
|
|
|
|
Action@ requestPlanetImprovement(Planet@ pl, uint toLevel) {
|
|
Action@ act = locateAction( improvePlanetHash(pl, toLevel) );
|
|
if(act is null)
|
|
@act = ImprovePlanet( pl, toLevel);
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestBuilding(Planet& pl, const BuildingType& type, bool force = false) {
|
|
Action@ act = locateAction( buildBuildingHash(pl, type) );
|
|
if(act is null)
|
|
@act = BuildBuilding( pl, type, force );
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ colonizeByResource(array<int>@ resources, ObjectReceiver@ inform = null, bool execute = true) {
|
|
Action@ act = locateAction( colonyByResHash(resources) );
|
|
if(act is null)
|
|
@act = ColonizeByResource( resources, inform );
|
|
if(execute)
|
|
return performKnownAction( act );
|
|
else {
|
|
insertAction( act );
|
|
return act;
|
|
}
|
|
}
|
|
|
|
Action@ requestColony(Planet@ pl, bool execute = true) {
|
|
Action@ act = locateAction( colonyHash(pl) );
|
|
if(act is null)
|
|
@act = Colonize(this, pl);
|
|
if(execute)
|
|
return performKnownAction( act );
|
|
else {
|
|
insertAction( act );
|
|
return act;
|
|
}
|
|
}
|
|
|
|
Action@ requestPopulate(Planet@ pl, bool execute = true) {
|
|
Action@ act = locateAction( populateHash(pl) );
|
|
if(act is null)
|
|
@act = Populate(this, pl);
|
|
if(execute)
|
|
return performKnownAction( act );
|
|
else {
|
|
insertAction( act );
|
|
return act;
|
|
}
|
|
}
|
|
|
|
Action@ requestExpansion() {
|
|
Action@ act = locateAction( expandHash() );
|
|
if(act is null)
|
|
@act = Expand();
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestExploration() {
|
|
Action@ act = locateAction( exploreHash() );
|
|
if(act is null)
|
|
@act = Explore();
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestDefense() {
|
|
Action@ act = locateAction( defendHash() );
|
|
if(act is null)
|
|
@act = Defend();
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
Action@ requestBudget() {
|
|
Action@ act = locateAction( budgetHash() );
|
|
if(act is null)
|
|
@act = GatherBudget();
|
|
return performKnownAction( act );
|
|
}
|
|
|
|
void removeIdle(Action& act) {
|
|
if(idles.contains(act.hash)) {
|
|
idles.erase(act.hash);
|
|
//if(idle.find(@act) < 0)
|
|
// ::debug();
|
|
idle.remove(@act);
|
|
}
|
|
}
|
|
|
|
void addIdle(Action@ act) {
|
|
if(act is null)
|
|
return;
|
|
|
|
if(!idles.contains(act.hash)) {
|
|
idle.insertLast(@act);
|
|
idles.insert(act.hash);
|
|
if(debug)
|
|
error("Adding idle action: " + act.state);
|
|
}
|
|
//else if(idle.find(@act) < 0) {
|
|
// ::debug();
|
|
//}
|
|
}
|
|
|
|
void addNewIdle(Action& act) {
|
|
insertAction(act);
|
|
addIdle(act);
|
|
}
|
|
|
|
void changeDifficulty(uint level) {
|
|
int prevCheatedResources = cheatLevel;
|
|
cheatLevel = 0;
|
|
|
|
cheatFlags = 0;
|
|
behaviorFlags = 0;
|
|
|
|
//$ -> Influence
|
|
empire.WelfareMode = 0;
|
|
|
|
if(level > 7)
|
|
level = 7;
|
|
|
|
switch(level) {
|
|
case 0: //Passive
|
|
skillEconomy = DIFF_Easy;
|
|
skillCombat = DIFF_Trivial;
|
|
skillDiplo = DIFF_Easy;
|
|
skillTech = DIFF_Easy;
|
|
skillScout = DIFF_Easy;
|
|
behaviorFlags = AIB_IgnorePlayer | AIB_IgnoreAI;
|
|
break;
|
|
case 1: //Easy
|
|
skillEconomy = DIFF_Easy;
|
|
skillCombat = DIFF_Easy;
|
|
skillDiplo = DIFF_Easy;
|
|
skillTech = DIFF_Easy;
|
|
skillScout = DIFF_Easy;
|
|
break;
|
|
case 2: //Medium
|
|
skillEconomy = DIFF_Medium;
|
|
skillCombat = DIFF_Medium;
|
|
skillDiplo = DIFF_Medium;
|
|
skillTech = DIFF_Medium;
|
|
skillScout = DIFF_Medium;
|
|
break;
|
|
case 3: //Hard
|
|
skillEconomy = DIFF_Hard;
|
|
skillCombat = DIFF_Hard;
|
|
skillDiplo = DIFF_Hard;
|
|
skillTech = DIFF_Hard;
|
|
skillScout = DIFF_Hard;
|
|
break;
|
|
case 4: //Murderous
|
|
skillEconomy = DIFF_Hard;
|
|
skillCombat = DIFF_Hard;
|
|
skillDiplo = DIFF_Hard;
|
|
skillTech = DIFF_Hard;
|
|
skillScout = DIFF_Hard;
|
|
behaviorFlags = AIB_IgnoreAI | AIB_QuickToWar;
|
|
break;
|
|
case 5: //Cheating
|
|
skillEconomy = DIFF_Max;
|
|
skillCombat = DIFF_Max;
|
|
skillDiplo = DIFF_Max;
|
|
skillTech = DIFF_Max;
|
|
skillScout = DIFF_Max;
|
|
cheatFlags = AIC_Vision | AIC_Resources;
|
|
cheatLevel = 10;
|
|
|
|
//$ -> Labor
|
|
empire.WelfareMode = 3;
|
|
break;
|
|
case 6: //Savage
|
|
skillEconomy = DIFF_Max;
|
|
skillCombat = DIFF_Max;
|
|
skillDiplo = DIFF_Max;
|
|
skillTech = DIFF_Max;
|
|
skillScout = DIFF_Max;
|
|
behaviorFlags = AIB_IgnoreAI | AIB_QuickToWar;
|
|
cheatFlags = AIC_Vision | AIC_Resources;
|
|
cheatLevel = 11;
|
|
|
|
//$ -> Labor
|
|
empire.WelfareMode = 3;
|
|
break;
|
|
case 7: //Barbaric
|
|
skillEconomy = DIFF_Max;
|
|
skillCombat = DIFF_Max;
|
|
skillDiplo = DIFF_Max;
|
|
skillTech = DIFF_Max;
|
|
skillScout = DIFF_Max;
|
|
cheatFlags = AIC_Vision | AIC_Resources;
|
|
cheatLevel = 12;
|
|
|
|
//$ -> Labor
|
|
empire.WelfareMode = 3;
|
|
break;
|
|
}
|
|
|
|
if(cheatFlags & AIC_Vision != 0)
|
|
empire.visionMask = ~0;
|
|
else
|
|
empire.visionMask = empire.mask;
|
|
|
|
if(cheatLevel != prevCheatedResources) {
|
|
double factor = double(cheatLevel - prevCheatedResources);
|
|
double tiles = double(cheatLevel - prevCheatedResources) * 3.0;
|
|
|
|
empire.modFTLCapacity(125.0 * factor);
|
|
empire.modFTLIncome(0.5 * factor);
|
|
empire.modEnergyIncome(TILE_ENERGY_RATE * double(tiles) / 2.0);
|
|
empire.modTotalBudget(int(TILE_MONEY_RATE * tiles));
|
|
empire.modResearchRate(TILE_RESEARCH_RATE * double(tiles));
|
|
}
|
|
}
|
|
|
|
void init(Empire& emp, EmpireSettings& settings) {
|
|
@empire = emp;
|
|
buildCommonLists();
|
|
|
|
changeDifficulty(settings.difficulty);
|
|
}
|
|
|
|
void init(Empire& emp) {
|
|
@empire = emp;
|
|
buildCommonLists();
|
|
addNewIdle(ExpendResources());
|
|
addNewIdle(ManageFactories());
|
|
addNewIdle(ManagePressureResources());
|
|
addNewIdle(createDiplomacyAI(this));
|
|
|
|
Planet@ hw = emp.Homeworld;
|
|
if(hw !is null && hw.valid && hw.owner is emp) {
|
|
@homeworld = hw;
|
|
addPlanet(hw, hw);
|
|
}
|
|
|
|
if(hw !is null)
|
|
focus = hw.position;
|
|
|
|
if(head is null) {
|
|
@head = MilitaryVictory();
|
|
insertAction(head);
|
|
}
|
|
|
|
uint objects = emp.objectCount;
|
|
for(uint i = 0; i < objects; ++i) {
|
|
Object@ obj = emp.objects[i];
|
|
|
|
switch(obj.type) {
|
|
case OT_Planet: {
|
|
Planet@ pl = cast<Planet>(obj);
|
|
markAsColony(pl.region).scout(this);
|
|
|
|
if(homeworld is null) {
|
|
@homeworld = pl;
|
|
addPlanet(pl, pl);
|
|
}
|
|
} break;
|
|
case OT_Ship: {
|
|
Ship@ ship = cast<Ship>(obj);
|
|
if(ship.hasLeaderAI) {
|
|
uint type = classifyDesign(this, ship.blueprint.design);
|
|
if(type != FT_INVALID) {
|
|
freeFleet(ship, FleetType(type));
|
|
if(type == FT_Mothership)
|
|
factories.insertLast(ship);
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void save(SaveFile& msg) {
|
|
msg << empire;
|
|
msg << homeworld;
|
|
|
|
msg << willpower;
|
|
|
|
msg << skillEconomy;
|
|
msg << skillCombat;
|
|
msg << skillDiplo;
|
|
msg << skillTech;
|
|
msg << skillScout;
|
|
|
|
msg << behaviorFlags;
|
|
msg << cheatFlags;
|
|
msg << cheatLevel;
|
|
|
|
msg << timeSinceLastExpand;
|
|
|
|
msg << nextNotification;
|
|
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ rel = relations[i];
|
|
msg << rel.standing;
|
|
msg << rel.bordered;
|
|
msg << rel.allied;
|
|
msg << rel.war;
|
|
msg << rel.brokeAlliance;
|
|
msg << rel.relStrength;
|
|
msg << int(rel.lastOffense);
|
|
msg << rel.offenseTime;
|
|
}
|
|
|
|
//System registry
|
|
uint cnt = ourSystems.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
ourSystems[i].save(msg);
|
|
|
|
cnt = exploredSystems.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
exploredSystems[i].save(msg);
|
|
|
|
//Planet registry
|
|
for(uint i = 0; i < planetsByResource.length; ++i)
|
|
planetsByResource[i].save(msg);
|
|
|
|
//Asteroid registry
|
|
cnt = idleAsteroids.length;
|
|
msg << cnt;
|
|
for(uint i = 0, cnt = idleAsteroids.length; i < cnt; ++i)
|
|
msg << idleAsteroids[i];
|
|
|
|
cnt = usedAsteroids.length;
|
|
msg << cnt;
|
|
for(uint i = 0, cnt = usedAsteroids.length; i < cnt; ++i)
|
|
msg << usedAsteroids[i];
|
|
|
|
//Orbital registry
|
|
cnt = orbitals.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << orbitals[i];
|
|
|
|
//Artifact registry
|
|
{
|
|
Artifact@ artifact = null;
|
|
for(uint i = 0; i < artifacts.length; ++i) {
|
|
@artifact = artifacts[i];
|
|
if(artifact !is null)
|
|
msg << artifact;
|
|
}
|
|
|
|
@artifact = null;
|
|
msg << artifact;
|
|
}
|
|
|
|
//Fleet registry
|
|
cnt = scoutFleets.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << scoutFleets[i];
|
|
|
|
cnt = combatFleets.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << combatFleets[i];
|
|
|
|
cnt = motherships.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << motherships[i];
|
|
|
|
cnt = slipstreamers.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << slipstreamers[i];
|
|
msg << lastSlipstream;
|
|
|
|
cnt = untrackedFleets.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << untrackedFleets[i];
|
|
|
|
//Design types
|
|
for(uint i = 0; i < ST_COUNT; ++i)
|
|
msg << dsgSupports[i];
|
|
for(uint i = 0; i < FST_COUNT; ++i)
|
|
msg << dsgFlagships[i];
|
|
for(uint i = 0; i < STT_COUNT; ++i)
|
|
msg << dsgStations[i];
|
|
|
|
//Revenant parts (owned by others)
|
|
cnt = revenantParts.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << revenantParts[i];
|
|
|
|
//Treaty responses
|
|
cnt = queuedTreatyJoin.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << queuedTreatyJoin[i];
|
|
|
|
cnt = queuedTreatyDecline.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << queuedTreatyDecline[i];
|
|
|
|
//Actions
|
|
map_iterator it = actions.iterator();
|
|
|
|
int64 hash = 0;
|
|
Action@ act;
|
|
|
|
msg << actions.getSize();
|
|
while(it.iterate(hash, @act)) {
|
|
msg << hash;
|
|
msg << uint(act.actionType);
|
|
act.save(this, msg);
|
|
}
|
|
|
|
msg << head.hash;
|
|
|
|
cnt = idle.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << idle[i].hash;
|
|
|
|
msg << thoughtCycle;
|
|
msg << diplomacyTick;
|
|
}
|
|
|
|
map newHashes;
|
|
void load(SaveFile& msg) {
|
|
msg >> empire;
|
|
msg >> homeworld;
|
|
|
|
buildCommonLists();
|
|
|
|
if(msg >= SV_0063)
|
|
msg >> willpower;
|
|
|
|
msg >> skillEconomy;
|
|
msg >> skillCombat;
|
|
msg >> skillDiplo;
|
|
msg >> skillTech;
|
|
msg >> skillScout;
|
|
|
|
if(msg >= SV_0046) {
|
|
msg >> behaviorFlags;
|
|
msg >> cheatFlags;
|
|
msg >> cheatLevel;
|
|
}
|
|
|
|
if(msg >= SV_0079)
|
|
msg >> timeSinceLastExpand;
|
|
|
|
if(msg >= SV_0016)
|
|
msg >> nextNotification;
|
|
|
|
if(msg >= SV_0131) {
|
|
for(uint i = 0, cnt = relations.length; i < cnt; ++i) {
|
|
auto@ rel = relations[i];
|
|
msg >> rel.standing;
|
|
msg >> rel.bordered;
|
|
msg >> rel.allied;
|
|
msg >> rel.war;
|
|
msg >> rel.brokeAlliance;
|
|
msg >> rel.relStrength;
|
|
int offense = 0;
|
|
msg >> offense;
|
|
rel.lastOffense = RecentAction(offense);
|
|
msg >> rel.offenseTime;
|
|
}
|
|
}
|
|
|
|
//System registry
|
|
uint cnt = 0;
|
|
msg >> cnt;
|
|
ourSystems.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
PlanRegion@ reg = PlanRegion(msg);
|
|
@ourSystems[i] = reg;
|
|
systems.set(reg.region.id, @reg);
|
|
knownSystems.insert(reg.region.id);
|
|
}
|
|
|
|
msg >> cnt;
|
|
exploredSystems.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
PlanRegion@ reg = PlanRegion(msg);
|
|
@exploredSystems[i] = reg;
|
|
systems.set(reg.region.id, @reg);
|
|
knownSystems.insert(reg.region.id);
|
|
}
|
|
|
|
//Planet registry
|
|
for(uint i = 0, cnt = msg.getPrevIdentifierCount(SI_Resource); i < cnt; ++i) {
|
|
uint newIndex = msg.getIdentifier(SI_Resource, i);
|
|
if(newIndex < planetsByResource.length) {
|
|
planetsByResource[newIndex].load(this, msg);
|
|
}
|
|
else {
|
|
PlanetList list;
|
|
list.load(this, msg);
|
|
}
|
|
}
|
|
|
|
//Asteroid registry
|
|
if(msg >= SV_0128) {
|
|
msg >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Asteroid@ rock;
|
|
msg >> rock;
|
|
if(rock !is null) {
|
|
idleAsteroids.insertLast(rock);
|
|
knownAsteroids.insert(rock.id);
|
|
}
|
|
}
|
|
|
|
msg >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Asteroid@ rock;
|
|
msg >> rock;
|
|
if(rock !is null) {
|
|
usedAsteroids.insertLast(rock);
|
|
knownAsteroids.insert(rock.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Orbital registry
|
|
if(msg >= SV_0051) {
|
|
msg >> cnt;
|
|
orbitals.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> orbitals[i];
|
|
}
|
|
|
|
//Artifact registry
|
|
if(msg >= SV_0036) {
|
|
Artifact@ artifact;
|
|
msg >> artifact;
|
|
while(artifact !is null) {
|
|
logArtifact(artifact);
|
|
msg >> artifact;
|
|
}
|
|
}
|
|
|
|
//Fleet registry
|
|
msg >> cnt;
|
|
scoutFleets.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> scoutFleets[i];
|
|
|
|
msg >> cnt;
|
|
combatFleets.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> combatFleets[i];
|
|
|
|
if(msg >= SV_0114) {
|
|
msg >> cnt;
|
|
motherships.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> motherships[i];
|
|
}
|
|
|
|
if(msg >= SV_0130) {
|
|
msg >> cnt;
|
|
slipstreamers.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> slipstreamers[i];
|
|
msg >> lastSlipstream;
|
|
}
|
|
|
|
if(msg >= SV_0016) {
|
|
msg >> cnt;
|
|
untrackedFleets.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> untrackedFleets[i];
|
|
}
|
|
|
|
//Design types
|
|
if(msg >= SV_0081) {
|
|
for(uint i = 0; i < ST_COUNT; ++i)
|
|
msg >> dsgSupports[i];
|
|
uint count = FST_COUNT;
|
|
if(msg < SV_0119)
|
|
count = FST_COUNT_OLD1;
|
|
else if(msg < SV_0130)
|
|
count = FST_COUNT_OLD2;
|
|
for(uint i = 0; i < count; ++i)
|
|
msg >> dsgFlagships[i];
|
|
for(uint i = 0; i < STT_COUNT; ++i)
|
|
msg >> dsgStations[i];
|
|
}
|
|
|
|
//Revenant parts (owned by others)
|
|
if(msg >= SV_0087) {
|
|
msg >> cnt;
|
|
revenantParts.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> revenantParts[i];
|
|
}
|
|
|
|
//Treaty responses
|
|
if(msg >= SV_0100) {
|
|
msg >> cnt;
|
|
queuedTreatyJoin.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> queuedTreatyJoin[i];
|
|
|
|
msg >> cnt;
|
|
queuedTreatyDecline.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> queuedTreatyDecline[i];
|
|
}
|
|
|
|
//Actions
|
|
int64 hash = 0;
|
|
Action@ act;
|
|
|
|
msg >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
msg >> hash;
|
|
uint type = 0;
|
|
msg >> type;
|
|
|
|
switch(type) {
|
|
case ACT_Colonize:
|
|
@act = Colonize(this, msg);
|
|
break;
|
|
case ACT_ColonizeRes:
|
|
@act = ColonizeByResource(this, msg);
|
|
break;
|
|
case ACT_Explore:
|
|
@act = Explore(this, msg);
|
|
break;
|
|
case ACT_Build:
|
|
@act = BuildFleet(this, msg);
|
|
break;
|
|
case ACT_Trade:
|
|
@act = ImportResource(this, msg);
|
|
break;
|
|
case ACT_Improve:
|
|
@act = ImprovePlanet(this, msg);
|
|
break;
|
|
case ACT_Budget:
|
|
@act = GatherBudget(this, msg);
|
|
break;
|
|
case ACT_Expend:
|
|
@act = ExpendResources(this, msg);
|
|
break;
|
|
case ACT_Combat:
|
|
@act = Combat(this, msg);
|
|
break;
|
|
case ACT_War:
|
|
@act = War(this, msg);
|
|
break;
|
|
case ACT_Defend:
|
|
@act = Defend(this, msg);
|
|
break;
|
|
case ACT_Expand:
|
|
@act = Expand(this, msg);
|
|
break;
|
|
case ACT_Building:
|
|
@act = BuildBuilding(this, msg);
|
|
break;
|
|
case ACT_BuildOrbital:
|
|
@act = BuildOrbital(this, msg);
|
|
break;
|
|
case ACT_Design:
|
|
@act = MakeDesign(this, msg);
|
|
break;
|
|
case ACT_Populate:
|
|
@act = Populate(this, msg);
|
|
break;
|
|
case ACT_Diplomacy:
|
|
@act = loadDiplomacyAI(this, msg);
|
|
break;
|
|
case ACT_ManageFactories:
|
|
@act = ManageFactories(this, msg);
|
|
break;
|
|
case ACT_ManagePressureResources:
|
|
@act = ManagePressureResources(this, msg);
|
|
break;
|
|
case STRAT_Military:
|
|
@act = MilitaryVictory(this, msg);
|
|
break;
|
|
case STRAT_Influence:
|
|
@act = InfluenceVictory(this, msg);
|
|
break;
|
|
}
|
|
|
|
if(act !is null) {
|
|
//Hashes may need to change.
|
|
//When they do, the AI may forget some things it was doing,
|
|
// but that is preferable to other issues that may arise (e.g. freezes)
|
|
if(act.hash == hash)
|
|
actions.set(hash, @act);
|
|
else {
|
|
newHashes.set(hash, act.hash);
|
|
actions.set(act.hash, @act);
|
|
}
|
|
}
|
|
else {
|
|
error("Could not find action: " + uint64(hash) + " (" + (hash >> ACT_BIT_OFFSET) + ")");
|
|
}
|
|
}
|
|
|
|
map_iterator it = actions.iterator();
|
|
while(it.iterate(hash, @act))
|
|
act.postLoad(this);
|
|
|
|
int64 headHash = 0;
|
|
msg >> headHash;
|
|
@head = locateAction(headHash);
|
|
if(head is null) {
|
|
error("Couldn't find head: " + uint64(headHash));
|
|
@head = MilitaryVictory();
|
|
insertAction(head);
|
|
}
|
|
|
|
if(msg < SV_0021) {
|
|
addNewIdle(ExpendResources());
|
|
addNewIdle(ManageFactories());
|
|
addNewIdle(ManagePressureResources());
|
|
}
|
|
else {
|
|
msg >> cnt;
|
|
int64 h = 0;
|
|
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
msg >> h;
|
|
Action@ act = locateAction(h);
|
|
if(act !is null) {
|
|
idle.insertLast(act);
|
|
idles.insert(h);
|
|
}
|
|
else {
|
|
error("Couldn't locate idle action: " + uint64(h));
|
|
error("Action Type: " + uint64(h >> ACT_BIT_OFFSET));
|
|
}
|
|
}
|
|
}
|
|
|
|
newHashes.deleteAll();
|
|
|
|
msg >> thoughtCycle;
|
|
msg >> diplomacyTick;
|
|
}
|
|
|
|
double diplomacyTick = 0.0;
|
|
|
|
void validatePlanets() {
|
|
WaitForSafeCalls wait(false);
|
|
int resourceID = randomi(0, planetsByResource.length - 1);
|
|
PlanetList@ list = planetsByResource[resourceID];
|
|
|
|
for(uint i = 0; i < 3 && list.used.length + list.idle.length == 0; ++i) {
|
|
resourceID = randomi(0, planetsByResource.length - 1);
|
|
@list = planetsByResource[resourceID];
|
|
}
|
|
|
|
list.validate(this, empire, getResource(resourceID));
|
|
|
|
if(usedAsteroids.length > 0) {
|
|
uint index = randomi(0, usedAsteroids.length-1);
|
|
Asteroid@ roid = usedAsteroids[index];
|
|
if(!roid.valid || roid.owner !is empire) {
|
|
removeAsteroid(roid);
|
|
}
|
|
else if(randomi(0,10) == 0 && roid.primaryResourceExported) {
|
|
Object@ dest = roid.nativeResourceDestination[0];
|
|
if(dest is null || !dest.valid || dest.owner !is empire)
|
|
markAsteroidIdle(roid);
|
|
}
|
|
}
|
|
|
|
if(idleAsteroids.length > 0) {
|
|
uint index = randomi(0, idleAsteroids.length-1);
|
|
Asteroid@ roid = idleAsteroids[index];
|
|
if(!roid.valid || roid.owner !is empire)
|
|
removeAsteroid(roid);
|
|
}
|
|
}
|
|
|
|
//Make sure we still own systems marked as ours
|
|
void validateSystems() {
|
|
if(ourSystems.length == 0)
|
|
return;
|
|
|
|
uint index = randomi(0, ourSystems.length-1);
|
|
PlanRegion@ region = ourSystems[index];
|
|
for(uint i = 0, cnt = region.planets.length; i < cnt; ++i)
|
|
if(region.planets[i].owner is empire)
|
|
return;
|
|
|
|
ourSystems.removeAt(index);
|
|
exploredSystems.insertLast(region);
|
|
}
|
|
|
|
array<int> queuedTreatyJoin, queuedTreatyDecline;
|
|
double nextConsideration = gameTime + randomd(8.0, 22.0);
|
|
bool checkJoin = true;
|
|
|
|
void processTreatyQueue() {
|
|
if(queuedTreatyJoin.length + queuedTreatyDecline.length == 0) {
|
|
nextConsideration = gameTime + randomd(6.0, 16.0);
|
|
}
|
|
else if(gameTime > nextConsideration) {
|
|
nextConsideration = gameTime + randomd(2.0, 8.0);
|
|
checkJoin = !checkJoin;
|
|
|
|
if(queuedTreatyJoin.length > 0 && checkJoin) {
|
|
uint index = randomi(0, queuedTreatyJoin.length-1);
|
|
joinTreaty(empire, queuedTreatyJoin[index]);
|
|
queuedTreatyJoin.removeAt(index);
|
|
}
|
|
else if(queuedTreatyDecline.length > 0) {
|
|
uint index = randomi(0, queuedTreatyDecline.length-1);
|
|
declineTreaty(empire, queuedTreatyDecline[index]);
|
|
queuedTreatyDecline.removeAt(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
set_int callOuts;
|
|
array<Notification@> notices;
|
|
void getNotifications() {
|
|
uint latest = empire.notificationCount;
|
|
if(latest == nextNotification)
|
|
return;
|
|
receiveNotifications(notices, empire.getNotifications(20, nextNotification, false));
|
|
nextNotification = latest;
|
|
|
|
for(uint i = 0, cnt = notices.length; i < cnt; ++i) {
|
|
auto@ notice = notices[i];
|
|
switch(notice.type) {
|
|
case NT_FlagshipBuilt:
|
|
{
|
|
Ship@ ship = cast<Ship>(notice.relatedObject);
|
|
if(ship !is null && ship.owner is empire && !knownLeaders.contains(ship.id)) {
|
|
knownLeaders.insert(ship.id);
|
|
untrackedFleets.insertLast(ship);
|
|
}
|
|
} break;
|
|
case NT_StructureBuilt:
|
|
{
|
|
Planet@ pl = cast<Planet>(notice.relatedObject);
|
|
if(pl !is null && pl.owner is empire)
|
|
optimizePlanetImports(pl, pl.nativeResourceDestination[0] is null ? min(pl.level+1, 4) : max(pl.resourceLevel, getResource(pl.primaryResourceType).level));
|
|
} break;
|
|
case NT_TreatyEvent:
|
|
{
|
|
TreatyEventNotification@ evt = cast<TreatyEventNotification>(notice);
|
|
if(evt.eventType == TET_Invite) {
|
|
double t = treatyWaits[evt.empOne.index];
|
|
if(gameTime + randomd(5.0,120.0) < t) {
|
|
queuedTreatyDecline.insertLast(evt.treaty.id);
|
|
getRelation(evt.empOne).standing -= 1;
|
|
break;
|
|
}
|
|
|
|
treatyWaits[evt.empOne.index] = gameTime + randomd(120.0,240.0);
|
|
}
|
|
|
|
if(evt.treaty.leader !is null) {
|
|
//Ignore constant requests
|
|
|
|
if(evt.eventType == TET_Invite && evt.treaty.hasClause("SubjugateClause")) {
|
|
//Get the approximate point ratio. We add a small value to both points to reduce the noise of very small point values
|
|
float pointRatio = float(evt.empOne.points.value + 200) / float(empire.points.value + 200);
|
|
float reqRatio = (empire.isHostile(evt.empOne) && evt.empOne.MilitaryStrength > empire.MilitaryStrength) ? 3.f : 4.f;
|
|
reqRatio *= pow(0.5f, -willpower / float(empire.TotalPlanets.value + 1));
|
|
|
|
if(pointRatio > reqRatio)
|
|
queuedTreatyJoin.insertLast(evt.treaty.id);
|
|
else
|
|
queuedTreatyDecline.insertLast(evt.treaty.id);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if(evt.eventType == TET_Invite && evt.treaty.hasClause("SubjugateClause")) {
|
|
//Someone wants to surrender, accept
|
|
queuedTreatyJoin.insertLast(evt.treaty.id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(evt.eventType == TET_Invite) {
|
|
bool defense = evt.treaty.hasClause("MutualDefenseClause");
|
|
//bool trade = evt.treaty.hasClause("TradeClause");
|
|
bool vision = evt.treaty.hasClause("VisionClause");
|
|
bool alliance = evt.treaty.hasClause("AllianceClause");
|
|
|
|
double basis = -1.0;
|
|
if(alliance && enemies.length > 0)
|
|
basis += double(enemies.length);
|
|
if(defense)
|
|
basis += double(evt.empOne.MilitaryStrength - empire.MilitaryStrength);
|
|
if(evt.empOne.isHostile(empire))
|
|
basis -= 1.0;
|
|
|
|
basis += double(getRelation(evt.empOne).standing) / 100.0;
|
|
|
|
double chance = 0.5; pow(0.5, -basis);
|
|
if(basis > 0)
|
|
chance = 1.0 - pow(chance, 1.0 + basis);
|
|
else if(basis < 0)
|
|
chance = pow(chance, 1.0 - basis);
|
|
|
|
if((defense || alliance) && behaviorFlags & AIB_QuickToWar != 0)
|
|
chance = 0.0;
|
|
|
|
if(randomd() < chance) {
|
|
getRelation(evt.empOne).standing += 5;
|
|
queuedTreatyJoin.insertLast(evt.treaty.id);
|
|
}
|
|
else {
|
|
getRelation(evt.empOne).standing -= 1;
|
|
queuedTreatyDecline.insertLast(evt.treaty.id);
|
|
}
|
|
}
|
|
} break;
|
|
case NT_Vote:
|
|
{
|
|
VoteNotification@ evt = cast<VoteNotification>(notice);
|
|
if(evt.type == IVET_Start) {
|
|
if(evt.vote.startedBy !is empire && isTargetOf(this, evt.vote, empire)) {
|
|
auto@ rel = getRelation(evt.vote.startedBy);
|
|
rel.standing -= 5;
|
|
rel.offense = RA_Influence;
|
|
}
|
|
}
|
|
else if(evt.type == IVET_Card) {
|
|
auto@ c = evt.event.cardEvent;
|
|
auto@ card = c.card;
|
|
if(card.owner !is empire) {
|
|
int stance = 0;
|
|
auto@ starter = evt.vote.startedBy;
|
|
if(starter is empire)
|
|
stance = 2;
|
|
else if(starter.ForcedPeaceMask & empire.mask != 0 || getRelation(starter).standing > 30)
|
|
stance = 1;
|
|
else if(empire.isHostile(starter))
|
|
stance = -2;
|
|
else if(getRelation(starter).standing < -30)
|
|
stance = -1;
|
|
|
|
//Nature of the card (+ good, - bad)
|
|
int nature = 0;
|
|
auto@ targs = card.targets;
|
|
|
|
if(card.type.ident == "Rider") {
|
|
nature = -1;
|
|
}
|
|
else if(card.type.ident == "Hedge") {
|
|
nature = 1;
|
|
}
|
|
else if(card.type.ident == "Rush") {
|
|
nature = 1;
|
|
}
|
|
else if(card.type.ident == "CallOut") {
|
|
auto@ t = targs.get("Empire");
|
|
if(t !is null && t.emp is empire) {
|
|
//We need to track when we've been called out (yo)
|
|
callOuts.insert(evt.vote.id);
|
|
|
|
int rel = (getRelation(card.owner).standing - 15) / 10;
|
|
if(rel > 0)
|
|
nature = 1;
|
|
else if(rel < 0)
|
|
nature = -1;
|
|
}
|
|
}
|
|
else if(card.type.cls == ICC_Support) {
|
|
//Base whether the card is good/bad off the side it was targeted against
|
|
auto@ t = targs.get("VoteSide");
|
|
if(t !is null)
|
|
nature = t.side ? 2 : -2;
|
|
}
|
|
|
|
int response = 0;
|
|
if((stance > 0 && nature > 0) || (stance < 0 && nature < 0))
|
|
response = abs(nature);
|
|
else
|
|
response = -abs(nature);
|
|
|
|
if(response != 0) {
|
|
auto@ rel = getRelation(card.owner);
|
|
rel.standing += response * 2;
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
case NT_Card:
|
|
{
|
|
CardNotification@ evt = cast<CardNotification>(notice);
|
|
} break;
|
|
case NT_Donation:
|
|
{
|
|
DonationNotification@ evt = cast<DonationNotification>(notice);
|
|
int value = 0;
|
|
switch(evt.offer.type) {
|
|
case DOT_Money:
|
|
value = clamp(evt.offer.value / double(empire.TotalBudget), 0.0, 1.0) * 15.0;
|
|
break;
|
|
case DOT_Energy:
|
|
value = clamp(evt.offer.value / 1500.0, 0.0, 1.0) * 15.0;
|
|
break;
|
|
case DOT_Card:
|
|
value = 2;
|
|
break;
|
|
case DOT_Fleet:
|
|
value = 2;
|
|
break;
|
|
case DOT_Planet:
|
|
value = 5;
|
|
break;
|
|
case DOT_Artifact:
|
|
value = 1;
|
|
break;
|
|
}
|
|
|
|
if(skillDiplo < DIFF_Medium)
|
|
value += 1;
|
|
|
|
if(value != 0) {
|
|
auto@ rel = getRelation(evt.fromEmpire);
|
|
rel.standing += value;
|
|
if(value >= 5)
|
|
rel.offense = RA_Donation;
|
|
}
|
|
} break;
|
|
case NT_Generic:
|
|
{
|
|
GenericNotification@ evt = cast<GenericNotification>(notice);
|
|
if(evt.obj !is null && evt.obj.isOrbital) {
|
|
//Check to see if this is a revenant part
|
|
Orbital@ part = cast<Orbital>(evt.obj);
|
|
int core = part.coreModule;
|
|
if( core == getOrbitalModuleID("RevenantCore") ||
|
|
core == getOrbitalModuleID("RevenantCannon") ||
|
|
core == getOrbitalModuleID("RevenantChassis") ||
|
|
core == getOrbitalModuleID("RevenantEngine") )
|
|
{
|
|
revenantParts.insertLast(part);
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
notices.length = 0;
|
|
}
|
|
|
|
double relationTick = randomd(0.0,60.0);
|
|
void tick(Empire& emp, double time) {
|
|
//Copy queued requests over, so we can access them quickly, and remove old requests
|
|
for(int i = requests.length - 1; i >= 0; --i)
|
|
if(requests[i].time < gameTime - 600.0)
|
|
requests.removeAt(i);
|
|
if(queuedRequests.length > 0) {
|
|
Lock lock(reqLock);
|
|
for(uint i = 0, cnt = queuedRequests.length; i < cnt; ++i)
|
|
requests.insertLast(queuedRequests[i]);
|
|
if(requests.length > 0 && protect is null)
|
|
@protect = getPlanRegion(requests[0].region);
|
|
}
|
|
while(requests.length > 5)
|
|
requests.removeAt(0);
|
|
|
|
timeSinceLastExpand += time;
|
|
didTickScan = false;
|
|
|
|
profile = profile_ai.value > 0.0;
|
|
double start = getExactTime();
|
|
|
|
diplomacyTick += time;
|
|
relationTick += time;
|
|
|
|
if(diplomacyTick >= 2.0) {
|
|
//Ticks happen at real time, so we compensate to make it behave similarly at all game speeds
|
|
willpower *= pow(willDecayPerBudget,diplomacyTick * gameSpeed/180.0);
|
|
diplomacyTick = 0.0;
|
|
}
|
|
|
|
if(relationTick >= 60.0) {
|
|
uint borderMask = 0;
|
|
auto@ border = getBorder();
|
|
for(uint i = 0, cnt = border.length; i < cnt; ++i)
|
|
borderMask |= border[i].planetMask;
|
|
auto@ inner = ourSystems;
|
|
for(uint i = 0, cnt = inner.length; i < cnt; ++i)
|
|
borderMask |= inner[i].planetMask;
|
|
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
auto@ other = getEmpire(i);
|
|
auto@ relation = getRelation(other);
|
|
relation.standing -= relation.standing / 50;
|
|
|
|
if(relation.lastOffense != RA_None && relation.offenseTime > gameTime - 180.0)
|
|
if(relation.allied && relation.standing < -25)
|
|
leaveTreatiesWith(emp, other.mask);
|
|
|
|
relation.war = other.hostileMask & emp.mask != 0;
|
|
relation.allied = other.ForcedPeaceMask.value & emp.mask != 0;
|
|
relation.bordered = borderMask & other.mask != 0;
|
|
relation.relStrength = other.MilitaryStrength - emp.MilitaryStrength;
|
|
|
|
int goalStanding = 0;
|
|
if(relation.war) {
|
|
if(emp.MilitaryStrength >= other.MilitaryStrength)
|
|
goalStanding = -50;
|
|
}
|
|
else {
|
|
if(relation.allied)
|
|
goalStanding = relation.brokeAlliance ? 15 : 60;
|
|
|
|
if(relation.bordered)
|
|
goalStanding -= 15;
|
|
|
|
if(emp.MilitaryStrength < other.MilitaryStrength)
|
|
goalStanding += 15;
|
|
if(emp.points.value + 400 < other.points.value)
|
|
goalStanding -= 5;
|
|
}
|
|
|
|
if(relation.standing > goalStanding)
|
|
relation.standing -= 1;
|
|
else if(relation.standing < goalStanding)
|
|
relation.standing += skillDiplo < DIFF_Medium ? 3 : 2;
|
|
}
|
|
relationTick -= 60.0 + randomd(-5.0,5.0);
|
|
|
|
auto@ emp = getEmpire(randomi(0, getEmpireCount()-1));
|
|
if(emp.valid && emp !is empire && emp.major && !emp.isHostile(empire) && empire.ContactMask & emp.mask != 0 &&
|
|
treatyWaits[emp.index] < gameTime && !isInTreatiesWith(empire, emp.mask))
|
|
{
|
|
//Consider sending a treaty
|
|
auto@ rel = getRelation(emp);
|
|
if(rel.standing >= 0) {
|
|
uint clauses = 0;
|
|
if(randomi(5,25) == 0)
|
|
clauses |= TC_Trade;
|
|
if(rel.standing > randomi(0,25))
|
|
clauses |= TC_Vision;
|
|
if(rel.standing > randomi(15,35))
|
|
clauses |= TC_MutualDefense;
|
|
if(rel.standing > randomi(15,60))
|
|
clauses |= TC_Alliance;
|
|
if(clauses != 0) {
|
|
proposeTreaty(emp, clauses);
|
|
treatyWaits[emp.index] = gameTime + randomd(120.0,180.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
getNotifications();
|
|
removeInvalidFleets();
|
|
validatePlanets();
|
|
validateSystems();
|
|
validateOrbitals();
|
|
validateFactories();
|
|
updateFactories();
|
|
processTreatyQueue();
|
|
for(int i = revenantParts.length-1; i >= 0; --i)
|
|
if(revenantParts[i] is null || !revenantParts[i].valid)
|
|
revenantParts.removeAt(i);
|
|
|
|
//Check for new fleets that may have been given to us
|
|
uint empFleets = empire.fleetCount;
|
|
{
|
|
if(empFleets > 0) {
|
|
auto@ fleet = cast<Ship>(empire.fleets[randomi(0, empFleets-1)]);
|
|
if(fleet !is null && fleet.valid && fleet.owner is empire && !knownLeaders.contains(fleet.id)) {
|
|
knownLeaders.insert(fleet.id);
|
|
auto@ design = fleet.blueprint.design;
|
|
if((design is null || design.owner is empire) && gameTime > 5.0) {
|
|
untrackedFleets.insertLast(fleet);
|
|
}
|
|
else {
|
|
uint type = classifyDesign(this, design);
|
|
if(type != FT_INVALID)
|
|
freeFleet(fleet, FleetType(type));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ourSystems.length == 0 && !(usesMotherships && empFleets > 0))
|
|
return;
|
|
|
|
double validateEnd = getExactTime();
|
|
|
|
|
|
//Print depth is used both for debugging and tracking infinite recursion
|
|
printDepth = 0;
|
|
if(debug || profile)
|
|
dbgMsg = empire.name + " {";
|
|
|
|
if(validateEnd - start < maxAIFrame) {
|
|
switch(thoughtCycle) {
|
|
case 0:
|
|
if(idle.length > 0) {
|
|
performAction(idle[randomi(0, idle.length-1)]);
|
|
break;
|
|
}
|
|
case 1:
|
|
//Improve homeworld to level 4, then begin importing level 3 resources
|
|
if(performAction(head)) {
|
|
int[] l3res;
|
|
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
|
|
const ResourceType@ type = getResource(i);
|
|
if(type.level >= 3 && type.exportable)
|
|
l3res.insertLast(type.id);
|
|
}
|
|
@head = ImportResource(homeworld, l3res);
|
|
}
|
|
break;
|
|
}
|
|
|
|
thoughtCycle = (thoughtCycle + 1) % 2;
|
|
}
|
|
|
|
double actionEnd = getExactTime();
|
|
|
|
if(!didTickScan && actionEnd - start < maxAIFrame)
|
|
updateRandomVision();
|
|
|
|
if(profile) {
|
|
double end = getExactTime();
|
|
dbgMsg += format("\n\tTook $1us to validate, $2us to process, $3us for vision", toString((validateEnd-start) * 1.0e6, 1), toString((actionEnd-validateEnd) * 1.0e6 , 1), toString((end-actionEnd) * 1.0e6 , 1));
|
|
}
|
|
|
|
if(debug || profile) {
|
|
dbgMsg += "\n}";
|
|
print(dbgMsg);
|
|
}
|
|
}
|
|
|
|
void pause(Empire& emp) {
|
|
}
|
|
|
|
void resume(Empire& emp) {
|
|
}
|
|
};
|
|
|
|
AIController@ createBasicAI() {
|
|
return BasicAI();
|
|
}
|