630 lines
14 KiB
ActionScript
630 lines
14 KiB
ActionScript
#priority init 1500
|
|
import generic_hooks;
|
|
from bonus_effects import BonusEffect;
|
|
|
|
import resources;
|
|
|
|
#section server
|
|
import systems;
|
|
import regions.regions;
|
|
import object_creation;
|
|
import attributes;
|
|
import oddity_navigation;
|
|
|
|
const ResourceClass@ foodClass;
|
|
const ResourceClass@ waterClass;
|
|
const ResourceClass@ scalableClass;
|
|
|
|
void init() {
|
|
@foodClass = getResourceClass("Food");
|
|
@waterClass = getResourceClass("WaterType");
|
|
@scalableClass = getResourceClass("Scalable");
|
|
|
|
coordination.length = getEmpireCount();
|
|
}
|
|
|
|
void EGCoordinationTick(Empire& emp, double time) {
|
|
auto@ coord = getCoordinate(emp);
|
|
if(coord !is null)
|
|
coord.tick(emp, time);
|
|
}
|
|
|
|
bool checkSlot(uint index, const ResourceType@ resource) {
|
|
switch(index) {
|
|
case 0:
|
|
return resource.cls is foodClass;
|
|
case 1:
|
|
return resource.cls is waterClass;
|
|
case 2:
|
|
return resource.level == 1;
|
|
case 3:
|
|
return resource.level == 2;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
final class Coordinate : Savable {
|
|
Mutex mtx;
|
|
array<Planet@> slots(4);
|
|
|
|
double sendTimer = 0.0;
|
|
|
|
set_int targetIds;
|
|
array<Planet@> curTargets;
|
|
array<ColonyShip@> curShips;
|
|
|
|
set_int penaltyIds;
|
|
array<Planet@> curPenalties;
|
|
array<double> penaltiesUntil;
|
|
|
|
array<RefugeeData@> spreading;
|
|
|
|
void tick(Empire& emp, double time) {
|
|
if(spreading.length == 0)
|
|
return;
|
|
|
|
Lock lck(mtx);
|
|
|
|
//Update colony targets
|
|
for(int i = curTargets.length-1; i >= 0; --i) {
|
|
Planet@ pl = curTargets[i];
|
|
ColonyShip@ colShip = curShips[i];
|
|
|
|
if(colShip is null || !colShip.valid || pl is null || !pl.valid) {
|
|
curTargets.removeAt(i);
|
|
curShips.removeAt(i);
|
|
|
|
if(pl !is null) {
|
|
targetIds.erase(pl.id);
|
|
|
|
Empire@ owner = pl.owner;
|
|
if(owner !is emp && owner !is null && owner.valid) {
|
|
if(!penaltyIds.contains(pl.id)) {
|
|
curPenalties.insertLast(pl);
|
|
penaltiesUntil.insertLast(gameTime + 90.0);
|
|
penaltyIds.insert(pl.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Update colony penalties
|
|
double curTime = gameTime;
|
|
for(int i = curPenalties.length-1; i >= 0; --i) {
|
|
if(curTime >= penaltiesUntil[i]) {
|
|
if(curPenalties[i] !is null)
|
|
penaltyIds.erase(curPenalties[i].id);
|
|
penaltiesUntil.removeAt(i);
|
|
curPenalties.removeAt(i);
|
|
}
|
|
}
|
|
|
|
//Manage resource type slots
|
|
for(uint i = 0, cnt = slots.length; i < cnt; ++i) {
|
|
Planet@ pl = slots[i];
|
|
if(pl is null)
|
|
continue;
|
|
if(!pl.valid) {
|
|
@slots[i] = null;
|
|
continue;
|
|
}
|
|
|
|
Empire@ owner = pl.owner;
|
|
if(owner !is emp && owner !is null) {
|
|
if(owner.valid) {
|
|
@slots[i] = null;
|
|
continue;
|
|
}
|
|
else if(!targetIds.contains(pl.id)) {
|
|
@slots[i] = null;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if(owner is emp && pl.primaryResourceUsable) {
|
|
uint resLevel = pl.primaryResourceLevel;
|
|
uint plLevel = pl.level;
|
|
if(resLevel >= 2 || plLevel > resLevel || pl.primaryResourceExported) {
|
|
@slots[i] = null;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Send colonizations
|
|
double speed = 1.0;
|
|
speed *= max(pow(emp.TotalPopulation / 10.0, 0.6), 1.0);
|
|
|
|
//Slow down expansion speed for lower difficulty AIs
|
|
if(emp.isAI) {
|
|
int diff = emp.difficulty;
|
|
if(diff == 0)
|
|
speed *= 0.3;
|
|
else if(diff == 1)
|
|
speed *= 0.65;
|
|
}
|
|
|
|
sendTimer -= time * speed;
|
|
|
|
if(sendTimer <= 0.0) {
|
|
Planet@ bestPlanet;
|
|
RefugeeData@ bestBeacon;
|
|
double bestWeight = 0.0;
|
|
|
|
uint cnt = spreading.length;
|
|
uint offset = randomi(0, cnt-1);
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
uint index = (i+offset) % cnt;
|
|
auto@ rd = spreading[index];
|
|
if(rd.obj is null || !rd.obj.valid) {
|
|
spreading.removeAt(index);
|
|
--i; --cnt;
|
|
continue;
|
|
}
|
|
|
|
Planet@ pl = rd.nextTarget;
|
|
if(pl !is null) {
|
|
double w = getWeight(emp, rd, pl);
|
|
if(rd.lastBeacon)
|
|
w *= 0.9;
|
|
if(w > bestWeight) {
|
|
@bestPlanet = pl;
|
|
@bestBeacon = rd;
|
|
bestWeight = w;
|
|
}
|
|
}
|
|
|
|
rd.lastBeacon = false;
|
|
}
|
|
|
|
if(bestPlanet !is null) {
|
|
send(bestBeacon.obj, bestPlanet);
|
|
@bestBeacon.nextTarget = null;
|
|
bestBeacon.lastBeacon = true;
|
|
sendTimer += 60.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
double getWeight(Empire& emp, RefugeeData& rd, Planet& pl) {
|
|
Empire@ owner = pl.visibleOwnerToEmp(emp);
|
|
double w = 1.0;
|
|
|
|
//Deal with sending population to planets that aren't full
|
|
if(owner is emp) {
|
|
double curPop = pl.population;
|
|
double maxPop = pl.maxPopulation;
|
|
if(curPop >= maxPop - 0.0001)
|
|
return 0.0;
|
|
|
|
//Track how much population we're currently sending
|
|
for(uint j = 0, jcnt = curTargets.length; j < jcnt; ++j) {
|
|
if(curTargets[j] is pl)
|
|
curPop += 1.0;
|
|
}
|
|
|
|
if(curPop < double(maxPop) - 0.0001)
|
|
w *= 200.0;
|
|
else
|
|
return 0.0;
|
|
}
|
|
else if(owner is null || owner.valid) {
|
|
return 0.0;
|
|
}
|
|
else {
|
|
//Don't target quarantined planets
|
|
if(pl.quarantined)
|
|
return 0.0;
|
|
//Don't colonize twice
|
|
if(targetIds.contains(pl.id))
|
|
return 0.0;
|
|
if(penaltyIds.contains(pl.id))
|
|
return 0.0;
|
|
}
|
|
|
|
//Deal with slots for particular types of resources
|
|
auto@ resource = getResource(pl.primaryResourceType);
|
|
if(resource is null)
|
|
return 0.0;
|
|
|
|
for(uint i = 0, cnt = slots.length; i < cnt; ++i) {
|
|
if(slots[i] !is null)
|
|
continue;
|
|
if(checkSlot(i, resource)) {
|
|
w *= 100.0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Deal with generic resource value
|
|
if(resource.cls is foodClass)
|
|
w *= 1.5;
|
|
else if(resource.cls is waterClass)
|
|
w *= 1.5;
|
|
|
|
w /= ceil(pl.position.distanceTo(rd.obj.position) / 5000.0);
|
|
return w;
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
file << sendTimer;
|
|
for(uint i = 0, cnt = slots.length; i < cnt; ++i)
|
|
file << slots[i];
|
|
|
|
uint cnt = curTargets.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
file << curTargets[i];
|
|
file << curShips[i];
|
|
}
|
|
|
|
cnt = curPenalties.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
file << curPenalties[i];
|
|
file << penaltiesUntil[i];
|
|
}
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
file >> sendTimer;
|
|
for(uint i = 0, cnt = slots.length; i < cnt; ++i)
|
|
file >> slots[i];
|
|
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Planet@ targ;
|
|
ColonyShip@ ship;
|
|
file >> targ;
|
|
file >> ship;
|
|
|
|
if(targ !is null && ship !is null) {
|
|
curTargets.insertLast(targ);
|
|
curShips.insertLast(ship);
|
|
targetIds.insert(targ.id);
|
|
}
|
|
}
|
|
|
|
file >> cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
Planet@ targ;
|
|
double until = 0.0;
|
|
file >> targ;
|
|
file >> until;
|
|
|
|
if(targ !is null) {
|
|
curPenalties.insertLast(targ);
|
|
penaltiesUntil.insertLast(until);
|
|
penaltyIds.insert(targ.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void send(Empire& emp, Planet@ toPlanet) {
|
|
Object@ best;
|
|
double bestDist = INFINITY;
|
|
for(uint i = 0, cnt = spreading.length; i < cnt; ++i) {
|
|
Object@ obj = spreading[i].obj;
|
|
if(obj is null || !obj.valid)
|
|
continue;
|
|
|
|
double d = getPathDistance(emp, obj.position, toPlanet.position);
|
|
if(d < bestDist) {
|
|
@best = obj;
|
|
bestDist = d;
|
|
}
|
|
}
|
|
|
|
if(best !is null)
|
|
send(best, toPlanet);
|
|
}
|
|
|
|
void send(Object@ obj, Planet@ bestPlanet) {
|
|
auto@ resource = getResource(bestPlanet.primaryResourceType);
|
|
if(resource !is null) {
|
|
for(uint i = 0, cnt = slots.length; i < cnt; ++i) {
|
|
if(slots[i] !is null)
|
|
continue;
|
|
if(checkSlot(i, resource)) {
|
|
@slots[i] = bestPlanet;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto@ colShip = createColonizer(obj, bestPlanet, 1.0, 1.0);
|
|
if(colShip !is null) {
|
|
curTargets.insertLast(bestPlanet);
|
|
curShips.insertLast(colShip);
|
|
targetIds.insert(bestPlanet.id);
|
|
}
|
|
}
|
|
};
|
|
|
|
void save(SaveFile& file) {
|
|
uint cnt = coordination.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << coordination[i];
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
coordination.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file >> coordination[i];
|
|
}
|
|
|
|
array<Coordinate> coordination;
|
|
Coordinate@ getCoordinate(Empire@ emp) {
|
|
if(emp is null)
|
|
return null;
|
|
if(uint(emp.index) >= coordination.length)
|
|
return null;
|
|
return coordination[emp.index];
|
|
}
|
|
|
|
final class SysData {
|
|
Region@ region;
|
|
array<Planet@> planets;
|
|
};
|
|
|
|
final class RefugeeData : Savable {
|
|
Object@ obj;
|
|
|
|
double updateTimer = 0.0;
|
|
bool lastBeacon = false;
|
|
bool registered = false;
|
|
|
|
Planet@ nextTarget;
|
|
|
|
array<SysData@>@ systems;
|
|
array<SysData@>@ updateData;
|
|
bool foundGate = false;
|
|
uint sysUpdate = 0;
|
|
|
|
void save(SaveFile& file) {
|
|
file << obj;
|
|
file << updateTimer;
|
|
file << lastBeacon;
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
file >> obj;
|
|
file >> updateTimer;
|
|
file >> lastBeacon;
|
|
}
|
|
};
|
|
|
|
#section all
|
|
|
|
class RefugeeColonization : GenericEffect {
|
|
Document doc("Automatically sends population to nearby planets with colony/refugee ships.");
|
|
|
|
#section server
|
|
void enable(Object& obj, any@ data) const override {
|
|
RefugeeData rd;
|
|
@rd.obj = obj;
|
|
|
|
data.store(@rd);
|
|
|
|
{
|
|
auto@ coord = getCoordinate(obj.owner);
|
|
Lock lck(coord.mtx);
|
|
coord.spreading.insertLast(rd);
|
|
rd.registered = true;
|
|
}
|
|
}
|
|
|
|
void disable(Object& obj, any@ data) const override {
|
|
RefugeeData@ rd;
|
|
data.retrieve(@rd);
|
|
|
|
if(rd !is null) {
|
|
auto@ coord = getCoordinate(obj.owner);
|
|
Lock lck(coord.mtx);
|
|
coord.spreading.remove(rd);
|
|
rd.registered = false;
|
|
}
|
|
}
|
|
|
|
void ownerChange(Object& obj, any@ data, Empire@ prevOwner, Empire@ newOwner) const {
|
|
RefugeeData@ rd;
|
|
data.retrieve(@rd);
|
|
|
|
if(prevOwner !is null) {
|
|
auto@ coord = getCoordinate(prevOwner);
|
|
Lock lck(coord.mtx);
|
|
coord.spreading.remove(rd);
|
|
}
|
|
if(newOwner !is null) {
|
|
auto@ coord = getCoordinate(newOwner);
|
|
Lock lck(coord.mtx);
|
|
coord.spreading.insertLast(rd);
|
|
rd.registered = true;
|
|
}
|
|
}
|
|
|
|
void tick(Object& obj, any@ data, double time) const override {
|
|
RefugeeData@ rd;
|
|
data.retrieve(@rd);
|
|
|
|
if(rd.nextTarget is null)
|
|
rd.updateTimer -= time * 10.0;
|
|
else
|
|
rd.updateTimer -= time;
|
|
if(!rd.registered) {
|
|
auto@ coord = getCoordinate(obj.owner);
|
|
if(coord !is null) {
|
|
Lock lck(coord.mtx);
|
|
rd.registered = true;
|
|
coord.spreading.insertLast(rd);
|
|
}
|
|
}
|
|
if(rd.updateTimer <= 0.0 && rd.systems !is null) {
|
|
rd.updateTimer += randomd(10.0, 20.0);
|
|
Empire@ emp = obj.owner;
|
|
|
|
auto@ coord = getCoordinate(emp);
|
|
Lock lck(coord.mtx);
|
|
|
|
//Send colonizations
|
|
Region@ reg = obj.region;
|
|
if(reg is null)
|
|
@reg = findNearestRegion(obj.position);
|
|
|
|
Planet@ bestPlanet;
|
|
double bestWeight = 0.0;
|
|
|
|
for(uint i = 0, cnt = rd.systems.length; i < cnt; ++i) {
|
|
auto@ sys = rd.systems[i];
|
|
for(uint n = 0, ncnt = sys.planets.length; n < ncnt; ++n) {
|
|
Planet@ pl = sys.planets[n];
|
|
|
|
double w = coord.getWeight(emp, rd, pl);
|
|
if(w > bestWeight) {
|
|
bestWeight = w;
|
|
@bestPlanet = pl;
|
|
}
|
|
}
|
|
}
|
|
|
|
@rd.nextTarget = bestPlanet;
|
|
}
|
|
|
|
//Update our internal representation of the surrounding area
|
|
if(rd.updateData is null || rd.sysUpdate >= rd.updateData.length) {
|
|
if(rd.updateData !is null)
|
|
@rd.systems = rd.updateData;
|
|
@rd.updateData = array<SysData@>();
|
|
rd.sysUpdate = 0;
|
|
rd.foundGate = false;
|
|
|
|
Region@ reg = obj.region;
|
|
if(reg is null)
|
|
@reg = findNearestRegion(obj.position);
|
|
|
|
set_int visited;
|
|
visitSystem(obj.owner, rd, reg, visited);
|
|
}
|
|
else {
|
|
updateSystem(obj.owner, rd, rd.updateData[rd.sysUpdate]);
|
|
rd.sysUpdate += 1;
|
|
}
|
|
}
|
|
|
|
void updateSystem(Empire& emp, RefugeeData& rd, SysData& sys) const {
|
|
if(sys.region.planetCount == sys.planets.length)
|
|
return;
|
|
|
|
sys.planets.length = 0;
|
|
|
|
auto@ datalist = sys.region.getPlanets();
|
|
Object@ obj;
|
|
while(receive(datalist, obj)) {
|
|
Planet@ pl = cast<Planet>(obj);
|
|
if(pl !is null)
|
|
sys.planets.insertLast(pl);
|
|
}
|
|
}
|
|
|
|
void visitSystem(Empire& emp, RefugeeData& rd, Region& reg, set_int& visited) const {
|
|
visited.insert(reg.id);
|
|
|
|
bool found = false;
|
|
if(rd.systems !is null) {
|
|
for(uint i = 0, cnt = rd.systems.length; i < cnt; ++i) {
|
|
if(rd.systems[i].region is reg) {
|
|
rd.updateData.insertLast(rd.systems[i]);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(!found) {
|
|
SysData sd;
|
|
@sd.region = reg;
|
|
rd.updateData.insertLast(sd);
|
|
}
|
|
|
|
uint curPresent = reg.PlanetsMask;
|
|
bool isBorder = curPresent & ~(emp.mask | emp.ForcedPeaceMask.value) != 0;
|
|
bool isEmpty = curPresent & emp.mask == 0;
|
|
|
|
auto@ system = getSystem(reg);
|
|
for(uint i = 0, cnt = system.spatialAdjacentCount; i < cnt; ++i) {
|
|
auto@ other = getSystem(system.spatialAdjacent[i]);
|
|
if(other is null)
|
|
continue;
|
|
if(other.object.SeenMask & emp.mask == 0)
|
|
continue;
|
|
if(visited.contains(other.object.id))
|
|
continue;
|
|
|
|
uint present = other.object.PlanetsMask;
|
|
if(present & emp.mask == 0) {
|
|
if(present & ~emp.ForcedPeaceMask.value != 0 && isBorder)
|
|
continue;
|
|
if(isEmpty)
|
|
continue;
|
|
}
|
|
|
|
visitSystem(emp, rd, other.object, visited);
|
|
}
|
|
|
|
//Can't do this: beacons themselves are gatemask for trade even though they can't port
|
|
//if(!rd.foundGate && reg.GateMask & emp.mask != 0 && !isEmpty) {
|
|
// rd.foundGate = true;
|
|
// for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
|
|
// auto@ sys = getSystem(i);
|
|
// if(sys.object.GateMask & emp.mask == 0)
|
|
// continue;
|
|
// if(sys.object is reg)
|
|
// continue;
|
|
// if(visited.contains(sys.object.id))
|
|
// continue;
|
|
|
|
// visitSystem(emp, rd, sys.object, visited);
|
|
// }
|
|
//}
|
|
}
|
|
|
|
void save(any@ data, SaveFile& file) const override {
|
|
RefugeeData@ rd;
|
|
data.retrieve(@rd);
|
|
if(data !is null)
|
|
file << rd;
|
|
else
|
|
file << RefugeeData();
|
|
}
|
|
|
|
void load(any@ data, SaveFile& file) const override {
|
|
RefugeeData rd;
|
|
file >> rd;
|
|
data.store(@rd);
|
|
}
|
|
#section all
|
|
};
|
|
|
|
class TriggerRefugees : BonusEffect {
|
|
Document doc("Immediately trigger the next refugee ship to leave to the targeted planet.");
|
|
|
|
#section server
|
|
void activate(Object@ target, Empire@ emp) const override {
|
|
if(emp is null)
|
|
return;
|
|
if(target is null || !target.isPlanet)
|
|
return;
|
|
|
|
auto@ coord = getCoordinate(emp);
|
|
{
|
|
Lock lck(coord.mtx);
|
|
coord.send(emp, cast<Planet>(target));
|
|
}
|
|
}
|
|
#section all
|
|
};
|