Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
import empire_ai.weasel.WeaselAI;
|
||||
import empire_ai.weasel.Movement;
|
||||
import empire_ai.weasel.Military;
|
||||
import empire_ai.weasel.Construction;
|
||||
import empire_ai.weasel.Designs;
|
||||
import empire_ai.weasel.Development;
|
||||
import empire_ai.weasel.Systems;
|
||||
import empire_ai.weasel.Budget;
|
||||
import empire_ai.weasel.Fleets;
|
||||
|
||||
import ftl;
|
||||
from orbitals import getOrbitalModuleID;
|
||||
|
||||
const double FLING_MIN_DISTANCE_STAGE = 10000;
|
||||
const double FLING_MIN_DISTANCE_DEVELOP = 20000;
|
||||
const double FLING_MIN_TIMER = 3.0 * 60.0;
|
||||
|
||||
int flingModule = -1;
|
||||
|
||||
void init() {
|
||||
flingModule = getOrbitalModuleID("FlingCore");
|
||||
}
|
||||
|
||||
class FlingRegion : Savable {
|
||||
Region@ region;
|
||||
Object@ obj;
|
||||
bool installed = false;
|
||||
vec3d destination;
|
||||
|
||||
void save(SaveFile& file) {
|
||||
file << region;
|
||||
file << obj;
|
||||
file << installed;
|
||||
file << destination;
|
||||
}
|
||||
|
||||
void load(SaveFile& file) {
|
||||
file >> region;
|
||||
file >> obj;
|
||||
file >> installed;
|
||||
file >> destination;
|
||||
}
|
||||
};
|
||||
|
||||
class Fling : FTL {
|
||||
Military@ military;
|
||||
Designs@ designs;
|
||||
Construction@ construction;
|
||||
Development@ development;
|
||||
Systems@ systems;
|
||||
Budget@ budget;
|
||||
Fleets@ fleets;
|
||||
|
||||
array<FlingRegion@> tracked;
|
||||
array<Object@> unused;
|
||||
|
||||
BuildOrbital@ buildFling;
|
||||
double nextBuildTry = 15.0 * 60.0;
|
||||
bool wantToBuild = false;
|
||||
|
||||
void create() override {
|
||||
@military = cast<Military>(ai.military);
|
||||
@designs = cast<Designs>(ai.designs);
|
||||
@construction = cast<Construction>(ai.construction);
|
||||
@development = cast<Development>(ai.development);
|
||||
@systems = cast<Systems>(ai.systems);
|
||||
@budget = cast<Budget>(ai.budget);
|
||||
@fleets = cast<Fleets>(ai.fleets);
|
||||
}
|
||||
|
||||
void save(SaveFile& file) override {
|
||||
construction.saveConstruction(file, buildFling);
|
||||
file << nextBuildTry;
|
||||
file << wantToBuild;
|
||||
|
||||
uint cnt = tracked.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << tracked[i];
|
||||
|
||||
cnt = unused.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << unused[i];
|
||||
}
|
||||
|
||||
void load(SaveFile& file) override {
|
||||
@buildFling = cast<BuildOrbital>(construction.loadConstruction(file));
|
||||
file >> nextBuildTry;
|
||||
file >> wantToBuild;
|
||||
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
FlingRegion fr;
|
||||
file >> fr;
|
||||
tracked.insertLast(fr);
|
||||
}
|
||||
|
||||
file >> cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
Object@ obj;
|
||||
file >> obj;
|
||||
if(obj !is null)
|
||||
unused.insertLast(obj);
|
||||
}
|
||||
}
|
||||
|
||||
uint order(MoveOrder& ord) override {
|
||||
if(!canFling(ord.obj))
|
||||
return F_Pass;
|
||||
|
||||
//Find the position to fling to
|
||||
vec3d toPosition;
|
||||
if(!targetPosition(ord, toPosition))
|
||||
return F_Pass;
|
||||
|
||||
//Don't fling if we're saving our FTL for a new beacon
|
||||
double avail = usableFTL(ai, ord);
|
||||
if((buildFling !is null && !buildFling.started) || wantToBuild)
|
||||
avail = min(avail, ai.empire.FTLStored - 250.0);
|
||||
|
||||
//Make sure we have the ftl to fling
|
||||
if(flingCost(ord.obj, toPosition) > avail)
|
||||
return F_Pass;
|
||||
|
||||
//Make sure we're in range of a beacon
|
||||
Object@ beacon = getClosest(ord.obj.position);
|
||||
if(beacon is null || beacon.position.distanceTo(ord.obj.position) > FLING_BEACON_RANGE)
|
||||
return F_Pass;
|
||||
|
||||
ord.obj.addFlingOrder(beacon, toPosition);
|
||||
return F_Continue;
|
||||
}
|
||||
|
||||
FlingRegion@ get(Region@ reg) {
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
if(tracked[i].region is reg)
|
||||
return tracked[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void remove(FlingRegion@ gt) {
|
||||
if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire)
|
||||
unused.insertLast(gt.obj);
|
||||
tracked.remove(gt);
|
||||
}
|
||||
|
||||
Object@ getClosest(const vec3d& position) {
|
||||
Object@ closest;
|
||||
double minDist = INFINITY;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
Object@ obj = tracked[i].obj;
|
||||
if(obj is null)
|
||||
continue;
|
||||
double d = obj.position.distanceTo(position);
|
||||
if(d < minDist) {
|
||||
minDist = d;
|
||||
@closest = obj;
|
||||
}
|
||||
}
|
||||
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
|
||||
Object@ obj = unused[i];
|
||||
if(obj is null)
|
||||
continue;
|
||||
double d = obj.position.distanceTo(position);
|
||||
if(d < minDist) {
|
||||
minDist = d;
|
||||
@closest = obj;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
FlingRegion@ getClosestRegion(const vec3d& position) {
|
||||
FlingRegion@ closest;
|
||||
double minDist = INFINITY;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
double d = tracked[i].region.position.distanceTo(position);
|
||||
if(d < minDist) {
|
||||
minDist = d;
|
||||
@closest = tracked[i];
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
void assignTo(FlingRegion@ track, Object@ closest) {
|
||||
unused.remove(closest);
|
||||
@track.obj = closest;
|
||||
}
|
||||
|
||||
bool trackingBeacon(Object@ obj) {
|
||||
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
|
||||
if(unused[i] is obj)
|
||||
return true;
|
||||
}
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
if(tracked[i].obj is obj)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shouldHaveBeacon(Region@ reg, bool always = false) {
|
||||
if(military.getBase(reg) !is null)
|
||||
return true;
|
||||
if(development.isDevelopingIn(reg))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void focusTick(double time) override {
|
||||
//Manage unused beacons list
|
||||
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
|
||||
Object@ obj = unused[i];
|
||||
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
|
||||
unused.removeAt(i);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new beacons
|
||||
auto@ data = ai.empire.getFlingBeacons();
|
||||
Object@ obj;
|
||||
while(receive(data, obj)) {
|
||||
if(obj is null)
|
||||
continue;
|
||||
if(!trackingBeacon(obj))
|
||||
unused.insertLast(obj);
|
||||
}
|
||||
|
||||
//Update existing beacons for staging bases
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
auto@ reg = tracked[i];
|
||||
bool checkAlways = false;
|
||||
if(reg.obj !is null) {
|
||||
if(!reg.obj.valid || reg.obj.owner !is ai.empire || reg.obj.region !is reg.region) {
|
||||
@reg.obj = null;
|
||||
checkAlways = true;
|
||||
}
|
||||
}
|
||||
if(!shouldHaveBeacon(reg.region, checkAlways)) {
|
||||
remove(tracked[i]);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new staging bases to build beacons at
|
||||
for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) {
|
||||
auto@ base = military.stagingBases[i];
|
||||
if(base.occupiedTime < FLING_MIN_TIMER)
|
||||
continue;
|
||||
|
||||
if(get(base.region) is null) {
|
||||
FlingRegion@ closest = getClosestRegion(base.region.position);
|
||||
if(closest !is null && closest.region.position.distanceTo(base.region.position) < FLING_MIN_DISTANCE_STAGE)
|
||||
continue;
|
||||
|
||||
FlingRegion gt;
|
||||
@gt.region = base.region;
|
||||
tracked.insertLast(gt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new important planets to build beacons at
|
||||
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
|
||||
auto@ focus = development.focuses[i];
|
||||
Region@ reg = focus.obj.region;
|
||||
if(reg is null)
|
||||
continue;
|
||||
|
||||
if(get(reg) is null) {
|
||||
FlingRegion@ closest = getClosestRegion(reg.position);
|
||||
if(closest !is null && closest.region.position.distanceTo(reg.position) < FLING_MIN_DISTANCE_DEVELOP)
|
||||
continue;
|
||||
|
||||
FlingRegion gt;
|
||||
@gt.region = reg;
|
||||
tracked.insertLast(gt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Destroy beacons if we're having ftl trouble
|
||||
if(ai.empire.FTLShortage) {
|
||||
Orbital@ leastImportant;
|
||||
double leastWeight = INFINITY;
|
||||
|
||||
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
|
||||
Orbital@ obj = cast<Orbital>(unused[i]);
|
||||
if(obj is null || !obj.valid)
|
||||
continue;
|
||||
|
||||
@leastImportant = obj;
|
||||
leastWeight = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
if(leastImportant !is null) {
|
||||
if(log)
|
||||
ai.print("Scuttle unused beacon for ftl", leastImportant.region);
|
||||
leastImportant.scuttle();
|
||||
}
|
||||
else {
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
Orbital@ obj = cast<Orbital>(tracked[i].obj);
|
||||
if(obj is null || !obj.valid)
|
||||
continue;
|
||||
|
||||
double weight = 1.0;
|
||||
auto@ base = military.getBase(tracked[i].region);
|
||||
if(base is null) {
|
||||
weight *= 5.0;
|
||||
}
|
||||
else if(base.idleTime >= 1) {
|
||||
weight *= 1.0 + (base.idleTime / 60.0);
|
||||
}
|
||||
else {
|
||||
weight /= 2.0;
|
||||
}
|
||||
|
||||
if(weight < leastWeight) {
|
||||
@leastImportant = obj;
|
||||
leastWeight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
if(leastImportant !is null) {
|
||||
if(log)
|
||||
ai.print("Scuttle unimportant beacon for ftl", leastImportant.region);
|
||||
leastImportant.scuttle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//See if we should build a new gate
|
||||
if(buildFling !is null) {
|
||||
if(buildFling.completed) {
|
||||
@buildFling = null;
|
||||
nextBuildTry = gameTime + 60.0;
|
||||
}
|
||||
}
|
||||
wantToBuild = false;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
auto@ gt = tracked[i];
|
||||
if(gt.obj is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) {
|
||||
Object@ found;
|
||||
for(uint n = 0, ncnt = unused.length; n < ncnt; ++n) {
|
||||
Object@ obj = unused[n];
|
||||
if(obj.region is gt.region) {
|
||||
@found = obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found !is null) {
|
||||
if(log)
|
||||
ai.print("Assign beacon to => "+gt.region.name, found.region);
|
||||
assignTo(gt, found);
|
||||
} else if(buildFling is null && gameTime > nextBuildTry && !ai.empire.isFTLShortage(0.15)) {
|
||||
if(ai.empire.FTLStored >= 250) {
|
||||
if(log)
|
||||
ai.print("Build beacon for this system", gt.region);
|
||||
|
||||
@buildFling = construction.buildOrbital(getOrbitalModule(flingModule), military.getStationPosition(gt.region));
|
||||
}
|
||||
else {
|
||||
wantToBuild = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Scuttle anything unused if we don't need beacons in those regions
|
||||
for(uint i = 0, cnt = unused.length; i < cnt; ++i) {
|
||||
if(get(unused[i].region) is null && unused[i].isOrbital) {
|
||||
cast<Orbital>(unused[i]).scuttle();
|
||||
unused.removeAt(i);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Try to get enough ftl storage that we can fling our largest fleet and have some remaining
|
||||
double highestCost = 0.0;
|
||||
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
|
||||
auto@ flAI = fleets.fleets[i];
|
||||
if(flAI.fleetClass != FC_Combat)
|
||||
continue;
|
||||
highestCost = max(highestCost, double(flingCost(flAI.obj, vec3d())));
|
||||
}
|
||||
development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal);
|
||||
}
|
||||
};
|
||||
|
||||
AIComponent@ createFling() {
|
||||
return Fling();
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
import empire_ai.weasel.WeaselAI;
|
||||
import empire_ai.weasel.Movement;
|
||||
import empire_ai.weasel.Military;
|
||||
import empire_ai.weasel.Construction;
|
||||
import empire_ai.weasel.Designs;
|
||||
import empire_ai.weasel.Development;
|
||||
import empire_ai.weasel.Systems;
|
||||
import empire_ai.weasel.Budget;
|
||||
|
||||
from statuses import getStatusID;
|
||||
from abilities import getAbilityID;
|
||||
|
||||
const double GATE_MIN_DISTANCE_STAGE = 10000;
|
||||
const double GATE_MIN_DISTANCE_DEVELOP = 20000;
|
||||
const double GATE_MIN_DISTANCE_BORDER = 30000;
|
||||
const double GATE_MIN_TIMER = 3.0 * 60.0;
|
||||
const int GATE_BUILD_MOVE_HOPS = 5;
|
||||
|
||||
int packAbility = -1;
|
||||
int unpackAbility = -1;
|
||||
|
||||
int packedStatus = -1;
|
||||
int unpackedStatus = -1;
|
||||
|
||||
void init() {
|
||||
packAbility = getAbilityID("GatePack");
|
||||
unpackAbility = getAbilityID("GateUnpack");
|
||||
|
||||
packedStatus = getAbilityID("GatePacked");
|
||||
unpackedStatus = getAbilityID("GateUnpacked");
|
||||
}
|
||||
|
||||
class GateRegion : Savable {
|
||||
Region@ region;
|
||||
Object@ gate;
|
||||
bool installed = false;
|
||||
vec3d destination;
|
||||
|
||||
void save(SaveFile& file) {
|
||||
file << region;
|
||||
file << gate;
|
||||
file << installed;
|
||||
file << destination;
|
||||
}
|
||||
|
||||
void load(SaveFile& file) {
|
||||
file >> region;
|
||||
file >> gate;
|
||||
file >> installed;
|
||||
file >> destination;
|
||||
}
|
||||
};
|
||||
|
||||
class Gate : FTL {
|
||||
Military@ military;
|
||||
Designs@ designs;
|
||||
Construction@ construction;
|
||||
Development@ development;
|
||||
Systems@ systems;
|
||||
Budget@ budget;
|
||||
|
||||
DesignTarget@ gateDesign;
|
||||
|
||||
array<GateRegion@> tracked;
|
||||
array<Object@> unassigned;
|
||||
|
||||
BuildStation@ buildGate;
|
||||
double nextBuildTry = 15.0 * 60.0;
|
||||
|
||||
void create() override {
|
||||
@military = cast<Military>(ai.military);
|
||||
@designs = cast<Designs>(ai.designs);
|
||||
@construction = cast<Construction>(ai.construction);
|
||||
@development = cast<Development>(ai.development);
|
||||
@systems = cast<Systems>(ai.systems);
|
||||
@budget = cast<Budget>(ai.budget);
|
||||
}
|
||||
|
||||
void save(SaveFile& file) override {
|
||||
designs.saveDesign(file, gateDesign);
|
||||
construction.saveConstruction(file, buildGate);
|
||||
file << nextBuildTry;
|
||||
|
||||
uint cnt = tracked.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << tracked[i];
|
||||
|
||||
cnt = unassigned.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << unassigned[i];
|
||||
}
|
||||
|
||||
void load(SaveFile& file) override {
|
||||
@gateDesign = designs.loadDesign(file);
|
||||
@buildGate = cast<BuildStation>(construction.loadConstruction(file));
|
||||
file >> nextBuildTry;
|
||||
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
GateRegion gt;
|
||||
file >> gt;
|
||||
tracked.insertLast(gt);
|
||||
}
|
||||
|
||||
file >> cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
Object@ obj;
|
||||
file >> obj;
|
||||
if(obj !is null)
|
||||
unassigned.insertLast(obj);
|
||||
}
|
||||
}
|
||||
|
||||
GateRegion@ get(Region@ reg) {
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
if(tracked[i].region is reg)
|
||||
return tracked[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void remove(GateRegion@ gt) {
|
||||
if(gt.gate !is null && gt.gate.valid && gt.gate.owner is ai.empire)
|
||||
unassigned.insertLast(gt.gate);
|
||||
tracked.remove(gt);
|
||||
}
|
||||
|
||||
Object@ getClosestGate(const vec3d& position) {
|
||||
Object@ closest;
|
||||
double minDist = INFINITY;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
Object@ gate = tracked[i].gate;
|
||||
if(gate is null)
|
||||
continue;
|
||||
if(!tracked[i].installed)
|
||||
continue;
|
||||
double d = gate.position.distanceTo(position);
|
||||
if(d < minDist) {
|
||||
minDist = d;
|
||||
@closest = gate;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
GateRegion@ getClosestGateRegion(const vec3d& position) {
|
||||
GateRegion@ closest;
|
||||
double minDist = INFINITY;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
double d = tracked[i].region.position.distanceTo(position);
|
||||
if(d < minDist) {
|
||||
minDist = d;
|
||||
@closest = tracked[i];
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
void assignTo(GateRegion@ gt, Object@ closest) {
|
||||
unassigned.remove(closest);
|
||||
@gt.gate = closest;
|
||||
gt.installed = false;
|
||||
|
||||
if(closest.region is gt.region) {
|
||||
if(closest.hasStatusEffect(unpackedStatus)) {
|
||||
gt.installed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!gt.installed) {
|
||||
gt.destination = military.getStationPosition(gt.region);
|
||||
closest.activateAbilityTypeFor(ai.empire, packAbility);
|
||||
closest.addMoveOrder(gt.destination);
|
||||
}
|
||||
}
|
||||
|
||||
bool trackingGate(Object@ obj) {
|
||||
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
|
||||
if(unassigned[i] is obj)
|
||||
return true;
|
||||
}
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
if(tracked[i].gate is obj)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shouldHaveGate(Region@ reg, bool always = false) {
|
||||
if(military.getBase(reg) !is null)
|
||||
return true;
|
||||
if(development.isDevelopingIn(reg))
|
||||
return true;
|
||||
if(!always) {
|
||||
auto@ sys = systems.getAI(reg);
|
||||
if(sys !is null) {
|
||||
if(sys.border && sys.bordersEmpires)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void turn() override {
|
||||
if(gateDesign !is null && gateDesign.active !is null) {
|
||||
int newSize = round(double(budget.spendable(BT_Military)) * 0.5 * ai.behavior.shipSizePerMoney / 64.0) * 64;
|
||||
if(newSize < 128)
|
||||
newSize = 128;
|
||||
if(newSize != gateDesign.targetSize) {
|
||||
@gateDesign = designs.design(DP_Gate, newSize);
|
||||
gateDesign.customName = "Gate";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void focusTick(double time) override {
|
||||
//Design a gate
|
||||
if(gateDesign is null) {
|
||||
@gateDesign = designs.design(DP_Gate, 128);
|
||||
gateDesign.customName = "Gate";
|
||||
}
|
||||
|
||||
//Manage unassigned gates list
|
||||
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
|
||||
Object@ obj = unassigned[i];
|
||||
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
|
||||
unassigned.removeAt(i);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new gates
|
||||
auto@ data = ai.empire.getStargates();
|
||||
Object@ obj;
|
||||
while(receive(data, obj)) {
|
||||
if(obj is null)
|
||||
continue;
|
||||
if(!trackingGate(obj))
|
||||
unassigned.insertLast(obj);
|
||||
}
|
||||
|
||||
//Update existing gates for staging bases
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
auto@ gt = tracked[i];
|
||||
bool checkAlways = false;
|
||||
if(gt.gate !is null) {
|
||||
if(!gt.gate.valid || gt.gate.owner !is ai.empire || (gt.installed && gt.gate.region !is gt.region)) {
|
||||
@gt.gate = null;
|
||||
gt.installed = false;
|
||||
checkAlways = true;
|
||||
}
|
||||
else if(!gt.installed && !gt.gate.hasOrders) {
|
||||
if(gt.destination.distanceTo(gt.gate.position) < 10.0) {
|
||||
gt.gate.activateAbilityTypeFor(ai.empire, unpackAbility, gt.destination);
|
||||
gt.installed = true;
|
||||
}
|
||||
else {
|
||||
gt.gate.activateAbilityTypeFor(ai.empire, packAbility);
|
||||
gt.gate.addMoveOrder(gt.destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!shouldHaveGate(gt.region, checkAlways)) {
|
||||
remove(tracked[i]);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new staging bases to build gates at
|
||||
for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) {
|
||||
auto@ base = military.stagingBases[i];
|
||||
if(base.occupiedTime < GATE_MIN_TIMER)
|
||||
continue;
|
||||
|
||||
if(get(base.region) is null) {
|
||||
GateRegion@ closest = getClosestGateRegion(base.region.position);
|
||||
if(closest !is null && closest.region.position.distanceTo(base.region.position) < GATE_MIN_DISTANCE_STAGE)
|
||||
continue;
|
||||
|
||||
GateRegion gt;
|
||||
@gt.region = base.region;
|
||||
tracked.insertLast(gt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new important planets to build gates at
|
||||
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
|
||||
auto@ focus = development.focuses[i];
|
||||
Region@ reg = focus.obj.region;
|
||||
if(reg is null)
|
||||
continue;
|
||||
|
||||
if(get(reg) is null) {
|
||||
GateRegion@ closest = getClosestGateRegion(reg.position);
|
||||
if(closest !is null && closest.region.position.distanceTo(reg.position) < GATE_MIN_DISTANCE_DEVELOP)
|
||||
continue;
|
||||
|
||||
GateRegion gt;
|
||||
@gt.region = reg;
|
||||
tracked.insertLast(gt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new border systems to build gates at
|
||||
uint offset = randomi(0, systems.border.length-1);
|
||||
for(uint i = 0, cnt = systems.border.length; i < cnt; ++i) {
|
||||
auto@ sys = systems.border[(i+offset)%cnt];
|
||||
Region@ reg = sys.obj;
|
||||
if(reg is null)
|
||||
continue;
|
||||
if(!sys.bordersEmpires)
|
||||
continue;
|
||||
|
||||
if(get(reg) is null) {
|
||||
GateRegion@ closest = getClosestGateRegion(reg.position);
|
||||
if(closest !is null && closest.region.position.distanceTo(reg.position) < GATE_MIN_DISTANCE_DEVELOP)
|
||||
continue;
|
||||
|
||||
GateRegion gt;
|
||||
@gt.region = reg;
|
||||
tracked.insertLast(gt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Destroy gates if we're having ftl trouble
|
||||
if(ai.empire.FTLShortage) {
|
||||
Ship@ leastImportant;
|
||||
double leastWeight = INFINITY;
|
||||
|
||||
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
|
||||
Ship@ ship = cast<Ship>(unassigned[i]);
|
||||
if(ship is null || !ship.valid)
|
||||
continue;
|
||||
|
||||
double weight = ship.blueprint.design.size;
|
||||
weight *= 10.0;
|
||||
|
||||
if(weight < leastWeight) {
|
||||
@leastImportant = ship;
|
||||
leastWeight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
if(leastImportant !is null) {
|
||||
if(log)
|
||||
ai.print("Scuttle unassigned gate for ftl", leastImportant.region);
|
||||
leastImportant.scuttle();
|
||||
}
|
||||
else {
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
Ship@ ship = cast<Ship>(tracked[i].gate);
|
||||
if(ship is null || !ship.valid)
|
||||
continue;
|
||||
|
||||
double weight = ship.blueprint.design.size;
|
||||
auto@ base = military.getBase(tracked[i].region);
|
||||
if(base is null) {
|
||||
weight *= 5.0;
|
||||
}
|
||||
else if(base.idleTime >= 1) {
|
||||
weight *= 1.0 + (base.idleTime / 60.0);
|
||||
}
|
||||
else {
|
||||
weight /= 2.0;
|
||||
}
|
||||
|
||||
if(weight < leastWeight) {
|
||||
@leastImportant = ship;
|
||||
leastWeight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
if(leastImportant !is null) {
|
||||
if(log)
|
||||
ai.print("Scuttle unimportant gate for ftl", leastImportant.region);
|
||||
leastImportant.scuttle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//See if we should build a new gate
|
||||
if(buildGate !is null) {
|
||||
if(buildGate.completed) {
|
||||
@buildGate = null;
|
||||
nextBuildTry = gameTime + 60.0;
|
||||
}
|
||||
}
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
auto@ gt = tracked[i];
|
||||
if(gt.gate is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) {
|
||||
Object@ closest;
|
||||
double closestDist = INFINITY;
|
||||
for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) {
|
||||
Object@ obj = unassigned[n];
|
||||
if(obj.region is gt.region) {
|
||||
@closest = obj;
|
||||
break;
|
||||
}
|
||||
if(!obj.hasMover)
|
||||
continue;
|
||||
if(buildGate is null && gameTime > nextBuildTry) {
|
||||
double d = obj.position.distanceTo(gt.region.position);
|
||||
if(d < closestDist) {
|
||||
closestDist = d;
|
||||
@closest = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(closest !is null) {
|
||||
if(log)
|
||||
ai.print("Assign gate to => "+gt.region.name, closest.region);
|
||||
assignTo(gt, closest);
|
||||
} else if(buildGate is null && gameTime > nextBuildTry && !ai.empire.isFTLShortage(0.15)) {
|
||||
if(log)
|
||||
ai.print("Build gate for this system", gt.region);
|
||||
|
||||
bool buildLocal = true;
|
||||
auto@ factory = construction.primaryFactory;
|
||||
if(factory !is null) {
|
||||
Region@ factRegion = factory.obj.region;
|
||||
if(factRegion !is null && systems.hopDistance(gt.region, factRegion) < GATE_BUILD_MOVE_HOPS)
|
||||
buildLocal = false;
|
||||
}
|
||||
|
||||
if(buildLocal)
|
||||
@buildGate = construction.buildLocalStation(gateDesign);
|
||||
else
|
||||
@buildGate = construction.buildStation(gateDesign, military.getStationPosition(gt.region));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AIComponent@ createGate() {
|
||||
return Gate();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import empire_ai.weasel.WeaselAI;
|
||||
import empire_ai.weasel.Movement;
|
||||
import empire_ai.weasel.Development;
|
||||
import empire_ai.weasel.Fleets;
|
||||
|
||||
import ftl;
|
||||
|
||||
from orders import OrderType;
|
||||
|
||||
const double REJUMP_MIN_DIST = 8000.0;
|
||||
const double STORAGE_AIM_DISTANCE = 40000;
|
||||
|
||||
class Hyperdrive : FTL {
|
||||
Development@ development;
|
||||
Fleets@ fleets;
|
||||
|
||||
void create() override {
|
||||
@development = cast<Development>(ai.development);
|
||||
@fleets = cast<Fleets>(ai.fleets);
|
||||
}
|
||||
|
||||
double hdETA(Object& obj, const vec3d& position) {
|
||||
double charge = HYPERDRIVE_CHARGE_TIME;
|
||||
if(obj.owner.HyperdriveNeedCharge == 0)
|
||||
charge = 0.0;
|
||||
double dist = position.distanceTo(obj.position);
|
||||
double speed = hyperdriveMaxSpeed(obj);
|
||||
return charge + dist / speed;
|
||||
}
|
||||
|
||||
double subETA(Object& obj, const vec3d& position) {
|
||||
return newtonArrivalTime(obj.maxAcceleration, position - obj.position, vec3d());
|
||||
}
|
||||
|
||||
bool shouldHD(Object& obj, const vec3d& position, uint priority) {
|
||||
//This makes me sad
|
||||
if(position.distanceTo(obj.position) < 3000)
|
||||
return false;
|
||||
double pathDist = cast<Movement>(ai.movement).getPathDistance(obj.position, position, obj.maxAcceleration);
|
||||
double straightDist = position.distanceTo(obj.position);
|
||||
return pathDist >= straightDist * 0.6;
|
||||
}
|
||||
|
||||
uint order(MoveOrder& ord) override {
|
||||
if(!canHyperdrive(ord.obj))
|
||||
return F_Pass;
|
||||
|
||||
double avail = usableFTL(ai, ord);
|
||||
if(avail > 0) {
|
||||
vec3d toPosition;
|
||||
if(targetPosition(ord, toPosition)) {
|
||||
if(shouldHD(ord.obj, toPosition, ord.priority)) {
|
||||
double cost = hyperdriveCost(ord.obj, toPosition);
|
||||
if(avail >= cost) {
|
||||
ord.obj.addHyperdriveOrder(toPosition);
|
||||
return F_Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return F_Pass;
|
||||
}
|
||||
|
||||
uint tick(MoveOrder& ord, double time) {
|
||||
if(ord.priority == MP_Critical && canHyperdrive(ord.obj) && ord.obj.firstOrderType != OT_Hyperdrive) {
|
||||
vec3d toPosition;
|
||||
if(targetPosition(ord, toPosition)) {
|
||||
double dist = ord.obj.position.distanceToSQ(toPosition);
|
||||
if(dist > REJUMP_MIN_DIST * REJUMP_MIN_DIST) {
|
||||
double avail = usableFTL(ai, ord);
|
||||
double cost = hyperdriveCost(ord.obj, toPosition);
|
||||
if(avail >= cost && shouldHD(ord.obj, toPosition, ord.priority)) {
|
||||
cast<Movement>(ai.movement).order(ord);
|
||||
return F_Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return F_Pass;
|
||||
}
|
||||
|
||||
void focusTick(double time) override {
|
||||
//Try to get enough ftl storage that we can ftl our largest fleet a fair distance and have some remaining
|
||||
double highestCost = 0.0;
|
||||
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
|
||||
auto@ flAI = fleets.fleets[i];
|
||||
if(flAI.fleetClass != FC_Combat)
|
||||
continue;
|
||||
vec3d toPosition = flAI.obj.position + vec3d(0, 0, STORAGE_AIM_DISTANCE);
|
||||
highestCost = max(highestCost, double(hyperdriveCost(flAI.obj, toPosition)));
|
||||
}
|
||||
development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal);
|
||||
}
|
||||
};
|
||||
|
||||
AIComponent@ createHyperdrive() {
|
||||
return Hyperdrive();
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import empire_ai.weasel.WeaselAI;
|
||||
import empire_ai.weasel.Movement;
|
||||
import empire_ai.weasel.Development;
|
||||
import empire_ai.weasel.Fleets;
|
||||
|
||||
import ftl;
|
||||
import system_flags;
|
||||
import regions.regions;
|
||||
import systems;
|
||||
|
||||
from orders import OrderType;
|
||||
|
||||
const double REJUMP_MIN_DIST = 8000.0;
|
||||
|
||||
class Jumpdrive : FTL {
|
||||
Development@ development;
|
||||
Fleets@ fleets;
|
||||
|
||||
int safetyFlag = -1;
|
||||
array<Region@> safeRegions;
|
||||
|
||||
void create() override {
|
||||
@development = cast<Development>(ai.development);
|
||||
@fleets = cast<Fleets>(ai.fleets);
|
||||
|
||||
safetyFlag = getSystemFlag("JumpdriveSafety");
|
||||
}
|
||||
|
||||
void save(SaveFile& file) {
|
||||
uint cnt = safeRegions.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << safeRegions[i];
|
||||
}
|
||||
|
||||
void load(SaveFile& file) {
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
safeRegions.length = cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file >> safeRegions[i];
|
||||
}
|
||||
|
||||
double jdETA(Object& obj, const vec3d& position) {
|
||||
double charge = JUMPDRIVE_CHARGE_TIME;
|
||||
return charge;
|
||||
}
|
||||
|
||||
double subETA(Object& obj, const vec3d& position) {
|
||||
return newtonArrivalTime(obj.maxAcceleration, position - obj.position, vec3d());
|
||||
}
|
||||
|
||||
bool shouldJD(Object& obj, const vec3d& position, uint priority) {
|
||||
//This makes me sad
|
||||
if(position.distanceTo(obj.position) < 3000)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
/*double factor = 0.8;*/
|
||||
/*if(priority == MP_Critical)*/
|
||||
/* factor = 1.0;*/
|
||||
/*return jdETA(obj, position) <= factor * subETA(obj, position);*/
|
||||
}
|
||||
|
||||
uint order(MoveOrder& ord) override {
|
||||
return order(ord, ord.obj.position, false);
|
||||
}
|
||||
|
||||
uint order(MoveOrder& ord, const vec3d& fromPos, bool secondary) {
|
||||
if(!canJumpdrive(ord.obj))
|
||||
return F_Pass;
|
||||
|
||||
double avail = usableFTL(ai, ord);
|
||||
if(avail > 0) {
|
||||
vec3d toPosition;
|
||||
if(targetPosition(ord, toPosition)) {
|
||||
double maxRange = jumpdriveRange(ord.obj);
|
||||
double dist = toPosition.distanceTo(fromPos);
|
||||
|
||||
bool isSafe = false;
|
||||
Region@ reg = getRegion(toPosition);
|
||||
if(reg !is null)
|
||||
isSafe = reg.getSystemFlag(ai.empire, safetyFlag);
|
||||
|
||||
if(dist > maxRange && !isSafe) {
|
||||
//See if we should jump to a safe region first
|
||||
if(!secondary) {
|
||||
double bestHop = INFINITY;
|
||||
Region@ hopRegion;
|
||||
vec3d bestPos;
|
||||
for(uint i = 0, cnt = safeRegions.length; i < cnt; ++i) {
|
||||
if(!safeRegions[i].getSystemFlag(ai.empire, safetyFlag))
|
||||
continue;
|
||||
vec3d hopPos = safeRegions[i].position;
|
||||
hopPos = hopPos + (fromPos-hopPos).normalized(safeRegions[i].radius * 0.85);
|
||||
double d = hopPos.distanceTo(toPosition);
|
||||
if(d < bestHop) {
|
||||
bestHop = d;
|
||||
@hopRegion = safeRegions[i];
|
||||
bestPos = hopPos;
|
||||
}
|
||||
}
|
||||
|
||||
if(bestHop < dist * 0.8) {
|
||||
double cost = jumpdriveCost(ord.obj, fromPos, bestPos);
|
||||
if(avail >= cost) {
|
||||
ord.obj.addJumpdriveOrder(bestPos);
|
||||
order(ord, bestPos, true);
|
||||
return F_Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Shorten our jump
|
||||
if(ord.priority < MP_Normal)
|
||||
return F_Pass;
|
||||
toPosition = fromPos + (toPosition - fromPos).normalized(maxRange);
|
||||
}
|
||||
|
||||
if(shouldJD(ord.obj, toPosition, ord.priority)) {
|
||||
double cost = jumpdriveCost(ord.obj, toPosition);
|
||||
if(avail >= cost) {
|
||||
ord.obj.addJumpdriveOrder(toPosition, append=secondary);
|
||||
return F_Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return F_Pass;
|
||||
}
|
||||
|
||||
uint tick(MoveOrder& ord, double time) {
|
||||
if(ord.priority == MP_Critical && canJumpdrive(ord.obj) && ord.obj.firstOrderType != OT_Jumpdrive) {
|
||||
vec3d toPosition;
|
||||
if(targetPosition(ord, toPosition)) {
|
||||
double dist = ord.obj.position.distanceToSQ(toPosition);
|
||||
if(dist > REJUMP_MIN_DIST * REJUMP_MIN_DIST) {
|
||||
double maxRange = jumpdriveRange(ord.obj);
|
||||
dist = sqrt(dist);
|
||||
|
||||
bool isSafe = false;
|
||||
Region@ reg = getRegion(toPosition);
|
||||
if(reg !is null)
|
||||
isSafe = reg.getSystemFlag(ai.empire, safetyFlag);
|
||||
|
||||
if(dist > maxRange && !isSafe)
|
||||
toPosition = ord.obj.position + (toPosition - ord.obj.position).normalized(maxRange);
|
||||
|
||||
if(shouldJD(ord.obj, toPosition, ord.priority)) {
|
||||
double avail = usableFTL(ai, ord);
|
||||
double cost = jumpdriveCost(ord.obj, toPosition);
|
||||
if(avail >= cost) {
|
||||
cast<Movement>(ai.movement).order(ord);
|
||||
return F_Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return F_Pass;
|
||||
}
|
||||
|
||||
uint sysChk = 0;
|
||||
void start() {
|
||||
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
|
||||
Region@ reg = getSystem(i).object;
|
||||
if(reg.getSystemFlag(ai.empire, safetyFlag))
|
||||
safeRegions.insertLast(reg);
|
||||
}
|
||||
}
|
||||
|
||||
void focusTick(double time) override {
|
||||
//Try to get enough ftl storage that we can ftl our largest fleet a fair distance and have some remaining
|
||||
double highestCost = 0.0;
|
||||
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
|
||||
auto@ flAI = fleets.fleets[i];
|
||||
if(flAI.fleetClass != FC_Combat)
|
||||
continue;
|
||||
double dist = jumpdriveRange(flAI.obj);
|
||||
vec3d toPosition = flAI.obj.position + vec3d(0, 0, dist);
|
||||
highestCost = max(highestCost, double(jumpdriveCost(flAI.obj, toPosition)));
|
||||
}
|
||||
development.aimFTLStorage = highestCost / (1.0 - ai.behavior.ftlReservePctCritical - ai.behavior.ftlReservePctNormal);
|
||||
|
||||
//Disable systems that are no longer safe
|
||||
for(uint i = 0, cnt = safeRegions.length; i < cnt; ++i) {
|
||||
if(!safeRegions[i].getSystemFlag(ai.empire, safetyFlag)) {
|
||||
safeRegions.removeAt(i);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Try to find regions that are safe for us
|
||||
{
|
||||
sysChk = (sysChk+1) % systemCount;
|
||||
auto@ reg = getSystem(sysChk).object;
|
||||
if(reg.getSystemFlag(ai.empire, safetyFlag)) {
|
||||
if(safeRegions.find(reg) == -1)
|
||||
safeRegions.insertLast(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AIComponent@ createJumpdrive() {
|
||||
return Jumpdrive();
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
import empire_ai.weasel.WeaselAI;
|
||||
import empire_ai.weasel.Movement;
|
||||
import empire_ai.weasel.Military;
|
||||
import empire_ai.weasel.Construction;
|
||||
import empire_ai.weasel.Designs;
|
||||
import empire_ai.weasel.Development;
|
||||
import empire_ai.weasel.Systems;
|
||||
import empire_ai.weasel.Budget;
|
||||
import empire_ai.weasel.Fleets;
|
||||
|
||||
from statuses import getStatusID;
|
||||
from abilities import getAbilityID;
|
||||
|
||||
from oddity_navigation import hasOddityLink;
|
||||
|
||||
const double SS_MIN_DISTANCE_STAGE = 0;
|
||||
const double SS_MIN_DISTANCE_DEVELOP = 10000;
|
||||
const double SS_MIN_TIMER = 3.0 * 60.0;
|
||||
const double SS_MAX_DISTANCE = 3000.0;
|
||||
|
||||
class SSRegion : Savable {
|
||||
Region@ region;
|
||||
Object@ obj;
|
||||
bool arrived = false;
|
||||
vec3d destination;
|
||||
|
||||
void save(SaveFile& file) {
|
||||
file << region;
|
||||
file << obj;
|
||||
file << arrived;
|
||||
file << destination;
|
||||
}
|
||||
|
||||
void load(SaveFile& file) {
|
||||
file >> region;
|
||||
file >> obj;
|
||||
file >> arrived;
|
||||
file >> destination;
|
||||
}
|
||||
};
|
||||
|
||||
class Slipstream : FTL {
|
||||
Military@ military;
|
||||
Designs@ designs;
|
||||
Construction@ construction;
|
||||
Development@ development;
|
||||
Systems@ systems;
|
||||
Budget@ budget;
|
||||
Fleets@ fleets;
|
||||
|
||||
DesignTarget@ ssDesign;
|
||||
|
||||
array<SSRegion@> tracked;
|
||||
array<Object@> unassigned;
|
||||
|
||||
BuildFlagship@ buildSS;
|
||||
double nextBuildTry = 15.0 * 60.0;
|
||||
|
||||
void create() override {
|
||||
@military = cast<Military>(ai.military);
|
||||
@designs = cast<Designs>(ai.designs);
|
||||
@construction = cast<Construction>(ai.construction);
|
||||
@development = cast<Development>(ai.development);
|
||||
@systems = cast<Systems>(ai.systems);
|
||||
@budget = cast<Budget>(ai.budget);
|
||||
@fleets = cast<Fleets>(ai.fleets);
|
||||
}
|
||||
|
||||
void save(SaveFile& file) override {
|
||||
designs.saveDesign(file, ssDesign);
|
||||
construction.saveConstruction(file, buildSS);
|
||||
file << nextBuildTry;
|
||||
|
||||
uint cnt = tracked.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << tracked[i];
|
||||
|
||||
cnt = unassigned.length;
|
||||
file << cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
file << unassigned[i];
|
||||
}
|
||||
|
||||
void load(SaveFile& file) override {
|
||||
@ssDesign = designs.loadDesign(file);
|
||||
@buildSS = cast<BuildFlagship>(construction.loadConstruction(file));
|
||||
file >> nextBuildTry;
|
||||
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
SSRegion gt;
|
||||
file >> gt;
|
||||
tracked.insertLast(gt);
|
||||
}
|
||||
|
||||
file >> cnt;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
Object@ obj;
|
||||
file >> obj;
|
||||
if(obj !is null)
|
||||
unassigned.insertLast(obj);
|
||||
}
|
||||
}
|
||||
|
||||
uint order(MoveOrder& ord) override {
|
||||
//Find the position to fling to
|
||||
vec3d toPosition;
|
||||
if(!targetPosition(ord, toPosition))
|
||||
return F_Pass;
|
||||
|
||||
//Check if we have a slipstream generator in this region
|
||||
auto@ gt = get(ord.obj.region);
|
||||
if(gt is null || gt.obj is null || !gt.arrived)
|
||||
return F_Pass;
|
||||
|
||||
//Make sure our generator is usable
|
||||
Object@ ssGen = gt.obj;
|
||||
if(!canSlipstream(ssGen))
|
||||
return F_Pass;
|
||||
|
||||
//Check if we already have a link
|
||||
if(hasOddityLink(gt.region, toPosition, SS_MAX_DISTANCE, minDuration=60.0))
|
||||
return F_Pass;
|
||||
|
||||
//See if we have the FTL to make a link
|
||||
double avail = usableFTL(ai, ord);
|
||||
if(!canSlipstreamTo(ssGen, toPosition))
|
||||
return F_Pass;
|
||||
if(slipstreamCost(ssGen, 0, toPosition.distanceTo(ssGen.position)) >= avail)
|
||||
return F_Pass;
|
||||
|
||||
ssGen.addSlipstreamOrder(toPosition, append=true);
|
||||
if(ssGen !is ord.obj) {
|
||||
ord.obj.addWaitOrder(ssGen, moveTo=true);
|
||||
ssGen.addSecondaryToSlipstream(ord.obj);
|
||||
}
|
||||
else {
|
||||
ord.obj.addMoveOrder(toPosition, append=true);
|
||||
}
|
||||
|
||||
return F_Continue;
|
||||
}
|
||||
|
||||
SSRegion@ get(Region@ reg) {
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
if(tracked[i].region is reg)
|
||||
return tracked[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void remove(SSRegion@ gt) {
|
||||
if(gt.obj !is null && gt.obj.valid && gt.obj.owner is ai.empire)
|
||||
unassigned.insertLast(gt.obj);
|
||||
tracked.remove(gt);
|
||||
}
|
||||
|
||||
Object@ getClosest(const vec3d& position) {
|
||||
Object@ closest;
|
||||
double minDist = INFINITY;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
Object@ obj = tracked[i].obj;
|
||||
if(obj is null)
|
||||
continue;
|
||||
if(!tracked[i].arrived)
|
||||
continue;
|
||||
double d = obj.position.distanceTo(position);
|
||||
if(d < minDist) {
|
||||
minDist = d;
|
||||
@closest = obj;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
SSRegion@ getClosestRegion(const vec3d& position) {
|
||||
SSRegion@ closest;
|
||||
double minDist = INFINITY;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
double d = tracked[i].region.position.distanceTo(position);
|
||||
if(d < minDist) {
|
||||
minDist = d;
|
||||
@closest = tracked[i];
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
void assignTo(SSRegion@ gt, Object@ closest) {
|
||||
unassigned.remove(closest);
|
||||
@gt.obj = closest;
|
||||
gt.arrived = false;
|
||||
military.stationFleet(fleets.getAI(closest), gt.region);
|
||||
|
||||
if(closest.region is gt.region)
|
||||
gt.arrived = true;
|
||||
|
||||
if(!gt.arrived) {
|
||||
gt.destination = military.getStationPosition(gt.region);
|
||||
closest.addMoveOrder(gt.destination);
|
||||
}
|
||||
}
|
||||
|
||||
bool trackingGen(Object@ obj) {
|
||||
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
|
||||
if(unassigned[i] is obj)
|
||||
return true;
|
||||
}
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
if(tracked[i].obj is obj)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shouldHaveGen(Region@ reg, bool always = false) {
|
||||
if(military.getBase(reg) !is null)
|
||||
return true;
|
||||
if(development.isDevelopingIn(reg))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void turn() override {
|
||||
if(ssDesign !is null && ssDesign.active !is null) {
|
||||
int newSize = round(double(budget.spendable(BT_Military)) * 0.2 * ai.behavior.shipSizePerMoney / 64.0) * 64;
|
||||
if(newSize < 128)
|
||||
newSize = 128;
|
||||
if(newSize != ssDesign.targetSize) {
|
||||
@ssDesign = designs.design(DP_Slipstream, newSize);
|
||||
ssDesign.customName = "Slipstream";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void focusTick(double time) override {
|
||||
//Design a generator
|
||||
if(ssDesign is null) {
|
||||
@ssDesign = designs.design(DP_Slipstream, 128);
|
||||
ssDesign.customName = "Slipstream";
|
||||
}
|
||||
|
||||
//Manage unassigned gens list
|
||||
for(uint i = 0, cnt = unassigned.length; i < cnt; ++i) {
|
||||
Object@ obj = unassigned[i];
|
||||
if(obj is null || !obj.valid || obj.owner !is ai.empire) {
|
||||
unassigned.removeAt(i);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new gens
|
||||
for(uint i = 0, cnt = fleets.fleets.length; i < cnt; ++i) {
|
||||
auto@ flAI = fleets.fleets[i];
|
||||
if(flAI.fleetClass != FC_Slipstream)
|
||||
continue;
|
||||
if(!trackingGen(flAI.obj))
|
||||
unassigned.insertLast(flAI.obj);
|
||||
}
|
||||
|
||||
//Update existing gens for staging bases
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
auto@ gt = tracked[i];
|
||||
bool checkAlways = false;
|
||||
if(gt.obj !is null) {
|
||||
if(!gt.obj.valid || gt.obj.owner !is ai.empire || (gt.arrived && gt.obj.region !is gt.region)) {
|
||||
@gt.obj = null;
|
||||
gt.arrived = false;
|
||||
checkAlways = true;
|
||||
}
|
||||
else if(!gt.arrived && !gt.obj.hasOrders) {
|
||||
if(gt.destination.distanceTo(gt.obj.position) < 10.0)
|
||||
gt.arrived = true;
|
||||
else
|
||||
assignTo(gt, gt.obj);
|
||||
}
|
||||
}
|
||||
if(!shouldHaveGen(gt.region, checkAlways)) {
|
||||
remove(tracked[i]);
|
||||
--i; --cnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new staging bases to build gens at
|
||||
for(uint i = 0, cnt = military.stagingBases.length; i < cnt; ++i) {
|
||||
auto@ base = military.stagingBases[i];
|
||||
if(base.occupiedTime < SS_MIN_TIMER)
|
||||
continue;
|
||||
|
||||
if(get(base.region) is null) {
|
||||
SSRegion@ closest = getClosestRegion(base.region.position);
|
||||
if(closest !is null && closest.region.position.distanceTo(base.region.position) < SS_MIN_DISTANCE_STAGE)
|
||||
continue;
|
||||
|
||||
SSRegion gt;
|
||||
@gt.region = base.region;
|
||||
tracked.insertLast(gt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Detect new important planets to build generator at
|
||||
for(uint i = 0, cnt = development.focuses.length; i < cnt; ++i) {
|
||||
auto@ focus = development.focuses[i];
|
||||
Region@ reg = focus.obj.region;
|
||||
if(reg is null)
|
||||
continue;
|
||||
|
||||
if(get(reg) is null) {
|
||||
SSRegion@ closest = getClosestRegion(reg.position);
|
||||
if(closest !is null && closest.region.position.distanceTo(reg.position) < SS_MIN_DISTANCE_DEVELOP)
|
||||
continue;
|
||||
|
||||
SSRegion gt;
|
||||
@gt.region = reg;
|
||||
tracked.insertLast(gt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//See if we should build a new generator
|
||||
if(buildSS !is null) {
|
||||
if(buildSS.completed) {
|
||||
@buildSS = null;
|
||||
nextBuildTry = gameTime + 60.0;
|
||||
}
|
||||
}
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
auto@ gt = tracked[i];
|
||||
if(gt.obj is null && gt.region.ContestedMask & ai.mask == 0 && gt.region.BlockFTLMask & ai.mask == 0) {
|
||||
Object@ closest;
|
||||
double closestDist = INFINITY;
|
||||
for(uint n = 0, ncnt = unassigned.length; n < ncnt; ++n) {
|
||||
Object@ obj = unassigned[n];
|
||||
if(obj.region is gt.region) {
|
||||
@closest = obj;
|
||||
break;
|
||||
}
|
||||
if(!obj.hasMover)
|
||||
continue;
|
||||
if(buildSS is null && gameTime > nextBuildTry) {
|
||||
double d = obj.position.distanceTo(gt.region.position);
|
||||
if(d < closestDist) {
|
||||
closestDist = d;
|
||||
@closest = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(closest !is null) {
|
||||
if(log)
|
||||
ai.print("Assign slipstream gen to => "+gt.region.name, closest.region);
|
||||
assignTo(gt, closest);
|
||||
} else if(buildSS is null && gameTime > nextBuildTry) {
|
||||
if(log)
|
||||
ai.print("Build slipstream gen for this system", gt.region);
|
||||
|
||||
@buildSS = construction.buildFlagship(ssDesign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Try to get enough ftl storage that we can permanently open a slipstream with each of generators
|
||||
double mostCost = 0.0;
|
||||
for(uint i = 0, cnt = tracked.length; i < cnt; ++i) {
|
||||
Ship@ obj = cast<Ship>(tracked[i].obj);
|
||||
if(obj is null)
|
||||
continue;
|
||||
|
||||
double baseCost = obj.blueprint.design.average(SV_SlipstreamCost);
|
||||
double duration = obj.blueprint.design.average(SV_SlipstreamDuration);
|
||||
mostCost += baseCost / duration;
|
||||
}
|
||||
development.aimFTLStorage = mostCost;
|
||||
}
|
||||
};
|
||||
|
||||
AIComponent@ createSlipstream() {
|
||||
return Slipstream();
|
||||
}
|
||||
Reference in New Issue
Block a user