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

2417 lines
60 KiB
ActionScript

import systems;
import planet_loyalty;
import region_effects;
import saving;
import region_effects.ZealotRegion;
import notifications;
import statuses;
from resources import getLaborCost;
from cargo import hasDesignCosts;
from settings.game_settings import gameSettings;
from region_effects.GrantVision import GrantVision;
import constructions;
import hooks;
#section server
import object_creation;
import resources;
import civilians;
import achievements;
#section all
const uint ZealotMask = 0;
const double CIV_TIMER = 3.0 * 60.0;
const double TRADE_TIMER = 3.0 * 60.0;
const double STATION_TRADES = 6.0;
const double CIVILIAN_LIMIT_POW = 0.85;
tidy class RegionObjects : Component_RegionObjects, Savable {
SystemDesc@ system;
Star@[] starList;
Object@[] objectList;
Object@[] shipyardList;
Planet@[] planetList;
Orbital@[] orbitalList;
Asteroid@[] asteroidList;
Anomaly@[] anomalyList;
Pickup@[] pickupList;
Artifact@[] artifactList;
Object@[] resourceHolders;
RegionEffect@[] effects;
int nextEffectId = 1;
SystemPlaneNode@ plane;
bool planeParented = false;
TradeLinesNode@ tradeLines;
array<IconRing@> icons;
float combatTimer = 60.0;
double StarTemperature = 0;
double StarRadius = 0;
PlanetBucket planetBucket;
PickupBucket pickupBucket;
AsteroidBucket asteroidBucket;
ArtifactBucket artifactBucket;
AnomalyBucket anomalyBucket;
StarBucket starBucket;
double PeriodicUpdate = 0.0;
double PeriodicTime = 0.0;
double regionDPS = 0.0;
double starDPS = 0.0;
int[] strengths(getEmpireCount(), 0);
int[] planetCounts(getEmpireCount(), 0);
int[] objectCounts(getEmpireCount(), 0);
int[] visionGrants(getEmpireCount(), 0);
int[] tradeGrants(getEmpireCount(), 0);
Empire@[] primaryVision(getEmpireCount(), defaultEmpire);
Empire@ primaryEmpire;
double[] empLoyaltyBonus(getEmpireCount(), 0);
double[] neighbourLoyalty(getEmpireCount(), 0);
double[] localLoyalty(getEmpireCount(), 0);
uint HasMilitaryMask = 0;
uint HasPlanetsMask = 0;
uint visibleContested = CM_None;
uint TradeRequestMask = 0;
array<int> tradeCounter(getEmpireCount(), 0);
double tradeTimer = 0.0;
array<Civilian@> tradeStations;
uint HaveStationsMask = 0;
array<uint> regionStatusTypes;
array<double> regionStatusTimers;
array<Empire@> regionStatusEmps;
array<locked_Territory> territories(getEmpireCount());
RegionObjects() {
}
#section server
void load(SaveFile& msg) {
PeriodicUpdate = randomd();
uint cnt = 0;
msg >> cnt;
starList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> starList[i];
if(starList[i] !is null)
starBucket.add(starList[i]);
}
msg >> cnt;
objectList.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> objectList[i];
msg >> cnt;
planetList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> planetList[i];
if(planetList[i] !is null)
planetBucket.add(planetList[i]);
}
msg >> cnt;
orbitalList.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> orbitalList[i];
msg >> cnt;
asteroidList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> asteroidList[i];
if(asteroidList[i] !is null)
asteroidBucket.add(asteroidList[i]);
}
if(msg >= SV_0039) {
msg >> cnt;
anomalyList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> anomalyList[i];
if(anomalyList[i] !is null)
anomalyBucket.add(anomalyList[i]);
}
}
if(msg >= SV_0163) {
msg >> cnt;
artifactList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> artifactList[i];
if(artifactList[i] !is null)
artifactBucket.add(artifactList[i]);
}
msg >> cnt;
pickupList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> pickupList[i];
if(pickupList[i] !is null)
pickupBucket.add(pickupList[i]);
}
}
msg >> cnt;
shipyardList.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> shipyardList[i];
msg >> cnt;
for(uint i = 0; i < cnt; ++i) {
uint id = msg.readIdentifier(SI_RegionEffect);
const RegionEffectType@ type = getRegionEffect(id);
RegionEffect@ eff = type.create();
msg >> eff.id;
msg >> eff.forEmpire;
eff.load(msg);
effects.insertLast(eff);
}
msg >> cnt;
regionStatusTypes.length = cnt;
regionStatusTimers.length = cnt;
regionStatusEmps.length = cnt;
for(uint i = 0; i < cnt; ++i) {
regionStatusTypes[i] = msg.readIdentifier(SI_Status);
msg >> regionStatusTimers[i];
msg >> regionStatusEmps[i];
}
msg >> nextEffectId;
msg >> primaryEmpire;
uint statCnt = getEmpireCount();
for(uint i = 0; i < statCnt; ++i) {
msg >> strengths[i];
msg >> objectCounts[i];
msg >> primaryVision[i];
msg >> planetCounts[i];
msg >> empLoyaltyBonus[i];
msg >> neighbourLoyalty[i];
msg >> localLoyalty[i];
msg >> visionGrants[i];
if(msg >= SV_0059)
msg >> tradeGrants[i];
territories[i].set(cast<Territory>(msg.readObject()));
if(msg >= SV_0048)
msg >> tradeCounter[i];
}
msg >> HasMilitaryMask;
msg >> HasPlanetsMask;
msg >> visibleContested;
msg >> combatTimer;
if(msg >= SV_0048) {
msg >> TradeRequestMask;
msg >> HaveStationsMask;
msg >> tradeTimer;
uint tCount = 0;
msg >> tCount;
tradeStations.length = tCount;
for(uint i = 0; i < tCount; ++i)
@tradeStations[i] = cast<Civilian>(msg.readObject());
}
if(msg >= SV_0103) {
msg >> regionDPS;
if(msg >= SV_0123)
msg >> starDPS;
}
}
void save(SaveFile& msg) {
uint cnt = starList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << starList[i];
cnt = objectList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << objectList[i];
cnt = planetList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << planetList[i];
cnt = orbitalList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << orbitalList[i];
cnt = asteroidList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << asteroidList[i];
cnt = anomalyList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << anomalyList[i];
cnt = artifactList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << artifactList[i];
cnt = pickupList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << pickupList[i];
cnt = shipyardList.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << shipyardList[i];
cnt = effects.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i) {
msg.writeIdentifier(SI_RegionEffect, effects[i].type.id);
msg << effects[i].id;
msg << effects[i].forEmpire;
effects[i].save(msg);
}
cnt = regionStatusTypes.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i) {
msg << regionStatusTypes[i];
msg << regionStatusTimers[i];
msg << regionStatusEmps[i];
}
msg << nextEffectId;
msg << primaryEmpire;
uint statCnt = getEmpireCount();
for(uint i = 0; i < statCnt; ++i) {
msg << strengths[i];
msg << objectCounts[i];
msg << primaryVision[i];
msg << planetCounts[i];
msg << empLoyaltyBonus[i];
msg << neighbourLoyalty[i];
msg << localLoyalty[i];
msg << visionGrants[i];
msg << tradeGrants[i];
msg << territories[i].get();
msg << tradeCounter[i];
}
msg << HasMilitaryMask;
msg << HasPlanetsMask;
msg << visibleContested;
msg << combatTimer;
msg << TradeRequestMask;
msg << HaveStationsMask;
msg << tradeTimer;
uint tCount = tradeStations.length;
msg << tCount;
for(uint i = 0; i < tCount; ++i)
msg << tradeStations[i];
msg << regionDPS;
msg << starDPS;
}
void regionPostLoad(Object& obj) {
Region@ region = cast<Region>(obj);
@system = getSystem(region);
@plane = SystemPlaneNode();
plane.establish(region);
plane.setPrimaryEmpire(primaryEmpire);
plane.setContested(visibleContested);
plane.hintParentObject(obj, false);
StarTemperature = 0;
for(uint i = 0, cnt = starList.length; i < cnt; ++i)
StarTemperature += starList[i].temperature;
if(starList.length != 0)
StarRadius = starList[0].radius;
else
StarRadius = 0;
}
#section all
void addShipDebris(vec3d position, uint count = 1) {
if(plane !is null)
plane.addMetalDebris(position, count);
}
#section shadow
void save(SaveFile&){}
void load(SaveFile&){}
#section all
void registerShipyard(Object& region, Object& obj) {
shipyardList.insertLast(obj);
calculateShipyards(region);
}
void unregisterShipyard(Object& region, Object& obj) {
shipyardList.remove(obj);
calculateShipyards(region);
}
void regionBuildSupport(Object& obj, uint id, Object& forLeader, const Design@ design) {
if(shipyardList.length == 0)
return;
bool hasCosts = hasDesignCosts(design);
double laborCost = getLaborCost(design);
Object@ secondary;
bool havePrimary = false;
for(uint i = 0, cnt = shipyardList.length; i < cnt; ++i) {
Object@ yard = shipyardList[i];
if(yard.owner !is forLeader.owner)
continue;
if(!yard.canBuildSupports)
continue;
if(hasCosts && (!yard.hasCargo || yard.cargoTypes == 0))
continue;
double laborIncome = yard.laborIncome;
bool efficient = laborIncome > 0 && (laborCost / laborIncome) < 3.0 * 60.0;
if(yard.constructingSupport) {
if(efficient)
havePrimary = true;
continue;
}
if(!efficient) {
if(secondary is null)
@secondary = yard;
continue;
}
yard.buildSupport(id, design, forLeader);
return;
}
if(secondary !is null && !havePrimary) {
secondary.buildSupport(id, design, forLeader);
return;
}
}
void requestConstructionOn(Object& onObj, uint constrId) {
auto@ constr = getConstructionType(constrId);
if(constr is null)
return;
if(shipyardList.length == 0)
return;
Targets targs(constr.targets);
if(targs.length != 0) {
targs[0].filled = true;
@targs[0].obj = onObj;
}
bool havePrimary = false;
Object@ primary;
Object@ secondary;
for(uint i = 0, cnt = shipyardList.length; i < cnt; ++i) {
auto@ yard = shipyardList[i];
if(yard is null || !constr.canBuild(yard, targs))
continue;
if(yard.owner !is onObj.owner)
continue;
double laborCost = constr.getLaborCost(yard, targs);
double laborIncome = yard.laborIncome;
bool efficient = laborIncome > 0 && (laborCost / laborIncome) < 3.0 * 60.0;
if(efficient) {
if(yard.constructionCount > 0) {
havePrimary = true;
continue;
}
@primary = yard;
}
else
@secondary = yard;
}
if(primary !is null)
primary.buildConstruction(constrId, objTarg=onObj);
else if(secondary !is null && !havePrimary)
secondary.buildConstruction(constrId, objTarg=onObj);
}
void initRegion(Object& obj) {
//Randomize vision check moment
PeriodicTime = PeriodicUpdate = double(uint8(obj.id)) / 255.0;
Region@ region = cast<Region>(obj);
region.AngleOffset = randomd(0, twopi);
//Create the plane
@plane = SystemPlaneNode();
plane.establish(region);
plane.hintParentObject(obj, false);
//Find system
@system = getSystem(cast<Region>(region));
}
double longTimer = 0.0;
void tickRegion(Object& region, double time) {
if(system is null) {
@system = getSystem(cast<Region>(region));
return;
}
if(plane !is null && !planeParented) {
planeParented = true;
plane.hintParentObject(region, false);
}
//Update contested flags
updateContested(region);
//Check vision periodically
if(PeriodicUpdate <= 0.0) {
//Update total interior vision
calculateVision(region);
#section server
//Update neighbouring loyalty
updateEmpLoyalty(region);
//Update effects of zealotry
updateZealotry(region);
//Update status effects
updateStatuses(region, PeriodicTime + (-PeriodicUpdate));
//Update civilian trade
updateCivilianTrade(region);
//Area damage
if(regionDPS > 0.0)
processDamage(region, PeriodicTime + (-PeriodicUpdate));
if(starDPS > 0.0) {
double time = PeriodicTime + (-PeriodicUpdate);
for(uint i = 0, cnt = starList.length; i < cnt; ++i)
starList[i].dealStarDamage(time * starDPS);
starDPS *= pow(0.75, time);
starDPS -= time;
}
#section all
PeriodicTime = PeriodicUpdate = randomd(0.9,1.1);
}
else {
PeriodicUpdate -= time;
}
longTimer -= time;
if(longTimer <= 0.0) {
Region@ reg = cast<Region>(region);
reg.SiegedMask = 0;
reg.SiegingMask = 0;
longTimer = 15.0;
}
#section server
//Update region effects
Region@ reg = cast<Region>(region);
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
if(!effects[i].tick(reg, time)) {
effects[i].end(reg);
effects.removeAt(i);
--i;
--cnt;
}
}
//Update the combat state
updateCombatState(reg, time);
#section shadow
if(prevMask != playerEmpire.visionMask) {
updatePlane(cast<Region>(region));
prevMask = playerEmpire.visionMask;
}
#section all
}
double getNeighbourLoyalty(Empire@ emp) {
if(emp is null || !emp.valid)
return 0;
return neighbourLoyalty[emp.index];
}
void modNeighbourLoyalty(Object& region, Empire@ emp, double mod) {
if(emp is null || !emp.valid)
return;
neighbourLoyalty[emp.index] += mod;
}
double getLocalLoyalty(Empire@ emp) {
if(emp is null || !emp.valid)
return 0;
return localLoyalty[emp.index];
}
void modLocalLoyalty(Object& region, Empire@ emp, double mod) {
if(emp is null || !emp.valid)
return;
localLoyalty[emp.index] += mod;
}
void updateStatuses(Object& obj, double time) {
for(int i = regionStatusTypes.length - 1; i >= 0; --i) {
if(regionStatusTimers[i] >= 0) {
regionStatusTimers[i] -= time;
if(regionStatusTimers[i] <= 0) {
regionStatusTypes.removeAt(i);
regionStatusTimers.removeAt(i);
regionStatusEmps.removeAt(i);
}
}
}
}
void applyStatuses(Region& reg, Object& obj, bool isOwnerChange = false) {
for(uint i = 0, cnt = regionStatusTypes.length; i < cnt; ++i) {
auto@ status = getStatusType(regionStatusTypes[i]);
Empire@ emp = regionStatusEmps[i];
if((emp is null && !isOwnerChange) || obj.owner is emp) {
if(status.shouldApply(emp, reg, obj))
obj.addStatus(status.id, regionStatusTimers[i], boundEmpire=emp, boundRegion=reg);
}
}
}
void addRegionStatus(Object& obj, Empire@ emp, uint statusId, double timer = -1.0) {
auto@ status = getStatusType(statusId);
if(status is null)
return;
Region@ reg = cast<Region>(obj);
regionStatusTypes.insertLast(statusId);
regionStatusTimers.insertLast(timer);
regionStatusEmps.insertLast(emp);
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
auto@ obj = objectList[i];
if(emp is null || obj.owner is emp) {
if(status.shouldApply(emp, reg, obj))
obj.addStatus(statusId, timer, boundEmpire=emp, boundRegion=reg);
}
}
}
void removeRegionStatus(Object& obj, Empire@ emp, uint statusId) {
auto@ status = getStatusType(statusId);
if(status is null)
return;
Region@ reg = cast<Region>(obj);
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
auto@ obj = objectList[i];
if(emp is null || obj.owner is emp) {
if(status.shouldApply(emp, reg, obj))
obj.removeRegionBoundStatus(reg, statusId);
}
}
for(int i = regionStatusTypes.length - 1; i >= 0; --i) {
if(regionStatusTypes[i] == statusId && regionStatusEmps[i] is emp
&& regionStatusTimers[i] == -1.0) {
regionStatusTypes.removeAt(i);
regionStatusTimers.removeAt(i);
regionStatusEmps.removeAt(i);
break;
}
}
}
void mirrorRegionStatusTo(Region& otherRegion) {
for(uint i = 0, cnt = regionStatusTypes.length; i < cnt; ++i)
otherRegion.addRegionStatus(regionStatusEmps[i], regionStatusTypes[i], regionStatusTimers[i]);
}
void addSystemDPS(double amount) {
regionDPS += amount;
}
void addStarDPS(double amount) {
starDPS += amount;
}
void dealStarDamage(double amount) {
for(uint i = 0, cnt = starList.length; i < cnt; ++i)
starList[i].dealStarDamage(amount);
}
void processDamage(Object& reg, double time) {
DamageEvent dmg;
if(regionDPS <= 0.0)
return;
double baseDamage = regionDPS * time / 100.0;
for(uint i = 0, cnt = time * 15.0; i < cnt; ++i) {
auto@ obj = trace(line3dd(reg.position, reg.position + random3d(reg.radius * 1.5)));
if(obj is null)
continue;
double deal = randomd(0.5,1.5) * baseDamage;
if(obj.isStar) {
cast<Star>(obj).dealStarDamage(deal);
}
else if(obj.isPlanet) {
cast<Planet>(obj).dealPlanetDamage(deal);
}
else {
@dmg.obj = obj;
@dmg.target = obj;
dmg.spillable = false;
dmg.damage = deal;
vec3d off = obj.position - reg.position;
vec2d dir = vec2d(off.x, off.z).normalized();
obj.damage(dmg, -1.0, dir);
}
}
uint objCount = objectList.length;
if(objCount > 0) {
uint traces = max(min(uint(time * 85.0), objCount * 10), 1);
for(uint i = 0; i < traces; ++i) {
auto@ obj = objectList[randomi(0,objCount-1)];
if(obj is null)
continue;
line3dd line = line3dd(reg.position, obj.position + random3d(5.0));
line.end = line.start + line.direction * (reg.radius * 1.5);
@obj = trace(line);
if(obj is null)
continue;
double deal = randomd(0.5,1.5) * baseDamage;
if(obj.isStar) {
cast<Star>(obj).dealStarDamage(deal);
}
else if(obj.isPlanet) {
cast<Planet>(obj).dealPlanetDamage(deal);
}
else {
@dmg.obj = obj;
@dmg.target = obj;
dmg.spillable = false;
dmg.damage = deal;
vec3d off = obj.position - reg.position;
vec2d dir = vec2d(off.x, off.z).normalized();
obj.damage(dmg, -1.0, dir);
}
}
}
regionDPS *= pow(0.95, time);
regionDPS -= time;
}
#section server
void grantExperience(Empire@ toEmpire, double amount, bool combatOnly = false) {
array<Object@> objects;
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
Object@ obj = objectList[i];
if(!obj.hasLeaderAI || !obj.isShip)
continue;
if(obj.owner !is toEmpire)
continue;
if(combatOnly && !obj.inCombat)
continue;
if(cast<Ship>(obj).getFleetStrength() < 1000.0)
continue;
objects.insertLast(obj);
}
if(objects.length != 0) {
amount /= double(objects.length);
for(uint i = 0, cnt = objects.length; i < cnt; ++i)
objects[i].addExperience(amount);
}
}
void renameSystem(Object& obj, string name) {
string oldname = obj.name;
obj.name = name;
obj.named = true;
objectRenamed(ALL_PLAYERS, obj, name);
for(uint i = 0, cnt = starList.length; i < cnt; ++i) {
Star@ star = starList[i];
if(!star.named) {
star.name = star.name.replaced(oldname, name);
star.named = true;
objectRenamed(ALL_PLAYERS, star, star.name);
}
}
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
Planet@ pl = planetList[i];
if(!pl.named) {
pl.name = pl.name.replaced(oldname, name);
objectRenamed(ALL_PLAYERS, pl, pl.name, setNamed=false);
}
}
}
#section all
void updateEmpLoyalty(Object& region) {
uint empCnt = empLoyaltyBonus.length;
//Calculate new loyalty bonuses
for(uint i = 0, cnt = empCnt; i < cnt; ++i)
empLoyaltyBonus[i] = neighbourLoyalty[i] + localLoyalty[i];
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
SystemDesc@ desc = getSystem(system.adjacent[i]);
Region@ other = desc.object;
for(uint n = 0; n < empCnt; ++n)
empLoyaltyBonus[n] += other.getNeighbourLoyalty(getEmpire(n));
}
//Update planet loyalty bonuses
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
Planet@ pl = planetList[i];
Empire@ owner = pl.owner;
if(owner !is null && owner.valid)
pl.setLoyaltyBonus(empLoyaltyBonus[owner.index]);
}
}
uint getContestedState(const Object& region, Empire@ forEmpire) {
if(forEmpire is null || !forEmpire.valid)
return CM_None;
const Region@ reg = cast<const Region>(region);
if(reg.ContestedMask & forEmpire.mask == 0)
return CM_None;
bool usProtected = reg.ProtectedMask & forEmpire.mask != 0;
bool usZealot = ZealotMask & forEmpire.mask != 0;
if(reg.SiegedMask & forEmpire.mask != 0) {
if(usZealot)
return CM_Zealot;
return CM_LosingLoyalty;
}
bool enemyProtected = false, enemyZealot = false;
for(uint i = 0, empCnt = getEmpireCount(); i < empCnt; ++i) {
Empire@ other = getEmpire(i);
if(!other.major)
continue;
if(!other.isHostile(forEmpire))
continue;
if(planetCounts[i] == 0)
continue;
if(reg.SiegingMask & forEmpire.mask != 0) {
bool zeal = ZealotMask & other.mask != 0;
if(zeal)
enemyZealot = true;
}
bool prot = reg.ProtectedMask & other.mask != 0;
if(prot)
enemyProtected = true;
}
if(usProtected || enemyProtected)
return CM_Protected;
if(enemyZealot)
return CM_Zealot;
if(reg.SiegingMask & forEmpire.mask != 0)
return CM_GainingLoyalty;
return CM_Contested;
}
void updateCombatState(Region& region, double time) {
region.CombatMask |= region.EngagedMask;
combatTimer -= time;
if(combatTimer <= 0) {
region.CombatMask = region.EngagedMask;
region.EngagedMask = 0;
combatTimer += 20.f;
}
}
void modMilitaryStrength(Empire@ emp, int amt) {
if(emp is null || !emp.valid)
return;
strengths[emp.index] += amt;
}
void updateContested(Object& region) {
int newMilitaryMask = 0;
int newPlanetsMask = 0;
for(uint i = 0, cnt = strengths.length; i < cnt; ++i) {
Empire@ other = getEmpire(i);
if(!other.major)
continue;
if(strengths[i] > 0)
newMilitaryMask |= other.mask;
if(planetCounts[i] > 0)
newPlanetsMask |= other.mask;
}
HasMilitaryMask = newMilitaryMask;
HasPlanetsMask = newPlanetsMask;
uint CMask = 0, OMask = 0;
uint empCnt = getEmpireCount();
Region@ reg = cast<Region>(region);
reg.PlanetsMask = HasPlanetsMask;
for(uint i = 0; i < empCnt; ++i) {
// Contested if this empire has planets or military,
// and an empire hostile to it has military.
Empire@ other = getEmpire(i);
int hostile = other.hostileMask;
bool contested = (planetCounts[i] > 0 || strengths[i] > 0)
&& (hostile & HasMilitaryMask != 0 || reg.SiegedMask & other.mask != 0);
if(contested && reg.CombatMask & other.mask != 0 && reg.CombatMask & hostile != 0) {
int totEnemy = 0;
for(uint n = 0; n < empCnt; ++n) {
uint mask = getEmpire(n).mask;
if(hostile & mask != 0 && reg.CombatMask & mask != 0)
totEnemy += strengths[n];
}
if(totEnemy > 20 || reg.SiegedMask & other.mask != 0) {
CMask |= other.mask;
if(isServer && reg.ContestedMask & other.mask == 0 && planetCounts[i] > 0)
other.notifyWarEvent(region, WET_ContestedSystem);
}
}
//Check if this is a core system
if(reg.TradeMask & other.mask != 0) {
bool isCore = true;
for(uint j = 0, jcnt = system.adjacent.length; j < jcnt; ++j) {
SystemDesc@ desc = getSystem(system.adjacent[j]);
if(desc.object.TradeMask & other.mask == 0) {
isCore = false;
break;
}
}
if(isCore)
OMask |= other.mask;
}
}
reg.ContestedMask = CMask;
reg.CoreSystemMask = OMask;
//Update whether the node is set as contested
if(plane !is null) {
uint newContested = getContestedState(region, playerEmpire);
if(visibleContested != newContested) {
plane.setContested(newContested);
visibleContested = newContested;
}
}
}
void updateZealotry(Object& obj) {
//Update zealotry for all empires
Region@ region = cast<Region>(obj);
uint isZealotMask = 0;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(ZealotMask & emp.mask == 0)
continue;
if(planetCounts[i] > 0 && region.SiegedMask & emp.mask != 0) {
isZealotMask |= emp.mask;
//Check which zealot effects we need to add
uint haveEffectMask = 0, needEffectMask = 0;
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
ZealotRegionEffect@ eff = cast<ZealotRegionEffect>(effects[i]);
if(eff !is null) {
if(eff.forEmpire is emp)
haveEffectMask |= eff.other.mask;
}
}
//Check which effects we need
for(uint j = 0; j < cnt; ++j) {
Empire@ other = getEmpire(j);
if(HasMilitaryMask & other.mask == 0)
continue;
if(!other.isHostile(emp))
continue;
//TODO: This is not 100% accurate. Fix zealot stuff to be fully working.
if(region.SiegingMask & other.mask != 0) {
needEffectMask |= other.mask;
if(haveEffectMask & other.mask == 0)
addRegionEffect(region, createZealotRegionEffect(emp, other));
}
}
//Check which effects to remove
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
ZealotRegionEffect@ eff = cast<ZealotRegionEffect>(effects[i]);
if(eff !is null && eff.forEmpire is emp) {
if(needEffectMask & eff.other.mask == 0) {
removeRegionEffectByIndex(region, i);
--i;
--cnt;
}
}
}
}
}
//Remove all zealotry-related effects
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
ZealotRegionEffect@ eff = cast<ZealotRegionEffect>(effects[i]);
if(eff !is null) {
if(isZealotMask & eff.forEmpire.mask == 0) {
removeRegionEffectByIndex(region, i);
--i;
--cnt;
}
}
}
}
uint get_planetCount() const {
return planetBucket.length;
}
Planet@ get_planets(uint index) const {
if(index >= planetBucket.length)
return null;
return planetBucket[index];
}
uint get_anomalyCount() const {
return anomalyBucket.length;
}
Anomaly@ get_anomalies(uint index) const {
if(index >= anomalyBucket.length)
return null;
return anomalyBucket[index];
}
uint get_asteroidCount() const {
return asteroidBucket.length;
}
Asteroid@ get_asteroids(uint index) const {
if(index >= asteroidBucket.length)
return null;
return asteroidBucket[index];
}
void castOnRandomAsteroid(Object@ obj, int ablId) {
if(obj is null)
return;
if(asteroidList.length == 0)
return;
obj.activateAbility(ablId, asteroidList[randomi(0, asteroidList.length-1)]);
}
uint getPlanetCount(Empire@ emp) const {
if(emp is null || !emp.valid)
return 0;
return planetCounts[emp.index];
}
#section server
void addStatusRandomPlanet(int statusType, double duration, uint mask) {
Planet@ pl;
double tot = 0;
for(uint i = 0, plCnt = planetList.length; i < plCnt; ++i) {
Planet@ check = planetList[i];
Empire@ owner = check.owner;
if(owner is null || !owner.valid)
continue;
if(owner.mask & mask == 0)
continue;
tot += 1.0;
if(randomd() <= 1.0 / tot)
@pl = check;
}
if(pl !is null)
pl.addStatus(statusType, duration);
}
void spawnSupportAtRandomPlanet(Empire@ owner, const Design@ design, bool free = true, Planet@ fallback = null) {
if(owner is null || design is null || !owner.valid)
return;
uint plCnt = planetCounts[owner.index];
if(plCnt == 0)
return;
uint index = randomi(0, plCnt-1);
Object@ pl;
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
Planet@ obj = planetList[i];
if(obj.owner is owner && obj.canGainSupports) {
if(index == 0) {
@pl = obj;
break;
}
--index;
}
}
if(pl is null)
@pl = fallback;
if(pl !is null) {
design.decBuilt(); //automatic built doesn't increment
createShip(pl, design, owner, pl, false, free);
}
}
void refreshSupportsFor(Object& dest, bool keepGhosts) {
auto@ owner = dest.owner;
if(!owner.valid)
return;
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
auto@ pl = planetList[i];
if(pl.owner is owner && pl.SupplyUsed > 0 && pl.allowFillFrom)
dest.refreshSupportsFrom(pl, keepGhosts);
}
for(uint i = 0, cnt = orbitalList.length; i < cnt; ++i) {
auto@ obj = orbitalList[i];
if(obj.owner is owner && obj.SupplyUsed > 0 && obj.allowFillFrom)
dest.refreshSupportsFrom(obj, keepGhosts);
}
}
void convertRandomSupport(Object@ toLeader, Empire@ toEmpire, uint mask, int maxSize) {
Object@ found;
double foundCount = 0;
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
Object@ obj = objectList[i];
if(obj is null || !obj.hasLeaderAI)
continue;
if(obj.owner.mask & mask == 0)
continue;
uint supCnt = obj.supportCount;
if(supCnt == 0)
continue;
foundCount += supCnt;
if(randomd() < double(supCnt) / foundCount)
@found = obj;
}
if(found !is null)
found.convertRandomSupport(toLeader, toEmpire, maxSize);
}
bool hasTradeStation(Empire@ owner) {
return HaveStationsMask & owner.mask != 0;
}
bool hasTradeStations() {
return HaveStationsMask != 0;
}
void getTradeStation(Civilian@ request, Empire@ owner, vec3d position) {
if(HaveStationsMask & owner.mask == 0) {
request.gotoTradeStation(null);
return;
}
Civilian@ best;
double bestDist = INFINITY;
for(uint i = 0, cnt = tradeStations.length; i < cnt; ++i) {
auto@ station = tradeStations[i];
if(station.owner !is owner)
continue;
double d = position.distanceToSQ(station.position);
if(d < bestDist) {
bestDist = d;
@best = station;
}
}
request.gotoTradeStation(best);
}
void getTradePlanet(Civilian@ request, Empire@ owner) {
uint index = randomi(0, planetCounts[owner.index]-1);
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
if(planetList[i].owner is owner) {
if(index == 0) {
request.gotoTradePlanet(planetList[i]);
return;
}
--index;
}
}
request.gotoTradePlanet(null);
}
void bumpTradeCounter(Empire@ emp) {
tradeCounter[emp.index] += 1;
}
void updateCivilianTrade(Object& region) {
//Make sure each planet has a civilian ship assigned
if(config::ENABLE_CIVILIAN_TRADE == 0.0 || config::CIVILIAN_TRADE_MULT == 0.0)
return;
uint needRequests = 0;
int stationLevels = 0;
for(uint i = 0; i < planetList.length; ++i) {
Planet@ pl = planetList[i];
Empire@ owner = pl.owner;
if(owner is null || !owner.valid)
continue;
Civilian@ civ = pl.getAssignedCivilian();
if(civ !is null)
continue;
double timer = pl.getCivilianTimer();
if(timer < CIV_TIMER / config::CIVILIAN_TRADE_MULT)
continue;
//Calculate maximum amount of civilians
int plCount = owner.TotalPlanets.value;
int civLimit = 0;
int plLimit = 100 * config::CIVILIAN_TRADE_MULT;
if(plCount > plLimit)
civLimit = plLimit + pow(double(plCount - plLimit), CIVILIAN_LIMIT_POW);
else
civLimit = plCount;
civLimit = double(civLimit) * config::CIVILIAN_TRADE_MULT;
if(owner.CivilianTradeShips.value < civLimit) {
Object@ destination = pl.getNativeResourceDestination(owner, 0);
if(destination is null)
continue;
@civ = createCivilian(pl.position, owner, type=CiT_Freighter,
radius = randomCivilianFreighterSize());
if(civ.radius >= CIV_SIZE_CARAVAN)
civ.modIncome(+CIV_CARAVAN_INCOME);
civ.pathTo(destination);
civ.setOrigin(pl);
civ.setCargoResource(pl.primaryResourceType);
pl.setAssignedCivilian(civ);
pl.setCivilianTimer(0.0);
}
else {
needRequests |= owner.mask;
}
}
if(needRequests != TradeRequestMask) {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(needRequests & emp.mask != 0) {
if(TradeRequestMask & emp.mask == 0)
emp.requestTradeCivilian(cast<Region>(region));
}
else {
if(TradeRequestMask & emp.mask != 0)
emp.stopRequestTradeCivilian(cast<Region>(region));
}
}
TradeRequestMask = needRequests;
}
if(gameTime >= tradeTimer) {
tradeTimer = gameTime + TRADE_TIMER;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major)
continue;
uint buildStations = min(planetCounts[i], uint(double(tradeCounter[i]) / STATION_TRADES));
uint maxStations = max(buildStations, uint(double(tradeCounter[i] * 2) / STATION_TRADES));
uint stationCount = 0;
for(uint i = 0, cnt = tradeStations.length; i < cnt; ++i) {
Empire@ owner = tradeStations[i].owner;
if(!tradeStations[i].valid) {
tradeStations.removeAt(i);
--i; --cnt;
}
else if(owner is emp) {
stationCount += 1;
}
}
if(stationCount > maxStations) {
for(uint i = 0, cnt = tradeStations.length; i < cnt && stationCount > maxStations; ++i) {
if(tradeStations[i].owner is emp) {
tradeStations[i].destroy();
stationCount -= 1;
}
}
}
else if(stationCount < buildStations) {
for(; stationCount < buildStations; ++stationCount) {
vec3d pos = system.position;
vec2d offset = random2d(system.radius * 0.5, system.radius * 0.8);
pos.x += offset.x;
pos.z += offset.y;
Civilian@ civ = createCivilian(pos, emp, CiT_Station,
radius=randomd(STATION_MIN_RAD, STATION_MAX_RAD));
civ.modIncome(+CIV_STATION_INCOME);
civ.setCargoType(CT_Goods);
tradeStations.insertLast(civ);
}
}
if(stationCount == 0)
HaveStationsMask &= ~emp.mask;
else
HaveStationsMask |= emp.mask;
tradeCounter[i] = 0;
}
}
}
void freeUpCivilian(Object& region, Civilian@ civ) {
//Check which planet here needs a trader the most
Planet@ bestPlanet;
Object@ bestDest;
Empire@ civOwner = civ.owner;
double bestTimer = 0.0;
for(uint i = 0; i < planetList.length; ++i) {
Planet@ pl = planetList[i];
Empire@ owner = pl.owner;
if(owner !is civOwner)
continue;
Civilian@ civ = pl.getAssignedCivilian();
if(civ !is null)
continue;
Object@ destination = pl.getNativeResourceDestination(owner, 0);
if(destination is null)
continue;
double timer = pl.getCivilianTimer();
if(timer > bestTimer) {
@bestPlanet = pl;
@bestDest = destination;
bestTimer = timer;
}
}
if(bestPlanet !is null && bestTimer > randomd(0.0, CIV_TIMER / config::CIVILIAN_TRADE_MULT)) {
//Reroute the trader
civ.pathTo(bestPlanet, bestDest);
civ.setCargoResource(bestPlanet.primaryResourceType);
civ.resetStepCount();
bestPlanet.setAssignedCivilian(civ);
bestPlanet.setCivilianTimer(0.0);
}
else {
//Send the trader to a random adjacent system
SystemDesc@ nextSys;
int stepCount = civ.getStepCount();
if(randomd() > pow(0.9, stepCount)) {
Region@ reqRegion = civOwner.getTradeCivilianRequest(civ.position);
if(reqRegion !is null)
@nextSys = getSystem(reqRegion);
civ.resetStepCount();
if(nextSys is null && bestPlanet !is null) {
civ.pathTo(bestPlanet, bestDest);
civ.setCargoResource(bestPlanet.primaryResourceType);
bestPlanet.setAssignedCivilian(civ);
bestPlanet.setCivilianTimer(0.0);
return;
}
}
if(nextSys is null) {
uint sysCount = 0;
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
SystemDesc@ other = getSystem(system.adjacent[i]);
if(other.object.getPlanetCount(civOwner) != 0)
sysCount += 1;
}
for(uint i = 0, cnt = system.wormholes.length; i < cnt; ++i) {
SystemDesc@ other = getSystem(system.wormholes[i]);
if(other.object.getPlanetCount(civOwner) != 0)
sysCount += 1;
}
if(sysCount != 0) {
uint index = randomi(0, sysCount-1);
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
SystemDesc@ other = getSystem(system.adjacent[i]);
if(other.object.getPlanetCount(civOwner) != 0) {
if(index == 0) {
@nextSys = other;
break;
}
else {
--index;
}
}
}
if(nextSys is null) {
for(uint i = 0, cnt = system.wormholes.length; i < cnt; ++i) {
SystemDesc@ other = getSystem(system.wormholes[i]);
if(other.object.getPlanetCount(civOwner) != 0) {
if(index == 0) {
@nextSys = other;
break;
}
else {
--index;
}
}
}
}
}
}
if(nextSys !is null) {
civ.setCargoType(CT_Goods);
civ.pathTo(nextSys.object);
civ.modStepCount(+1);
}
else {
if(bestPlanet is null) {
civ.destroy();
}
else {
//TODO: Remember the civilian for later
civ.destroy();
}
}
}
}
#section all
uint get_objectCount() const {
return objectList.length;
}
Object@ get_objects(uint index) const {
if(index >= objectList.length)
return null;
return objectList[index];
}
Object@ getOrbitObject(vec3d point) const {
uint cnt = planetList.length;
for(uint i = 0; i < cnt; ++i) {
Planet@ pl = planetList[i];
double d = point.distanceToSQ(pl.position);
if(d < pl.OrbitSize * pl.OrbitSize)
return pl;
}
return null;
}
uint get_starCount() const {
return starBucket.length;
}
Star@ get_stars(uint index) const {
if(index >= starBucket.length)
return null;
return starBucket[index];
}
double get_starTemperature() const {
return StarTemperature;
}
double get_starRadius() const {
return StarRadius;
}
void getPlanets() {
for(uint i = 0, cnt = planetBucket.length; i < cnt; ++i)
yield(planetBucket[i]);
}
void getPickups() {
for(uint i = 0, cnt = pickupBucket.length; i < cnt; ++i)
yield(pickupBucket[i]);
}
void getAsteroids() {
for(uint i = 0, cnt = asteroidBucket.length; i < cnt; ++i)
yield(asteroidBucket[i]);
}
void getAnomalies() {
for(uint i = 0, cnt = anomalyBucket.length; i < cnt; ++i)
yield(anomalyBucket[i]);
}
void getArtifacts() {
for(uint i = 0, cnt = artifactBucket.length; i < cnt; ++i)
yield(artifactBucket[i]);
}
int getStrength(Empire@ emp) const {
if(emp is null || !emp.valid)
return 0;
return strengths[emp.index];
}
Territory@ getTerritory(Empire@ emp) const {
if(emp is null || !emp.valid)
return null;
return territories[emp.index].get();
}
bool sharesTerritory(Empire& emp, Region& other) const {
Territory@ my = getTerritory(emp);
Territory@ their = other.getTerritory(emp);
return my !is null && my is their;
}
bool isTradableRegion(const Object& obj, Empire& emp) const {
const Region@ reg = cast<const Region>(obj);
if(reg.TradeMask & emp.TradeMask.value != 0)
return true;
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
SystemDesc@ desc = getSystem(system.adjacent[i]);
if(desc.object.TradeMask & emp.TradeMask.value != 0)
return true;
}
return false;
}
double getTotalFleetStrength(uint empireMask, bool fleets = true, bool stations = true, bool planets = true) const {
double str = 0.0;
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
Object@ obj = objectList[i];
Empire@ owner = obj.owner;
if(owner is null || owner.mask & empireMask == 0)
continue;
if(!obj.hasLeaderAI)
continue;
if(!fleets && obj.isShip && !cast<Ship>(obj).isStation)
continue;
if(!stations && obj.isShip && cast<Ship>(obj).isStation)
continue;
if(!stations && obj.isOrbital)
continue;
if(!planets && obj.isPlanet)
continue;
str += sqrt(obj.getFleetStrength());
}
return str * str;
}
Object@ findEnemy(Empire@ emp, uint empireMask, bool fleets = true, bool stations = true, bool planets = false) const {
uint offset = randomi(0, objectList.length-1);
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
Object@ obj = objectList[(i+offset)%cnt];
Empire@ owner = obj.owner;
if(owner is null || owner.mask & empireMask == 0)
continue;
uint type = obj.type;
if(emp !is null && !obj.isVisibleTo(emp))
continue;
switch(type) {
case OT_Ship:
if(!obj.hasLeaderAI)
continue;
if(cast<Ship>(obj).isStation) {
if(!stations)
continue;
}
else {
if(!fleets)
continue;
}
if(obj.getFleetStrength() < 100.0)
continue;
break;
case OT_Orbital:
if(!stations)
continue;
break;
case OT_Planet:
if(!planets)
continue;
break;
default:
continue;
}
return obj;
}
return null;
}
void getEnemies(Empire@ emp, uint empireMask, bool fleets = true, bool stations = true, bool planets = false) const {
uint offset = randomi(0, objectList.length-1);
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
Object@ obj = objectList[(i+offset)%cnt];
Empire@ owner = obj.owner;
if(owner is null || owner.mask & empireMask == 0)
continue;
uint type = obj.type;
if(emp !is null && !obj.isVisibleTo(emp))
continue;
switch(type) {
case OT_Ship:
if(!obj.hasLeaderAI)
continue;
if(cast<Ship>(obj).isStation) {
if(!stations)
continue;
}
else {
if(!fleets)
continue;
}
if(obj.getFleetStrength() < 100.0)
continue;
break;
case OT_Orbital:
if(!stations)
continue;
break;
case OT_Planet:
if(!planets)
continue;
break;
default:
continue;
}
yield(obj);
}
}
void destroyOwnedBy(uint mask, bool ships, bool planets) {
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
Object@ obj = objectList[i];
Empire@ owner = obj.owner;
if(owner is null || owner.mask & mask == 0)
continue;
if(obj.isShip && ships)
obj.destroy();
else if(obj.isPlanet && planets)
obj.destroy();
}
}
void setTerritory(Empire@ emp, Territory@ terr) {
if(emp is null || !emp.valid)
return;
Territory@ prev = territories[emp.index].get();
territories[emp.index].set(terr);
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
Planet@ pl = planetList[i];
pl.changeResourceTerritory(prev, terr);
pl.changeSurfaceTerritory(prev, terr);
}
for(uint i = 0, cnt = resourceHolders.length; i < cnt; ++i) {
Object@ obj = resourceHolders[i];
//Planets were already handled before
if(obj.isPlanet)
continue;
obj.changeResourceTerritory(prev, terr);
}
}
void clearTerritory(Empire@ emp, Territory@ old) {
if(emp is null || !emp.valid)
return;
Territory@ prev = territories[emp.index].get();
if(prev is old)
territories[emp.index].set(null);
}
void regionObjectOwnerChange(Object& thisObj, Object& obj, Empire@ prevOwner, Empire@ newOwner) {
Region@ region = cast<Region>(thisObj);
//Change effect owner
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
RegionEffect@ eff = effects[i];
eff.ownerChange(region, obj, prevOwner, newOwner);
if(eff.forEmpire is null)
continue;
if(prevOwner is eff.forEmpire)
eff.disable(region, obj);
else if(newOwner is eff.forEmpire)
eff.enable(region, obj);
}
//Check planet trade
switch(obj.type) {
case OT_Planet:
if(prevOwner !is null && prevOwner.valid) {
planetCounts[prevOwner.index] -= 1;
}
if(newOwner !is null && newOwner.valid) {
planetCounts[newOwner.index] += 1;
cast<Planet>(obj).setLoyaltyBonus(empLoyaltyBonus[obj.owner.index]);
}
else {
cast<Planet>(obj).setLoyaltyBonus(0);
}
calculatePlanets(region);
break;
case OT_Ship:
{
//Update ship strength
Ship@ ship = cast<Ship>(obj);
if(ship.hasLeaderAI) {
int value = round(ship.blueprint.design.size);
if(prevOwner !is null && prevOwner.valid)
strengths[prevOwner.index] -= value;
if(newOwner !is null && newOwner.valid)
strengths[newOwner.index] += value;
}
calculateShips(region);
} break;
case OT_Orbital:
{
Orbital@ orbital = cast<Orbital>(obj);
calculateTradeAccess(region);
} break;
}
//Apply statuses
applyStatuses(region, obj, isOwnerChange=true);
//Update object counts
if(prevOwner !is null && prevOwner.valid)
objectCounts[prevOwner.index] -= 1;
if(newOwner !is null && newOwner.valid)
objectCounts[newOwner.index] += 1;
//Update shipyards
if(obj.hasConstruction)
calculateShipyards(region);
}
void leaveRegion(Object& thisObj, Object& obj) {
Region@ region = cast<Region>(thisObj);
//Remove effects
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
RegionEffect@ eff = effects[i];
if(eff.forEmpire is null || obj.owner is eff.forEmpire)
eff.disable(region, obj);
}
switch(obj.type)
{
case OT_Ship:
{
Ship@ ship = cast<Ship>(obj);
auto@ dsg = ship.blueprint.design;
if(obj.owner !is null && obj.owner.valid && ship.hasLeaderAI) {
int value = round(dsg.size);
strengths[obj.owner.index] -= value;
}
calculateShips(region);
}
break;
case OT_Planet:
{
Planet@ pl = cast<Planet>(obj);
planetList.remove(pl);
planetBucket.remove(pl);
if(obj.owner !is null && obj.owner.valid)
planetCounts[obj.owner.index] -= 1;
calculatePlanets(region);
}
break;
case OT_Star:
{
Star@ star = cast<Star>(obj);
starList.remove(star);
starBucket.remove(star);
StarTemperature -= star.temperature;
if(starList.length != 0)
StarRadius = starList[0].radius;
else
StarRadius = 0;
}
break;
case OT_Orbital:
{
Orbital@ orbital = cast<Orbital>(obj);
orbitalList.remove(orbital);
calculateTradeAccess(region);
}
break;
case OT_Asteroid:
{
Asteroid@ roid = cast<Asteroid>(obj);
asteroidList.remove(roid);
asteroidBucket.remove(roid);
}
break;
case OT_Anomaly:
{
Anomaly@ anomaly = cast<Anomaly>(obj);
anomalyList.remove(anomaly);
anomalyBucket.remove(anomaly);
}
break;
case OT_Pickup:
{
Pickup@ pickup = cast<Pickup>(obj);
pickupList.remove(pickup);
pickupBucket.remove(pickup);
}
break;
case OT_Artifact:
{
Artifact@ artifact = cast<Artifact>(obj);
artifactList.remove(artifact);
artifactBucket.remove(artifact);
}
break;
}
//Handle shipyards
if(obj.hasConstruction)
unregisterShipyard(region, obj);
//Remove from all objects
if(obj.owner !is null && obj.owner.valid)
objectCounts[obj.owner.index] -= 1;
objectList.remove(obj);
//Remove from component-based lists
if(obj.hasResources)
resourceHolders.remove(obj);
}
void enterRegion(Object& thisObj, Object& obj) {
Region@ region = cast<Region>(thisObj);
switch(obj.type)
{
case OT_Ship:
{
Ship@ ship = cast<Ship>(obj);
if(obj.owner !is null && obj.owner.valid && ship.hasLeaderAI) {
auto@ dsg = ship.blueprint.design;
if(dsg !is null) {
int value = round(dsg.size);
strengths[obj.owner.index] += value;
}
}
calculateShips(region);
}
break;
case OT_Planet:
{
Planet@ pl = cast<Planet>(obj);
planetList.insertLast(pl);
planetBucket.add(pl);
if(obj.owner !is null && obj.owner.valid)
planetCounts[obj.owner.index] += 1;
calculatePlanets(region);
}
break;
case OT_Star:
{
Star@ star = cast<Star>(obj);
starList.insertLast(star);
starBucket.add(star);
StarTemperature += star.temperature;
if(starList.length != 0)
StarRadius = starList[0].radius;
else
StarRadius = 0;
}
break;
case OT_Orbital:
{
Orbital@ orbital = cast<Orbital>(obj);
orbitalList.insertLast(orbital);
/*int value = orbital.MilitaryValue;*/
/*if(obj.owner !is null && obj.owner.valid)*/
/* strengths[obj.owner.index] += value;*/
calculateTradeAccess(region);
}
break;
case OT_Asteroid:
{
Asteroid@ roid = cast<Asteroid>(obj);
asteroidList.insertLast(roid);
asteroidBucket.add(roid);
}
break;
case OT_Anomaly:
{
Anomaly@ anomaly = cast<Anomaly>(obj);
anomalyList.insertLast(anomaly);
anomalyBucket.add(anomaly);
}
break;
case OT_Pickup:
{
Pickup@ pickup = cast<Pickup>(obj);
pickupList.insertLast(pickup);
pickupBucket.add(pickup);
}
break;
case OT_Artifact:
{
Artifact@ artifact = cast<Artifact>(obj);
artifactList.insertLast(artifact);
artifactBucket.add(artifact);
}
break;
}
//Add to all objects
if(obj.owner !is null && obj.owner.valid)
objectCounts[obj.owner.index] += 1;
objectList.insertLast(obj);
//Add to component-based lists
if(obj.hasResources)
resourceHolders.insertLast(obj);
//Apply statuses
applyStatuses(region, obj);
//Handle shipyards
if(obj.hasConstruction)
registerShipyard(region, obj);
//Add effects
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
RegionEffect@ eff = effects[i];
if(eff.forEmpire is null || obj.owner is eff.forEmpire)
eff.enable(region, obj);
}
}
int addRegionEffect(Object& obj, RegionEffect@ eff) {
Region@ region = cast<Region>(obj);
eff.id = nextEffectId++;
effects.insertLast(eff);
eff.start(region);
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
if(eff.forEmpire is null || objectList[i].owner is eff.forEmpire)
eff.enable(region, objectList[i]);
}
return eff.id;
}
int addRegionEffect(Object& obj, Empire@ forEmpire, uint id) {
Region@ region = cast<Region>(obj);
const RegionEffectType@ type = getRegionEffect(id);
RegionEffect@ eff = type.create();
@eff.forEmpire = forEmpire;
return addRegionEffect(obj, eff);
}
void addTemporaryVision(Object& obj, Empire@ forEmpire, double timer) {
addRegionEffect(obj, GrantVision(forEmpire, timer));
}
void removeRegionEffect(Object& obj, int id) {
Region@ region = cast<Region>(obj);
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
RegionEffect@ eff = effects[i];
if(eff.id != id)
continue;
removeRegionEffectByIndex(obj, i);
break;
}
}
void removeRegionEffectByIndex(Object& obj, uint withIndex) {
Region@ region = cast<Region>(obj);
RegionEffect@ eff = effects[withIndex];
eff.end(region);
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
if(eff.forEmpire is null || objectList[i].owner is eff.forEmpire)
eff.disable(region, objectList[i]);
}
effects.removeAt(withIndex);
}
void changeRegionEffectOwner(Object& obj, int id, Empire@ newOwner) {
Region@ region = cast<Region>(obj);
for(uint i = 0, cnt = effects.length; i < cnt; ++i) {
RegionEffect@ eff = effects[i];
if(eff.id != id)
continue;
//Disable for anything that is no longer affected, and enable
//for everything that is now newly affected
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
if(newOwner !is null && (eff.forEmpire is null || objectList[i].owner is eff.forEmpire))
eff.disable(region, objectList[i]);
}
Empire@ prevOwner = eff.forEmpire;
@eff.forEmpire = newOwner;
eff.changeEffectOwner(region, prevOwner, newOwner);
for(uint i = 0, cnt = objectList.length; i < cnt; ++i) {
if((newOwner is null && objectList[i].owner !is eff.forEmpire) || objectList[i].owner is newOwner)
eff.enable(region, objectList[i]);
}
break;
}
}
void calculateShips(Object& obj) {
//Update the vision
calculateVision(obj);
}
void addTradePathing(int toSystem, Object@ from, Object@ to, int resId, uint resource) {
//Create line node if needed
if(tradeLines is null) {
if(system is null)
return;
@tradeLines = TradeLinesNode();
tradeLines.establish(system.index);
}
//Find other system index
int other = -1;
uint sys = toSystem;
if(sys != system.index && toSystem != -1) {
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
if(system.adjacent[i] == sys) {
other = i;
break;
}
}
if(other == -1)
return;
}
//Add to the node
tradeLines.addPathing(other, from, to, resId, resource);
}
void removeTradePathing(int toSystem, Object@ from, int resId) {
if(tradeLines is null)
return;
//Find other system index
int other = -1;
uint sys = toSystem;
if(sys != system.index && toSystem != -1) {
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
if(system.adjacent[i] == sys) {
other = i;
break;
}
}
if(other == -1)
return;
}
//Remove from the node
tradeLines.removePathing(other, from, resId);
}
void calculatePlanets(Object& obj) {
Region@ region = cast<Region>(obj);
//Calculate primary empire (most planets)
Empire@ primary = null;
int amount = 0;
uint cnt = planetCounts.length;
for(uint i = 0; i < cnt; ++i) {
int value = planetCounts[i];
if(value > amount) {
amount = value;
@primary = getEmpire(i);
}
}
if(primary !is primaryEmpire) {
@primaryEmpire = primary;
if(primary is null)
region.PrimaryEmpire = -1;
else
region.PrimaryEmpire = primary.id;
updatePlane(region);
}
calculateVision(obj);
calculateTradeAccess(obj);
}
Empire@ get_visiblePrimaryEmpire(Player& pl, const Object& obj) const {
const Region@ region = cast<const Region>(obj);
Empire@ emp = pl.emp;
if(emp is null)
return null;
if(region.VisionMask & emp.visionMask != 0)
return primaryEmpire;
if(!emp.valid)
return null;
return primaryVision[emp.index];
}
void updatePlane(const Region& obj) {
for(uint i = 0, cnt = primaryVision.length; i < cnt; ++i) {
Empire@ other = getEmpire(i);
if(obj.VisionMask & other.visionMask != 0)
@primaryVision[i] = primaryEmpire;
}
//Inform the nodes
if(playerEmpire is null || !playerEmpire.valid)
plane.setPrimaryEmpire(primaryEmpire);
else
plane.setPrimaryEmpire(primaryVision[playerEmpire.index]);
}
void calculateTradeAccess(Object& obj) {
#section server
Region@ region = cast<Region>(obj);
uint mask = 0;
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
Planet@ pl = planetList[i];
if(pl.owner is null || !pl.owner.valid)
continue;
mask |= pl.owner.mask;
}
for(uint i = 0, cnt = tradeGrants.length; i < cnt; ++i) {
if(tradeGrants[i] > 0)
mask |= getEmpire(i).mask;
}
region.TradeMask = mask;
#section all
}
void grantMemory(Empire@ emp) {
for(uint i = 0, cnt = planetList.length; i < cnt; ++i)
planetList[i].giveHistoricMemory(emp);
}
void grantVision(Object& obj, Empire@ emp) {
if(emp !is null && emp.valid) {
visionGrants[emp.index] += 1;
calculateVision(obj);
}
}
void revokeVision(Object& obj, Empire@ emp) {
if(emp !is null && emp.valid) {
visionGrants[emp.index] -= 1;
calculateVision(obj);
}
}
void grantTrade(Object& obj, Empire@ emp) {
if(emp !is null && emp.valid) {
tradeGrants[emp.index] += 1;
calculateTradeAccess(obj);
}
}
void revokeTrade(Object& obj, Empire@ emp) {
if(emp !is null && emp.valid) {
tradeGrants[emp.index] -= 1;
calculateTradeAccess(obj);
}
}
void forceSiegeAllPlanets(Empire@ emp, uint mask, uint doMask = ~0) {
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
Planet@ pl = planetList[i];
Empire@ owner = pl.owner;
if(owner is emp)
continue;
if(!pl.valid || owner is null)
continue;
if(owner.mask & mask != 0)
continue;
if(owner.mask & doMask == 0)
continue;
pl.forceSiege(mask);
if(pl.getLoyaltyFacing(emp) <= 0)
pl.annex(emp);
}
}
void clearForceSiegeAllPlanets(uint mask) {
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
Planet@ pl = planetList[i];
pl.clearForceSiege(mask);
}
}
uint prevMask = 0;
void calculateVision(Object& obj) {
Region@ region = cast<Region>(obj);
int prevSeen = region.SeenMask;
uint avSupMask = 0;
for(uint i = 0, cnt = planetList.length; i < cnt; ++i) {
auto@ pl = planetList[i];
Empire@ owner = pl.owner;
if(owner is null || !owner.valid)
continue;
if(pl.supportCount > 0 && pl.SupplyUsed > pl.SupplySatellite && pl.allowFillFrom)
avSupMask |= owner.mask;
}
for(uint i = 0, cnt = orbitalList.length; i < cnt; ++i) {
auto@ obj = orbitalList[i];
Empire@ owner = obj.owner;
if(owner is null || !owner.valid)
continue;
if(obj.supportCount > 0 && obj.allowFillFrom)
avSupMask |= owner.mask;
}
region.AvailSupportMask = avSupMask;
#section server
uint mask = 0, basicMask = 0;
for(uint i = 0, cnt = objectCounts.length; i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(objectCounts[i] > 0) {
basicMask |= emp.mask;
mask |= emp.mask;
}
if(visionGrants[i] > 0) {
mask |= emp.mask;
}
}
for(uint i = 0, cnt = objectCounts.length; i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(emp.visionMask & mask != 0)
mask |= emp.mask;
}
if(region.VisionMask != mask || prevMask != playerEmpire.visionMask || basicMask != region.BasicVisionMask) {
region.VisionMask = mask;
region.MemoryMask |= mask;
region.SeenMask |= mask;
updatePlane(region);
if(system.donateVision)
region.DonateVisionMask = mask;
else
region.DonateVisionMask = 0;
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major)
continue;
if(basicMask & emp.mask == 0)
continue;
uint contact = uint(emp.ContactMask.value);
if((~contact) & basicMask == 0)
continue;
for(uint j = 0; j < cnt; ++j) {
Empire@ other = getEmpire(j);
if(!other.major)
continue;
if(basicMask & other.mask == 0)
continue;
uint prevContact = (emp.ContactMask |= other.mask);
if(prevContact & other.mask != 0)
continue;
bool gainsBonus = config::INFLUENCE_CONTACT_BONUS > 0
&& (other.ContactMask & ~(emp.mask | other.mask) == 0);
if(gainsBonus) {
emp.addInfluence(config::INFLUENCE_CONTACT_BONUS);
giveAchievement(emp, "ACH_FIRST_SIGHT");
}
emp.notifyEmpireMet(obj, other, gainsBonus);
}
}
prevMask = playerEmpire.visionMask;
}
region.BasicVisionMask = basicMask;
#section shadow
if(system !is null)
region.SeenMask |= region.VisionMask;
#section all
int newSeen = region.SeenMask;
if(prevSeen != newSeen && system !is null) {
region.ExploredMask |= newSeen;
for(uint i = 0, cnt = system.adjacent.length; i < cnt; ++i) {
auto@ other = getSystem(system.adjacent[i]);
if(other !is null)
other.object.ExploredMask |= newSeen;
}
}
for(uint i = 0, cnt = icons.length; i < cnt; ++i)
icons[i].update(region);
}
void updateRegionPlane(const Object& obj) {
updatePlane(cast<const Region>(obj));
}
void calculateShipyards(Object& obj) {
Region@ region = cast<Region>(obj);
uint mask = 0;
for(uint i = 0, cnt = shipyardList.length; i < cnt; ++i)
mask |= shipyardList[i].owner.mask;
region.ShipyardMask = mask;
}
IconRing@ getIconRing(int level, bool create) {
IconRing@ ring;
for(uint i = 0, cnt = icons.length; i < cnt; ++i) {
if(icons[i].level == level)
return icons[i];
}
if(create) {
IconRing ring;
ring.level = level;
icons.insertLast(ring);
return ring;
}
return null;
}
void addStrategicIcon(Object& region, int level, Object& obj, Node& node) {
IconRing@ ring = getIconRing(level, true);
ring.add(cast<Region>(region), obj, node);
}
void removeStrategicIcon(Object& region, int level, Node& node) {
IconRing@ ring = getIconRing(level, false);
if(ring !is null)
ring.remove(cast<Region>(region), node);
}
};
tidy class IconRing {
int level = 0;
array<Object@> objects;
array<Node@> nodes;
array<uint> sortList;
void update(Region& region) {
uint cnt = nodes.length;
if(cnt == 0)
return;
double angleStep = (twopi / double(cnt));
vec3d sysPos = region.position;
double radius = region.OuterRadius + (800 * level);
vec3d basePos = objects[0].position;
double baseAngle = vec2d(basePos.x - sysPos.x, basePos.z - sysPos.z).radians();
sortList.length = cnt;
for(uint i = 0; i < cnt; ++i) {
Node@ node = nodes[i];
vec3d pos = objects[i].position;
double myAngle = vec2d(pos.x - sysPos.x, pos.z - sysPos.z).radians();
double diff = angleDiff(myAngle, baseAngle);
if(diff < 0)
diff += twopi;
sortList[i] = uint(diff * 10000.0) << 8 | i;
}
sortList.sortAsc();
uint mask = 0;
for(uint i = 0; i < cnt; ++i) {
uint index = sortList[i] & 0x000000ff;
Node@ node = nodes[index];
double myAngle = baseAngle + double(i) * angleStep;
vec3d pos = sysPos + vec3d(cos(myAngle) * radius, 0, sin(myAngle) * radius);
node.hintParentObject(region, false);
{
PlanetIconNode@ icon = cast<PlanetIconNode@>(node);
if(icon !is null) {
icon.setStrategic(pos, sysPos);
continue;
}
}
{
StrategicIconNode@ icon = cast<StrategicIconNode@>(node);
if(icon !is null) {
icon.setStrategic(pos, sysPos);
continue;
}
}
}
}
void add(Region& region, Object@ obj, Node@ node) {
objects.insertLast(obj);
nodes.insertLast(node);
update(region);
}
void remove(Region& region, Node@ node) {
int index = nodes.find(node);
if(index != -1) {
nodes.removeAt(index);
objects.removeAt(index);
}
{
PlanetIconNode@ icon = cast<PlanetIconNode@>(node);
if(icon !is null)
icon.clearStrategic();
}
{
StrategicIconNode@ icon = cast<StrategicIconNode@>(node);
if(icon !is null)
icon.clearStrategic();
}
node.hintParentObject(null, false);
update(region);
}
};