543 lines
12 KiB
ActionScript
543 lines
12 KiB
ActionScript
// Intelligence
|
|
// ------------
|
|
// Keeps track of the existence and movement of enemy fleets and other assets.
|
|
//
|
|
|
|
import empire_ai.weasel.WeaselAI;
|
|
import empire_ai.weasel.Fleets;
|
|
import empire_ai.weasel.Systems;
|
|
|
|
import regions.regions;
|
|
|
|
final class FleetIntel {
|
|
Object@ obj;
|
|
bool known = false;
|
|
bool visible = false;
|
|
double lastSeen = 0;
|
|
double seenStrength = 0;
|
|
double predictStrength = 0;
|
|
|
|
vec3d seenPosition;
|
|
Region@ seenRegion;
|
|
vec3d seenDestination;
|
|
Region@ seenTarget;
|
|
|
|
void save(SaveFile& file) {
|
|
file << obj;
|
|
file << known;
|
|
file << visible;
|
|
file << lastSeen;
|
|
file << seenStrength;
|
|
file << predictStrength;
|
|
file << seenPosition;
|
|
file << seenRegion;
|
|
file << seenDestination;
|
|
file << seenTarget;
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
file >> obj;
|
|
file >> known;
|
|
file >> visible;
|
|
file >> lastSeen;
|
|
file >> seenStrength;
|
|
file >> predictStrength;
|
|
file >> seenPosition;
|
|
file >> seenRegion;
|
|
file >> seenDestination;
|
|
file >> seenTarget;
|
|
}
|
|
|
|
bool get_isSignificant() {
|
|
return obj.getFleetStrength() > 0.1;
|
|
}
|
|
|
|
bool tick(AI& ai, Intelligence& intelligence, Intel& intel) {
|
|
if(visible) {
|
|
if(!obj.valid || obj.owner !is intel.empire)
|
|
return false;
|
|
}
|
|
else {
|
|
if(!obj.valid || obj.owner !is intel.empire) {
|
|
if(!known || lastSeen < gameTime - 300.0)
|
|
return false;
|
|
}
|
|
}
|
|
if(obj.isVisibleTo(ai.empire)) {
|
|
known = true;
|
|
visible = true;
|
|
lastSeen = gameTime;
|
|
|
|
seenStrength = obj.getFleetStrength();
|
|
predictStrength = obj.getFleetMaxStrength();
|
|
int supCap = obj.SupplyCapacity;
|
|
double fillPct = 1.0;
|
|
if(supCap != 0) {
|
|
double fillPct = double(obj.SupplyUsed) / double(supCap);
|
|
if(fillPct > 0.5)
|
|
predictStrength /= fillPct;
|
|
else
|
|
predictStrength *= 2.0;
|
|
}
|
|
|
|
seenPosition = obj.position;
|
|
@seenRegion = obj.region;
|
|
|
|
if(obj.isMoving) {
|
|
seenDestination = obj.computedDestination;
|
|
if(seenRegion !is null && inRegion(seenRegion, seenDestination))
|
|
@seenTarget = seenRegion;
|
|
else if(seenTarget !is null && inRegion(seenTarget, seenDestination))
|
|
@seenTarget = seenTarget;
|
|
else
|
|
@seenTarget = getRegion(seenDestination);
|
|
}
|
|
else {
|
|
seenDestination = seenPosition;
|
|
@seenTarget = seenRegion;
|
|
}
|
|
}
|
|
else {
|
|
visible = false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
final class Intel {
|
|
Empire@ empire;
|
|
uint borderedTo = 0;
|
|
|
|
array<FleetIntel@> fleets;
|
|
array<SystemAI@> shared;
|
|
array<SystemAI@> theirBorder;
|
|
array<SystemAI@> theirOwned;
|
|
|
|
void save(Intelligence& intelligence, SaveFile& file) {
|
|
uint cnt = fleets.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
fleets[i].save(file);
|
|
|
|
cnt = shared.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
intelligence.systems.saveAI(file, shared[i]);
|
|
|
|
cnt = theirBorder.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
intelligence.systems.saveAI(file, theirBorder[i]);
|
|
|
|
cnt = theirOwned.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
intelligence.systems.saveAI(file, theirOwned[i]);
|
|
|
|
file << borderedTo;
|
|
}
|
|
|
|
void load(Intelligence& intelligence, SaveFile& file) {
|
|
uint cnt = 0;
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
FleetIntel flIntel;
|
|
flIntel.load(file);
|
|
if(flIntel.obj !is null)
|
|
fleets.insertLast(flIntel);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ sys = intelligence.systems.loadAI(file);
|
|
if(sys !is null)
|
|
shared.insertLast(sys);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ sys = intelligence.systems.loadAI(file);
|
|
if(sys !is null)
|
|
theirBorder.insertLast(sys);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ sys = intelligence.systems.loadAI(file);
|
|
if(sys !is null)
|
|
theirOwned.insertLast(sys);
|
|
}
|
|
|
|
file >> borderedTo;
|
|
}
|
|
|
|
//TODO: If a fleet is going to drop out of cutoff range soon,
|
|
// queue up a scouting mission to its last known position so we
|
|
// can try to regain intel on it.
|
|
|
|
double getSeenStrength(double cutOff = 600.0) {
|
|
double total = 0.0;
|
|
cutOff = gameTime - cutOff;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
auto@ flIntel = fleets[i];
|
|
if(!flIntel.known)
|
|
continue;
|
|
if(flIntel.lastSeen < cutOff)
|
|
continue;
|
|
total += sqrt(fleets[i].seenStrength);
|
|
}
|
|
return total * total;
|
|
}
|
|
|
|
double getPredictiveStrength(double cutOff = 600.0) {
|
|
double total = 0.0;
|
|
cutOff = gameTime - cutOff;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
auto@ flIntel = fleets[i];
|
|
if(!flIntel.known)
|
|
continue;
|
|
if(flIntel.lastSeen < cutOff)
|
|
continue;
|
|
total += sqrt(fleets[i].predictStrength);
|
|
}
|
|
return total * total;
|
|
}
|
|
|
|
double accuracy(AI& ai, Intelligence& intelligence, double cutOff = 600.0) {
|
|
uint total = 0;
|
|
uint known = 0;
|
|
|
|
cutOff = gameTime - cutOff;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
auto@ flIntel = fleets[i];
|
|
if(!flIntel.isSignificant)
|
|
continue;
|
|
|
|
total += 1;
|
|
if(flIntel.known && flIntel.lastSeen >= cutOff)
|
|
known += 1;
|
|
}
|
|
|
|
if(total == 0)
|
|
return 1.0;
|
|
return double(known) / double(total);
|
|
}
|
|
|
|
double defeatability(AI& ai, Intelligence& intelligence, double cutOff = 600.0) {
|
|
double acc = accuracy(ai, intelligence, cutOff);
|
|
double ourStrength = 0, theirStrength = 0;
|
|
|
|
if(acc < 0.6) {
|
|
//In low-accuracy situations, base it on the empire overall strength metric
|
|
theirStrength = empire.TotalMilitary;
|
|
ourStrength = ai.empire.TotalMilitary;
|
|
}
|
|
else {
|
|
theirStrength = getPredictiveStrength(cutOff * 10.0);
|
|
ourStrength = intelligence.fleets.totalStrength;
|
|
}
|
|
|
|
if(theirStrength == 0)
|
|
return 10.0;
|
|
return ourStrength / theirStrength;
|
|
}
|
|
|
|
void tick(AI& ai, Intelligence& intelligence) {
|
|
//Keep known fleets updated
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
if(!fleets[i].tick(ai, intelligence, this)) {
|
|
fleets.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isShared(AI& ai, SystemAI@ sys) {
|
|
return sys.seenPresent & ai.empire.mask != 0 && sys.seenPresent & empire.mask != 0;
|
|
}
|
|
|
|
bool isBorder(AI& ai, SystemAI@ sys) {
|
|
return sys.outsideBorder && sys.seenPresent & empire.mask != 0;
|
|
}
|
|
|
|
void focusTick(AI& ai, Intelligence& intelligence) {
|
|
//Detect newly created fleets
|
|
auto@ data = empire.getFlagships();
|
|
Object@ obj;
|
|
while(receive(data, obj)) {
|
|
if(obj is null)
|
|
continue;
|
|
|
|
bool found = false;
|
|
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
|
|
if(fleets[i].obj is obj) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found) {
|
|
FleetIntel flIntel;
|
|
@flIntel.obj = obj;
|
|
fleets.insertLast(flIntel);
|
|
}
|
|
}
|
|
|
|
//Remove no longer shared and border systems
|
|
for(uint i = 0, cnt = shared.length; i < cnt; ++i) {
|
|
if(!isShared(ai, shared[i])) {
|
|
shared.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
for(uint i = 0, cnt = theirBorder.length; i < cnt; ++i) {
|
|
if(!isBorder(ai, theirBorder[i])) {
|
|
theirBorder.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
|
|
borderedTo = 0;
|
|
for(uint i = 0, cnt = theirOwned.length; i < cnt; ++i) {
|
|
auto@ sys = theirOwned[i];
|
|
uint seen = sys.seenPresent;
|
|
if(seen & empire.mask == 0) {
|
|
theirOwned.removeAt(i);
|
|
--i; --cnt;
|
|
continue;
|
|
}
|
|
|
|
for(uint n = 0, ncnt = sys.desc.adjacent.length; n < ncnt; ++n) {
|
|
auto@ other = intelligence.systems.getAI(sys.desc.adjacent[n]);
|
|
if(other !is null)
|
|
borderedTo |= other.seenPresent & ~empire.mask;
|
|
}
|
|
|
|
for(uint n = 0, ncnt = sys.desc.wormholes.length; n < ncnt; ++n) {
|
|
auto@ other = intelligence.systems.getAI(sys.desc.wormholes[n]);
|
|
if(other !is null)
|
|
borderedTo |= other.seenPresent & ~empire.mask;
|
|
}
|
|
}
|
|
|
|
//Detect shared and border systems
|
|
for(uint i = 0, cnt = intelligence.systems.owned.length; i < cnt; ++i) {
|
|
auto@ sys = intelligence.systems.owned[i];
|
|
if(isShared(ai, sys)) {
|
|
if(shared.find(sys) == -1)
|
|
shared.insertLast(sys);
|
|
}
|
|
}
|
|
for(uint i = 0, cnt = intelligence.systems.outsideBorder.length; i < cnt; ++i) {
|
|
auto@ sys = intelligence.systems.outsideBorder[i];
|
|
if(isBorder(ai, sys)) {
|
|
if(theirBorder.find(sys) == -1)
|
|
theirBorder.insertLast(sys);
|
|
}
|
|
}
|
|
for(uint i = 0, cnt = intelligence.systems.all.length; i < cnt; ++i) {
|
|
auto@ sys = intelligence.systems.all[i];
|
|
if(sys.seenPresent & empire.mask != 0) {
|
|
if(theirOwned.find(sys) == -1)
|
|
theirOwned.insertLast(sys);
|
|
}
|
|
}
|
|
|
|
//Try to update some stuff
|
|
SystemAI@ check;
|
|
double lru = 0;
|
|
|
|
for(uint i = 0, cnt = shared.length; i < cnt; ++i) {
|
|
auto@ sys = shared[i];
|
|
double update = sys.lastStrengthCheck;
|
|
if(update < lru && sys.visible) {
|
|
@check = sys;
|
|
lru = update;
|
|
}
|
|
}
|
|
|
|
for(uint i = 0, cnt = theirBorder.length; i < cnt; ++i) {
|
|
auto@ sys = theirBorder[i];
|
|
double update = sys.lastStrengthCheck;
|
|
if(update < lru && sys.visible) {
|
|
@check = sys;
|
|
lru = update;
|
|
}
|
|
}
|
|
|
|
if(check !is null)
|
|
check.strengthCheck(ai);
|
|
}
|
|
};
|
|
|
|
class Intelligence : AIComponent {
|
|
Fleets@ fleets;
|
|
Systems@ systems;
|
|
|
|
array<Intel@> intel;
|
|
|
|
void create() {
|
|
@fleets = cast<Fleets>(ai.fleets);
|
|
@systems = cast<Systems>(ai.systems);
|
|
}
|
|
|
|
void start() {
|
|
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
|
Empire@ emp = getEmpire(i);
|
|
if(emp is ai.empire)
|
|
continue;
|
|
if(!emp.major)
|
|
continue;
|
|
|
|
Intel empIntel;
|
|
@empIntel.empire = emp;
|
|
|
|
if(intel.length <= uint(emp.index))
|
|
intel.length = uint(emp.index)+1;
|
|
@intel[emp.index] = empIntel;
|
|
}
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
uint cnt = intel.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
if(intel[i] is null) {
|
|
file.write0();
|
|
continue;
|
|
}
|
|
|
|
file.write1();
|
|
intel[i].save(this, file);
|
|
}
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
intel.length = cnt;
|
|
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
if(!file.readBit())
|
|
continue;
|
|
|
|
@intel[i] = Intel();
|
|
@intel[i].empire = getEmpire(i);
|
|
intel[i].load(this, file);
|
|
}
|
|
}
|
|
|
|
Intel@ get(Empire@ emp) {
|
|
if(emp is null)
|
|
return null;
|
|
if(!emp.major)
|
|
return null;
|
|
if(uint(emp.index) >= intel.length)
|
|
return null;
|
|
return intel[emp.index];
|
|
}
|
|
|
|
uint ind = 0;
|
|
void tick(double time) override {
|
|
if(intel.length == 0)
|
|
return;
|
|
ind = (ind+1)%intel.length;
|
|
if(intel[ind] !is null)
|
|
intel[ind].tick(ai, this);
|
|
}
|
|
|
|
uint fInd = 0;
|
|
void focusTick(double time) override {
|
|
if(intel.length == 0)
|
|
return;
|
|
fInd = (fInd+1)%intel.length;
|
|
if(intel[fInd] !is null)
|
|
intel[fInd].focusTick(ai, this);
|
|
}
|
|
|
|
string strdisplay(double str) {
|
|
return standardize(str * 0.001, true);
|
|
}
|
|
|
|
double defeatability(Empire@ emp) {
|
|
auto@ empIntel = get(emp);
|
|
if(empIntel is null)
|
|
return 0.0;
|
|
return empIntel.defeatability(ai, this);
|
|
}
|
|
|
|
double defeatability(uint theirMask, uint myMask = 0, double cutOff = 600.0) {
|
|
if(myMask == 0)
|
|
myMask = ai.empire.mask;
|
|
|
|
double minAcc = 1.0;
|
|
for(uint i = 0, cnt = intel.length; i < cnt; ++i) {
|
|
auto@ itl = intel[i];
|
|
if(itl is null || itl.empire is null)
|
|
continue;
|
|
if((theirMask | myMask) & itl.empire.mask == 0)
|
|
continue;
|
|
minAcc = min(itl.accuracy(ai, this, cutOff), minAcc);
|
|
}
|
|
|
|
double ourStrength = 0, theirStrength = 0;
|
|
for(uint i = 0, cnt = intel.length; i < cnt; ++i) {
|
|
auto@ itl = intel[i];
|
|
if(itl is null || itl.empire is null)
|
|
continue;
|
|
if((theirMask | myMask) & itl.empire.mask == 0)
|
|
continue;
|
|
|
|
double str = 0.0;
|
|
if(minAcc < 0.6)
|
|
str = itl.empire.TotalMilitary;
|
|
else
|
|
str = itl.getPredictiveStrength(cutOff * 10.0);
|
|
|
|
if(itl.empire.mask & theirMask != 0)
|
|
theirStrength += str;
|
|
if(itl.empire.mask & myMask != 0)
|
|
ourStrength += str;
|
|
}
|
|
|
|
if(myMask & ai.empire.mask != 0) {
|
|
if(minAcc < 0.6)
|
|
ourStrength += ai.empire.TotalMilitary;
|
|
else
|
|
ourStrength += fleets.totalStrength;
|
|
}
|
|
if(theirMask & ai.empire.mask != 0) {
|
|
if(minAcc < 0.6)
|
|
theirStrength += ai.empire.TotalMilitary;
|
|
else
|
|
theirStrength += fleets.totalStrength;
|
|
}
|
|
|
|
if(theirStrength == 0)
|
|
return 10.0;
|
|
return ourStrength / theirStrength;
|
|
}
|
|
|
|
void turn() override {
|
|
if(log) {
|
|
ai.print("Intelligence Report on Empires:");
|
|
ai.print(ai.pad(" Our strength: ", 18)+strdisplay(fleets.totalStrength)+" / "+strdisplay(fleets.totalMaxStrength));
|
|
for(uint i = 0, cnt = intel.length; i < cnt; ++i) {
|
|
auto@ empIntel = intel[i];
|
|
if(empIntel is null)
|
|
continue;
|
|
ai.print(" "+ai.pad(empIntel.empire.name, 16)
|
|
+ai.pad(" "+strdisplay(empIntel.getSeenStrength())
|
|
+" / "+strdisplay(empIntel.getPredictiveStrength()), 20)
|
|
+" defeatability "+toString(empIntel.defeatability(ai, this), 2)
|
|
+" accuracy "+toString(empIntel.accuracy(ai, this), 2));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
AIComponent@ createIntelligence() {
|
|
return Intelligence();
|
|
}
|