506 lines
12 KiB
ActionScript
506 lines
12 KiB
ActionScript
// Scouting
|
|
// --------
|
|
// Orders the construction of scouts, explores the galaxy with them and makes
|
|
// sure we have vision where we need vision, as well as scanning anomalies.
|
|
//
|
|
|
|
import empire_ai.weasel.WeaselAI;
|
|
|
|
import empire_ai.weasel.Fleets;
|
|
import empire_ai.weasel.Systems;
|
|
import empire_ai.weasel.Designs;
|
|
import empire_ai.weasel.Construction;
|
|
import empire_ai.weasel.Movement;
|
|
import empire_ai.weasel.Creeping;
|
|
|
|
final class ScoutingMission : Mission {
|
|
Region@ region;
|
|
MoveOrder@ move;
|
|
|
|
void save(Fleets& fleets, SaveFile& file) override {
|
|
file << region;
|
|
fleets.movement.saveMoveOrder(file, move);
|
|
}
|
|
|
|
void load(Fleets& fleets, SaveFile& file) override {
|
|
file >> region;
|
|
@move = fleets.movement.loadMoveOrder(file);
|
|
}
|
|
|
|
double getPerformWeight(AI& ai, FleetAI& fleet) {
|
|
if(fleet.fleetClass != FC_Scout) {
|
|
if(fleet.fleetClass == FC_Mothership)
|
|
return 0.0;
|
|
if(gameTime > ai.behavior.scoutAllTimer)
|
|
return 0.0;
|
|
}
|
|
return 1.0 / region.position.distanceTo(fleet.obj.position);
|
|
}
|
|
|
|
void start(AI& ai, FleetAI& fleet) override {
|
|
uint mprior = MP_Background;
|
|
if(gameTime < 6.0 * 60.0)
|
|
mprior = MP_Critical;
|
|
else if(priority > MiP_Normal)
|
|
mprior = MP_Normal;
|
|
@move = cast<Movement>(ai.movement).move(fleet.obj, region, mprior);
|
|
}
|
|
|
|
void tick(AI& ai, FleetAI& fleet, double time) {
|
|
if(move.failed)
|
|
canceled = true;
|
|
if(move.completed) {
|
|
//We managed to scout this system
|
|
if(fleet.obj.region !is region) {
|
|
@move = cast<Movement>(ai.movement).move(fleet.obj, region.position + random3d(400.0));
|
|
return;
|
|
}
|
|
completed = true;
|
|
|
|
//Detect any anomalies and put them into the scanning queue
|
|
//TODO: Detect newly created anomalies in systems we already have vision over?
|
|
if(region.anomalyCount != 0) {
|
|
auto@ list = region.getAnomalies();
|
|
Object@ obj;
|
|
while(receive(list, obj)) {
|
|
Anomaly@ anom = cast<Anomaly>(obj);
|
|
if(anom !is null)
|
|
cast<Scouting>(ai.scouting).recordAnomaly(anom);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
final class ScanningMission : Mission {
|
|
Anomaly@ anomaly;
|
|
MoveOrder@ move;
|
|
|
|
void save(Fleets& fleets, SaveFile& file) override {
|
|
file << anomaly;
|
|
fleets.movement.saveMoveOrder(file, move);
|
|
}
|
|
|
|
void load(Fleets& fleets, SaveFile& file) override {
|
|
file >> anomaly;
|
|
@move = fleets.movement.loadMoveOrder(file);
|
|
}
|
|
|
|
double getPerformWeight(AI& ai, FleetAI& fleet) {
|
|
if(fleet.fleetClass != FC_Scout) {
|
|
if(gameTime > ai.behavior.scoutAllTimer)
|
|
return 0.0;
|
|
}
|
|
return 1.0 / anomaly.position.distanceTo(fleet.obj.position);
|
|
}
|
|
|
|
void start(AI& ai, FleetAI& fleet) override {
|
|
uint mprior = MP_Background;
|
|
if(priority > MiP_Normal)
|
|
mprior = MP_Normal;
|
|
@move = cast<Movement>(ai.movement).move(fleet.obj, anomaly, mprior);
|
|
}
|
|
|
|
void tick(AI& ai, FleetAI& fleet, double time) {
|
|
if(move !is null) {
|
|
if(move.failed) {
|
|
canceled = true;
|
|
return;
|
|
}
|
|
if(move.completed)
|
|
@move = null;
|
|
}
|
|
if(move is null) {
|
|
if(anomaly is null || !anomaly.valid) {
|
|
completed = true;
|
|
return;
|
|
}
|
|
|
|
if(anomaly.getEmpireProgress(ai.empire) >= 1.f) {
|
|
uint choose = 0;
|
|
uint possibs = 0;
|
|
uint optCnt = anomaly.getOptionCount();
|
|
for(uint i = 0; i < optCnt; ++i) {
|
|
if(anomaly.isOptionSafe[i]) {
|
|
possibs += 1;
|
|
if(randomd() < 1.0 / double(possibs))
|
|
choose = i;
|
|
}
|
|
}
|
|
|
|
if(possibs != 0) {
|
|
anomaly.choose(ai.empire, choose);
|
|
}
|
|
else {
|
|
completed = true;
|
|
}
|
|
}
|
|
else {
|
|
if(!fleet.obj.hasOrders)
|
|
fleet.obj.addScanOrder(anomaly);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class Scouting : AIComponent {
|
|
Fleets@ fleets;
|
|
Systems@ systems;
|
|
Designs@ designs;
|
|
Construction@ construction;
|
|
Movement@ movement;
|
|
Creeping@ creeping;
|
|
|
|
DesignTarget@ scoutDesign;
|
|
|
|
array<ScoutingMission@> queue;
|
|
array<ScoutingMission@> active;
|
|
|
|
array<Anomaly@> anomalies;
|
|
array<ScanningMission@> scanQueue;
|
|
array<ScanningMission@> scanActive;
|
|
|
|
array<BuildFlagship@> constructing;
|
|
|
|
int exploreHops = 0;
|
|
bool buildScouts = true;
|
|
|
|
void create() {
|
|
@fleets = cast<Fleets>(ai.fleets);
|
|
@systems = cast<Systems>(ai.systems);
|
|
@designs = cast<Designs>(ai.designs);
|
|
@construction = cast<Construction>(ai.construction);
|
|
@movement = cast<Movement>(ai.movement);
|
|
@creeping = cast<Creeping>(ai.creeping);
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
designs.saveDesign(file, scoutDesign);
|
|
file << exploreHops;
|
|
|
|
uint cnt = queue.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
fleets.saveMission(file, queue[i]);
|
|
|
|
cnt = active.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
fleets.saveMission(file, active[i]);
|
|
|
|
cnt = anomalies.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << anomalies[i];
|
|
|
|
cnt = scanQueue.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
fleets.saveMission(file, scanQueue[i]);
|
|
|
|
cnt = scanActive.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
fleets.saveMission(file, scanActive[i]);
|
|
|
|
cnt = constructing.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
construction.saveConstruction(file, constructing[i]);
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
@scoutDesign = designs.loadDesign(file);
|
|
file >> exploreHops;
|
|
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ miss = cast<ScoutingMission>(fleets.loadMission(file));
|
|
if(miss !is null)
|
|
queue.insertLast(miss);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ miss = cast<ScoutingMission>(fleets.loadMission(file));
|
|
if(miss !is null)
|
|
active.insertLast(miss);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Anomaly@ anom;
|
|
file >> anom;
|
|
if(anom !is null)
|
|
anomalies.insertLast(anom);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ miss = cast<ScanningMission>(fleets.loadMission(file));
|
|
if(miss !is null)
|
|
scanQueue.insertLast(miss);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ miss = cast<ScanningMission>(fleets.loadMission(file));
|
|
if(miss !is null)
|
|
scanActive.insertLast(miss);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ cons = cast<BuildFlagship>(construction.loadConstruction(file));
|
|
if(cons !is null)
|
|
constructing.insertLast(cons);
|
|
}
|
|
}
|
|
|
|
void start() {
|
|
@scoutDesign = DesignTarget(DP_Scout, 16);
|
|
scoutDesign.targetMaintenance = 40;
|
|
designs.design(scoutDesign);
|
|
}
|
|
|
|
bool isScouting(Region@ region) {
|
|
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
|
|
if(queue[i].region is region)
|
|
return true;
|
|
}
|
|
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
|
|
if(active[i].region is region)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ScoutingMission@ scout(Region@ region, uint priority = MiP_High) {
|
|
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
|
|
if(queue[i].region is region)
|
|
return queue[i];
|
|
}
|
|
|
|
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
|
|
if(active[i].region is region)
|
|
return active[i];
|
|
}
|
|
|
|
if(log)
|
|
ai.print("Queue scouting mission", region);
|
|
|
|
ScoutingMission mission;
|
|
@mission.region = region;
|
|
mission.priority = priority;
|
|
fleets.register(mission);
|
|
|
|
queue.insertLast(mission);
|
|
return mission;
|
|
}
|
|
|
|
void recordAnomaly(Anomaly@ anom) {
|
|
for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) {
|
|
if(scanActive[i].anomaly is anom)
|
|
return;
|
|
}
|
|
|
|
if(anomalies.find(anom) == -1)
|
|
anomalies.insertLast(anom);
|
|
}
|
|
|
|
ScanningMission@ scan(Anomaly& anomaly, uint priority = MiP_Normal) {
|
|
for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) {
|
|
if(scanActive[i].anomaly is anomaly)
|
|
return scanActive[i];
|
|
}
|
|
|
|
if(log)
|
|
ai.print("Queue scanning mission on "+anomaly.name, anomaly.region);
|
|
|
|
ScanningMission mission;
|
|
@mission.anomaly = anomaly;
|
|
mission.priority = priority;
|
|
fleets.register(mission);
|
|
anomalies.remove(anomaly);
|
|
|
|
scanQueue.insertLast(mission);
|
|
return mission;
|
|
}
|
|
|
|
void focusTick(double time) {
|
|
//Remove completed scouting missions
|
|
for(uint i = 0, cnt = active.length; i < cnt; ++i) {
|
|
if(active[i].completed || active[i].canceled) {
|
|
active.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
|
|
//Remove completed scanning missions
|
|
for(uint i = 0, cnt = scanActive.length; i < cnt; ++i) {
|
|
if(scanActive[i].completed || scanActive[i].canceled) {
|
|
scanActive.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
|
|
//Make sure we have enough scouts active and scouting
|
|
if(fleets.count(FC_Scout) + constructing.length < ai.behavior.scoutsActive && buildScouts)
|
|
constructing.insertLast(construction.buildFlagship(scoutDesign));
|
|
for(uint i = 0, cnt = constructing.length; i < cnt; ++i) {
|
|
if(constructing[i].completed && constructing[i].completedAt + 30.0 < gameTime) {
|
|
constructing.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
|
|
//See if we can fill the scouting queue with something nice
|
|
uint scoutClass = FC_Scout;
|
|
if(gameTime < ai.behavior.scoutAllTimer)
|
|
scoutClass = FC_ALL;
|
|
bool haveIdle = fleets.haveIdle(scoutClass);
|
|
|
|
//See if we should queue up a new anomaly scan
|
|
if(scanQueue.length == 0 && anomalies.length != 0 && scanActive.length < ai.behavior.maxScanningMissions && haveIdle && (!ai.behavior.prioritizeScoutOverScan || active.length > 0)) {
|
|
Anomaly@ best;
|
|
double bestDist = INFINITY;
|
|
for(uint i = 0, cnt = anomalies.length; i < cnt; ++i) {
|
|
auto@ anom = anomalies[i];
|
|
if(anom is null || !anom.valid) {
|
|
anomalies.removeAt(i);
|
|
--i; --cnt;
|
|
continue;
|
|
}
|
|
if(creeping.isQuarantined(anom.region))
|
|
continue;
|
|
|
|
double d = fleets.closestIdleTo(scoutClass, anom.position);
|
|
if(d < bestDist) {
|
|
@best = anom;
|
|
bestDist = d;
|
|
}
|
|
}
|
|
|
|
if(best !is null)
|
|
scan(best);
|
|
}
|
|
|
|
//Scan anomalies in our scan queue
|
|
if(scanQueue.length != 0) {
|
|
auto@ mission = scanQueue[0];
|
|
if(mission.anomaly is null || !mission.anomaly.valid) {
|
|
scanQueue.removeAt(0);
|
|
}
|
|
else {
|
|
auto@ flAI = fleets.performMission(mission);
|
|
if(flAI !is null) {
|
|
if(log)
|
|
ai.print("Perform scanning mission with "+flAI.obj.name, mission.anomaly.region);
|
|
|
|
scanQueue.remove(mission);
|
|
scanActive.insertLast(mission);
|
|
}
|
|
}
|
|
}
|
|
|
|
//TODO: In large maps we should probably devote scouts to scouting enemies even before the map is fully explored
|
|
if(queue.length == 0 && active.length < ai.behavior.maxScoutingMissions && haveIdle) {
|
|
//Explore systems from the inside out
|
|
if(exploreHops != -1) {
|
|
double bestDist = INFINITY;
|
|
bool remainingHops = false;
|
|
bool emptyHops = true;
|
|
Region@ best;
|
|
|
|
for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) {
|
|
auto@ sys = systems.all[i];
|
|
|
|
if(sys.hopDistance == exploreHops)
|
|
emptyHops = false;
|
|
|
|
if(sys.explored || isScouting(sys.obj))
|
|
continue;
|
|
|
|
if(sys.hopDistance == exploreHops)
|
|
remainingHops = true;
|
|
|
|
double d = fleets.closestIdleTo(scoutClass, sys.obj.position);
|
|
if(sys.hopDistance != exploreHops)
|
|
d *= pow(ai.behavior.exploreBorderWeight, double(sys.hopDistance - exploreHops));
|
|
|
|
if(d < bestDist) {
|
|
bestDist = d;
|
|
@best = sys.obj;
|
|
}
|
|
}
|
|
|
|
if(best !is null)
|
|
scout(best, priority=MiP_Normal);
|
|
|
|
if(emptyHops)
|
|
exploreHops = -1;
|
|
else if(!remainingHops)
|
|
exploreHops += 1;
|
|
}
|
|
else {
|
|
//Gain vision over systems we haven't recently seen
|
|
Region@ best;
|
|
double bestWeight = 0;
|
|
double curTime = gameTime;
|
|
|
|
for(uint i = 0, cnt = systems.all.length; i < cnt; ++i) {
|
|
auto@ sys = systems.all[i];
|
|
if(sys.visible || sys.visibleNow(ai))
|
|
continue;
|
|
if(isScouting(sys.obj))
|
|
continue;
|
|
|
|
double timer = curTime - sys.lastVisible;
|
|
if(timer < ai.behavior.minScoutingInterval)
|
|
continue;
|
|
|
|
double w = 1.0;
|
|
w *= timer / ai.behavior.minScoutingInterval;
|
|
w /= fleets.closestIdleTo(scoutClass, sys.obj.position);
|
|
|
|
if(!sys.explored)
|
|
w *= 10.0;
|
|
if(sys.seenPresent & ~ai.visionMask != 0)
|
|
w *= 2.0;
|
|
if(sys.seenPresent & ai.enemyMask != 0) {
|
|
if(sys.hopDistance < 2)
|
|
w *= 4.0;
|
|
w *= 4.0;
|
|
}
|
|
|
|
if(w > bestWeight) {
|
|
bestWeight = w;
|
|
@best = sys.obj;
|
|
}
|
|
}
|
|
|
|
if(best !is null)
|
|
scout(best, priority=MiP_Normal);
|
|
}
|
|
}
|
|
|
|
//Try to find a scout to perform our top scouting mission from the queue
|
|
if(queue.length != 0) {
|
|
auto@ mission = queue[0];
|
|
auto@ flAI = fleets.performMission(mission);
|
|
if(flAI !is null) {
|
|
if(log)
|
|
ai.print("Perform scouting mission with "+flAI.obj.name, mission.region);
|
|
|
|
active.insertLast(mission);
|
|
queue.removeAt(0);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
AIComponent@ createScouting() {
|
|
return Scouting();
|
|
}
|