Files
starruler-linux/scripts/server/empire_ai/weasel/Intelligence.as
T
2018-07-17 14:15:37 +02:00

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();
}