589 lines
12 KiB
ActionScript
589 lines
12 KiB
ActionScript
import empire_ai.weasel.WeaselAI;
|
|
import empire_ai.weasel.searches;
|
|
|
|
import systems;
|
|
import system_pathing;
|
|
|
|
final class SystemAI {
|
|
const SystemDesc@ desc;
|
|
Region@ obj;
|
|
double prevTick = 0.0;
|
|
|
|
array<Planet@> planets;
|
|
array<Pickup@> pickups;
|
|
array<Object@> pickupProtectors;
|
|
array<Artifact@> artifacts;
|
|
array<Asteroid@> asteroids;
|
|
|
|
bool explored = false;
|
|
bool owned = false;
|
|
bool visible = false;
|
|
|
|
int hopDistance = 0;
|
|
bool visited = false;
|
|
|
|
bool border = false;
|
|
bool bordersEmpires = false;
|
|
bool outsideBorder = false;
|
|
|
|
double lastVisible = 0;
|
|
uint seenPresent = 0;
|
|
|
|
double focusDuration = 0;
|
|
|
|
double enemyStrength = 0;
|
|
double lastStrengthCheck = 0;
|
|
|
|
double nextDetailed = 0;
|
|
|
|
SystemAI() {
|
|
}
|
|
|
|
SystemAI(const SystemDesc@ sys) {
|
|
@desc = sys;
|
|
@obj = desc.object;
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
file << obj;
|
|
file << prevTick;
|
|
|
|
uint cnt = planets.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << planets[i];
|
|
|
|
cnt = pickups.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << pickups[i];
|
|
|
|
cnt = pickupProtectors.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << pickupProtectors[i];
|
|
|
|
cnt = artifacts.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << artifacts[i];
|
|
|
|
cnt = asteroids.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << asteroids[i];
|
|
|
|
file << explored;
|
|
file << owned;
|
|
file << visible;
|
|
file << hopDistance;
|
|
file << border;
|
|
file << bordersEmpires;
|
|
file << outsideBorder;
|
|
file << lastVisible;
|
|
file << seenPresent;
|
|
file << enemyStrength;
|
|
file << lastStrengthCheck;
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
file >> obj;
|
|
file >> prevTick;
|
|
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
planets.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file >> planets[i];
|
|
|
|
file >> cnt;
|
|
pickups.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file >> pickups[i];
|
|
|
|
file >> cnt;
|
|
pickupProtectors.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file >> pickupProtectors[i];
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Artifact@ artif;
|
|
file >> artif;
|
|
if(artif !is null)
|
|
artifacts.insertLast(artif);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Asteroid@ roid;
|
|
file >> roid;
|
|
if(roid !is null)
|
|
asteroids.insertLast(roid);
|
|
}
|
|
|
|
file >> explored;
|
|
file >> owned;
|
|
file >> visible;
|
|
file >> hopDistance;
|
|
file >> border;
|
|
file >> bordersEmpires;
|
|
file >> outsideBorder;
|
|
file >> lastVisible;
|
|
file >> seenPresent;
|
|
file >> enemyStrength;
|
|
file >> lastStrengthCheck;
|
|
}
|
|
|
|
bool visibleNow(AI& ai) {
|
|
return obj.VisionMask & ai.visionMask != 0;
|
|
}
|
|
|
|
void strengthCheck(AI& ai, double minInterval = 30.0) {
|
|
if(lastStrengthCheck + minInterval > gameTime)
|
|
return;
|
|
if(!visible && lastVisible < gameTime - 30.0)
|
|
return;
|
|
lastStrengthCheck = gameTime;
|
|
enemyStrength = getTotalFleetStrength(obj, ai.enemyMask);
|
|
}
|
|
|
|
void tick(AI& ai, Systems& systems, double time) {
|
|
//Check if we should be visible
|
|
bool shouldVisible = obj.VisionMask & ai.visionMask != 0;
|
|
if(visible != shouldVisible) {
|
|
if(visible)
|
|
lastVisible = gameTime;
|
|
visible = shouldVisible;
|
|
}
|
|
|
|
//Check if we should be owned
|
|
bool shouldOwned = obj.PlanetsMask & ai.mask != 0;
|
|
if(owned != shouldOwned) {
|
|
if(shouldOwned) {
|
|
systems.owned.insertLast(this);
|
|
systems.hopsChanged = true;
|
|
hopDistance = 0;
|
|
}
|
|
else {
|
|
hopDistance = 1;
|
|
systems.owned.remove(this);
|
|
systems.hopsChanged = true;
|
|
}
|
|
owned = shouldOwned;
|
|
}
|
|
|
|
//Check if we should be border
|
|
bool shouldBorder = false;
|
|
bordersEmpires = false;
|
|
if(owned) {
|
|
for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) {
|
|
auto@ other = systems.getAI(desc.adjacent[i]);
|
|
if(other !is null && !other.owned) {
|
|
if(other.seenPresent & ~ai.teamMask != 0)
|
|
bordersEmpires = true;
|
|
shouldBorder = true;
|
|
break;
|
|
}
|
|
}
|
|
for(uint i = 0, cnt = desc.wormholes.length; i < cnt; ++i) {
|
|
auto@ other = systems.getAI(desc.wormholes[i]);
|
|
if(other !is null && !other.owned) {
|
|
if(other.seenPresent & ~ai.teamMask != 0)
|
|
bordersEmpires = true;
|
|
shouldBorder = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(border != shouldBorder) {
|
|
if(shouldBorder) {
|
|
systems.border.insertLast(this);
|
|
}
|
|
else {
|
|
systems.border.remove(this);
|
|
}
|
|
border = shouldBorder;
|
|
}
|
|
|
|
//Check if we should be outsideBorder
|
|
bool shouldOutsideBorder = !owned && hopDistance == 1;
|
|
if(outsideBorder != shouldOutsideBorder) {
|
|
if(shouldOutsideBorder) {
|
|
systems.outsideBorder.insertLast(this);
|
|
}
|
|
else {
|
|
systems.outsideBorder.remove(this);
|
|
}
|
|
outsideBorder = shouldOutsideBorder;
|
|
}
|
|
|
|
//Check if we've been explored
|
|
if(visible && !explored) {
|
|
//Find all remnants in this system
|
|
auto@ objs = findType(obj, null, OT_Pickup);
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Pickup@ p = cast<Pickup>(objs[i]);
|
|
if(p !is null) {
|
|
pickups.insertLast(p);
|
|
pickupProtectors.insertLast(p.getProtector());
|
|
}
|
|
}
|
|
|
|
explored = true;
|
|
}
|
|
|
|
//Deal with recording new data on this system
|
|
if(explored) {
|
|
uint plCnt = obj.planetCount;
|
|
if(plCnt != planets.length) {
|
|
auto@ objs = findType(obj, null, OT_Planet);
|
|
planets.length = 0;
|
|
planets.reserve(objs.length);
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Planet@ pl = cast<Planet>(objs[i]);
|
|
if(pl !is null)
|
|
planets.insertLast(pl);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(visible) {
|
|
seenPresent = obj.PlanetsMask;
|
|
|
|
uint astrCount = obj.asteroidCount;
|
|
if(astrCount != asteroids.length) {
|
|
auto@ objs = findType(obj, null, OT_Asteroid);
|
|
asteroids.length = 0;
|
|
asteroids.reserve(objs.length);
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Asteroid@ a = cast<Asteroid>(objs[i]);
|
|
if(a !is null)
|
|
asteroids.insertLast(a);
|
|
}
|
|
}
|
|
|
|
for(uint i = 0, cnt = pickups.length; i < cnt; ++i) {
|
|
if(!pickups[i].valid) {
|
|
pickups.removeAt(i);
|
|
pickupProtectors.removeAt(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(nextDetailed < gameTime) {
|
|
nextDetailed = gameTime + randomd(40.0, 100.0);
|
|
|
|
auto@ objs = findType(obj, null, OT_Artifact);
|
|
artifacts.length = 0;
|
|
artifacts.reserve(objs.length);
|
|
for(uint i = 0, cnt = objs.length; i < cnt; ++i) {
|
|
Artifact@ a = cast<Artifact>(objs[i]);
|
|
if(a !is null)
|
|
artifacts.insertLast(a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class Systems : AIComponent {
|
|
//All owned systems
|
|
array<SystemAI@> owned;
|
|
|
|
//All owned systems that are considered our empire's border
|
|
array<SystemAI@> border;
|
|
|
|
//All systems just outside our border
|
|
array<SystemAI@> outsideBorder;
|
|
|
|
//All systems
|
|
array<SystemAI@> all;
|
|
|
|
//Systems that need to be processed soon
|
|
array<SystemAI@> bumped;
|
|
array<SystemAI@> focused;
|
|
|
|
uint sysIdx = 0;
|
|
bool hopsChanged = false;
|
|
|
|
void save(SaveFile& file) {
|
|
uint cnt = all.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
all[i].save(file);
|
|
|
|
cnt = owned.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
saveAI(file, owned[i]);
|
|
|
|
cnt = border.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
saveAI(file, border[i]);
|
|
|
|
cnt = outsideBorder.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
saveAI(file, outsideBorder[i]);
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
all.length = max(all.length, cnt);
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
if(all[i] is null)
|
|
@all[i] = SystemAI();
|
|
all[i].load(file);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ data = loadAI(file);
|
|
if(data !is null)
|
|
owned.insertLast(data);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ data = loadAI(file);
|
|
if(data !is null)
|
|
border.insertLast(data);
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ data = loadAI(file);
|
|
if(data !is null)
|
|
outsideBorder.insertLast(data);
|
|
}
|
|
}
|
|
|
|
void loadFinalize(AI& ai) override {
|
|
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
|
|
auto@ sys = getSystem(i);
|
|
@all[i].desc = sys;
|
|
@all[i].obj = sys.object;
|
|
}
|
|
}
|
|
|
|
void saveAI(SaveFile& file, SystemAI@ ai) {
|
|
Region@ reg;
|
|
if(ai !is null)
|
|
@reg = ai.obj;
|
|
file << reg;
|
|
}
|
|
|
|
SystemAI@ loadAI(SaveFile& file) {
|
|
Region@ reg;
|
|
file >> reg;
|
|
|
|
if(reg is null)
|
|
return null;
|
|
|
|
uint id = reg.SystemId;
|
|
if(id >= all.length) {
|
|
all.length = id+1;
|
|
@all[id] = SystemAI();
|
|
@all[id].obj = reg;
|
|
}
|
|
|
|
return all[id];
|
|
}
|
|
|
|
void focusTick(double time) {
|
|
if(all.length != systemCount) {
|
|
uint prevCount = all.length;
|
|
all.length = systemCount;
|
|
for(uint i = prevCount, cnt = all.length; i < cnt; ++i)
|
|
@all[i] = SystemAI(getSystem(i));
|
|
}
|
|
if(hopsChanged)
|
|
calculateHops();
|
|
}
|
|
|
|
void tick(double time) override {
|
|
double curTime = gameTime;
|
|
|
|
if(all.length != 0) {
|
|
uint tcount = max(ceil(time / 0.2), double(all.length)/20.0);
|
|
for(uint n = 0; n < tcount; ++n) {
|
|
sysIdx = (sysIdx+1) % all.length;
|
|
|
|
auto@ sys = all[sysIdx];
|
|
sys.tick(ai, this, curTime - sys.prevTick);
|
|
sys.prevTick = curTime;
|
|
}
|
|
}
|
|
|
|
for(uint i = 0, cnt = bumped.length; i < cnt; ++i) {
|
|
auto@ sys = bumped[i];
|
|
double tickTime = curTime - sys.prevTick;
|
|
if(tickTime != 0) {
|
|
sys.tick(ai, this, tickTime);
|
|
sys.prevTick = curTime;
|
|
}
|
|
}
|
|
bumped.length = 0;
|
|
|
|
for(uint i = 0, cnt = focused.length; i < cnt; ++i) {
|
|
auto@ sys = focused[i];
|
|
sys.focusDuration -= time;
|
|
|
|
double tickTime = curTime - sys.prevTick;
|
|
if(tickTime != 0) {
|
|
sys.tick(ai, this, tickTime);
|
|
sys.prevTick = curTime;
|
|
}
|
|
|
|
if(sys.focusDuration <= 0) {
|
|
focused.removeAt(i);
|
|
--i; --cnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
void calculateHops() {
|
|
if(!hopsChanged)
|
|
return;
|
|
hopsChanged = false;
|
|
priority_queue q;
|
|
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
|
|
auto@ sys = all[i];
|
|
sys.visited = false;
|
|
if(sys.owned) {
|
|
sys.hopDistance = 0;
|
|
q.push(int(i), 0);
|
|
}
|
|
else
|
|
sys.hopDistance = INT_MAX;
|
|
}
|
|
|
|
while(!q.empty()) {
|
|
uint index = uint(q.top());
|
|
q.pop();
|
|
|
|
auto@ sys = all[index];
|
|
if(sys.visited)
|
|
continue;
|
|
|
|
int dist = sys.hopDistance;
|
|
sys.visited = true;
|
|
|
|
for(uint i = 0, cnt = sys.desc.adjacent.length; i < cnt; ++i) {
|
|
uint otherInd = sys.desc.adjacent[i];
|
|
if(otherInd < all.length) {
|
|
auto@ other = all[otherInd];
|
|
if(other.hopDistance > dist+1) {
|
|
other.hopDistance = dist+1;
|
|
q.push(otherInd, -other.hopDistance);
|
|
}
|
|
}
|
|
}
|
|
for(uint i = 0, cnt = sys.desc.wormholes.length; i < cnt; ++i) {
|
|
uint otherInd = sys.desc.wormholes[i];
|
|
if(otherInd < all.length) {
|
|
auto@ other = all[otherInd];
|
|
if(other.hopDistance > dist+1) {
|
|
other.hopDistance = dist+1;
|
|
q.push(otherInd, -other.hopDistance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void focus(Region@ reg, double duration = 30.0) {
|
|
bool found = false;
|
|
for(uint i = 0, cnt = focused.length; i < cnt; ++i) {
|
|
if(focused[i].obj is reg) {
|
|
focused[i].focusDuration = max(focused[i].focusDuration, duration);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found) {
|
|
auto@ sys = getAI(reg);
|
|
if(sys !is null) {
|
|
sys.focusDuration = duration;
|
|
focused.insertLast(sys);
|
|
}
|
|
}
|
|
}
|
|
|
|
void bump(Region@ sys) {
|
|
if(sys !is null)
|
|
bump(getAI(sys));
|
|
}
|
|
|
|
void bump(SystemAI@ sys) {
|
|
if(sys !is null)
|
|
bumped.insertLast(sys);
|
|
}
|
|
|
|
SystemAI@ getAI(uint idx) {
|
|
if(idx < all.length)
|
|
return all[idx];
|
|
return null;
|
|
}
|
|
|
|
SystemAI@ getAI(Region@ region) {
|
|
if(region is null)
|
|
return null;
|
|
uint idx = region.SystemId;
|
|
if(idx < all.length)
|
|
return all[idx];
|
|
return null;
|
|
}
|
|
|
|
SystemPath pather;
|
|
int hopDistance(Region& fromRegion, Region& toRegion){
|
|
pather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true);
|
|
if(!pather.valid)
|
|
return INT_MAX;
|
|
return pather.pathSize - 1;
|
|
}
|
|
|
|
TradePath tradePather;
|
|
int tradeDistance(Region& fromRegion, Region& toRegion) {
|
|
@tradePather.forEmpire = ai.empire;
|
|
tradePather.generate(getSystem(fromRegion), getSystem(toRegion), keepCache=true);
|
|
if(!tradePather.valid)
|
|
return -1;
|
|
return tradePather.pathSize - 1;
|
|
}
|
|
|
|
bool canTrade(Region& fromRegion, Region& toRegion) {
|
|
if(fromRegion.sharesTerritory(ai.empire, toRegion))
|
|
return true;
|
|
int dist = tradeDistance(fromRegion, toRegion);
|
|
if(dist < 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
SystemAI@ getAI(const string& name) {
|
|
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
|
|
if(all[i].obj.name.equals_nocase(name))
|
|
return all[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
uint index(const string& name) {
|
|
for(uint i = 0, cnt = all.length; i < cnt; ++i) {
|
|
if(all[i].obj.name.equals_nocase(name))
|
|
return i;
|
|
}
|
|
return uint(-1);
|
|
}
|
|
};
|
|
|
|
AIComponent@ createSystems() {
|
|
return Systems();
|
|
}
|