2700 lines
69 KiB
ActionScript
2700 lines
69 KiB
ActionScript
//Locates a system to explore
|
|
import systems;
|
|
import ftl;
|
|
from pings import sendPing;
|
|
import design_settings;
|
|
#include "include/resource_constants.as"
|
|
|
|
int64 exploreHash() {
|
|
return int64(ACT_Explore) << ACT_BIT_OFFSET;
|
|
}
|
|
|
|
void moveToFastest(Ship@ ship, const vec3d& pos, FTLType ftl, bool alwaysHyper = false) {
|
|
auto@ emp = ship.owner;
|
|
switch(ftl) {
|
|
case FTL_Hyperdrive: {
|
|
int cost = hyperdriveCost(ship, pos);
|
|
if(emp.FTLStored >= cost && (alwaysHyper || ship.position.distanceToSQ(pos) > 1.0e6)) {
|
|
ship.addHyperdriveOrder(pos);
|
|
ship.addMoveOrder(pos, append=true);
|
|
break;
|
|
}
|
|
ship.addMoveOrder(pos);
|
|
} break;
|
|
case FTL_Jumpdrive: {
|
|
int cost = jumpdriveCost(ship, pos);
|
|
double range = jumpdriveRange(ship);
|
|
double dist = ship.position.distanceToSQ(pos);
|
|
if(emp.FTLStored >= cost && (alwaysHyper || dist > 1.0e6) && dist <= range*1.25) {
|
|
ship.addJumpdriveOrder(pos);
|
|
ship.addMoveOrder(pos, append=true);
|
|
break;
|
|
}
|
|
ship.addMoveOrder(pos);
|
|
} break;
|
|
case FTL_Fling: {
|
|
int cost = flingCost(ship, pos);
|
|
if(emp.hasFlingBeacons && emp.FTLStored >= cost && (alwaysHyper || ship.position.distanceToSQ(pos) > 1.0e6)) {
|
|
Object@ fling = emp.getFlingBeacon(ship.position);
|
|
if(fling !is null) {
|
|
ship.addFlingOrder(fling, pos);
|
|
ship.addMoveOrder(pos, append=true);
|
|
break;
|
|
}
|
|
}
|
|
ship.addMoveOrder(pos);
|
|
} break;
|
|
default:
|
|
ship.addMoveOrder(pos);
|
|
break;
|
|
}
|
|
}
|
|
|
|
class Explore : Action {
|
|
Region@ system;
|
|
Ship@ fleet;
|
|
SysSearch search;
|
|
|
|
Artifact@ telescope;
|
|
const ArtifactType@ scope = getArtifactType("Telescope");
|
|
|
|
Explore() {
|
|
}
|
|
|
|
Explore(BasicAI@ ai, SaveFile& file) {
|
|
file >> system;
|
|
file >> fleet;
|
|
if(file < SV_0031) {
|
|
int dummy = 0;
|
|
file >> dummy;
|
|
}
|
|
search.load(file);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& file) {
|
|
file << system;
|
|
file << fleet;
|
|
search.save(file);
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return exploreHash();
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Explore;
|
|
}
|
|
|
|
void reset(BasicAI@ ai, bool automate = true) {
|
|
if(fleet !is null) {
|
|
fleet.addAutoExploreOrder(useFTL=true);
|
|
ai.freeFleet(fleet, FT_Scout);
|
|
@fleet = null;
|
|
}
|
|
|
|
@system = null;
|
|
search.reset();
|
|
}
|
|
|
|
string get_state() const {
|
|
if(fleet is null)
|
|
return "Exploring";
|
|
else if(system is null)
|
|
return "Exploring with " + fleet.name + " fleet";
|
|
else
|
|
return "Awaiting " + fleet.name + " scout fleet arrival at " + system.name;
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
if(ai.skillScout < DIFF_Medium) {
|
|
Ship@ scout = ai.getAvailableFleet(FT_Scout);
|
|
if(scout !is null) {
|
|
if(!scout.hasOrders)
|
|
scout.addAutoExploreOrder(useFTL=true);
|
|
ai.freeFleet(scout, FT_Scout);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//Telescope may become inaccessible
|
|
if(telescope !is null && (!telescope.valid || telescope.region.getTerritory(ai.empire) !is null))
|
|
@telescope = null;
|
|
|
|
if(ai.skillScout >= DIFF_Medium)
|
|
if(telescope is null && ai.empire.EnergyStored > 0.0)
|
|
@telescope = ai.getArtifact(scope);
|
|
|
|
//At times, we might encouter a situation where the scout is stuck in limbo
|
|
if(fleet !is null && fleet.inFTL && fleet.ftlSpeed < 25.0) {
|
|
fleet.scuttle();
|
|
@fleet = null;
|
|
}
|
|
|
|
//The fleet may die
|
|
// We should also consider trying to scout a different system
|
|
if(fleet !is null && (!fleet.valid || fleet.owner !is ai.empire)) {
|
|
@system = null;
|
|
@fleet = null;
|
|
}
|
|
|
|
if(system !is null) {
|
|
if(system.VisionMask & ai.empire.visionMask != 0) {
|
|
PlanRegion@ reg = ai.findSystem(system);
|
|
if(reg is null || !ai.knownSystem(system)) {
|
|
@reg = ai.addExploredSystem(system);
|
|
reg.scout(ai);
|
|
}
|
|
|
|
//Scout anomalies
|
|
if(fleet !is null) {
|
|
for(uint i = 0, cnt = reg.anomalies.length; i < cnt; ++i) {
|
|
auto@ a = reg.anomalies[i];
|
|
if(!a.valid)
|
|
continue;
|
|
if(a.getEmpireProgress(ai.empire) < 1.f) {
|
|
fleet.addScanOrder(a);
|
|
ai.focus = a.position;
|
|
return false;
|
|
}
|
|
else {
|
|
uint optCount = a.getOptionCount();
|
|
uint off = randomi(0,optCount-1);
|
|
for(uint i = 0; i < optCount; ++i) {
|
|
uint o = (i + off) % optCount;
|
|
if(a.isOptionSafe[o]) {
|
|
a.choose(ai.empire, o);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
reset(ai);
|
|
return true;
|
|
}
|
|
else if(telescope !is null && ai.empire.EnergyStored > scope.abilities[0].energyCost + 300.0) {
|
|
telescope.activateAbilityFor(ai.empire, 0, system);
|
|
}
|
|
else {
|
|
if(ai.ftl == FTL_Slipstream && ai.skillScout >= DIFF_Medium && (ai.lastSlipstream + 300.0 <= gameTime || clamp(ai.lastSlipstream - gameTime, 0.0, 300.0) < 20.0)) {
|
|
if(gameTime > ai.lastSlipstream + 300.0) {
|
|
auto@ slip = ai.getAvailableFleet(FT_Slipstream);
|
|
if(slip !is null && !slip.hasOrders) {
|
|
vec2d off = random2d(250.0, system.radius);
|
|
vec3d dest = system.position + vec3d(off.x, 0.0, off.y);
|
|
auto cost = slipstreamCost(slip, 1, dest.distanceTo(slip.position));
|
|
if(cost <= ai.empire.FTLStored) {
|
|
slip.addSlipstreamOrder(dest);
|
|
ai.lastSlipstream = gameTime;
|
|
return false;
|
|
}
|
|
else {
|
|
error("Cost: " + cost + " > " + ai.empire.FTLStored);
|
|
}
|
|
}
|
|
|
|
if(slip !is null)
|
|
ai.freeFleet(slip, FT_Slipstream);
|
|
}
|
|
else {
|
|
//We kinda think maybe the slipstream was for scouting, so we just sit here looking stupid
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(fleet !is null) {
|
|
if(fleet.region !is system && !fleet.hasOrders)
|
|
moveToFastest(fleet, system.position + (fleet.position - system.position).normalized(system.radius * 0.85), ai.ftl);
|
|
}
|
|
else if(ai.fleets[FT_Scout].length > 0) {
|
|
double closest = INFINITY;
|
|
array<Ship@>@ scouts = ai.fleets[FT_Scout];
|
|
uint index = 0;
|
|
for(uint i = 0, cnt = scouts.length; i < cnt; ++i) {
|
|
Ship@ ship = scouts[i];
|
|
double d = ship.position.distanceToSQ(system.position);
|
|
if(d < closest) {
|
|
@fleet = ship;
|
|
index = i;
|
|
closest = d;
|
|
}
|
|
}
|
|
|
|
if(fleet !is null) {
|
|
scouts.removeAt(index);
|
|
fleet.clearOrders();
|
|
ai.focus = fleet.position;
|
|
}
|
|
}
|
|
else {
|
|
ai.requestFleetBuild(FT_Scout);
|
|
}
|
|
}
|
|
}
|
|
else if(ai.ourSystems.length + ai.exploredSystems.length >= systemCount)
|
|
{
|
|
//Only Hard AIs have the focus to keep scouting late-game
|
|
if(ai.skillScout < DIFF_Hard)
|
|
return true;
|
|
//Move a scout to a random nearby system
|
|
if(fleet is null) {
|
|
@fleet = ai.getAvailableFleet(FT_Scout);
|
|
if(fleet !is null)
|
|
fleet.clearOrders();
|
|
}
|
|
else {
|
|
auto@ start = fleet.region;
|
|
if(start !is null) {
|
|
auto@ sys = getSystem(start);
|
|
if(sys.adjacent.length > 0) {
|
|
@system = getSystem(sys.adjacent[randomi(0, sys.adjacent.length-1)]).object;
|
|
if(system.VisionMask & ai.empire.visionMask != 0)
|
|
@system = null;
|
|
}
|
|
}
|
|
|
|
//If we didn't find a suitable system, pick a random system we don't have vision in
|
|
if(system is null && ai.exploredSystems.length > 0) {
|
|
@system = ai.exploredSystems[randomi(0,ai.exploredSystems.length-1)].region;
|
|
if(system.VisionMask & ai.empire.visionMask != 0)
|
|
@system = null;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
array<Region@> systems;
|
|
|
|
//Search for systems bordering our own that we haven't seen yet
|
|
for(uint trySteps = 0; trySteps < 8; ++trySteps) {
|
|
PlanRegion@ target;
|
|
if(ai.skillScout <= DIFF_Easy)
|
|
@target = search.random(ai.ourSystems);
|
|
else
|
|
@target = search.next(ai.ourSystems);
|
|
|
|
if(target is null)
|
|
@target = search.random(ai.exploredSystems);
|
|
|
|
if(target !is null) {
|
|
const SystemDesc@ sys = target.system;
|
|
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
|
|
Region@ other = getSystem(sys.adjacent[i]).object;
|
|
if(!ai.knownSystem(other))
|
|
systems.insertLast(other);
|
|
}
|
|
}
|
|
else {
|
|
search.reset();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(systems.length == 0) {
|
|
for(uint i = 0; i < 4; ++i) {
|
|
auto@ sys = getSystem(randomi(0,systemCount-1)).object;
|
|
if(!ai.knownSystem(sys))
|
|
systems.insertLast(sys);
|
|
}
|
|
}
|
|
|
|
if(systems.length != 0) {
|
|
if(ai.skillScout <= DIFF_Easy) {
|
|
@system = systems[randomi(0,systems.length-1)];
|
|
}
|
|
else {
|
|
//Medium+ AIs will try to select shorter scouting patterns
|
|
Object@ home = ai.homeworld;
|
|
if(home is null)
|
|
@home = ai.empire.HomeObj;
|
|
|
|
vec3d nearTo = home !is null ? home.position : vec3d();
|
|
auto@ scouts = ai.fleets[FT_Scout];
|
|
if(scouts.length > 0 && randomi(0,4) != 0)
|
|
nearTo = scouts[randomi(0,scouts.length-1)].position;
|
|
|
|
double nearestDist = INFINITY;
|
|
for(uint i = 0, cnt = systems.length; i < cnt; ++i) {
|
|
double dist = nearTo.distanceToSQ(systems[i].position);
|
|
if(system is null || dist < nearestDist) {
|
|
nearestDist = dist;
|
|
@system = systems[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
if(system !is null)
|
|
ai.focus = system.position;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
atomic_int nextArmadaID(0);
|
|
|
|
final class Armada {
|
|
int id = ++nextArmadaID;
|
|
array<Ship@> fleets;
|
|
Region@ target;
|
|
|
|
bool inCombat = false;
|
|
|
|
Planet@ siegePlanet;
|
|
|
|
void save(SaveFile& file) {
|
|
file << uint(fleets.length);
|
|
for(uint i = 0; i < fleets.length; ++i)
|
|
file << fleets[i];
|
|
file << target;
|
|
file << inCombat;
|
|
file << siegePlanet;
|
|
}
|
|
|
|
Armada() {}
|
|
|
|
Armada(SaveFile& file) {
|
|
uint count = 0;
|
|
file >> count;
|
|
fleets.length = count;
|
|
for(uint i = 0; i < fleets.length; ++i)
|
|
file >> fleets[i];
|
|
file >> target;
|
|
file >> inCombat;
|
|
file >> siegePlanet;
|
|
if(file < SV_0010) {
|
|
Object@ dummy;
|
|
file >> dummy;
|
|
}
|
|
}
|
|
|
|
bool validate(BasicAI@ ai) {
|
|
for(int i = fleets.length - 1; i >= 0; --i) {
|
|
Ship@ ship = fleets[i];
|
|
if(ship.owner !is ai.empire || !ship.valid) {
|
|
auto@ bp = ship.blueprint;
|
|
if(bp !is null && bp.design !is null)
|
|
ai.willpower -= lostFleetWill * float(bp.design.size);
|
|
fleets.removeAt(i);
|
|
}
|
|
}
|
|
|
|
return fleets.length != 0;
|
|
}
|
|
|
|
uint get_fleetCount() const {
|
|
return fleets.length;
|
|
}
|
|
|
|
vec3d get_position() const {
|
|
if(fleets.length == 0)
|
|
return vec3d();
|
|
else
|
|
return fleets[0].position;
|
|
}
|
|
|
|
Region@ get_region() const {
|
|
if(fleets.length == 0)
|
|
return null;
|
|
else
|
|
return fleets[0].region;
|
|
}
|
|
|
|
void addFleet(BasicAI@ ai, Ship@ ship) {
|
|
if(target !is null && ship.region !is target) {
|
|
vec3d pos;
|
|
if(fleets.length == 0)
|
|
pos = target.position + (ship.position - target.position).normalized(target.radius * 0.8);
|
|
else
|
|
pos = position + random3d(50.0);
|
|
|
|
moveToFastest(ship, pos, ai.ftl);
|
|
if(siegePlanet !is null)
|
|
ship.addCaptureOrder(siegePlanet, append=true);
|
|
}
|
|
else if(siegePlanet !is null) {
|
|
ship.addCaptureOrder(siegePlanet);
|
|
}
|
|
|
|
fleets.insertLast(ship);
|
|
}
|
|
|
|
bool isInCombat(Empire@ empire) const {
|
|
if(inCombat)
|
|
return true;
|
|
if(target is null)
|
|
return false;
|
|
|
|
bool anyEnemyPresence = false;
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
auto@ emp = getEmpire(i);
|
|
if(emp.valid && emp.major && empire.isHostile(emp)) {
|
|
if(target.getStrength(empire) > 15) {
|
|
anyEnemyPresence = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!anyEnemyPresence && target.ContestedMask & empire.mask == 0 && target.TradeMask & empire.hostileMask == 0)
|
|
return false;
|
|
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
if(fleets[i].region is target)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool get_isInTransit() const {
|
|
if(target is null)
|
|
return false;
|
|
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
Ship@ fleet = fleets[i];
|
|
if(fleet.region !is target && fleet.hasOrders)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
double get_lowestSupplyPct() const {
|
|
double lowest = 1.0;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
const Ship@ ship = fleets[i];
|
|
double pct = double(ship.Supply) / double(ship.MaxSupply);
|
|
if(pct < lowest)
|
|
lowest = pct;
|
|
}
|
|
return lowest;
|
|
}
|
|
|
|
double get_totalSupply() const {
|
|
double supply = 0;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
supply += double(fleets[i].Supply);
|
|
return supply;
|
|
}
|
|
|
|
double get_maxSupply() const {
|
|
double supply = 0;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
supply += double(fleets[i].MaxSupply);
|
|
return supply;
|
|
}
|
|
|
|
double get_totalEffectiveness() const {
|
|
double hp = 0, dps = 0;
|
|
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
Ship@ leader = fleets[i];
|
|
hp += leader.getFleetHP();
|
|
dps += leader.getFleetDPS();
|
|
}
|
|
|
|
return hp * dps;
|
|
}
|
|
|
|
Ship@ freeToStrength(double goalStr) {
|
|
goalStr = sqrt(goalStr);
|
|
|
|
array<double> strengths(fleets.length);
|
|
double totalStr = 0.0;
|
|
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
Ship@ leader = fleets[i];
|
|
double str = sqrt(leader.getFleetStrength());
|
|
totalStr += str;
|
|
|
|
strengths[i] = str;
|
|
}
|
|
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
double str = strengths[i];
|
|
if(totalStr - str > goalStr) {
|
|
Ship@ fleet = fleets[i];
|
|
strengths.removeAt(i);
|
|
fleets.removeAt(i);
|
|
return fleet;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
void clear() {
|
|
fleets.length = 0;
|
|
@target = null;
|
|
inCombat = false;
|
|
@siegePlanet = null;
|
|
}
|
|
|
|
void freeFleets(BasicAI@ ai, FleetType asType) {
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
ai.freeFleet(fleets[i], asType);
|
|
fleets.length = 0;
|
|
}
|
|
|
|
bool get_hasOrderedSupports() const {
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
if(fleets[i].hasOrderedSupports)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
uint get_supportCapacityAvailable() const {
|
|
uint avail = 0;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
avail += fleets[i].SupplyAvailable;
|
|
return avail;
|
|
}
|
|
|
|
void moveTo(BasicAI@ ai, Region@ region, bool tryFTL = true, Object@ around = null) {
|
|
if(fleets.length == 0)
|
|
return;
|
|
|
|
@siegePlanet = null;
|
|
vec3d pos;
|
|
if(around is null)
|
|
pos = region.position + (fleets[0].position - region.position).normalized(region.radius * 0.8);
|
|
else
|
|
pos = around.position + (fleets[0].position - around.position).normalized(around.radius + 100.0);
|
|
|
|
bool doFTL = false;
|
|
if(tryFTL && (ai.ftl == FTL_Hyperdrive || ai.ftl == FTL_Fling || ai.ftl == FTL_Jumpdrive)) {
|
|
int cost = 0;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
Ship@ ship = fleets[i];
|
|
if(ship.region is region)
|
|
continue;
|
|
|
|
if(ai.ftl == FTL_Hyperdrive)
|
|
cost += hyperdriveCost(ship, pos);
|
|
else if(ai.ftl == FTL_Jumpdrive) {
|
|
if(ship.position.distanceTo(pos) > jumpdriveRange(ship) * 1.25) {
|
|
doFTL = false;
|
|
break;
|
|
}
|
|
cost += jumpdriveCost(ship, pos);
|
|
}
|
|
else
|
|
cost += flingCost(ship, pos);
|
|
|
|
}
|
|
|
|
doFTL = ai.empire.FTLStored >= cost;
|
|
}
|
|
|
|
Object@ fling = null;
|
|
if(doFTL && ai.empire.hasFlingBeacons) {
|
|
@fling = ai.empire.getFlingBeacon(fleets[0].position);
|
|
if(fling is null)
|
|
doFTL = false;
|
|
}
|
|
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
Ship@ ship = fleets[i];
|
|
if(ship.region is region)
|
|
continue;
|
|
|
|
vec3d d = pos + random3d(double(fleets.length) * 25.0);
|
|
|
|
if(doFTL) {
|
|
if(ai.ftl == FTL_Hyperdrive)
|
|
ship.addHyperdriveOrder(d);
|
|
else if(ai.ftl == FTL_Jumpdrive)
|
|
ship.addJumpdriveOrder(d);
|
|
else
|
|
ship.addFlingOrder(fling, d);
|
|
ship.addMoveOrder(d, append=true);
|
|
}
|
|
else {
|
|
ship.addMoveOrder(d);
|
|
}
|
|
}
|
|
|
|
@target = region;
|
|
}
|
|
|
|
void attack(Object@ target) {
|
|
@siegePlanet = null;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
fleets[i].addAttackOrder(target);
|
|
}
|
|
|
|
void pickup(Pickup@ bonus) {
|
|
@siegePlanet = null;
|
|
if(fleets.length != 0)
|
|
fleets[0].addPickupOrder(bonus);
|
|
}
|
|
|
|
void rebuildAllGhosts() {
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
fleets[i].rebuildAllGhosts();
|
|
}
|
|
|
|
void refreshSupportsFrom(Object@ obj) {
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
fleets[i].refreshSupportsFrom(obj);
|
|
}
|
|
|
|
void fillFleets(BasicAI@ ai) {
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
ai.fillFleet(fleets[i]);
|
|
}
|
|
|
|
void clearGhosts() {
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
fleets[i].clearAllGhosts();
|
|
}
|
|
|
|
void conquer(Planet@ pl) {
|
|
if(siegePlanet is pl)
|
|
return;
|
|
@siegePlanet = pl;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
fleets[i].addCaptureOrder(pl);
|
|
}
|
|
};
|
|
|
|
int64 defendHash() {
|
|
return int64(ACT_Defend) << ACT_BIT_OFFSET;
|
|
}
|
|
|
|
class Defend : Action {
|
|
Region@ region;
|
|
Armada@ armada;
|
|
SysSearch search;
|
|
|
|
Defend() {
|
|
@armada = Armada();
|
|
}
|
|
|
|
Defend(BasicAI@ ai, SaveFile& file) {
|
|
file >> region;
|
|
@armada = Armada(file);
|
|
search.load(file);
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& file) {
|
|
file << region;
|
|
armada.save(file);
|
|
search.save(file);
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return defendHash();
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Defend;
|
|
}
|
|
|
|
string get_state() const {
|
|
if(region is null)
|
|
return "Defending";
|
|
else if(armada.fleetCount == 0)
|
|
return "Defending " + region.name;
|
|
else
|
|
return "Awaiting defense armada arrival at " + region.name;
|
|
}
|
|
|
|
double getRegionStrength(BasicAI@ ai, Region@ atRegion) {
|
|
if(atRegion is null)
|
|
return 0;
|
|
PlanRegion@ reg = ai.getPlanRegion(atRegion);
|
|
if(reg is null)
|
|
return 0;
|
|
|
|
double str = 0.0;
|
|
uint hostileMask = ai.empire.hostileMask;
|
|
for(uint i = 0; i < getEmpireCount(); ++i) {
|
|
Empire@ emp = getEmpire(i);
|
|
if(emp.mask & hostileMask == 0 || !emp.valid)
|
|
continue;
|
|
|
|
str += reg.strengths[emp.index];
|
|
}
|
|
return str * str;
|
|
}
|
|
|
|
bool searchBorder = false;
|
|
PlanRegion@ searchForRegion(BasicAI@ ai, uint tries, double maxStr = INFINITY) {
|
|
double weakest = 0;
|
|
PlanRegion@ result;
|
|
for(uint i = 0; i < tries; ++i) {
|
|
PlanRegion@ reg;
|
|
if(ai.skillCombat < DIFF_Medium)
|
|
@reg = search.random(ai.ourSystems);
|
|
else
|
|
@reg = search.next(ai.ourSystems);
|
|
|
|
if(reg !is null) {
|
|
if(searchBorder || (ai.skillCombat < DIFF_Medium && randomi(0,2) == 0)) {
|
|
@reg = ai.getBorderSystem(reg);
|
|
if(reg is null)
|
|
continue;
|
|
}
|
|
else if(reg.region.VisionMask & ai.empire.visionMask == 0) {
|
|
if(reg.age > 360.0 && !ai.didTickScan)
|
|
reg.scout(ai);
|
|
continue;
|
|
}
|
|
|
|
|
|
//See if there are any enemies in the system
|
|
double str = getRegionStrength(ai, reg.region);
|
|
if(str > 0.0 && str < maxStr && (result is null || str < weakest)) {
|
|
@result = reg;
|
|
weakest = str;
|
|
}
|
|
}
|
|
else {
|
|
search.reset();
|
|
searchBorder = !searchBorder;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(result !is null) {
|
|
search.reset();
|
|
searchBorder = false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Region@ getHome(BasicAI@ ai) {
|
|
Object@ obj = ai.homeworld;
|
|
if(obj !is null && obj.owner is ai.empire && obj.region !is null)
|
|
return obj.region;
|
|
if(ai.factories.length > 0) {
|
|
@obj = ai.factories[0];
|
|
if(obj !is null && obj.owner is ai.empire && obj.region !is null)
|
|
return obj.region;
|
|
}
|
|
if(ai.ourSystems.length > 0)
|
|
return ai.ourSystems[0].region;
|
|
return null;
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
//Look for a system that needs defending
|
|
if(region is null) {
|
|
PlanRegion@ reg = searchForRegion(ai, 2 + ai.skillCombat * 2);
|
|
if(reg !is null)
|
|
@region = reg.region;
|
|
|
|
if(armada.fleetCount != 0 && armada.hasOrderedSupports) {
|
|
if(!armada.isInTransit) {
|
|
auto@ home = getHome(ai);
|
|
if(home is null)
|
|
return true;
|
|
if(armada.target is home && armada.region is home) {
|
|
ai.focus = armada.target.position;
|
|
//Use up any support ships available here
|
|
for(uint i = 0, cnt = armada.target.planetCount; i < cnt; ++i) {
|
|
Planet@ pl = armada.target.planets[i];
|
|
if(pl.owner is ai.empire)
|
|
armada.refreshSupportsFrom(pl);
|
|
}
|
|
armada.fillFleets(ai);
|
|
}
|
|
else {
|
|
armada.moveTo(ai, home);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else if(region.VisionMask & ai.empire.visionMask == 0 && !ai.isBorderSystem(ai.findSystem(region))) {
|
|
@region = null;
|
|
armada.fillFleets(ai);
|
|
ai.focus = armada.position;
|
|
return false;
|
|
}
|
|
|
|
armada.validate(ai);
|
|
if(armada.fleetCount == 0) {
|
|
Ship@ fleet = ai.getAvailableFleet(FT_Combat);
|
|
if(fleet !is null) {
|
|
armada.addFleet(ai, fleet);
|
|
ai.focus = fleet.position;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if(armada.target !is region) {
|
|
if(!armada.isInTransit) {
|
|
if(armada.hasOrderedSupports) {
|
|
auto@ home = getHome(ai);
|
|
if(armada.region !is home)
|
|
armada.moveTo(ai, home);
|
|
}
|
|
else {
|
|
//Build up defense fleets until we can take them
|
|
double enemy = getRegionStrength(ai, region);
|
|
double us = armada.totalEffectiveness;
|
|
if(us > 0.8 * enemy) {
|
|
if(us > 1.25 * enemy && armada.fleetCount > 1) {
|
|
Ship@ freed = armada.freeToStrength(1.25 * enemy);
|
|
if(freed !is null)
|
|
ai.freeFleet(freed, FT_Combat);
|
|
}
|
|
armada.moveTo(ai, region);
|
|
}
|
|
else {
|
|
//Look for alternate targets weaker than ourselves while trying to build up
|
|
PlanRegion@ weaker = searchForRegion(ai, 1 + ai.skillCombat * 1, us * 1.1);
|
|
if(weaker is null) {
|
|
auto@ ship = armada.fleets[randomi(0, armada.fleetCount-1)];
|
|
ai.fillFleet(ship);
|
|
|
|
//Add available fleets as necessary
|
|
// If there aren't any available, see if other systems need defending
|
|
Ship@ fleet = ai.getAvailableFleet(FT_Combat);
|
|
if(fleet !is null)
|
|
armada.addFleet(ai, fleet);
|
|
else
|
|
@region = null;
|
|
}
|
|
else {
|
|
@region = weaker.region;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
else if(!armada.isInTransit) {
|
|
if(armada.region !is region) {
|
|
armada.moveTo(ai, region);
|
|
return false;
|
|
}
|
|
|
|
ai.focus = region.position;
|
|
//Check that the enemies died
|
|
PlanRegion@ plan = ai.getPlanRegion(region);
|
|
array<Object@>@ objects = plan.scout(ai);
|
|
if(plan.planetMask & ai.empire.mask == 0 && !ai.isBorderSystem(plan)) {
|
|
@region = null;
|
|
armada.fillFleets(ai);
|
|
return false;
|
|
}
|
|
|
|
Pickup@ pickup;
|
|
Object@ support;
|
|
|
|
uint hostileMask = ai.empire.hostileMask;
|
|
for(uint i = 0; i < objects.length; ++i) {
|
|
Object@ obj = objects[i];
|
|
if(obj.isShip || obj.isOrbital) {
|
|
if(obj.owner.mask & hostileMask != 0) {
|
|
if(obj.hasSupportAI)
|
|
@support = obj;
|
|
else {
|
|
ai.focus = obj.position;
|
|
armada.attack(obj);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if(obj.isPickup) {
|
|
@pickup = cast<Pickup>(obj);
|
|
}
|
|
}
|
|
|
|
if(pickup !is null) {
|
|
ai.focus = pickup.position;
|
|
armada.pickup(pickup);
|
|
return false;
|
|
}
|
|
|
|
if(support !is null) {
|
|
ai.focus = support.position;
|
|
armada.attack(support);
|
|
return false;
|
|
}
|
|
|
|
@region = null;
|
|
armada.fillFleets(ai);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int64 buildFleetHash(FleetType type) {
|
|
return (int64(ACT_Build) << ACT_BIT_OFFSET) | (int64(type));
|
|
}
|
|
|
|
class BuildFleet : Action {
|
|
PlanRegion@ system;
|
|
Object@ builder;
|
|
Ship@ leader;
|
|
FleetType type;
|
|
FlagshipTask flag;
|
|
int64 Hash;
|
|
|
|
BuildFleet(FleetType Type) {
|
|
Hash = buildFleetHash(Type);
|
|
type = Type;
|
|
switch(type) {
|
|
case FT_Scout:
|
|
flag = FST_Scout; break;
|
|
case FT_Mothership:
|
|
flag = FST_Mothership; break;
|
|
default:
|
|
flag = FST_Combat; break;
|
|
}
|
|
}
|
|
|
|
BuildFleet(BasicAI@ ai, SaveFile& msg) {
|
|
Object@ focus;
|
|
msg >> focus;
|
|
if(focus !is null)
|
|
@system = ai.getPlanRegion(focus);
|
|
|
|
msg >> builder;
|
|
|
|
uint tp = 0;
|
|
msg >> tp;
|
|
type = FleetType(tp);
|
|
|
|
msg >> Hash;
|
|
|
|
msg >> leader;
|
|
|
|
switch(type) {
|
|
case FT_Scout:
|
|
flag = FST_Scout; break;
|
|
case FT_Mothership:
|
|
flag = FST_Mothership; break;
|
|
default:
|
|
flag = FST_Combat; break;
|
|
}
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
if(system is null) {
|
|
Object@ none;
|
|
msg << none;
|
|
}
|
|
else {
|
|
msg << system.region;
|
|
}
|
|
|
|
msg << builder;
|
|
msg << uint(type);
|
|
msg << Hash;
|
|
msg << leader;
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return Hash;
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Build;
|
|
}
|
|
|
|
string get_state() const {
|
|
if(system is null)
|
|
return "Building fleet (type " + type + ")";
|
|
else if(builder is null)
|
|
return "Building fleet in " + system.region.name + " (type " + type + ")";
|
|
else
|
|
return "Building fleet at " + builder.name + " (type " + type + ")";
|
|
}
|
|
|
|
Ship@ findShipIn(BasicAI@ ai, Region@ region) {
|
|
auto@ freeFleets = ai.untrackedFleets;
|
|
auto@ expected = ai.getDesign(DT_Flagship, flag, create=false);
|
|
if(expected is null)
|
|
return null;
|
|
@expected = expected.newest();
|
|
|
|
for(uint i = 0, cnt = freeFleets.length; i < cnt; ++i) {
|
|
Ship@ ship = freeFleets[i];
|
|
if(ship.owner !is ai.empire || !ship.valid) {
|
|
freeFleets.removeAt(i);
|
|
--i; --cnt;
|
|
continue;
|
|
}
|
|
|
|
if(region !is null && ship.region !is region)
|
|
continue;
|
|
if(ship.region is null)
|
|
continue;
|
|
|
|
const Design@ design = ship.blueprint.design;
|
|
if(design.newest() !is expected)
|
|
continue;
|
|
|
|
ai.focus = ship.position;
|
|
|
|
freeFleets.removeAt(i);
|
|
|
|
switch(type) {
|
|
case FT_Scout:
|
|
ship.setHoldPosition(true);
|
|
break;
|
|
case FT_Mothership:
|
|
break;
|
|
default: {
|
|
ai.willpower += gotFleetWill * float(design.size);
|
|
//Use up any support ships available here
|
|
for(uint j = 0, jcnt = ship.region.planetCount; j < jcnt; ++j) {
|
|
Planet@ pl = ship.region.planets[j];
|
|
if(pl.owner is ai.empire)
|
|
ship.refreshSupportsFrom(pl);
|
|
}
|
|
|
|
ship.wait();
|
|
uint supports = ship.SupplyAvailable;
|
|
|
|
if(supports > 0) {
|
|
//Heavy Gunships
|
|
const Design@ hvy, lit, tnk, fill;
|
|
@hvy = ai.getDesign(DT_Support, ST_AntiFlagship);
|
|
if(hvy is null)
|
|
@hvy = ai.empire.getDesign("Heavy Gunship");
|
|
@lit = ai.getDesign(DT_Support, ST_AntiSupport);
|
|
if(lit is null)
|
|
@lit = ai.empire.getDesign("Beamship");
|
|
@tnk = ai.getDesign(DT_Support, ST_Tank);
|
|
if(tnk is null)
|
|
@tnk = ai.empire.getDesign("Missile Boat");
|
|
@fill = ai.getDesign(DT_Support, ST_Filler);
|
|
if(fill is null)
|
|
@fill = ai.empire.getDesign("Gunship");
|
|
|
|
uint heavy = (supports / 3) / uint(hvy.size);
|
|
uint light = (supports / 3) / uint(lit.size);
|
|
uint tank = (supports / 5) / uint(tnk.size);
|
|
uint filler = (supports - (heavy * uint(hvy.size) + light * uint(lit.size) + tank * uint(tnk.size))) / uint(fill.size);
|
|
|
|
if(heavy > 0)
|
|
ship.orderSupports(hvy, heavy);
|
|
if(light > 0)
|
|
ship.orderSupports(lit, light);
|
|
if(tank > 0)
|
|
ship.orderSupports(tnk, tank);
|
|
if(filler > 0)
|
|
ship.orderSupports(fill, filler);
|
|
}
|
|
} break;
|
|
}
|
|
|
|
return ship;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
if(leader !is null) {
|
|
if(!leader.valid || leader.owner !is ai.empire) {
|
|
@leader = null;
|
|
}
|
|
else {
|
|
ai.freeFleet(leader, type);
|
|
if(type == FT_Mothership)
|
|
ai.factories.insertLast(leader);
|
|
@leader = null;
|
|
return true;
|
|
}
|
|
}
|
|
else if(system !is null) {
|
|
Region@ region = system.region;
|
|
|
|
@leader = findShipIn(ai, system.region);
|
|
if(leader !is null) {
|
|
@builder = null;
|
|
@system = null;
|
|
return false;
|
|
}
|
|
|
|
//Sometimes our constructor fails, oh well
|
|
if(!builder.valid || builder.owner !is ai.empire || ai.isBuildIdle(builder) || builder.laborIncome == 0.0 || !builder.canBuildShips) {
|
|
@builder = null;
|
|
@system = null;
|
|
}
|
|
}
|
|
else {
|
|
@leader = findShipIn(ai, null);
|
|
if(leader !is null) {
|
|
@builder = null;
|
|
@system = null;
|
|
return false;
|
|
}
|
|
|
|
const Design@ dsg = ai.getDesign(DT_Flagship, flag);
|
|
if(dsg is null)
|
|
return false;
|
|
|
|
for(uint i = 0, cnt = ai.factories.length; i < cnt; ++i) {
|
|
Object@ buildAt = ai.factories[i];
|
|
|
|
if(buildAt.owner is ai.empire && buildAt.hasConstruction && buildAt.canBuildShips &&
|
|
ai.isBuildIdle(buildAt) && buildAt.laborIncome > TILE_LABOR_RATE * 2.95)
|
|
{
|
|
buildAt.buildFlagship(dsg);
|
|
|
|
if(ai.skillEconomy >= DIFF_Medium) {
|
|
int buildID = buildAt.constructionID[0];
|
|
if(buildID != -1) {
|
|
//Pre-order supports
|
|
uint supports = dsg.total(SV_SupportCapacity);
|
|
if(ai.skillEconomy < DIFF_Hard)
|
|
supports /= 2;
|
|
|
|
const Design@ hvy, lit, tnk, fill;
|
|
@hvy = ai.getDesign(DT_Support, ST_AntiFlagship);
|
|
if(hvy is null)
|
|
@hvy = ai.empire.getDesign("Heavy Gunship");
|
|
@lit = ai.getDesign(DT_Support, ST_AntiSupport);
|
|
if(lit is null)
|
|
@lit = ai.empire.getDesign("Beamship");
|
|
@tnk = ai.getDesign(DT_Support, ST_Tank);
|
|
if(tnk is null)
|
|
@tnk = ai.empire.getDesign("Missile Boat");
|
|
@fill = ai.getDesign(DT_Support, ST_Filler);
|
|
if(fill is null)
|
|
@fill = ai.empire.getDesign("Gunship");
|
|
|
|
uint heavy = (supports / 3) / uint(hvy.size);
|
|
uint light = (supports / 3) / uint(lit.size);
|
|
uint tank = (supports / 5) / uint(tnk.size);
|
|
uint filler = (supports - (heavy * uint(hvy.size) + light * uint(lit.size) + tank * uint(tnk.size))) / uint(fill.size);
|
|
|
|
if(heavy > 0)
|
|
buildAt.addSupportShipConstruction(buildID, hvy, heavy);
|
|
if(light > 0)
|
|
buildAt.addSupportShipConstruction(buildID, lit, light);
|
|
if(tank > 0)
|
|
buildAt.addSupportShipConstruction(buildID, tnk, tank);
|
|
if(filler > 0)
|
|
buildAt.addSupportShipConstruction(buildID, fill, filler);
|
|
}
|
|
}
|
|
|
|
ai.focus = buildAt.position;
|
|
@builder = buildAt;
|
|
auto@ reg = buildAt.region;
|
|
@system = reg !is null ? ai.findSystem(buildAt.region) : null;
|
|
|
|
if(ai.skillEconomy >= DIFF_Medium && buildAt.isPlanet && buildAt.laborIncome < TILE_LABOR_RATE * min(gameTime/60.0 * 0.667, 25.0))
|
|
ai.addIdle( ai.requestImport(cast<Planet>(buildAt), ai.getResourceList(RT_LaborZero, onlyExportable=true), execute=false) );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int64 buildCombatHash(int systemID) {
|
|
return (int64(ACT_Combat) << ACT_BIT_OFFSET) | int64(systemID);
|
|
}
|
|
|
|
uint classifyDesign(BasicAI@ ai, const Design@ design) {
|
|
auto@ newest = design.newest();
|
|
int task = -1;
|
|
|
|
if(newest.hasTag(ST_Mothership))
|
|
return FT_Mothership;
|
|
if(design.hasTag(ST_Gate))
|
|
return FT_INVALID;
|
|
if(design.hasTag(ST_Slipstream))
|
|
return FT_Slipstream;
|
|
|
|
for(uint i = 0; i < FST_COUNT; ++i) {
|
|
auto@ dsg = ai.getDesign(DT_Flagship, i, create=false);
|
|
if(dsg !is null)
|
|
@dsg = dsg.newest();
|
|
if(newest is dsg) {
|
|
task = int(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if(task >= 0) {
|
|
switch(task) {
|
|
case FST_Scout:
|
|
return FT_Scout;
|
|
default:
|
|
return FT_Combat;
|
|
}
|
|
}
|
|
|
|
string name = newest.name;
|
|
if(name == "Scout")
|
|
return FST_Scout;
|
|
|
|
return FT_Combat;
|
|
}
|
|
|
|
class Combat : Action {
|
|
Object@ system;
|
|
Ship@ fleet;
|
|
bool checked = false;
|
|
int64 Hash;
|
|
|
|
Combat(Object@ System) {
|
|
Hash = buildCombatHash(System.id);
|
|
@system = System;
|
|
}
|
|
|
|
Combat(BasicAI@ ai, SaveFile& msg) {
|
|
msg >> system;
|
|
msg >> fleet;
|
|
Hash = buildCombatHash(system.id);
|
|
checked = fleet !is null;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& msg) {
|
|
msg << system;
|
|
msg << fleet;
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return Hash;
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Combat;
|
|
}
|
|
|
|
string get_state() const {
|
|
return "Engaging in combat in " + system.name;
|
|
}
|
|
|
|
void freeFleet(BasicAI@ ai) {
|
|
if(fleet !is null) {
|
|
fleet.rebuildAllGhosts();
|
|
ai.freeFleet(fleet, FT_Combat);
|
|
@fleet = null;
|
|
}
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
if(fleet is null) {
|
|
if(!checked) {
|
|
vec3d center = system.position;
|
|
vec3d bound(2000.0);
|
|
array<Object@>@ objs = findInBox(center - bound, center + bound, ai.empire.hostileMask);
|
|
if(objs.length == 0)
|
|
return true;
|
|
checked = true;
|
|
}
|
|
|
|
@fleet = ai.getAvailableFleet(FT_Combat);
|
|
}
|
|
else if(!fleet.valid) {
|
|
@fleet = null;
|
|
}
|
|
else if(fleet.orderCount == 0) {
|
|
vec3d center = system.position;
|
|
if(fleet.position.distanceTo(center) > 2000.0) {
|
|
vec3d off = fleet.position - center;
|
|
off.y = 0;
|
|
fleet.addMoveOrder(center + off.normalized(1000.0));
|
|
}
|
|
else {
|
|
vec3d bound(2000.0);
|
|
array<Object@>@ objs = findInBox(center - bound, center + bound, ai.empire.hostileMask);
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Object@ obj = objs[i];
|
|
if(obj.isShip || obj.isOrbital) {
|
|
fleet.addAttackOrder(obj);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//No enemies
|
|
freeFleet(ai);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int64 buildWarHash(Empire& target) {
|
|
return (int64(ACT_War) << ACT_BIT_OFFSET) | int64(target.id);
|
|
}
|
|
|
|
enum TargetGoal {
|
|
TG_Capture,
|
|
TG_Protect,
|
|
TG_Assist
|
|
};
|
|
|
|
final class WarTarget {
|
|
PlanRegion@ region;
|
|
TargetGoal goal = TG_Capture;
|
|
uint enemyPlanets = 0;
|
|
double enemyStrength = 0;
|
|
double value = 0;
|
|
|
|
double minSiegeSupply = 0;
|
|
double requestedSupply = 0;
|
|
double enrouteStr = 0;
|
|
|
|
int64 get_id() const {
|
|
return region.region.id;
|
|
}
|
|
|
|
int opCmp(const WarTarget& other) const {
|
|
double diff = value - other.value;
|
|
if(diff > 0.01)
|
|
return 1;
|
|
else if(diff < -0.01)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
Object@ getJumpTarget(Empire& emp) const {
|
|
if(goal == TG_Protect) {
|
|
Planet@ anyOwned;
|
|
for(uint i = 0, cnt = region.planets.length; i < cnt; ++i) {
|
|
Planet@ pl = region.planets[i];
|
|
if(pl.owner is emp) {
|
|
if(pl.captureEmpire !is null)
|
|
return pl;
|
|
else
|
|
@anyOwned = pl;
|
|
}
|
|
}
|
|
return anyOwned;
|
|
}
|
|
else {
|
|
for(uint i = 0, cnt = region.planets.length; i < cnt; ++i) {
|
|
Planet@ pl = region.planets[i];
|
|
if(emp.isHostile(pl.owner))
|
|
return pl;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
};
|
|
|
|
final class War : Action {
|
|
Empire@ enemy;
|
|
array<Armada@> fleets;
|
|
array<WarTarget@> orders;
|
|
array<WarTarget@> targets;
|
|
map targetMap;
|
|
|
|
Region@ stage;
|
|
|
|
Region@ scoutTarget;
|
|
Armada@ scout = Armada();
|
|
int64 Hash;
|
|
|
|
War(Empire@ Enemy) {
|
|
Hash = buildWarHash(Enemy);
|
|
@enemy = Enemy;
|
|
}
|
|
|
|
War(BasicAI@ ai, SaveFile& file) {
|
|
file >> enemy;
|
|
Hash = buildWarHash(enemy);
|
|
|
|
uint8 fleetCount = 0;
|
|
file >> fleetCount;
|
|
|
|
fleets.length = fleetCount;
|
|
orders.length = fleetCount;
|
|
for(uint i = 0; i < fleets.length; ++i) {
|
|
@fleets[i] = Armada(file);
|
|
if(file > SV_0030) {
|
|
int id = -1;
|
|
file >> id;
|
|
if(id != -1)
|
|
@orders[i] = getTarget(ai.getPlanRegion(cast<Region>(getObjectByID(id))));
|
|
}
|
|
}
|
|
|
|
if(file > SV_0030) {
|
|
file >> stage;
|
|
file >> scoutTarget;
|
|
}
|
|
@scout = Armada(file);
|
|
}
|
|
|
|
void freeFleets(BasicAI@ ai) {
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i)
|
|
fleets[i].freeFleets(ai, FT_Combat);
|
|
scout.freeFleets(ai, FT_Scout);
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& file) {
|
|
file << enemy;
|
|
|
|
uint8 fleetCount = uint8(fleets.length);
|
|
file << fleetCount;
|
|
|
|
for(uint i = 0; i < fleetCount; ++i) {
|
|
fleets[i].save(file);
|
|
int id = -1;
|
|
if(orders[i] !is null)
|
|
id = orders[i].region.region.id;
|
|
file << id;
|
|
}
|
|
|
|
file << stage;
|
|
|
|
file << scoutTarget;
|
|
scout.save(file);
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return Hash;
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_War;
|
|
}
|
|
|
|
string get_state() const {
|
|
return "Waging war against the " + enemy.name;
|
|
}
|
|
|
|
Region@ findNearestSafe(BasicAI@ ai, const vec3d& pos) {
|
|
if(stage !is null && stage.ContestedMask & ai.empire.mask == 0 && pos.distanceTo(stage.position) > stage.radius)
|
|
return stage;
|
|
|
|
Region@ nearest;
|
|
double dist = 0;
|
|
|
|
array<PlanRegion@>@ systems = ai.ourSystems;
|
|
for(uint i = 0, cnt = systems.length; i < cnt; ++i) {
|
|
Region@ region = systems[i].region;
|
|
if(region.ContestedMask & ai.empire.mask != 0)
|
|
continue;
|
|
double d = region.position.distanceToSQ(pos);
|
|
if(nearest is null || d < dist) {
|
|
dist = d;
|
|
@nearest = region;
|
|
}
|
|
}
|
|
|
|
return nearest;
|
|
}
|
|
|
|
bool scoutRegion(BasicAI@ ai, Region@ region) {
|
|
if(region.VisionMask & ai.empire.visionMask != 0) {
|
|
auto@ pr = ai.findSystem(region);
|
|
if(pr is null || !ai.knownSystem(region))
|
|
@pr = ai.addExploredSystem(region);
|
|
pr.scout(ai);
|
|
return true;
|
|
}
|
|
|
|
scout.validate(ai);
|
|
if(scout.fleetCount == 0) {
|
|
Ship@ scoutFleet = ai.getAvailableFleet(FT_Scout);
|
|
if(scoutFleet !is null) {
|
|
scoutFleet.clearOrders();
|
|
scout.addFleet(ai, scoutFleet);
|
|
}
|
|
}
|
|
else if(!scout.isInTransit) {
|
|
scout.moveTo(ai, region);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void processCombat(BasicAI@ ai, Armada@ fleet) {
|
|
uint empMask = ai.empire.mask;
|
|
uint visionMask = ai.empire.visionMask;
|
|
uint hostileMask = ai.empire.hostileMask;
|
|
Region@ region = fleet.target;
|
|
|
|
//We must wait until we get vision to make any more decisions
|
|
if(region.VisionMask & visionMask == 0)
|
|
return;
|
|
|
|
PlanRegion@ plan = ai.getPlanRegion(region);
|
|
|
|
array<Planet@> planets;
|
|
array<Ship@> enemyFleets;
|
|
array<Orbital@> enemyOrbitals;
|
|
|
|
array<Object@>@ objects = plan.scout(ai);
|
|
for(uint i = 0, cnt = objects.length; i < cnt; ++i) {
|
|
Object@ obj = objects[i];
|
|
if(obj.visibleMask & visionMask == 0 || obj.region !is region || obj.owner.mask & hostileMask == 0)
|
|
continue;
|
|
if(obj.isShip) {
|
|
if(obj.hasLeaderAI) {
|
|
enemyFleets.insertLast(cast<Ship>(obj));
|
|
}
|
|
}
|
|
else if(obj.isOrbital) {
|
|
enemyOrbitals.insertLast(cast<Orbital>(obj));
|
|
}
|
|
else if(obj.isPlanet) {
|
|
if(obj.owner.valid)
|
|
planets.insertLast(cast<Planet>(obj));
|
|
}
|
|
}
|
|
|
|
Planet@ targetPlanet;
|
|
int targetLoyalty = 0;
|
|
if(planets.length > 0) {
|
|
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
|
|
Planet@ pl = planets[i];
|
|
if(pl.owner is ai.empire) {
|
|
if(!ai.isKnownPlanet(pl)) {
|
|
ai.addPlanet(pl);
|
|
ai.markAsColony(pl.region);
|
|
}
|
|
}
|
|
else {
|
|
if(pl.owner.mask & ai.empire.hostileMask == 0 || pl.owner.mask & region.ProtectedMask.value != 0)
|
|
continue;
|
|
|
|
int loy = pl.getLoyaltyFacing(ai.empire);
|
|
if(targetPlanet is null || loy < targetLoyalty || (loy == targetLoyalty && pl.id < targetPlanet.id)) {
|
|
//Take planets in order of lowest loyalty first, and guarantee we always pick them in the same order (lowest id)
|
|
@targetPlanet = pl;
|
|
targetLoyalty = loy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Attack orbitals first
|
|
if(enemyOrbitals.length > 0) {
|
|
Orbital@ target = enemyOrbitals[0];
|
|
vec3d from = fleet.position;
|
|
double nearest = from.distanceToSQ(enemyOrbitals[0].position);
|
|
for(uint i = 1, cnt = enemyOrbitals.length; i < cnt; ++i) {
|
|
double dist = from.distanceToSQ(enemyOrbitals[i].position);
|
|
if(dist < nearest) {
|
|
@target = enemyOrbitals[i];
|
|
nearest = dist;
|
|
}
|
|
}
|
|
|
|
fleet.attack(target);
|
|
return;
|
|
}
|
|
|
|
//Calculate enemy strength
|
|
double strength = 0, weak = 0;
|
|
Ship@ weakest;
|
|
|
|
double ourStrength = fleet.totalEffectiveness;
|
|
|
|
for(uint i = 0, cnt = enemyFleets.length; i < cnt; ++i) {
|
|
double hp = 0, dps = 0;
|
|
|
|
Ship@ leader = enemyFleets[i];
|
|
double str = leader.getFleetStrength();
|
|
if(str > 1000.0 && str > ourStrength * 1.0e-2 && str < ourStrength * 2.5) {
|
|
if(weakest is null || str < weak) {
|
|
@weakest = enemyFleets[i];
|
|
weak = str;
|
|
}
|
|
}
|
|
strength += sqrt(str);
|
|
}
|
|
strength *= strength;
|
|
|
|
bool underSiege = empMask & region.SiegedMask.value & region.ContestedMask != 0;
|
|
//Attack the criminal scum
|
|
if(underSiege) {
|
|
if(weakest !is null) {
|
|
fleet.attack(weakest);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//If there's a planet to siege, take it
|
|
if(targetPlanet !is null) {
|
|
//However, if we've run out of supply, we need to leave to a different region
|
|
double supply = fleet.totalSupply;
|
|
if(supply == 0.0 || supply < ((double(targetLoyalty) - 0.5) * 3000.0)) {
|
|
Region@ nearest = findNearestSafe(ai, fleet.position);
|
|
if(nearest !is null)
|
|
fleet.moveTo(ai, nearest);
|
|
return;
|
|
}
|
|
|
|
fleet.conquer(targetPlanet);
|
|
return;
|
|
}
|
|
|
|
//Attack the criminal scum
|
|
if(weakest !is null && strength > ourStrength * 0.02) {
|
|
fleet.attack(weakest);
|
|
return;
|
|
}
|
|
|
|
@fleet.target = null;
|
|
}
|
|
|
|
WarTarget@ getTarget(PlanRegion@ region) {
|
|
WarTarget@ targ;
|
|
targetMap.get(region.region.id, @targ);
|
|
if(targ is null) {
|
|
@targ = WarTarget();
|
|
@targ.region = region;
|
|
targetMap.set(region.region.id, @targ);
|
|
targets.insertLast(@targ);
|
|
}
|
|
return targ;
|
|
}
|
|
|
|
|
|
bool contestedFriendly = false;
|
|
|
|
void evaluateTargets(BasicAI@ ai) {
|
|
contestedFriendly = false;
|
|
double rescoutDelay = 12.0 * 60.0;
|
|
if(ai.skillScout < DIFF_Medium)
|
|
rescoutDelay *= 2.0;
|
|
|
|
for(uint i = 0, cnt = targets.length; i < cnt; ++i) {
|
|
auto@ target = targets[i];
|
|
target.value = -1.0;
|
|
target.enrouteStr = 0.0;
|
|
}
|
|
|
|
for(uint i = 0, cnt = orders.length; i < cnt; ++i) {
|
|
auto@ order = orders[i];
|
|
if(order is null)
|
|
continue;
|
|
order.enrouteStr = sqr(sqrt(order.enrouteStr) + sqrt(fleets[i].totalEffectiveness));
|
|
}
|
|
|
|
Region@ oldestUnknown, anyBorder, enemyBorder;
|
|
double oldest = 0, dist = 1e9;
|
|
|
|
vec3d averageUnknown;
|
|
uint unknownCount = 0;
|
|
array<Region@> borders;
|
|
vec3d averageEnemy;
|
|
uint enemyCount = 0;
|
|
array<Region@> enemyBorders;
|
|
|
|
//Go after systems that we know have enemy planets, and that border our systems
|
|
array<PlanRegion@>@ systems = ai.exploredSystems;
|
|
for(uint i = 0, cnt = systems.length; i < cnt; ++i) {
|
|
PlanRegion@ region = systems[i];
|
|
|
|
if(region.planetMask & enemy.mask != 0) {
|
|
averageUnknown += region.region.position;
|
|
enemyCount += 1;
|
|
}
|
|
else {
|
|
averageUnknown += region.region.position;
|
|
unknownCount += 1;
|
|
}
|
|
|
|
Region@ adjacent;
|
|
bool bordered = false;
|
|
const SystemDesc@ sys = region.system;
|
|
for(uint j = 0, jcnt = sys.adjacent.length; j < jcnt; ++j) {
|
|
@adjacent = getSystem(sys.adjacent[j]).object;
|
|
if(adjacent.TradeMask & ai.empire.mask != 0) {
|
|
bordered = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(region.age > 12.0 * 60.0) {
|
|
if(region.region.VisionMask & ai.empire.visionMask == 0) {
|
|
if(oldestUnknown is null || region.age > oldest) {
|
|
oldest = region.age;
|
|
@oldestUnknown = region.region;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bordered)
|
|
borders.insertLast(adjacent);
|
|
|
|
auto@ target = getTarget(region);
|
|
|
|
target.minSiegeSupply = 0;
|
|
target.requestedSupply = 0;
|
|
target.goal = TG_Capture;
|
|
|
|
if(ai.allyMask & region.region.ContestedMask != 0)
|
|
target.goal = TG_Assist;
|
|
else if(region.planetMask & enemy.mask == 0 || enemy.mask & region.region.ProtectedMask.value != 0)
|
|
continue;
|
|
|
|
if(bordered)
|
|
enemyBorders.insertLast(adjacent);
|
|
|
|
bool sysIsVisible = region.region.VisionMask & ai.empire.visionMask != 0;
|
|
|
|
target.enemyStrength = sqr(region.strengths[enemy.index]);
|
|
target.enemyPlanets = region.planetMask & enemy.mask != 0 ? 1 : 0;
|
|
if(sysIsVisible) {
|
|
target.minSiegeSupply = 1.0e10;
|
|
target.requestedSupply = 0;
|
|
for(uint j = 0, jcnt = region.planets.length; j < jcnt; ++j) {
|
|
Planet@ pl = region.planets[j];
|
|
Empire@ owner = pl.visibleOwnerToEmp(ai.empire);
|
|
if(ai.empire.isHostile(owner)) {
|
|
double siegeCost = 3000.0 * double(pl.currentLoyalty);
|
|
target.minSiegeSupply = min(target.minSiegeSupply, siegeCost);
|
|
target.requestedSupply += siegeCost;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
target.minSiegeSupply = max(9000.0, target.minSiegeSupply);
|
|
target.requestedSupply = 9000.0;
|
|
}
|
|
|
|
if(ai.allyMask & region.region.ContestedMask != 0)
|
|
target.value = 3.0;
|
|
else if(target.enemyStrength > 1000.0 || target.enemyPlanets > 0)
|
|
target.value = 0.5 + double(target.enemyPlanets);
|
|
if(!bordered)
|
|
target.value *= 0.125;
|
|
}
|
|
|
|
if(unknownCount > 0)
|
|
averageUnknown /= double(unknownCount);
|
|
if(enemyCount > 0)
|
|
averageEnemy /= double(enemyCount);
|
|
|
|
if(enemyBorders.length > 0) {
|
|
Region@ closest;
|
|
double dist = 1.0e35;
|
|
|
|
for(uint i = 0, cnt = enemyBorders.length; i < cnt; ++i) {
|
|
auto@ reg = enemyBorders[i];
|
|
double d = reg.position.distanceToSQ(averageEnemy);
|
|
if(d < dist) {
|
|
dist = d;
|
|
@closest = reg;
|
|
}
|
|
}
|
|
|
|
if(closest !is null)
|
|
@stage = closest;
|
|
}
|
|
else if(borders.length > 0) {
|
|
Region@ closest;
|
|
double dist = 1.0e35;
|
|
|
|
for(uint i = 0, cnt = borders.length; i < cnt; ++i) {
|
|
auto@ reg = borders[i];
|
|
double d = reg.position.distanceToSQ(averageUnknown);
|
|
if(d < dist) {
|
|
dist = d;
|
|
@closest = reg;
|
|
}
|
|
}
|
|
|
|
if(closest !is null)
|
|
@stage = closest;
|
|
}
|
|
|
|
if(oldestUnknown !is null && scoutTarget is null)
|
|
@scoutTarget = oldestUnknown;
|
|
|
|
//Look for systems we own that are under attack
|
|
@systems = ai.ourSystems;
|
|
for(uint i = 0, cnt = systems.length, offset = randomi(0,cnt-1); i < cnt; ++i) {
|
|
auto@ reg = systems[(i + offset) % cnt];
|
|
auto@ target = getTarget(reg);
|
|
bool sysIsVisible = reg.region.VisionMask & ai.empire.visionMask != 0;
|
|
|
|
if(!ai.didTickScan && reg.age > 3.0 * 60.0)
|
|
if(sysIsVisible && reg.strengths[enemy.index] <= 0.0 && reg.planetMask & enemy.mask == 0)
|
|
reg.scout(ai);
|
|
|
|
target.goal = TG_Protect;
|
|
target.enemyStrength = sqr(reg.strengths[enemy.index]);
|
|
target.enemyPlanets = reg.planetMask & enemy.mask != 0 ? 1 : 0;
|
|
target.requestedSupply = 0.0;
|
|
|
|
if(sysIsVisible) {
|
|
target.minSiegeSupply = 1.0e10;
|
|
for(uint j = 0, jcnt = reg.planets.length; j < jcnt; ++j) {
|
|
Planet@ pl = reg.planets[j];
|
|
if(pl.owner is enemy) {
|
|
double siegeCost = 3000.0 * double(pl.currentLoyalty);
|
|
target.minSiegeSupply = min(target.minSiegeSupply, siegeCost);
|
|
target.requestedSupply += siegeCost;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
target.minSiegeSupply = max(9000.0, target.minSiegeSupply);
|
|
target.requestedSupply = 9000.0;
|
|
}
|
|
|
|
if(!contestedFriendly)
|
|
if(reg.region.ContestedMask & ~reg.region.ProtectedMask.value & ai.empire.mask != 0 || reg.planetMask & ~reg.region.ProtectedMask.value & enemy.mask != 0)
|
|
contestedFriendly = true;
|
|
|
|
//Planets being sieged, in a contested system, which isn't protected
|
|
if(ai.empire.mask & reg.region.SiegedMask.value & reg.region.ContestedMask & ~reg.region.ProtectedMask.value != 0)
|
|
target.value = 10.0;
|
|
else if(target.enemyStrength > 1000.0)
|
|
target.value = 2.0 + double(target.enemyPlanets);
|
|
else if(target.enemyPlanets > 0) {
|
|
target.goal = TG_Capture;
|
|
target.value = 2.0 + double(target.enemyPlanets);
|
|
}
|
|
}
|
|
|
|
//Increase importance of requested systems
|
|
for(uint i = 0, cnt = ai.requests.length; i < cnt && i < 5; ++i) {
|
|
Region@ reg = ai.requests[i].region;
|
|
if(reg is null)
|
|
continue;
|
|
auto@ pr = ai.findSystem(reg);
|
|
if(pr is null)
|
|
continue;
|
|
auto@ target = getTarget(pr);
|
|
if(target is null)
|
|
continue;
|
|
|
|
target.value = (target.value * 1.2) + 0.3;
|
|
}
|
|
|
|
//Increase importance of systems that have revenant parts owned by enemies
|
|
for(uint i = 0, cnt = ai.revenantParts.length; i < cnt && i < 5; ++i) {
|
|
auto@ part = ai.revenantParts[i];
|
|
Region@ reg = part.region;
|
|
if(reg is null || !ai.empire.isHostile(part.owner))
|
|
continue;
|
|
auto@ pr = ai.findSystem(reg);
|
|
if(pr is null)
|
|
continue;
|
|
auto@ target = getTarget(pr);
|
|
if(target is null)
|
|
continue;
|
|
|
|
target.value = (target.value * 1.5 + 1.0);
|
|
}
|
|
}
|
|
|
|
Ship@ pullFleet(BasicAI@ ai, bool onlyFull = true, bool purchase = false) {
|
|
Ship@ ship;
|
|
if(ai.fleets[FT_Titan].length > 0)
|
|
@ship = ai.getAvailableFleet(FT_Titan);
|
|
else if(ai.fleets[FT_Carrier].length > 0)
|
|
@ship = ai.getAvailableFleet(FT_Carrier);
|
|
else if(ai.fleets[FT_Combat].length > 1)
|
|
@ship = ai.getAvailableFleet(FT_Combat);
|
|
else if(purchase)
|
|
@ship = ai.getAvailableFleet(FT_Titan);
|
|
if(onlyFull && ship !is null) {
|
|
if(ship.hasOrderedSupports) {
|
|
ai.freeFleet(ship, FT_Combat);
|
|
@ship = null;
|
|
}
|
|
else if(ship.SupplyAvailable > 0) {
|
|
//Wait till the fleet is full
|
|
if(!ai.fillFleet(ship)) {
|
|
ai.freeFleet(ship, FT_Combat);
|
|
@ship = null;
|
|
}
|
|
}
|
|
}
|
|
return ship;
|
|
}
|
|
|
|
string get_warID() const {
|
|
return enemy.name + " War: ";
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
if(!ai.empire.isHostile(enemy) || !enemy.hasPlanets) {
|
|
if(ai.logWar) ai.log.writeLine("War against " + ai.empire.name + " is over.");
|
|
freeFleets(ai);
|
|
return true;
|
|
}
|
|
|
|
//Idle the war, otherwise we won't free our fleets when the war ends
|
|
ai.addIdle(this);
|
|
|
|
//Check all current siege targets to see if they've been captured
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
Planet@ sieging = fleets[i].siegePlanet;
|
|
if(sieging !is null) {
|
|
if(sieging.owner is ai.empire) {
|
|
ai.addPlanet(sieging);
|
|
ai.markAsColony(sieging.region);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Clear out invalid/dead fleets
|
|
for(int i = int(fleets.length) - 1; i >= 0; --i) {
|
|
if(!fleets[i].validate(ai)) {
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleet " + fleets[i].id + " destroyed.");
|
|
fleets.removeAt(i);
|
|
orders.removeAt(i);
|
|
}
|
|
}
|
|
|
|
if(scoutTarget !is null)
|
|
if(scoutRegion(ai, scoutTarget))
|
|
@scoutTarget = null;
|
|
|
|
//Update target evaluations, then decide the most valuable deployments
|
|
// We are limited to a set number of deployments, based on difficulty (Easy=1, Medium=3, Hard=8)
|
|
evaluateTargets(ai);
|
|
|
|
bool newFleet = false;
|
|
|
|
if(fleets.length == 0) {
|
|
Ship@ fleet = pullFleet(ai, onlyFull = !contestedFriendly, purchase=true);
|
|
if(fleet !is null) {
|
|
fleets.insertLast(Armada());
|
|
orders.insertLast(null);
|
|
fleets[0].addFleet(ai, fleet);
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleet " + fleets[0].id + " created.");
|
|
}
|
|
}
|
|
else {
|
|
uint fleetCap = 1;
|
|
if(ai.skillCombat >= DIFF_Hard)
|
|
fleetCap = 8;
|
|
else if(ai.skillCombat >= DIFF_Medium)
|
|
fleetCap = 3;
|
|
|
|
//Create a spare fleet if we have a lot of unused fleets
|
|
if(fleets.length < fleetCap && (ai.fleets[FT_Combat].length > 0 || fleets[0].fleetCount >= 3)) {
|
|
bool allBusy = true;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
auto@ order = orders[i];
|
|
if(order is null || order.region.region is stage) {
|
|
allBusy = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(allBusy) {
|
|
Ship@ fleet = pullFleet(ai, purchase=true);
|
|
if(fleet !is null) {
|
|
fleets.insertLast(Armada());
|
|
orders.insertLast(null);
|
|
fleets[fleets.length-1].addFleet(ai, fleet);
|
|
newFleet = true;
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleet " + fleets[fleets.length-1].id + " created (active fleets busy).");
|
|
}
|
|
}
|
|
}
|
|
|
|
//Free excess idle fleets
|
|
if(fleets.length > 1) {
|
|
bool allIdle = true;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
auto@ order = orders[i];
|
|
if(order !is null && (order.region.region !is stage || stage.ContestedMask & ai.empire.mask != 0)) {
|
|
allIdle = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(allIdle) {
|
|
uint n = fleets.length-1;
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleet " + fleets[n].id + " released (fleets idle).");
|
|
fleets[n].freeFleets(ai, FT_Combat);
|
|
fleets.length = n;
|
|
orders.length = n;
|
|
}
|
|
}
|
|
|
|
uint index = newFleet ? fleets.length-1 : randomi(0, fleets.length-1);
|
|
Armada@ fleet = fleets[index];
|
|
if(fleet.isInTransit)
|
|
return false;
|
|
|
|
WarTarget@ target = orders[index];
|
|
if(target !is null)
|
|
ai.focus = target.region.region.position;
|
|
else
|
|
ai.focus = fleet.position;
|
|
|
|
double strength = fleet.totalEffectiveness;
|
|
double supply = fleet.totalSupply;
|
|
double siegeSupply = supply - 3000.0;
|
|
vec3d pos = fleet.position;
|
|
|
|
Region@ underAttack;
|
|
double scariest = 0;
|
|
|
|
double strThresh = 1.5;
|
|
if(ai.skillCombat < DIFF_Medium)
|
|
strThresh = 0.75;
|
|
|
|
//Find the best (other) target we have
|
|
WarTarget@ best;
|
|
double bestScore = 0;
|
|
for(uint i = 0, cnt = targets.length; i < cnt; ++i) {
|
|
auto@ other = targets[i];
|
|
if(other is target || other.value == 0.0)
|
|
continue;
|
|
double score = other.value * (8000.0 / (8000.0 + other.region.region.position.distanceTo(pos)));
|
|
|
|
if(other.enrouteStr > 0.0 && other.enrouteStr > other.enemyStrength * strThresh)
|
|
score *= 0.25 * other.enemyStrength / other.enrouteStr;
|
|
|
|
bool weak = false;
|
|
|
|
double totStr = sqr(sqrt(strength) + sqrt(other.enrouteStr));
|
|
if(totStr < other.enemyStrength * strThresh) {
|
|
score *= totStr / (other.enemyStrength * strThresh);
|
|
weak = true;
|
|
}
|
|
|
|
if(totStr < other.enemyStrength * 0.25)
|
|
score *= 0.1;
|
|
|
|
if(other.goal == TG_Protect && weak && (underAttack is null || scariest < other.value)) {
|
|
@underAttack = other.region.region;
|
|
scariest = other.value;
|
|
}
|
|
|
|
if(other.goal == TG_Capture) {
|
|
if(siegeSupply < other.minSiegeSupply)
|
|
score *= 0.0;
|
|
else if(siegeSupply < other.requestedSupply)
|
|
score *= 0.9;
|
|
}
|
|
|
|
if(best is null || score > bestScore) {
|
|
@best = other;
|
|
bestScore = score;
|
|
}
|
|
}
|
|
|
|
if(underAttack !is null && gameTime > ai.lastPing + 60.0) {
|
|
@ai.protect = ai.getPlanRegion(underAttack);
|
|
ai.lastPing = gameTime;
|
|
if(ai.allyMask != 0) {
|
|
vec2d p = random2d(250.0, underAttack.radius * 0.9);
|
|
sendPing(ai.empire, underAttack.position + vec3d(p.x, 0.0, p.y));
|
|
if(ai.logWar) ai.log.writeLine(warID + "Requesting help at " + underAttack.name + ".");
|
|
}
|
|
else {
|
|
if(ai.logWar) ai.log.writeLine(warID + "Protecting " + underAttack.name + ".");
|
|
}
|
|
}
|
|
|
|
double currentScore = 0;
|
|
if(target !is null) {
|
|
double score = target.value;
|
|
|
|
double totStr = sqr(sqrt(strength) + sqrt(target.enrouteStr));
|
|
if(totStr < target.enemyStrength * strThresh)
|
|
score *= totStr / (target.enemyStrength * strThresh);
|
|
if(totStr < target.enemyStrength * 0.25)
|
|
score *= 0.1;
|
|
|
|
if(target.goal == TG_Capture) {
|
|
if(siegeSupply < target.minSiegeSupply)
|
|
score *= 0.25;
|
|
else if(siegeSupply < target.requestedSupply)
|
|
score *= 0.9;
|
|
}
|
|
|
|
if(target.region.region.SiegingMask.value & (ai.empire.mask | ai.allyMask) != 0)
|
|
score *= 2.0;
|
|
|
|
currentScore = score;
|
|
}
|
|
|
|
if(fleet.isInCombat(ai.empire)) {
|
|
processCombat(ai, fleet);
|
|
|
|
if(target !is null) {
|
|
//Update target data
|
|
target.enemyStrength = sqr(target.region.strengths[enemy.index]);
|
|
target.enemyPlanets = (target.region.planetMask & enemy.mask != 0) ? 1 : 0;
|
|
double regionStr = target.enrouteStr;
|
|
|
|
//Decide if we should end the combat
|
|
bool endCombat = false;
|
|
if(target.goal == TG_Protect) {
|
|
if(regionStr < target.enemyStrength * (strThresh * 0.3333)) {
|
|
endCombat = true;
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleeing fleet " + fleet.id + " from " + target.region.region.name + " due to strong opposition.");
|
|
}
|
|
}
|
|
else if(ai.skillCombat >= DIFF_Medium) {
|
|
if(regionStr < target.enemyStrength * (strThresh * 0.5)) {
|
|
endCombat = true;
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleeing fleet " + fleet.id + " from " + target.region.region.name + " due to strong opposition.");
|
|
}
|
|
else if(target.enemyPlanets > 0 && siegeSupply < target.minSiegeSupply) {
|
|
endCombat = true;
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleeing fleet " + fleet.id + " from " + target.region.region.name + " due to insufficient supply.");
|
|
}
|
|
}
|
|
|
|
if(endCombat) {
|
|
Region@ safe = findNearestSafe(ai, pos);
|
|
if(safe !is null) {
|
|
if(ai.logWar) ai.log.writeLine("\tFleeing to " + safe.name);
|
|
fleet.moveTo(ai, safe);
|
|
return false;
|
|
}
|
|
else {
|
|
if(ai.logWar) ai.log.writeLine("\tNowhere to flee");
|
|
}
|
|
}
|
|
|
|
//Consider changing targets
|
|
if(bestScore > currentScore && best !is null) {
|
|
@orders[index] = best;
|
|
@target = best;
|
|
Object@ jumpTo = null;
|
|
if(ai.skillCombat >= DIFF_Medium)
|
|
@jumpTo = best.getJumpTarget(ai.empire);
|
|
fleet.moveTo(ai, best.region.region, around=jumpTo);
|
|
if(ai.logWar) ai.log.writeLine(warID + "Changing fleet " + fleet.id + " target to " + best.region.region.name + " due to better score.");
|
|
}
|
|
else if(regionStr < target.enemyStrength * 2.0) {
|
|
Ship@ ship = pullFleet(ai, onlyFull = (target.goal != TG_Protect));
|
|
if(ship !is null) {
|
|
fleet.addFleet(ai, ship);
|
|
if(ai.logWar) ai.log.writeLine(warID + "Assigning fleet to fleet " + fleet.id + " to improve strength.");
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if(target !is null) {
|
|
if(fleet.region is target.region.region) {
|
|
@target = best;
|
|
@orders[index] = target;
|
|
|
|
fleet.rebuildAllGhosts();
|
|
|
|
if(stage !is null && fleet.target !is stage) {
|
|
if(fleet.supportCapacityAvailable > 15 || fleet.hasOrderedSupports) {
|
|
fleet.moveTo(ai, stage);
|
|
if(ai.logWar) ai.log.writeLine(warID + "Staging fleet " + fleet.id + " to recover supports.");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//Consider changing targets
|
|
if(bestScore > target.value) {
|
|
@target = best;
|
|
@orders[index] = target;
|
|
}
|
|
|
|
//Attack only if we have enough strength, and gather fleets till we do
|
|
// When protecting systems, we accept more risk
|
|
// Easy AIs purposefully attack in worse situations
|
|
bool doAssault = true;
|
|
double reqStr = target.enemyStrength;
|
|
if(target.goal == TG_Protect)
|
|
reqStr *= 0.67;
|
|
else if(ai.skillCombat >= DIFF_Medium)
|
|
reqStr *= 1.5;
|
|
else
|
|
reqStr *= 0.75;
|
|
|
|
if(strength < reqStr) {
|
|
Ship@ ship = ai.getAvailableFleet(FT_Carrier);
|
|
if(ship !is null)
|
|
fleet.addFleet(ai, ship);
|
|
doAssault = false;
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleet " + fleet.id + " requires more strength to attack " + target.region.region.name + ".");
|
|
}
|
|
else if(target.goal == TG_Capture && fleet.fleetCount > 1) {
|
|
bool freedAny = false;
|
|
double needSupply = target.requestedSupply;
|
|
while(strength > reqStr * 2.5) {
|
|
Ship@ ship = fleet.freeToStrength(reqStr * 2.5);
|
|
if(ship is null)
|
|
break;
|
|
if(fleet.totalSupply > needSupply) {
|
|
ai.freeFleet(ship, FT_Combat);
|
|
freedAny = true;
|
|
}
|
|
else {
|
|
fleet.addFleet(ai, ship);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(freedAny && ai.logWar) ai.log.writeLine(warID + "Freed fleets from fleet " + fleet.id + " to meet needs.");
|
|
}
|
|
|
|
if(doAssault && target.goal == TG_Capture && (fleet.totalSupply <= target.minSiegeSupply || fleet.totalSupply < fleet.maxSupply * 0.9)) {
|
|
doAssault = false;
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleet " + fleet.id + " requires more supply to siege " + target.region.region.name + ".");
|
|
}
|
|
|
|
if(doAssault) {
|
|
Object@ jumpTo = null;
|
|
if(ai.skillCombat >= DIFF_Medium)
|
|
@jumpTo = target.getJumpTarget(ai.empire);
|
|
fleet.moveTo(ai, target.region.region, around=jumpTo);
|
|
if(ai.logWar) ai.log.writeLine(warID + "Fleet " + fleet.id + " sent to assault " + target.region.region.name + ".");
|
|
}
|
|
else {
|
|
Ship@ ship = ai.getAvailableFleet(FT_Carrier);
|
|
if(ship !is null)
|
|
fleet.addFleet(ai, ship);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if(target is null && stage !is null && fleet.target !is stage)
|
|
fleet.moveTo(ai, stage);
|
|
}
|
|
|
|
if(stage !is null && fleet.target is stage) {
|
|
fleet.fillFleets(ai);
|
|
|
|
@orders[index] = best;
|
|
@target = best;
|
|
Ship@ ship = pullFleet(ai);
|
|
if(ship !is null)
|
|
fleet.addFleet(ai, ship);
|
|
|
|
if(ai.logWar) ai.log.writeLine(warID + "Changing fleet " + fleet.id + " target to " + best.region.region.name + " because it is staging.");
|
|
|
|
if(ai.needsMainframes && ai.mainframeMod !is null) {
|
|
Orbital@ orb = ai.empire.getClosestOrbital(ai.mainframeMod.id, stage.position);
|
|
if(orb is null || orb.position.distanceTo(stage.position) > stage.radius)
|
|
ai.requestOrbital(stage, OT_Mainframe);
|
|
}
|
|
|
|
if(ai.ftl == FTL_Fling) {
|
|
if(ai.empire.getFlingBeacon(stage.position) is null)
|
|
ai.requestOrbital(stage, OT_FlingBeacon);
|
|
}
|
|
else if(ai.ftl == FTL_Gate) {
|
|
auto@ gate = ai.empire.getStargate(stage.position);
|
|
if(gate is null || gate.position.distanceTo(stage.position) > 10000.0)
|
|
ai.requestOrbital(stage, OT_Gate);
|
|
}
|
|
else if(ai.ftl == FTL_Slipstream) {
|
|
if(gameTime > ai.lastSlipstream + 300.0) {
|
|
auto@ slip = ai.getAvailableFleet(FT_Slipstream);
|
|
if(slip !is null && !slip.hasOrders) {
|
|
vec2d off = random2d(250.0, stage.radius);
|
|
vec3d dest = stage.position + vec3d(off.x, 0.0, off.y);
|
|
if(slipstreamCost(slip, 1, dest.distanceTo(slip.position)) <= ai.empire.FTLStored) {
|
|
slip.addSlipstreamOrder(dest);
|
|
ai.lastSlipstream = gameTime;
|
|
}
|
|
}
|
|
|
|
if(slip !is null)
|
|
ai.freeFleet(slip, FT_Slipstream);
|
|
}
|
|
}
|
|
|
|
if(fleet.hasOrderedSupports) {
|
|
if(!ai.factoriesInRegion(stage) && !ai.isMachineRace) {
|
|
ai.requestOrbital(stage, OT_Shipyard);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
int64 designHash(uint type, uint task) {
|
|
return int64(ACT_Design) << ACT_BIT_OFFSET | int64(type) << 32 | int64(task);
|
|
}
|
|
|
|
class MakeDesign : Action {
|
|
uint type, task;
|
|
Designer@ designer;
|
|
bool prepared = false;
|
|
array<const Design@> tries;
|
|
|
|
double goalSpeed = 0;
|
|
|
|
MakeDesign(uint Type, uint Task) {
|
|
type = Type;
|
|
task = Task;
|
|
}
|
|
|
|
MakeDesign(BasicAI@ ai, SaveFile& file) {
|
|
file >> type >> task;
|
|
}
|
|
|
|
void save(BasicAI@ ai, SaveFile& file) {
|
|
file << type << task;
|
|
}
|
|
|
|
void postLoad(BasicAI@ ai) {
|
|
}
|
|
|
|
int64 get_hash() const {
|
|
return designHash(type, task);
|
|
}
|
|
|
|
ActionType get_actionType() const {
|
|
return ACT_Design;
|
|
}
|
|
|
|
string get_state() const {
|
|
return "Designing " + type + ":" + task;
|
|
}
|
|
|
|
const Design@ getCurrentDesign(BasicAI@ ai) {
|
|
switch(type) {
|
|
case DT_Support:
|
|
if(task < ST_COUNT)
|
|
return ai.dsgSupports[task];
|
|
break;
|
|
case DT_Flagship:
|
|
if(task < FST_COUNT)
|
|
return ai.dsgFlagships[task];
|
|
break;
|
|
case DT_Station:
|
|
if(task < STT_COUNT)
|
|
return ai.dsgStations[task];
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void replaceDesign(BasicAI@ ai, const Design@ dsg) {
|
|
if(dsg is null)
|
|
return;
|
|
|
|
bool added = false;
|
|
const Design@ prev = getCurrentDesign(ai);
|
|
|
|
switch(type) {
|
|
case DT_Support:
|
|
if(task < ST_COUNT) {
|
|
@ai.dsgSupports[task] = dsg;
|
|
added = true;
|
|
}
|
|
if(dsg.total(SV_SupportSupplyCapacity) > 0.001) {
|
|
DesignSettings settings;
|
|
settings.behavior = SG_Brawler;
|
|
|
|
dsg.setSettings(settings);
|
|
}
|
|
else if(task == ST_Tank) {
|
|
DesignSettings settings;
|
|
settings.behavior = SG_Shield;
|
|
|
|
dsg.setSettings(settings);
|
|
}
|
|
break;
|
|
case DT_Flagship:
|
|
if(task < FST_COUNT) {
|
|
@ai.dsgFlagships[task] = dsg;
|
|
added = true;
|
|
}
|
|
break;
|
|
case DT_Station:if(task < STT_COUNT) {
|
|
@ai.dsgStations[task] = dsg;
|
|
added = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if(added) {
|
|
string name = dsg.name;
|
|
uint try = 0;
|
|
while(ai.empire.getDesign(name) !is null) {
|
|
name = dsg.name + " ";
|
|
appendRoman(++try, name);
|
|
}
|
|
if(name != dsg.name)
|
|
dsg.rename(name);
|
|
if(prev is null)
|
|
ai.empire.addDesign(ai.empire.getDesignClass("Combat", true), dsg);
|
|
else
|
|
ai.empire.changeDesign(prev, dsg, ai.empire.getDesignClass("Combat", true));
|
|
}
|
|
}
|
|
|
|
bool perform(BasicAI@ ai) {
|
|
if(!prepared) {
|
|
prepared = true;
|
|
|
|
double budget = ai.empire.TotalBudget;
|
|
auto@ cur = getCurrentDesign(ai);
|
|
|
|
double supportScaleFactor = max(1.0, log(budget / 500.0) / log(2.0));
|
|
double flagScaleFactor = supportScaleFactor;
|
|
if(ai.skillCombat >= DIFF_Medium) {
|
|
flagScaleFactor = max(flagScaleFactor, double(ai.empire.EstNextBudget) / 4.0 / 64.0);
|
|
flagScaleFactor = clamp(flagScaleFactor, 1.0, ((ai.getMaxFactoryLabor() * 60.0 * 10.0) * 2.5) / 64.0);
|
|
}
|
|
|
|
double goalScale = 1;
|
|
|
|
switch(type) {
|
|
case DT_Support:
|
|
goalSpeed = 3.0;
|
|
switch(task) {
|
|
case ST_Filler:
|
|
goalScale = supportScaleFactor; break;
|
|
case ST_AntiSupport:
|
|
goalScale = supportScaleFactor * 4.0; break;
|
|
case ST_AntiFlagship:
|
|
goalScale = supportScaleFactor * 9.0; break;
|
|
case ST_Tank:
|
|
goalScale = supportScaleFactor * 5.0; break;
|
|
case ST_Supplies:
|
|
goalScale = supportScaleFactor * 4.0; break;
|
|
default:
|
|
return true;
|
|
}
|
|
break;
|
|
case DT_Flagship:
|
|
goalSpeed = 1.45;
|
|
switch(task) {
|
|
case FST_Scout:
|
|
goalScale = 16.0;
|
|
if(ai.ftl != FTL_Hyperdrive && ai.ftl != FTL_Jumpdrive)
|
|
goalSpeed *= 2.0;
|
|
goalSpeed *= 2.5;
|
|
break;
|
|
case FST_Combat:
|
|
goalScale = flagScaleFactor * 64.0; break;
|
|
case FST_SuperHeavy:
|
|
goalScale = flagScaleFactor * 128.0; break;
|
|
default:
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
|
|
goalScale = max(floor(goalScale), 1.0);
|
|
if(cur !is null && floor(cur.size * 1.1) >= goalScale)
|
|
return true;
|
|
|
|
if(ai.cannotDesign) {
|
|
const Design@ baseDesign;
|
|
string name, tag;
|
|
switch(type) {
|
|
case DT_Support:
|
|
switch(randomi(0,3)) {
|
|
case 0: @baseDesign = ai.empire.getDesign("Gunship"); break;
|
|
case 1: @baseDesign = ai.empire.getDesign("Missile Boat"); break;
|
|
case 2: @baseDesign = ai.empire.getDesign("Beamship"); break;
|
|
case 3: @baseDesign = ai.empire.getDesign("Heavy Gunship"); break;
|
|
}
|
|
name = autoSupportNames[randomi(0,autoSupportNames.length-1)];
|
|
tag = "Support";
|
|
break;
|
|
case DT_Flagship:
|
|
if(task == FST_Scout) {
|
|
@baseDesign = ai.empire.getDesign("Scout");
|
|
}
|
|
else {
|
|
switch(randomi(0,2)) {
|
|
case 0: @baseDesign = ai.empire.getDesign("Heavy Carrier"); break;
|
|
case 1: @baseDesign = ai.empire.getDesign("Dreadnaught"); break;
|
|
case 2: @baseDesign = ai.empire.getDesign("Battleship"); break;
|
|
}
|
|
}
|
|
name = autoFlagNames[randomi(0,autoFlagNames.length-1)];
|
|
tag = "Flagship";
|
|
break;
|
|
}
|
|
|
|
if(baseDesign is null)
|
|
return true;
|
|
|
|
DesignDescriptor desc;
|
|
desc.name = name;
|
|
desc.className = "Combat";
|
|
desc.gridSize = getDesignGridSize(tag, goalScale);
|
|
desc.size = goalScale;
|
|
@desc.hull = getBestHull(desc, tag, ai.empire);
|
|
@desc.owner = ai.empire;
|
|
|
|
uint sysCnt = baseDesign.subsystemCount;
|
|
for(uint i = 0; i < sysCnt; ++i) {
|
|
const Subsystem@ sys = baseDesign.subsystems[i];
|
|
if(sys.type.isHull)
|
|
continue;
|
|
if(sys.type.isApplied) {
|
|
desc.applySubsystem(sys.type);
|
|
continue;
|
|
}
|
|
|
|
desc.addSystem(sys.type);
|
|
desc.setDirection(sys.direction);
|
|
uint hexCnt = sys.hexCount;
|
|
for(uint j = 0; j < hexCnt; ++j) {
|
|
vec2u hex = sys.hexagon(j);
|
|
desc.addHex(hex, sys.module(j));
|
|
}
|
|
}
|
|
|
|
auto@ dsg = makeDesign(desc);
|
|
if(dsg !is null && !dsg.hasFatalErrors())
|
|
replaceDesign(ai, dsg);
|
|
return true;
|
|
}
|
|
|
|
@designer = Designer(type, goalScale, ai.empire);
|
|
designer.randomHull = true;
|
|
if(task == FST_Scout)
|
|
designer.composeScout();
|
|
}
|
|
|
|
auto@ dsg = designer.design(1);
|
|
if(dsg !is null)
|
|
tries.insertLast(dsg);
|
|
|
|
if(tries.length < uint(2 + ai.skillTech)) {
|
|
ai.addIdle(this);
|
|
return false;
|
|
}
|
|
|
|
//Evaluate the available designs and pick one to use
|
|
array<double> values(tries.length);
|
|
for(uint i = 0, cnt = tries.length; i < cnt; ++i) {
|
|
//error("Design " + i);
|
|
@dsg = tries[i];
|
|
|
|
double value = 1.0;
|
|
|
|
double hyperSpeed = dsg.total(SV_HyperdriveSpeed);
|
|
if(hyperSpeed > 0.0) {
|
|
//Hyperdrive races are guaranteed to receive a hyperdrive in their flagships
|
|
if(hyperSpeed < 125.0)
|
|
value /= 10.0;
|
|
else if(hyperSpeed < 175.0)
|
|
value /= 2.0;
|
|
else if(hyperSpeed < 375.0) {
|
|
if(task == FST_Scout)
|
|
value /= 1.8;
|
|
}
|
|
else if(task != FST_Scout) {
|
|
value /= 1.5;
|
|
}
|
|
}
|
|
|
|
double jumpRange = dsg.total(SV_JumpRange);
|
|
if(jumpRange > 0.0) {
|
|
if(jumpRange < 5000.0)
|
|
value /= 10.0;
|
|
else if(jumpRange < 15000.0)
|
|
value /= 2.0;
|
|
else if(jumpRange < 25000.0) {
|
|
if(task == FST_Scout)
|
|
value /= 1.8;
|
|
}
|
|
else if(task != FST_Scout) {
|
|
value /= 1.5;
|
|
}
|
|
}
|
|
|
|
double thrust = dsg.total(SV_Thrust);
|
|
double mass = max(dsg.total(HV_Mass), 0.01);
|
|
{
|
|
double speed = thrust / mass;
|
|
//error("Speed: " + speed + "/" + goalSpeed);
|
|
if(speed < goalSpeed)
|
|
value *= pow(speed / goalSpeed, 2.0);
|
|
else if(speed > goalSpeed * 1.5)
|
|
value *= goalSpeed / speed;
|
|
}
|
|
|
|
double str = 0.0;
|
|
if(task != FST_Scout) {
|
|
double dps = dsg.total(SV_DPS);
|
|
double hp = dsg.totalHP + dsg.total(SV_ShieldCapacity);
|
|
str = sqrt(dps * hp);
|
|
value *= str;
|
|
}
|
|
|
|
if(type == DT_Flagship) {
|
|
auto@ support = ai.dsgSupports[ST_AntiFlagship];
|
|
double supportStr = support is null ? 30.0 : sqrt(support.total(SV_DPS) * (dsg.totalHP + dsg.total(SV_ShieldCapacity)));
|
|
double supportSize = support is null ? 1.0 : double(support.size);
|
|
|
|
if(dsg.hasTag(ST_SupportCap))
|
|
str += dsg.total(SV_SupportCapacity) / supportSize * supportStr;
|
|
|
|
//Avoid wasting resources
|
|
double commandUsed = dsg.variable(ShV_REQUIRES_Command);
|
|
double commandNeed = dsg.total(SV_Command);
|
|
if(commandNeed < commandUsed * 0.8)
|
|
value *= 0.5 + commandNeed / commandUsed * 0.5;
|
|
|
|
double powerUsed = dsg.variable(ShV_REQUIRES_Power);
|
|
double powerNeed = dsg.total(SV_Power);
|
|
if(powerNeed < powerUsed * 0.8)
|
|
value *= 0.5 + powerNeed / powerUsed * 0.5;
|
|
|
|
double maint = dsg.total(HV_MaintainCost);
|
|
value /= maint;
|
|
}
|
|
|
|
value /= sqrt(dsg.total(HV_BuildCost));
|
|
|
|
if(str != 0.0)
|
|
value *= str;
|
|
|
|
values[i] = value;
|
|
}
|
|
|
|
double best = -INFINITY;
|
|
//uint bestInd = 0;
|
|
for(uint i = 0, cnt = tries.length; i < cnt; ++i) {
|
|
double value = values[i];
|
|
if(value > best) {
|
|
best = value;
|
|
@dsg = tries[i];
|
|
//bestInd = i;
|
|
}
|
|
}
|
|
//error("Chose " + dsg.name + " id " + bestInd);
|
|
|
|
replaceDesign(ai, dsg);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|