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

2784 lines
70 KiB
ActionScript

#include "include/resource_constants.as"
import saving;
import biomes;
import planet_levels;
import resources;
import systems;
import planets.SurfaceGrid;
import planet_loyalty;
import object_creation;
import planet_types;
import notifications;
import attributes;
from influence import DiplomacyEdictType;
from influence_global import giveRandomReward;
from components.ObjectManager import getDefenseDesign;
import bool getCheatsEverOn() from "cheats";
const string TAG_SUPPORT("Support");
bool INSTANT_COLONIZE = false;
void setInstantColonize(bool value) {
INSTANT_COLONIZE = value;
}
tidy class SurfaceComponent : Component_SurfaceComponent, Savable {
array<const Biome@> biomes;
uint biome0, biome1, biome2;
array<Object@> fleetsInOrbit;
uint orbitsMask = 0;
uint fakeSiegeOrbit = 0;
uint forceSiegeMask = 0;
vec2u originalSurfaceSize;
double Population = 0.0;
int MaxPopulation = 0;
int PopulationPenalty = 0;
int overpopulation = 0;
double incomingPop = 0;
int bombardDecay = 0;
bool disableProtection = false;
bool needsPopulationForLevel = true;
uint protectedFromMask = 0;
int maxPlanetLevel = -1;
int prevIncome = 0;
int popIncome = 0;
int bonusIncome = 0;
int prevPopulation = 0;
int prevBaseLoyalty = 1;
double prevInfluence = 0;
double prevEnergy = 0;
double prevResearch = 0;
double prevDefense = 0;
const Design@ defenseDesign;
double defenseLabor = -1;
PlanetIconNode@ icon;
array<int> iconMemory(getEmpireCount(), -1);
ColonizationOrder[] colonization;
float colonyShipTimer = 0.f;
bool isSendingColonizers = false;
float pressureCapFactor = 1.f;
int pressureCapMod = 0;
uint ResourceModID = 0;
array<uint> affinities;
uint ColonizingMask = 0;
uint Level = 0;
uint DecayLevel = 0;
uint ResourceLevel = 0;
uint PopLevel = 0;
uint LevelChainId = 0;
double DecayTimer = -1.0;
double ResourceCheck = 1.0;
bool deltaRes = false;
bool deltaAff = false;
bool deltaPop = false;
bool deltaCol = false;
bool deltaLoy = false;
bool wasMoving = false;
int Quarantined = 0;
int Contestion = 0;
int BaseLoyalty = 10;
int LoyaltyBonus = 0;
float WaitingBaseLoyalty = 0;
float StoredBaseLoyalty = 0;
array<float> LoyaltyEffect(getEmpireCount(), 0);
double growthRate = 1.0;
double tileDevelopRate = 1.0;
double bldConstructRate = 1.0;
double undevelopedMaint = 1.0;
double colonyshipAccel = 1.0;
uint gfxFlags = 0;
SurfaceGrid grid;
uint SurfaceModId = 0;
SurfaceComponent() {}
void save(SaveFile& file) {
file << Population;
file << MaxPopulation;
file << PopulationPenalty;
file << overpopulation;
file << ResourceModID;
file << pressureCapFactor;
file << pressureCapMod;
file << bombardDecay;
file << incomingPop;
file << prevIncome;
file << prevPopulation;
file << prevInfluence;
file << prevEnergy;
file << prevResearch;
file << prevDefense;
file << popIncome;
file << bonusIncome;
file << growthRate;
file << tileDevelopRate;
file << bldConstructRate;
file << undevelopedMaint;
file << colonyshipAccel;
file << Level;
file << DecayLevel;
file << PopLevel;
file.writeIdentifier(SI_PlanetLevelChain, LevelChainId);
file << ResourceLevel;
file << prevBaseLoyalty;
file << needsPopulationForLevel;
file << DecayTimer;
file << deltaPop;
file << deltaRes;
file << ColonizingMask;
file << protectedFromMask;
file << maxPlanetLevel;
file << Quarantined;
file << Contestion;
file << BaseLoyalty;
file << WaitingBaseLoyalty;
file << StoredBaseLoyalty;
file << LoyaltyBonus;
file << disableProtection;
uint cnt = getEmpireCount();
for(uint i = 0; i < cnt; ++i) {
file << LoyaltyEffect[i];
file << iconMemory[i];
}
grid.save(file);
file << colonyShipTimer;
file << isSendingColonizers;
file << uint8(colonization.length);
for(uint8 i = 0; i < uint8(colonization.length); ++i)
colonization[i].save(file);
file << uint8(biomes.length);
for(uint i = 0; i < biomes.length; ++i)
file.writeIdentifier(SI_Biome, int(biomes[i].id));
cnt = affinities.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << affinities[i];
file << gfxFlags;
file << originalSurfaceSize;
}
void load(SaveFile& file) {
file >> Population;
file >> MaxPopulation;
file >> PopulationPenalty;
file >> overpopulation;
file >> ResourceModID;
file >> pressureCapFactor;
file >> pressureCapMod;
if(file >= SV_0038)
file >> bombardDecay;
if(file < SV_0088)
file.readObject();
if(file >= SV_0033)
file >> incomingPop;
file >> prevIncome;
file >> prevPopulation;
file >> prevInfluence;
file >> prevEnergy;
file >> prevResearch;
file >> prevDefense;
if(file < SV_0088)
prevDefense = 0;
file >> popIncome;
if(file >= SV_0125)
file >> bonusIncome;
file >> growthRate;
file >> tileDevelopRate;
file >> bldConstructRate;
file >> undevelopedMaint;
file >> colonyshipAccel;
ResourceCheck = 0;
file >> Level;
file >> DecayLevel;
if(file >= SV_0058) {
file >> PopLevel;
if(file >= SV_0143) {
LevelChainId = file.readIdentifier(SI_PlanetLevelChain);
if(LevelChainId == uint(-1))
LevelChainId = 0;
}
file >> ResourceLevel;
file >> prevBaseLoyalty;
file >> needsPopulationForLevel;
}
file >> DecayTimer;
file >> deltaPop;
file >> deltaRes;
file >> ColonizingMask;
if(file >= SV_0110)
file >> protectedFromMask;
if(file >= SV_0127)
file >> maxPlanetLevel;
file >> Quarantined;
file >> Contestion;
file >> BaseLoyalty;
file >> WaitingBaseLoyalty;
file >> StoredBaseLoyalty;
file >> LoyaltyBonus;
file >> disableProtection;
uint cnt = getEmpireCount();
for(uint i = 0; i < cnt; ++i) {
file >> LoyaltyEffect[i];
if(file < SV_0056) {
float dummy = 0.f;
file >> dummy;
file >> dummy;
}
file >> iconMemory[i];
}
grid.load(file);
file >> colonyShipTimer;
file >> isSendingColonizers;
uint8 colonyOrders = 0;
file >> colonyOrders;
colonization.length = colonyOrders;
for(uint i = 0; i < colonization.length; ++i)
colonization[i].load(file);
uint8 biomeCount = 0;
file >> biomeCount;
biomes.length = biomeCount;
for(uint i = 0; i < biomes.length; ++i) {
uint8 id = uint8(file.readIdentifier(SI_Biome));
@biomes[i] = getBiome(id);
}
if(biomes.length >= 1)
biome0 = biomes[0].id;
if(biomes.length >= 2)
biome1 = biomes[1].id;
if(biomes.length >= 3)
biome2 = biomes[2].id;
if(file < SV_0105) {
bool b = false;
uint u = 0;
file >> b >> b >> b;
file >> u;;
file >> b;
if(b) {
file >> u;
if(u == 0)
file >> u;
}
}
file >> cnt;
affinities.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> affinities[i];
if(file >= SV_0122)
file >> gfxFlags;
if(file >= SV_0150)
file >> originalSurfaceSize;
}
void surfacePostLoad(Object& obj) {
Planet@ pl = cast<Planet>(obj);
if(pl !is null) {
@icon = PlanetIconNode();
icon.establish(pl);
icon.setBeingColonized(ColonizingMask & playerEmpire.mask != 0);
updateIcon(obj);
if(obj.region !is null)
obj.region.addStrategicIcon(0, obj, icon);
}
}
void initSurface(Object& obj, int width, int height, uint one, uint two, uint three, uint resource) {
initSurface(obj, width, height, getBiome(one), getBiome(two), getBiome(three), getResource(resource));
}
void initSurface(Object& obj, int width, int height, const Biome@ base, const Biome@ two, const Biome@ three, const ResourceType@ res) {
//Set biomes
biomes.length = 3;
@biomes[0] = base;
@biomes[1] = two;
@biomes[2] = three;
biome0 = base.id;
biome1 = two.id;
biome2 = three.id;
//Generate grid
grid.create(width, height, base);
grid.generateContinent(two);
grid.generateContinent(three);
grid.rotateFor(base);
//Create icon
Planet@ pl = cast<Planet>(obj);
if(pl !is null) {
@icon = PlanetIconNode();
icon.establish(pl);
if(res !is null)
icon.setResource(res.id);
if(obj.region !is null)
obj.region.addStrategicIcon(0, obj, icon);
}
setLevel(obj, 0, true);
if(res !is null)
obj.addResource(res.id);
changeSurfaceOwner(obj, null);
obj.canBuildShips = false;
obj.canBuildAsteroids = false;
obj.canBuildOrbitals = false;
obj.canTerraform = false;
++SurfaceModId;
originalSurfaceSize = grid.size;
}
void addSurfaceArea(Object& obj, vec2i size, uint biome, uint voidBiome, bool separate, bool developed, bool vertical) {
if(separate) {
if(vertical)
size.y += 1;
else
size.x += 1;
}
array<uint8> addBiomes(size.x * size.y);
for(int x = 0; x < size.x; ++x) {
for(int y = 0; y < size.y; ++y) {
uint index = y * size.width + x;
if(separate && (vertical ? (y == 0) : (x == 0)) && voidBiome != uint(-1))
addBiomes[index] = voidBiome;
else
addBiomes[index] = biome;
}
}
grid.addSurfaceArea(size, addBiomes, voidBiome, developed, vertical);
++SurfaceModId;
}
void regenSurface(Object& obj, int width, int height, uint biomeCount) {
biomes.length = biomeCount;
for(uint i = 1; i < biomeCount; ++i)
@biomes[i] = getDistributedBiome();
if(biomes[0] is null)
@biomes[0] = getDistributedBiome();
if(biomes.length >= 1)
biome0 = biomes[0].id;
if(biomes.length >= 2)
biome1 = biomes[1].id;
if(biomes.length >= 3)
biome2 = biomes[2].id;
grid.create(width, height, biomes[0]);
for(uint i = 1; i < biomeCount; ++i)
grid.generateContinent(biomes[i]);
grid.rotateFor(biomes[0]);
++SurfaceModId;
originalSurfaceSize = grid.size;
}
uint get_Biome0() {
return biome0;
}
uint get_Biome1() {
return biome1;
}
uint get_Biome2() {
return biome2;
}
void forceUsefulSurface(double pct, uint biomeId) {
double useful = 1.0;
double perTile = 1.0 / (double(grid.size.width) * double(grid.size.height));
auto@ fillBiome = getBiome(biomeId);
if(fillBiome is null)
return;
do {
useful = 0;
for(uint i = 0, cnt = grid.biomes.length; i < cnt; ++i) {
auto@ other = getBiome(grid.biomes[i]);
if(other !is null && other.buildCost <= 1.01f && other.buildTime <= 1.01f)
useful += perTile;
}
if(useful < pct)
grid.generateContinent(fillBiome, true);
} while(useful < pct);
++SurfaceModId;
}
bool hasBiome(uint id) const {
for(uint i = 0, cnt = biomes.length; i < cnt; ++i) {
if(biomes[i].id == id)
return true;
}
return false;
}
void getPlanetSurface() {
yield(grid);
}
Image@ surfaceData;
bool reqSurfaceData = false;
uint surfaceDataMod = uint(-1);
uint getSurfaceData(Object& obj, Image& img) {
reqSurfaceData = true;
if(surfaceData is null) {
obj.requestSurface();
return uint(-1);
}
img = surfaceData;
return surfaceDataMod;
}
void requestSurface(Object& obj) {
if(surfaceData is null)
@surfaceData = Image(originalSurfaceSize, 4);
if(surfaceDataMod != SurfaceModId) {
renderSurfaceData(obj, grid, surfaceData, sizeLimit=originalSurfaceSize, citiesMode=true);
surfaceDataMod = SurfaceModId;
}
reqSurfaceData = false;
}
uint get_surfaceModId() {
return SurfaceModId;
}
uint get_emptyDevelopedTiles() {
return grid.emptyDeveloped;
}
void replaceAllBiomesWith(Object& obj, uint id) {
for(uint i = 0, cnt = biomes.length; i < cnt; ++i)
replaceBiome(obj, i, id);
}
void replaceFirstBiomeWith(Object& obj, uint id) {
replaceBiome(obj, 0, id);
}
void replaceBiome(Object& obj, uint index, uint id) {
auto@ repl = getBiome(id);
if(repl is null)
return;
if(index >= biomes.length)
return;
if(biomes[index] is repl)
return;
//Replace the base biome with the new one
uint8 prevId = uint8(biomes[index].id);
uint8 newId = uint8(repl.id);
if(prevId == newId)
return;
if(index == 0)
@grid.baseBiome = repl;
for(uint i = 0, cnt = biomes.length; i < cnt; ++i) {
if(biomes[i].id == prevId)
@biomes[i] = repl;
}
for(uint i = 0, cnt = grid.biomes.length; i < cnt; ++i) {
if(grid.biomes[i] == prevId)
grid.biomes[i] = newId;
}
grid.delta = true;
//Figure out planet type
Planet@ pl = cast<Planet>(obj);
if(pl !is null) {
const PlanetType@ planetType;
if(biomes.length == 3)
@planetType = getBestPlanetType(biomes[0], biomes[1], biomes[2]);
else if(biomes.length == 1)
@planetType = getBestPlanetType(biomes[0], null, null);
if(planetType !is null) {
pl.PlanetType = planetType.id;
PlanetNode@ plNode = cast<PlanetNode>(pl.getNode());
if(plNode !is null)
plNode.planetType = planetType.id;
}
}
++SurfaceModId;
if(biomes.length >= 1)
biome0 = biomes[0].id;
if(biomes.length >= 2)
biome1 = biomes[1].id;
if(biomes.length >= 3)
biome2 = biomes[2].id;
}
void mirrorSurfaceFrom(Object& obj, Object& other) {
PlanetSurface surf;
receive(other.getPlanetSurface(), surf);
for(uint i = 0, cnt = min(grid.biomes.length, surf.biomes.length); i < cnt; ++i)
grid.biomes[i] = surf.biomes[i];
Planet@ pl = cast<Planet>(obj);
Planet@ otherPl = cast<Planet>(other);
if(pl !is null) {
pl.PlanetType = otherPl.PlanetType;
PlanetNode@ plNode = cast<PlanetNode>(pl.getNode());
if(plNode !is null)
plNode.planetType = pl.PlanetType;
}
++SurfaceModId;
if(biomes.length >= 1)
biome0 = biomes[0].id;
if(biomes.length >= 2)
biome1 = biomes[1].id;
if(biomes.length >= 3)
biome2 = biomes[2].id;
}
void removeFinalSurfaceRows(Object& obj, uint rows = 1) {
grid.delta = true;
for(uint x = 0; x < grid.size.width; ++x) {
for(uint y = 0; y < rows; ++y) {
uint index = (grid.size.height - rows + y) * grid.size.width + x;
auto@ bld = grid.tileBuildings[index];
if(bld !is null)
grid.destroyBuilding(obj, vec2i(bld.position), force=true);
if(grid.flags[index] & SuF_Usable != 0)
grid.emptyDeveloped -= 1;
}
}
grid.size = vec2u(grid.size.width, grid.size.height - rows);
grid.biomes.length = grid.size.x * grid.size.y;
grid.flags.length = grid.size.x * grid.size.y;
grid.tileBuildings.length = grid.size.x * grid.size.y;
++SurfaceModId;
}
void stealFinalSurfaceRowsFrom(Object& obj, Object& other, uint rows = 1, uint voidBiome = uint(-1)) {
PlanetSurface surf;
receive(other.getPlanetSurface(), surf);
//Detect the non-void area in the center
uint leftOff = 0;
uint rightOff = 0;
for(uint x = 0; x < surf.size.width; ++x) {
bool fullVoid = true;
for(uint y = 0; y < rows; ++y) {
uint copyIndex = (surf.size.height - rows + y) * surf.size.width + x;
if(surf.biomes[copyIndex] != voidBiome) {
fullVoid = false;
break;
}
}
if(fullVoid)
leftOff += 1;
else
break;
}
for(uint x = 0; x < surf.size.width; ++x) {
bool fullVoid = true;
for(uint y = 0; y < rows; ++y) {
uint copyIndex = (surf.size.height - rows + y) * surf.size.width + (surf.size.width - x - 1);
if(surf.biomes[copyIndex] != voidBiome) {
fullVoid = false;
break;
}
}
if(fullVoid)
rightOff += 1;
else
break;
}
int addWidth = surf.size.width - leftOff - rightOff;
if(addWidth <= 0)
return;
//Add the biomes to our surface
array<uint8> addBiomes(rows * addWidth);
array<bool> devState(rows * addWidth);
for(int x = 0; x < addWidth; ++x) {
for(uint y = 0; y < rows; ++y) {
uint addIndex = y * addWidth + x;
uint copyIndex = (surf.size.height - rows + y) * surf.size.width + (x + leftOff);
addBiomes[addIndex] = surf.biomes[copyIndex];
devState[addIndex] = surf.flags[copyIndex] & SuF_Usable != 0;
}
}
grid.addSurfaceArea(vec2i(addWidth, rows), addBiomes, voidBiome, false, true, @devState);
//Spwan copies of all the buildings
vec2i bldOffset((grid.size.width - addWidth) / 2 - leftOff, grid.size.height - surf.size.height);
for(int x = 0; x < addWidth; ++x) {
for(uint y = 0; y < rows; ++y) {
uint copyIndex = (surf.size.height - rows + y) * surf.size.width + (x + leftOff);
auto@ bld = surf.tileBuildings[copyIndex];
if(bld !is null && (bld.type.laborCost <= 0 || bld.completion >= 1.f)) {
vec2i newPos = vec2i(bld.position) + bldOffset;
uint newIndex = newPos.y * grid.size.width + newPos.x;
if(grid.tileBuildings[newIndex] is null) {
float completion = bld.completion;
if(completion < 1.f && bld.type.cls != BC_Building)
completion = 1.f;
grid.build(obj, bld.type, newPos, true, false, completion);
}
}
}
}
//Tell the other to remove their rows
other.removeFinalSurfaceRows(rows);
++SurfaceModId;
}
void changeSurfaceTerritory(Territory@ prev, Territory@ terr) {
}
vec3d get_planetIconPosition(const Object& obj) const {
if(icon is null)
return obj.position;
return icon.position;
}
uint get_visibleLevel(Player& pl, const Object& obj) const {
Empire@ emp = pl.emp;
if(emp is null)
return 0;
if(obj.isVisibleTo(emp))
return Level;
if(obj.isKnownTo(emp) && emp.valid)
return iconMemory[emp.index] & 0xff;
return 0;
}
Empire@ get_visibleOwner(Player& pl, const Object& obj) const {
Empire@ emp = pl.emp;
if(emp is null)
return defaultEmpire;
if(obj.isVisibleTo(emp))
return obj.owner;
if(obj.isKnownTo(emp) && emp.valid) {
Empire@ ownerEmp = getEmpireByID((iconMemory[emp.index] & 0xff00) >> 8);
if(ownerEmp is null)
return defaultEmpire;
else
return ownerEmp;
}
return defaultEmpire;
}
uint getBuildingCount(uint buildingId) const {
uint amount = 0;
for(uint i = 0, cnt = grid.buildings.length; i < cnt; ++i) {
if(grid.buildings[i].type.id == buildingId)
amount += 1;
}
return amount;
}
uint getBuildingCount() const {
return grid.buildings.length;
}
uint get_buildingType(uint index) const {
if(index >= grid.buildings.length)
return uint(-1);
return grid.buildings[index].type.id;
}
Empire@ visibleOwnerToEmp(const Object& obj, Empire@ emp) const {
if(emp is null)
return defaultEmpire;
if(obj.isVisibleTo(emp))
return obj.owner;
if(obj.isKnownTo(emp) && emp.valid) {
Empire@ ownerEmp = getEmpireByID((iconMemory[emp.index] & 0xff00) >> 8);
if(ownerEmp is null)
return defaultEmpire;
else
return ownerEmp;
}
return defaultEmpire;
}
void buildBuilding(Object& obj, uint tid, vec2i pos) {
const BuildingType@ type = getBuildingType(tid);
if(type is null)
return;
if(type.civilian || !type.canBuildOn(obj))
return;
if(pos.x < 0 || pos.y < 0)
return;
if(pos.x >= int(grid.size.x) || pos.y >= int(grid.size.y))
return;
grid.build(obj, type, pos);
++SurfaceModId;
}
int getBuildingAt(uint x, uint y) {
auto@ bldg = grid.getBuilding(x, y);
if(bldg !is null)
return bldg.type.id;
else
return -1;
}
void setBuildingCompletion(Object& obj, uint x, uint y, float progress) {
grid.setBuildingCompletion(obj, vec2i(x,y), progress);
}
float getBuildingProgressAt(uint x, uint y) {
auto@ bldg = grid.getBuilding(x, y);
if(bldg !is null)
return bldg.completion;
else
return 0.f;
}
void destroyBuilding(Object& obj, vec2i pos) {
grid.destroyBuilding(obj, pos);
++SurfaceModId;
}
void forceDestroyBuilding(Object& obj, vec2i pos, bool undevelop = false) {
grid.destroyBuilding(obj, pos, undevelop=undevelop, force=true);
++SurfaceModId;
}
void spawnBuilding(Object& obj, uint tid, vec2i pos, bool develop = false) {
const BuildingType@ type = getBuildingType(tid);
if(type is null)
return;
if(type.civilian)
return;
if(pos.x < 0 || pos.y < 0)
return;
if(pos.x >= int(grid.size.x) || pos.y >= int(grid.size.y))
return;
grid.build(obj, type, pos, true, develop=develop);
++SurfaceModId;
}
void modCityCount(Object& obj, int mod) {
grid.citiesBuilt += mod;
}
double getResourceProduction(uint resource) {
if(resource >= grid.resources.length)
return 0.0;
return grid.resources[resource];
}
double getResourcePressure(uint resource) {
if(resource >= grid.pressures.length)
return 0.0;
return grid.pressures[resource];
}
bool isPressureSaturated(uint resource) {
if(resource >= grid.pressures.length)
return true;
return grid.saturates[resource] >= grid.pressures[resource] - 0.01;
}
double getCivilianProduction(uint resource) {
if(resource >= grid.civResources.length)
return 0.0;
return grid.civResources[resource];
}
void modResource(uint resource, double amount) {
if(resource >= grid.resources.length)
return;
grid.resources[resource] += amount;
}
void modCivResourceMod(uint resource, double amount) {
if(resource >= grid.civResourceMods.length)
return;
float prev = grid.civResourceMods[resource];
grid.civResourceMods[resource] += amount;
double oldRes = prev * double(grid.civResources[resource]);
double newRes = grid.civResourceMods[resource] * double(grid.civResources[resource]);
if(oldRes != newRes)
grid.resources[resource] += (newRes - oldRes);
}
float getCivResourceMod(uint resource) {
if(resource >= grid.civResourceMods.length)
return 0.f;
return grid.civResourceMods[resource];
}
void modOverpopulation(int steps) {
overpopulation += steps;
}
double get_undevelopedMaintenance() const {
return undevelopedMaint;
}
double get_buildingConstructRate() const {
return bldConstructRate;
}
double get_tileDevelopmentRate() const {
return tileDevelopRate;
}
void developTiles(Object& obj, uint amount) {
grid.developTiles(obj, amount);
}
void destroyRandomTile(Object& obj, bool civilOnly) {
grid.destroyRandomTile(obj, civilOnly);
}
void modBombardment(int amount) {
bombardDecay += amount;
}
void modPressureCapFactor(Object& obj, float amt) {
pressureCapFactor += amt;
calculatePopVars(obj);
}
void modPressureCapMod(Object& obj, int amt) {
pressureCapMod += amt;
calculatePopVars(obj);
}
void modBuildingMaintenanceRefund(int amt) {
grid.bldMaintenanceRefund += amt;
}
uint get_pressureCap() const {
return grid.pressureCap;
}
float get_totalPressure() const {
return grid.totalPressure;
}
void modGrowthRate(double amt) {
growthRate += amt;
deltaPop = true;
}
void modTileDevelopRate(double amt) {
tileDevelopRate += amt;
}
void modBuildingConstructRate(double amt) {
bldConstructRate += amt;
}
void modUndevelopedMaintenance(double amt) {
undevelopedMaint *= amt;
}
int get_income() const {
return prevIncome - max(grid.Maintenance, 0);
}
int get_buildingMaintenance() const {
return grid.Maintenance;
}
void set_maxLevel(Object& obj, int lv) {
if(maxPlanetLevel != lv) {
maxPlanetLevel = lv;
deltaRes = true;
if(Level > uint(maxPlanetLevel))
updateDecayLevel(obj, 0.0, true);
else if(uint(maxPlanetLevel) > Level)
ResourceModID = 0;
}
}
int get_maxLevel() {
return maxPlanetLevel;
}
double get_decayTime() const {
return DecayTimer;
}
void setProtectionDisabled(bool val) {
disableProtection = val;
deltaRes = true;
}
bool isProtected(const Object& obj, Empire@ siegeEmp = null) const {
if(disableProtection)
return false;
if(siegeEmp !is null && protectedFromMask & siegeEmp.mask != 0)
return true;
const Region@ region = obj.region;
if(region !is null) {
Empire@ owner = obj.owner;
if(region.ProtectedMask & owner.mask != 0) {
if(disableProtection)
return false;
return true;
}
}
return false;
}
int get_baseLoyalty(const Object& obj) const {
return BaseLoyalty + obj.owner.GlobalLoyalty.value;
}
int get_lowestLoyalty(const Object& obj) const {
int global = obj.owner.GlobalLoyalty.value;
int lowest = BaseLoyalty + global;
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major || emp is obj.owner)
continue;
int loy = BaseLoyalty + global + ceil(LoyaltyEffect[i]);
if(loy < lowest)
lowest = loy;
}
return lowest;
}
Empire@ get_captureEmpire(const Object& obj) const {
double best = 0;
Empire@ bestEmp;
uint owner = obj.owner.index;
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i) {
if(LoyaltyEffect[i] < best && i != owner) {
best = LoyaltyEffect[i];
@bestEmp = getEmpire(i);
}
}
return bestEmp;
}
float get_capturePct(const Object& obj) const {
double baseLoy = double(BaseLoyalty + obj.owner.GlobalLoyalty.value);
if(baseLoy == 0)
return 1.f;
float best = 0;
uint owner = obj.owner.index;
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i) {
if(i != owner) {
float pct = (-LoyaltyEffect[i]) / baseLoy;
if(pct > best)
best = pct;
}
}
return best;
}
bool get_isUnderSiege(const Object& obj) const {
bool haveSiege = false;
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i) {
if(LoyaltyEffect[i] < -0.01) {
haveSiege = true;
break;
}
}
if((orbitsMask | fakeSiegeOrbit | forceSiegeMask) & obj.owner.hostileMask != 0)
return haveSiege;
return false;
}
bool get_isOverPressure() const {
return grid.totalPressure > int(grid.pressureCap);
}
bool get_isGettingRelief(const Object& obj) const {
Empire@ owner = obj.owner;
if(owner.ForcedPeaceMask & fakeSiegeOrbit != 0)
return true;
for(uint i = 0, cnt = fleetsInOrbit.length; i < cnt; ++i) {
Empire@ other = fleetsInOrbit[i].owner;
if(owner is other || owner.ForcedPeaceMask & other.mask != 0)
return true;
}
return false;
}
bool get_enemiesInOrbit(const Object& obj) const {
Empire@ owner = obj.owner;
for(uint i = 0, cnt = fleetsInOrbit.length; i < cnt; ++i) {
Empire@ other = fleetsInOrbit[i].owner;
if(owner.isHostile(other))
return true;
}
return false;
}
int get_currentLoyalty(Player& requestor, const Object& obj) const {
Empire@ emp = requestor.emp;
if(emp is null || !emp.valid)
return BaseLoyalty + obj.owner.GlobalLoyalty.value;
if(emp is obj.owner)
return get_lowestLoyalty(obj);
return BaseLoyalty + obj.owner.GlobalLoyalty.value + ceil(LoyaltyEffect[emp.index]);
}
int getLoyaltyFacing(Player& requestor, const Object& obj, Empire@ emp) const {
Empire@ reqEmp = requestor.emp;
if(requestor != SERVER_PLAYER && reqEmp !is emp && emp !is obj.owner)
return BaseLoyalty + obj.owner.GlobalLoyalty.value;
if(!emp.valid)
return BaseLoyalty + obj.owner.GlobalLoyalty.value;
return max(BaseLoyalty + obj.owner.GlobalLoyalty.value + int(ceil(LoyaltyEffect[emp.index])), 0);
}
void modLoyaltyFacing(Empire@ emp, double mod) {
deltaLoy = true;
LoyaltyEffect[emp.index] += mod;
}
void restoreLoyalty(double amount) {
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i)
LoyaltyEffect[i] = min(LoyaltyEffect[i] + float(amount), 0.f);
}
void modBaseLoyalty(Object& obj, int mod) {
deltaLoy = true;
if(mod > 0) {
WaitingBaseLoyalty += mod;
}
else {
int take = floor(min(-double(mod), WaitingBaseLoyalty));
if(take != 0) {
WaitingBaseLoyalty -= take;
mod += take;
}
BaseLoyalty += mod;
double base = max(double(BaseLoyalty + obj.owner.GlobalLoyalty.value), 1.0);
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i)
LoyaltyEffect[i] = clamp(LoyaltyEffect[i], -base, 0.0);
}
}
void applyBaseLoyalty() {
double apply = floor(WaitingBaseLoyalty + StoredBaseLoyalty);
if(apply > 0) {
BaseLoyalty += int(apply);
double take = max(StoredBaseLoyalty, apply);
StoredBaseLoyalty -= take;
WaitingBaseLoyalty -= (apply - take);
}
}
void setLoyaltyBonus(Object& obj, int bonus) {
int prev = LoyaltyBonus;
if(prev != bonus) {
LoyaltyBonus = bonus;
modBaseLoyalty(obj, LoyaltyBonus - prev);
}
}
uint get_totalSurfaceTiles() const {
return grid.tileBuildings.length;
}
uint get_usedSurfaceTiles() const {
uint used = 0;
for(uint i = 0, cnt = grid.buildings.length; i < cnt; ++i) {
vec2u size = grid.buildings[i].type.size;
used += size.x * size.y;
}
return used;
}
vec2i get_surfaceGridSize() {
return vec2i(grid.size);
}
vec2i get_originalGridSize() {
return vec2i(originalSurfaceSize);
}
void destroySurface(Object& obj) {
if(Level >= 3)
obj.owner.modAttribute(EA_Level3Planets, AC_Add, -1);
if(icon !is null) {
Region@ region = obj.region;
if(region !is null)
region.removeStrategicIcon(0, icon);
icon.markForDeletion();
}
grid.gridDestroy(obj);
}
void fakeSiege(uint mask) {
fakeSiegeOrbit |= mask;
}
void clearFakeSiege(uint mask) {
fakeSiegeOrbit &= ~mask;
}
void forceSiege(uint mask) {
forceSiegeMask |= mask;
}
void clearForceSiege(uint mask) {
forceSiegeMask &= ~mask;
}
uint siegeMask = 0;
void updateLoyalty(Object& obj, double time) {
Region@ reg = obj.region;
if(obj.owner is null || !obj.owner.valid || reg is null) {
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i)
LoyaltyEffect[i] = 0;
return;
}
Empire@ owner = obj.owner;
bool contested = (reg !is null && reg.ContestedMask & owner.mask != 0) || obj.inCombat;
bool isSiege = false;
bool isRelief = false, externalRelief = false;
bool protect = isProtected(obj);
double baseLoyalty = max(double(BaseLoyalty + obj.owner.GlobalLoyalty.value), 1.0);
double loyTimer = config::SIEGE_LOYALTY_TIME * ceil(baseLoyalty / 10.0);
double loyMod = time * baseLoyalty / loyTimer / obj.owner.CaptureTimeDifficulty;
double orbRadiusSQ = sqr(cast<Planet>(obj).OrbitSize);
siegeMask = 0;
uint prevOrbits = orbitsMask;
uint newOrbits = 0;
//Find out if we're getting any relief
if(obj.supportCount > 0)
isRelief = true;
for(uint i = 0, cnt = fleetsInOrbit.length; i < cnt; ++i) {
Empire@ otherOwner = fleetsInOrbit[i].owner;
newOrbits |= otherOwner.mask;
if(otherOwner is owner) {
isRelief = true;
externalRelief = true;
}
else if(owner.ForcedPeaceMask & otherOwner.mask != 0) {
isRelief = true;
externalRelief = true;
}
}
if(fakeSiegeOrbit != 0) {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ otherOwner = getEmpire(i);
if(fakeSiegeOrbit & otherOwner.mask == 0)
continue;
newOrbits |= otherOwner.mask;
if(otherOwner is owner) {
isRelief = true;
externalRelief = true;
}
else if(owner.ForcedPeaceMask & otherOwner.mask != 0) {
isRelief = true;
externalRelief = true;
}
}
}
if(prevOrbits != newOrbits) {
orbitsMask = newOrbits;
deltaLoy = true;
}
//Find out our total siege status
if(fakeSiegeOrbit != 0 || forceSiegeMask != 0) {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ otherOwner = getEmpire(i);
if(forceSiegeMask & otherOwner.mask != 0 || (fakeSiegeOrbit & otherOwner.mask != 0
&& !protect && !isRelief && !isProtected(obj, otherOwner) && owner.ForcedPeaceMask & otherOwner.mask == 0)) {
if(siegeMask & otherOwner.mask != 0)
continue;
siegeMask |= otherOwner.mask;
isSiege = true;
obj.engaged = true;
contested = true;
if(forceSiegeMask & otherOwner.mask != 0)
externalRelief = false;
double localMod = loyMod / otherOwner.CaptureTimeFactor;
LoyaltyEffect[otherOwner.index] = clamp(LoyaltyEffect[otherOwner.index] - localMod, -baseLoyalty, 0.0);
deltaLoy = true;
}
}
}
uint i = randomi(0, fleetsInOrbit.length-1);
for(uint n = 0, cnt = fleetsInOrbit.length; n < cnt; ++n) {
i = (i+1) % cnt;
Object@ fleet = fleetsInOrbit[i];
Ship@ ship = cast<Ship>(fleet);
if(fleet is null || !fleet.valid || fleet.position.distanceToSQ(obj.position) > orbRadiusSQ) {
fleetsInOrbit.removeAt(i);
--i; --cnt;
continue;
}
if(ship !is null && ship.Supply <= 0.001)
continue;
Empire@ otherOwner = fleetsInOrbit[i].owner;
if(owner.isHostile(otherOwner) && !isProtected(obj, otherOwner) && !isRelief) {
if(siegeMask & otherOwner.mask != 0)
continue;
if(ship !is null && ship.isStation)
continue;
siegeMask |= otherOwner.mask;
isSiege = true;
obj.engaged = true;
contested = true;
double localMod = loyMod / otherOwner.CaptureTimeFactor;
if(ship !is null)
ship.consumeSupply(localMod * config::SIEGE_LOYALTY_SUPPLY_COST * otherOwner.CaptureSupplyFactor * owner.CaptureSupplyDifficulty);
LoyaltyEffect[otherOwner.index] = clamp(LoyaltyEffect[otherOwner.index] - localMod, -baseLoyalty, 0.0);
deltaLoy = true;
}
}
//Update base loyalty over time when not contested
if(!contested) {
if(WaitingBaseLoyalty > 0) {
double take = min(WaitingBaseLoyalty, loyMod);
if(take != 0) {
StoredBaseLoyalty += take;
WaitingBaseLoyalty -= take;
}
}
while(StoredBaseLoyalty > 1.0) {
StoredBaseLoyalty -= 1.0;
BaseLoyalty += 1;
}
}
//Regain loyalty over time when not contested
if(!contested || externalRelief) {
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i) {
double prevEff = LoyaltyEffect[i];
LoyaltyEffect[i] = clamp(prevEff + loyMod, -baseLoyalty, 0.0);
if(LoyaltyEffect[i] != prevEff)
deltaLoy = true;
}
}
if(deltaLoy && icon !is null) {
Empire@ captEmp = get_captureEmpire(obj);
float captPct = get_capturePct(obj);
icon.setCapture(captEmp, captPct);
}
//Planets under siege cannot gain supports
obj.canGainSupports = !isSiege;
}
void enterIntoOrbit(Object@ ship) {
if(fleetsInOrbit.find(ship) == -1)
fleetsInOrbit.insertLast(ship);
}
void leaveFromOrbit(Object@ ship) {
fleetsInOrbit.remove(ship);
}
void absoluteSiege(Object& obj, Empire@ fromEmpire, double loyAmount) {
LoyaltyEffect[fromEmpire.index] -= loyAmount;
obj.engaged = true;
deltaLoy = true;
}
void setQuarantined(Object& obj, bool value) {
if(value) {
if(Quarantined <= 0) {
obj.setExportEnabled(false);
obj.setImportEnabled(false);
}
++Quarantined;
}
else {
--Quarantined;
if(Quarantined <= 0) {
obj.setExportEnabled(true);
obj.setImportEnabled(true);
}
}
deltaAff = true;
}
void setContestion(Object& obj, bool value) {
if(value)
++Contestion;
else
--Contestion;
deltaPop = true;
}
bool get_hasContestion() {
return Contestion > 0;
}
bool get_isContested(const Object& obj) const {
int cont = Contestion;
if(cont > 0)
return true;
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(!emp.major || emp is obj.owner)
continue;
if(LoyaltyEffect[i] < -0.01)
return true;
}
return false;
}
uint get_level() {
return Level;
}
uint get_levelChain() {
return LevelChainId;
}
uint get_resourceLevel() {
return ResourceLevel;
}
bool get_quarantined() {
return Quarantined > 0;
}
void setResourceLevel(Object& obj, uint lvl) {
if(maxPlanetLevel >= 0)
lvl = min(lvl, maxPlanetLevel);
if(lvl != ResourceLevel)
deltaRes = true;
ResourceLevel = lvl;
}
void setNeedsPopulationForLevel(bool value) {
needsPopulationForLevel = value;
deltaPop = true;
}
void updateDecayLevel(Object& obj, double time, bool wasManual = false) {
//Find the correct current target level
uint TargetLevel = min(ResourceLevel, uint(maxPlanetLevel));
bool isInstant = false;
while(TargetLevel > 0 && needsPopulationForLevel) {
if(Population < getPlanetLevelRequiredPop(LevelChainId, TargetLevel) - 0.001) {
TargetLevel -= 1;
}
else {
break;
}
}
if(TargetLevel < ResourceLevel && Level > TargetLevel) {
isInstant = true;
if(isSendingColonizers)
wasManual = true;
}
//Update population level
uint newPopLevel = max(Level, ResourceLevel);
if(newPopLevel != PopLevel) {
const PlanetLevel@ prevLevel = getPlanetLevel(LevelChainId, PopLevel);
const PlanetLevel@ newLevel = getPlanetLevel(LevelChainId, newPopLevel);
modMaxPopulation(obj, int(newLevel.population) - int(prevLevel.population));
PopLevel = newPopLevel;
}
//Update decay if we dropped levels
if(TargetLevel >= Level || (TargetLevel < Level && (wasManual || isInstant))) {
bool wasDecay = DecayTimer >= 0.0;
DecayLevel = TargetLevel;
DecayTimer = -1.0;
if(wasDecay)
updateIcon(obj);
setLevel(obj, TargetLevel, false, wasManual);
}
else if(TargetLevel < Level) {
DecayLevel = TargetLevel;
double timer = config::LEVEL_DECAY_TIMER / obj.owner.PlanetDecaySpeed;
if(DecayTimer < 0 || DecayTimer > timer)
DecayTimer = timer;
updateIcon(obj);
deltaRes = true;
}
//Update decay timer
if(time > 0 && DecayLevel < Level) {
DecayTimer -= time;
if(DecayTimer < 0) {
if(DecayLevel < Level)
DecayTimer = config::LEVEL_DECAY_TIMER * (DecayLevel + 1) / obj.owner.PlanetDecaySpeed;
else
DecayTimer = -1.0;
setLevel(obj, Level - 1, false);
}
}
}
void setLevel(Object& obj, uint lvl, bool first = false, bool wasManual = false) {
if(!first && lvl == Level)
return;
const PlanetLevel@ prevLevel = getPlanetLevel(LevelChainId, Level);
const PlanetLevel@ newLevel = getPlanetLevel(LevelChainId, lvl);
uint pLevel = Level;
Level = lvl;
deltaRes = true;
applyLevel(obj, prevLevel, newLevel, first);
//Unlock achievement "Reach Level 4"
if(pLevel < 4 && Level == 4 && obj.owner is playerEmpire && !getCheatsEverOn())
unlockAchievement("ACH_LEVEL4");
obj.setResourceLevel(lvl, wasManual);
if(pLevel < 3 && Level >= 3)
obj.owner.modAttribute(EA_Level3Planets, AC_Add, 1);
else if(pLevel >= 3 && Level < 3)
obj.owner.modAttribute(EA_Level3Planets, AC_Add, -1);
}
void applyLevel(Object& obj, const PlanetLevel@ prevLevel, const PlanetLevel@ newLevel, bool first = false) {
Region@ region = obj.region;
double NeighbourLoyalty;
if(first) {
MaxPopulation = newLevel.population;
BaseLoyalty = newLevel.baseLoyalty;
NeighbourLoyalty = newLevel.neighbourLoyalty;
prevBaseLoyalty = BaseLoyalty;
DecayLevel = Level;
DecayTimer = -1.0;
obj.modSupplyCapacity(+newLevel.baseSupport);
}
else {
NeighbourLoyalty = newLevel.neighbourLoyalty - prevLevel.neighbourLoyalty;
obj.modSupplyCapacity(newLevel.baseSupport - prevLevel.baseSupport);
}
if(obj.owner !is null && obj.owner.valid) {
obj.owner.points += (newLevel.points - prevLevel.points);
if(region !is null)
region.modNeighbourLoyalty(obj.owner, NeighbourLoyalty);
}
if(icon !is null)
updateIcon(obj);
}
void setLevelChain(Object& obj, uint chainId, bool wasManual = true) {
if(chainId == LevelChainId)
return;
auto@ chain = getLevelChain(chainId);
if(chain is null)
return;
//Reset back to that chain's level 0 to apply everything
const PlanetLevel@ prevLevel = getPlanetLevel(LevelChainId, Level);
const PlanetLevel@ newLevel = getPlanetLevel(chainId, 0);
applyLevel(obj, prevLevel, newLevel);
Level = 0;
deltaRes = true;
LevelChainId = chainId;
obj.setResourceLevel(0, wasManual);
ResourceModID = uint(-1);
}
void changeSurfaceOwner(Object& obj, Empire@ prevOwner) {
const PlanetLevel@ lvl = getPlanetLevel(LevelChainId, obj.level);
Region@ region = obj.region;
//Reset all loyalty values
for(uint i = 0, cnt = LoyaltyEffect.length; i < cnt; ++i)
LoyaltyEffect[i] = 0;
applyBaseLoyalty();
deltaLoy = true;
grid.gridChangeOwner(obj, prevOwner, obj.owner);
if(icon !is null) {
Empire@ captEmp = get_captureEmpire(obj);
float captPct = get_capturePct(obj);
icon.setCapture(captEmp, captPct);
}
//Remove stuff from old empire
if(prevOwner !is null && prevOwner.valid) {
if(prevIncome != 0) {
if(prevIncome > 0)
prevOwner.modTotalBudget(-prevIncome, MoT_Planet_Income);
else
prevOwner.modTotalBudget(-prevIncome, MoT_Planet_Upkeep);
}
if(prevResearch != 0)
prevOwner.modResearchRate(-double(prevResearch) * TILE_RESEARCH_RATE);
if(prevInfluence != 0)
prevOwner.modInfluenceIncome(-prevInfluence);
if(prevEnergy != 0)
prevOwner.modEnergyIncome(-double(prevEnergy) * TILE_ENERGY_RATE);
if(region !is null)
region.modNeighbourLoyalty(prevOwner, -lvl.neighbourLoyalty);
if(prevPopulation != 0)
prevOwner.modTotalPopulation(-prevPopulation);
if(prevDefense < 0)
prevOwner.modDefenseRate(prevDefense);
else if(prevDefense > 0)
prevOwner.modLocalDefense(-prevDefense);
if(Level >= 3)
prevOwner.modAttribute(EA_Level3Planets, AC_Add, -1);
prevOwner.points -= lvl.points;
}
//Move stuff to new empire
if(obj.owner !is null && obj.owner.valid) {
if(prevIncome != 0) {
if(prevIncome > 0)
obj.owner.modTotalBudget(prevIncome, MoT_Planet_Income);
else
obj.owner.modTotalBudget(prevIncome, MoT_Planet_Upkeep);
}
if(prevResearch != 0)
obj.owner.modResearchRate(double(prevResearch) * TILE_RESEARCH_RATE);
if(prevInfluence != 0)
obj.owner.modInfluenceIncome(prevInfluence);
if(prevEnergy != 0)
obj.owner.modEnergyIncome(double(prevEnergy) * TILE_ENERGY_RATE);
if(region !is null)
region.modNeighbourLoyalty(obj.owner, +lvl.neighbourLoyalty);
if(grid.usableTiles == 0)
grid.colonize(obj);
if(prevPopulation != 0)
obj.owner.modTotalPopulation(+prevPopulation);
if(prevDefense < 0)
obj.owner.modDefenseRate(-prevDefense);
else if(prevDefense > 0)
obj.owner.modLocalDefense(prevDefense);
if(Level >= 3)
obj.owner.modAttribute(EA_Level3Planets, AC_Add, +1);
obj.owner.points += lvl.points;
}
//Remove colonization orders
for(uint i = 0, cnt = colonization.length; i < cnt; ++i)
prevOwner.unregisterColonization(obj, colonization[i].target, false);
colonization.length = 0;
}
void changeSurfaceRegion(Object& obj, Region@ prevRegion, Region@ newRegion) {
if(icon !is null)
icon.hintParentObject(obj.region, false);
const PlanetLevel@ lvl = getPlanetLevel(LevelChainId, obj.level);
if(obj.owner !is null && obj.owner.valid) {
if(prevRegion !is null)
prevRegion.modNeighbourLoyalty(obj.owner, -lvl.neighbourLoyalty);
if(newRegion !is null)
newRegion.modNeighbourLoyalty(obj.owner, +lvl.neighbourLoyalty);
}
if(icon !is null) {
if(!wasMoving) {
if(prevRegion !is null)
prevRegion.removeStrategicIcon(0, icon);
if(newRegion !is null)
newRegion.addStrategicIcon(0, obj, icon);
else
icon.clearStrategic();
}
}
}
double get_population() const {
return Population;
}
uint get_maxPopulation() const {
return MaxPopulation;
}
void colonyShipArrival(Object& obj, Empire@ owner, double population) {
if(Quarantined != 0)
return;
if(obj.owner !is owner && (obj.owner is null || !obj.owner.valid)) {
@obj.owner = owner;
owner.recordEvent(stat::Planets, 1, obj.name);
}
addPopulation(obj, population);
modIncomingPop(-population);
}
void addPopulation(Object& obj, double amt, bool allowOver = true) {
if(Population < double(MaxPopulation) || allowOver) {
Population = Population + amt;
if(!allowOver)
Population = min(Population, double(MaxPopulation));
calculatePopVars(obj);
deltaPop = true;
}
}
void removePopulation(Object& obj, double amt, double minimum) {
if(Population > minimum) {
Population = max(Population - amt, minimum);
calculatePopVars(obj);
if(Population <= 0.00001)
@obj.owner = defaultEmpire;
deltaPop = true;
}
}
void modMaxPopulation(Object& obj, int amt) {
deltaPop = true;
if(amt > 0) {
if(PopulationPenalty >= amt) {
PopulationPenalty -= amt;
}
else if(PopulationPenalty > 0) {
amt -= PopulationPenalty;
PopulationPenalty = 0;
MaxPopulation += amt;
}
else {
MaxPopulation += amt;
}
}
else if(amt < 0) {
int prev = MaxPopulation;
MaxPopulation = max(1, MaxPopulation - (-amt));
PopulationPenalty += (-amt) - (prev - MaxPopulation);
}
}
//Colonize different planets
void colonize(Object& obj, Object& other, double toPopulation) {
if(toPopulation < 1.0)
return;
if(!other.isPlanet || other.quarantined)
return;
for(uint i = 0, cnt = colonization.length; i < cnt; ++i) {
if(colonization[i].target is other) {
if(colonization[i].targetPopulation < toPopulation)
colonization[i].targetPopulation = toPopulation;
return;
}
}
ColonizationOrder order;
@order.target = other;
order.targetPopulation = toPopulation;
colonization.insertLast(order);
obj.owner.registerColonization(obj, other);
deltaCol = true;
isSendingColonizers = true;
}
void processColonization(Object& obj) {
bool isSending = false;
const double popPerShip = obj.owner.PopulationPerColonizer;
if(Population < 1.0 + popPerShip || MaxPopulation <= 1.0 || obj.owner.ForbidColonization != 0) {
//If we have insufficient population due to a low maximum, cancel all colony orders
if(MaxPopulation <= 1.0 || obj.owner.ForbidColonization != 0) {
uint cnt = colonization.length;
if(cnt > 0) {
for(uint i = 0; i < cnt; ++i)
obj.owner.unregisterColonization(obj, colonization[i].target, cancel=false);
colonization.length = 0;
}
isSendingColonizers = false;
}
return;
}
//Process a random colonization order per attempt
for(uint i = 0, cnt = colonization.length; i < cnt; ++i) {
ColonizationOrder@ order = colonization[i];
//Remove invalid colonization orders
if(order.target is null || !order.target.valid) {
colonization.removeAt(i);
obj.owner.unregisterColonization(obj, order.target);
--i; --cnt;
continue;
}
if(INSTANT_COLONIZE) {
order.target.colonyShipArrival(obj.owner, 1.0);
colonization.removeAt(i);
obj.owner.unregisterColonization(obj, order.target);
--i; --cnt;
continue;
}
Empire@ owner = obj.owner;
Empire@ otherOwner = order.target.owner;
bool targetVisible = owner is otherOwner || order.target.isVisibleTo(owner);
//Also remove colonization orders that are directed at colonies owned by foreign empires
// but only if we can see the target
if(otherOwner.valid && otherOwner !is obj.owner && targetVisible) {
colonization.removeAt(i);
obj.owner.unregisterColonization(obj, order.target);
deltaCol = true;
--i; --cnt;
continue;
}
double targPop = targetVisible ? order.target.population : 0.0;
if(targPop >= order.targetPopulation) {
//Colonization is actually complete
colonization.removeAt(i);
obj.owner.unregisterColonization(obj, order.target);
--i; --cnt;
deltaCol = true;
continue;
}
if(targPop + order.inTransit < order.targetPopulation - 0.0001) {
bool canSend = false;
if(order.totalSent < 1.0)
canSend = true;
else if(!needsPopulationForLevel)
canSend = true;
else {
auto@ lv = getPlanetLevel(LevelChainId, ResourceLevel);
canSend = Population - popPerShip >= lv.requiredPop;
}
if(canSend) {
//Takes one minute to send 1B population
colonyShipTimer += float(60.0 / (1.0 / popPerShip));
Population -= popPerShip;
order.inTransit += popPerShip;
order.totalSent += popPerShip;
createColonizer(obj, order.target, popPerShip, colonyshipAccel);
deltaPop = true;
isSending = true;
}
break;
}
}
isSendingColonizers = isSending;
}
bool get_isBeingColonized(Player& pl, const Object& obj) {
Empire@ emp = pl.emp;
if(emp is null)
return false;
return ColonizingMask & emp.mask != 0;
}
bool isEmpireColonizing(Empire@ emp) const {
if(emp is null)
return false;
return ColonizingMask & emp.mask != 0;
}
void setBeingColonized(Empire@ emp, bool value) {
if(value)
ColonizingMask |= emp.mask;
else
ColonizingMask &= ~emp.mask;
if(emp is playerEmpire && icon !is null)
icon.setBeingColonized(value);
deltaRes = true;
}
void modColonyShipAccel(double amt) {
if(amt > 1.0)
colonyshipAccel += amt - 1.0;
else
colonyshipAccel -= (1.0 / amt) - 1.0;
}
double get_colonyShipAccel(const Object& obj) {
return colonyshipAccel * COLONYSHIP_BASE_ACCEL * obj.owner.ModSpeed.value * obj.owner.ColonizerSpeed;
}
bool get_isColonizing() const {
return colonization.length != 0;
}
bool get_canSafelyColonize(const Object& obj) const {
if(Level < 1)
return false;
auto@ lv = getPlanetLevel(LevelChainId, ResourceLevel);
//Calculate growth rate
double growthFactor = growthRate;
float debtFactor = obj.owner.DebtFactor;
for(; debtFactor > 0; debtFactor -= 1.f)
growthFactor *= 0.33f + 0.67f * (1.f - min(debtFactor, 1.f));
growthFactor *= obj.owner.PopulationGrowthFactor;
growthFactor *= config::COLONIZING_GROWTH_PENALTY;
growthFactor *= getPlanetLevel(LevelChainId, min(Level,obj.primaryResourceLimitLevel)).popGrowth;
if(obj.inCombat)
growthFactor = 0;
//Calculate projected final population
double colonizes = colonization.length + 1.0;
if(!isSendingColonizers)
colonizes = 1.0;
double finalPop = Population;
finalPop -= colonizes;
finalPop += colonizes * (1.0 - obj.owner.PopulationPerColonizer) * growthFactor;
if(!needsPopulationForLevel)
return finalPop >= 1.0;
else
return finalPop >= lv.requiredPop;
}
uint get_colonyOrderCount() const {
return colonization.length;
}
bool get_isSendingColonyShips() const {
return isSendingColonizers;
}
void flagColonizing() {
isSendingColonizers = true;
}
Object@ get_colonyTarget(uint index) const {
uint count = colonization.length;
if(index < count)
return colonization[index].target;
else
return null;
}
bool hasColonyTarget(Object& other) const {
for(uint i = 0, cnt = colonization.length; i < cnt; ++i)
if(colonization[i].target is other)
return true;
return false;
}
void stopColonizing(Object& obj, Object& other) {
for(uint i = 0, cnt = colonization.length; i < cnt; ++i) {
if(colonization[i].target is other) {
colonization.removeAt(i);
obj.owner.unregisterColonization(obj, other);
deltaCol = true;
break;
}
}
}
void modIncomingPop(double amount) {
incomingPop += amount;
if(incomingPop < 0.0)
incomingPop = 0.0;
}
double get_IncomingPop() {
return incomingPop;
}
void reducePopInTransit(Object& target, double amount) {
for(uint i = 0, cnt = colonization.length; i < cnt; ++i) {
if(colonization[i].target is target) {
colonization[i].inTransit -= amount;
return;
}
}
}
void buildWithDefense(Object& obj, double time, double defense) {
if(defense <= 0 || !obj.canGainSupports) {
@defenseDesign = null;
defenseLabor = -1.0;
return;
}
if(defenseDesign is null) {
@defenseDesign = getDefenseDesign(obj.owner, defense);
if(defenseDesign is null)
return;
}
if(defenseLabor < 0) {
//Start the build
defenseLabor = getLaborCost(defenseDesign, 1);
}
defenseLabor -= time * defense;
if(defenseLabor <= 0) {
//Build defense here
defenseDesign.decBuilt(); //automatic built doesn't increment
createShip(obj, defenseDesign, obj.owner, obj, false, true);
defenseLabor = -1.0;
@defenseDesign = null;
}
}
void spawnDefenseShips(Object& obj, double totalLaborValue) {
Empire@ owner = obj.owner;
array<const Design@> designs;
{
set_int chosenDesigns;
ReadLock lock(owner.designMutex);
uint designCount = owner.designCount;
for(uint i = 0; i < designCount; ++i) {
const Design@ dsg = owner.designs[i];
if(dsg.obsolete)
continue;
if(dsg.newest() !is dsg)
continue;
if(!dsg.hasTag(ST_Support))
continue;
if(dsg.hasTag(ST_HasMaintenanceCost))
continue;
@dsg = dsg.newest();
if(chosenDesigns.contains(dsg.id))
continue;
chosenDesigns.insert(dsg.id);
designs.insertLast(dsg);
}
}
uint dsgCount = designs.length;
if(dsgCount == 0)
return;
double cheapest = 0;
array<double> costs(dsgCount);
for(uint i = 0; i < dsgCount; ++i) {
double cost = getLaborCost(designs[i], 1);
costs[i] = cost;
if(i == 0 || cost < cheapest)
cheapest = cost;
}
while(totalLaborValue >= cheapest && totalLaborValue > 0) {
bool spawnedAny = false;
uint base = randomi(0,dsgCount-1);
for(uint i = 0; i < dsgCount; ++i) {
uint index = (i + base) % dsgCount;
double cost = costs[index];
if(cost <= totalLaborValue) {
designs[index].decBuilt(); //automatic built doesn't increment
createShip(obj, designs[index], owner, obj, detectLeader=false, free=true, forceLeader=true);
spawnedAny = true;
totalLaborValue -= cost;
}
}
if(!spawnedAny)
break;
}
}
void takeoverPlanet(Object& obj, Empire@ newOwner, double supportRatio) {
if(obj.owner is newOwner)
return;
//Conquer edict rewards
if(newOwner !is null) {
Empire@ prevOwner = obj.owner;
Empire@ master = newOwner.SubjugatedBy;
if(master !is null && master.getEdictType() == DET_Conquer) {
if(master.getEdictEmpire() is prevOwner)
giveRandomReward(newOwner, (double(Level)+1.0)*2.0);
}
}
//Check for 'Capture Enemy Homeworld' achievement
if(obj is obj.owner.Homeworld && !getCheatsEverOn()) {
if(newOwner is playerEmpire)
unlockAchievement("ACH_TAKE_HOMEWORLD");
if(mpServer && newOwner.player !is null)
clientAchievement(newOwner.player, "ACH_TAKE_HOMEWORLD");
}
//Take over the planet
@obj.owner = newOwner;
obj.takeoverFleet(newOwner, supportRatio);
newOwner.recordEvent(stat::Planets, 1, obj.name);
}
void annex(Player& from, Object& obj, Empire@ forEmpire = null) {
//Check that we're allowed to do this annex
if(from != SERVER_PLAYER) {
if(forEmpire is null)
@forEmpire = from.emp;
Empire@ fromEmp = from.emp;
if(fromEmp is null || fromEmp !is forEmpire)
return;
}
if(forEmpire is obj.owner || forEmpire is null)
return;
if(obj.owner is null || !obj.owner.valid)
return;
//Check that the loyalty is 0
if(obj.getLoyaltyFacing(forEmpire) > 0)
return;
//Notify previous owner
obj.owner.notifyWarEvent(obj, WET_LostPlanet);
//Annex the planet
obj.takeoverPlanet(forEmpire);
forEmpire.modAttribute(EA_PlanetsConquered, AC_Add, 1.0);
}
void abandon(Object& obj) {
if(obj.isContested)
return;
@obj.owner = defaultEmpire;
Population = 0;
calculatePopVars(obj);
deltaPop = true;
}
void forceAbandon(Object& obj) {
@obj.owner = defaultEmpire;
Population = 0;
calculatePopVars(obj);
deltaPop = true;
}
void giveBasicIconVision(Object& obj, Empire@ emp) {
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ e = getEmpire(i);
if(e is emp) {
int mem = iconMemory[i];
mem &= 0xffff;
uint resource = 0xfffe;
if(obj.nativeResourceCount != 0) {
const ResourceType@ type = getResource(obj.nativeResourceType[0]);
resource = type.id;
}
mem |= resource << 16;
iconMemory[i] = mem;
if(emp is playerEmpire)
updateIcon(obj);
break;
}
}
}
void updateIconVision(Object& obj) {
uint resource = 0xfffe;
if(obj.nativeResourceCount != 0) {
const ResourceType@ type = getResource(obj.nativeResourceType[0]);
resource = type.id;
}
//Update remembered icon states
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
Empire@ emp = getEmpire(i);
if(obj.isVisibleTo(emp)) {
int mem = iconMemory[i];
if(mem != -1) {
iconMemory[i] = -1;
if(emp is playerEmpire)
updateIcon(obj);
}
}
else {
int mem = iconMemory[i];
if(mem == -1) {
mem = 0;
mem |= Level;
mem |= obj.owner.id << 8;
mem |= resource << 16;
iconMemory[i] = mem;
if(emp is playerEmpire)
updateIcon(obj);
}
}
}
}
void updateIcon(Object& obj) {
//Update actual icon
if(icon !is null) {
if(obj.isVisibleTo(playerEmpire)) {
//Use active
icon.setLevel(Level);
if(obj.nativeResourceCount != 0) {
const ResourceType@ type = getResource(obj.nativeResourceType[0]);
Object@ destination = obj.getNativeResourceDestination(playerEmpire, 0);
icon.setResource(type.id);
icon.setState(!obj.nativeResourceUsable[0],
destination !is null,
!obj.exportEnabled || ((type.isMaterial(Level) || destination !is null)
&& (destination is null || !destination.owner.valid || destination.owner is obj.owner)),
DecayTimer > 0.0);
}
else {
icon.setResource(uint(-1));
icon.setState(false, false, true, false);
}
}
else if(obj.isKnownTo(playerEmpire) && playerEmpire.valid) {
int mem = iconMemory[playerEmpire.index];
int level = mem & 0xff;
int empId =(mem & 0xff00) >> 8;
int res = (mem & 0xffff0000) >> 16;
icon.setLevel(level);
icon.setOwner(getEmpireByID(empId));
if(res == 0xfffe) {
icon.setResource(uint(-1));
icon.setState(false, false, true, false);
}
else {
icon.setResource(res);
Object@ destination = obj.getNativeResourceDestination(playerEmpire, 0);
icon.setState(false, destination !is null, true, false);
}
}
}
}
void onManualResourceRemoved(Object& obj) {
setResourceLevel(obj, obj.getResourceTargetLevel());
updateDecayLevel(obj, 0.0, wasManual = true);
}
void addAffinity(uint aff) {
affinities.insertLast(aff);
deltaAff = true;
}
void removeAffinity(uint aff) {
uint cnt = affinities.length;
for(uint i = 0; i < cnt; ++i) {
if(aff == affinities[i]) {
affinities.removeAt(i);
deltaAff = true;
break;
}
}
}
uint getAffinitiesMatching(uint resource) {
uint amt = 0;
for(uint i = 0, cnt = affinities.length; i < cnt; ++i) {
if(affinityHas(affinities[i], resource))
amt += 1;
}
return amt;
}
void calculatePopVars(Object& obj) {
double income = 0.0;
double prevTotal = 0.0;
double prevPop = 0.0;
double pop = Population;
if(pop > double(maxPopulation))
pop = double(maxPopulation) + (pop - double(maxPopulation)) * obj.owner.OverpopulationBenefitFactor;
uint prevCap = 0;
double baseCap = 0.0;
double loy = 0.0, prevLoy = 1.0;
uint maxPlanetLevel = getMaxPlanetLevel(LevelChainId);
uint nativeCnt = obj.nativeResourceCount;
uint nativeLevel = 0;
uint limitLevel = obj.primaryResourceLimitLevel;
if(nativeCnt != 0) {
auto@ type = getResource(obj.nativeResourceType[0]);
if(type !is null)
nativeLevel = type.level;
}
float nativeLevelPct = 0.f;
for(uint i = 0; i <= maxPlanetLevel && pop > 0; ++i) {
const PlanetLevel@ level = getPlanetLevel(LevelChainId, i);
double levelPop = level.population;
double popDiff = levelPop - prevPop;
double popFactor = min(pop / popDiff, 1.0);
int lvIncome = level.baseIncome;
if(nativeLevel == i)
nativeLevelPct = popFactor;
if(i <= limitLevel)
lvIncome += level.resourceIncome;
if(Level == 0 && i != 0)
popFactor = 0.0;
//Calculate base pressure
double capDiff = level.basePressure - prevCap;
baseCap += popFactor * capDiff;
//Calculate base income
double incDiff = lvIncome - prevTotal;
income += popFactor * incDiff;
//Calculate base loyalty
if(i <= Level) {
double loyDiff = level.baseLoyalty - prevLoy;
loy += popFactor * loyDiff;
}
pop -= popDiff;
prevTotal = lvIncome;
prevPop = level.population;
prevCap = level.basePressure;
prevLoy = level.baseLoyalty;
}
if(pop > 0) {
const PlanetLevel@ level = getPlanetLevel(LevelChainId, maxPlanetLevel);
baseCap += pop * 3.0;
income += pop * 30.0;
}
int newLoy = max(int(loy), 1);
if(newLoy != prevBaseLoyalty) {
modBaseLoyalty(obj, newLoy - prevBaseLoyalty);
prevBaseLoyalty = newLoy;
}
popIncome = income;
if(needsPopulationForLevel)
obj.resourceEfficiency = nativeLevelPct;
else
obj.resourceEfficiency = 1.f;
int cap = int(round(baseCap * pressureCapFactor)) + pressureCapMod + obj.owner.GlobalPressureCap;
if(nativeCnt > 0) {
Object@ dest = obj.nativeResourceDestination[0];
if(dest !is null && dest.owner is obj.owner && obj.nativeResourceUsable[0])
cap -= int(getPlanetLevel(LevelChainId, nativeLevel).exportPressurePenalty);
}
grid.pressureCap = uint(max(cap, 0));
int newPop = 0;
if(Population < MaxPopulation)
newPop = int(Population);
else
newPop = int(Population) + (Population - MaxPopulation) * obj.owner.OverpopulationBenefitFactor;
if(prevPopulation != newPop) {
obj.owner.modTotalPopulation(newPop - prevPopulation);
prevPopulation = newPop;
}
}
void modIncome(int amount) {
bonusIncome += amount;
deltaPop = true;
}
void setGraphicsFlag(Object& obj, uint flag, bool value) {
uint newValue = gfxFlags;
if(value)
newValue |= flag;
else
newValue &= ~flag;
if(newValue != gfxFlags) {
gfxFlags = newValue;
deltaRes = true;
PlanetNode@ plNode = cast<PlanetNode>(obj.getNode());
if(plNode !is null)
plNode.flags = gfxFlags;
}
}
uint get_planetGraphicsFlags() const {
return gfxFlags;
}
float occTimer = 0.f;
void surfaceTick(Object& obj, double time) {
//Set icon visibility
if(icon !is null) {
icon.visible = obj.isVisibleTo(playerEmpire);
icon.hintParentObject(obj.region, false);
updateIconVision(obj);
if(wasMoving != obj.isMoving) {
if(wasMoving) {
if(obj.region !is null)
obj.region.addStrategicIcon(0, obj, icon);
}
else {
if(obj.region !is null)
obj.region.removeStrategicIcon(0, icon);
}
wasMoving = obj.isMoving;
}
}
//Grow population over time
double localMax = double(MaxPopulation);
if(overpopulation > 0)
localMax += double(overpopulation);
grid.localMax = localMax;
if(Population >= 0.9999 && Population < localMax && bombardDecay <= 0 && MaxPopulation > 1) {
double growthFactor = growthRate;
if(Quarantined != 0)
growthFactor = 0;
float debtFactor = obj.owner.DebtFactor;
for(; debtFactor > 0; debtFactor -= 1.f)
growthFactor *= 0.33f + 0.67f * (1.f - min(debtFactor, 1.f));
if(needsPopulationForLevel)
growthFactor *= getPlanetLevel(LevelChainId, min(Level,obj.primaryResourceLimitLevel)).popGrowth / 60.0;
else
growthFactor *= getPlanetLevel(LevelChainId, Level).popGrowth / 60.0;
growthFactor *= obj.owner.PopulationGrowthFactor;
if(colonization.length != 0)
growthFactor *= config::COLONIZING_GROWTH_PENALTY;
if(obj.inCombat)
growthFactor *= 0.1;
Population = min(localMax, Population + growthFactor * time);
calculatePopVars(obj);
deltaPop = true;
}
else if(Population > localMax) {
double decay = (1.0 / 60.0) * time * obj.owner.PopulationDecayFactor;
Population = max(double(localMax), Population - decay);
calculatePopVars(obj);
deltaPop = true;
}
else if(bombardDecay > 0 && Population > 1.0) {
double decay = (Population * 0.25 * double(bombardDecay) / 60.0) * time;
Population = max(1.0, Population - decay);
calculatePopVars(obj);
deltaPop = true;
}
//Check if the resources should be enabled
if(Population >= 0.9999) {
if(!obj.areResourcesEnabled) {
obj.enableResources();
setBeingColonized(obj.owner, false);
}
}
else {
if(obj.areResourcesEnabled)
obj.disableResources();
}
//Check planet level based on resources
uint mod = obj.resourceModID;
if(mod != ResourceModID) {
ResourceModID = mod;
setResourceLevel(obj, obj.getResourceTargetLevel());
updateIcon(obj);
calculatePopVars(obj);
}
//Update pressures from resource component
grid.totalPressure = obj.totalResourcePressure;
for(uint i = 0; i < TR_COUNT; ++i) {
float prev = grid.pressures[i];
float newVal = obj.resourcePressure[i];
if(prev != newVal) {
grid.pressures[i] = newVal;
grid.delta = true;
}
}
//Update object loyalty
occTimer += time;
if(occTimer > 1.f) {
updateLoyalty(obj, occTimer);
occTimer = 0.f;
}
Region@ reg = obj.region;
if(siegeMask != 0 && reg !is null) {
reg.SiegedMask |= obj.owner.mask;
reg.SiegingMask |= siegeMask;
}
//Do level decay
updateDecayLevel(obj, time);
//Update stuff on the surface
uint prevBuildings = grid.buildings.length;
grid.tick(obj, time);
if(prevBuildings != grid.buildings.length)
++SurfaceModId;
//Update resources from grid
int newIncome = ceil(grid.resources[TR_Money] * TILE_MONEY_RATE * obj.owner.MoneyGenerationFactor) + popIncome + bonusIncome;
if(prevIncome != newIncome) {
if(prevIncome < 0) {
if(newIncome < 0) {
obj.owner.modMaintenance(-(newIncome - prevIncome), MoT_Planet_Upkeep);
}
else {
obj.owner.modMaintenance(prevIncome, MoT_Planet_Upkeep);
obj.owner.modTotalBudget(newIncome, MoT_Planet_Income);
}
}
else {
if(newIncome < 0) {
obj.owner.modTotalBudget(-prevIncome, MoT_Planet_Income);
obj.owner.modMaintenance(-newIncome, MoT_Planet_Upkeep);
}
else {
obj.owner.modTotalBudget(newIncome - prevIncome, MoT_Planet_Income);
}
}
prevIncome = newIncome;
}
double newEnergy = max(grid.resources[TR_Energy], 0.0);
if(newEnergy != prevEnergy) {
obj.owner.modEnergyIncome(double(newEnergy - prevEnergy) * TILE_ENERGY_RATE);
prevEnergy = newEnergy;
}
double newInfluence = max(grid.resources[TR_Influence], 0.0);
if(int(newInfluence) != int(prevInfluence)) {
obj.owner.modInfluenceIncome(int(newInfluence) - int(prevInfluence));
prevInfluence = newInfluence;
}
double newResearch = max(grid.resources[TR_Research], 0.0);
if(newResearch != prevResearch) {
obj.owner.modResearchRate(double(newResearch - prevResearch) * TILE_RESEARCH_RATE);
prevResearch = newResearch;
}
double newDefense = max(grid.resources[TR_Defense], 0.0) * DEFENSE_LABOR_PM / 60.0 * obj.owner.DefenseGenerationFactor;
bool pooled = obj.canGainSupports && obj.owner.hasDefending;
if(!pooled) {
if(prevDefense < -0.01) {
prevDefense = -prevDefense;
obj.owner.modDefenseRate(-prevDefense);
obj.owner.modLocalDefense(newDefense);
}
buildWithDefense(obj, time, newDefense);
if(newDefense != prevDefense) {
obj.owner.modLocalDefense(newDefense - prevDefense);
prevDefense = newDefense;
}
}
else {
if(prevDefense > 0.01) {
obj.owner.modLocalDefense(-prevDefense);
obj.owner.modDefenseRate(prevDefense);
prevDefense = -prevDefense;
}
if(-newDefense != prevDefense) {
obj.owner.modDefenseRate(newDefense - (-prevDefense));
prevDefense = -newDefense;
}
}
double prevLabor = obj.distributedLabor;
double laborRes = max(grid.resources[TR_Labor], 0.0);
double labor = laborRes * TILE_LABOR_RATE * obj.owner.LaborGenerationFactor;
if(prevLabor != labor) {
bool hasLabor = laborRes > 0.0001 || obj.laborIncome > 0.0001 || obj.currentLaborStored > 0.001;
obj.canBuildShips = hasLabor;
obj.canBuildAsteroids = hasLabor;
obj.canBuildOrbitals = hasLabor;
obj.canTerraform = hasLabor;
obj.setDistributedLabor(labor);
}
//Apply colonization steps
uint colCnt = colonization.length;
if(colCnt != 0) {
//Send colonization step when ready
if(colonyShipTimer > 0)
colonyShipTimer -= float(time);
if(colonyShipTimer <= 0) {
processColonization(obj);
deltaPop = true;
}
}
else {
if(isSendingColonizers)
deltaPop = true;
isSendingColonizers = false;
}
//Deal with surface data grids
if(reqSurfaceData)
requestSurface(obj);
}
void _writePop(Message& msg) {
int maxLevel = getLevelChain(LevelChainId).levels.length-1;
msg.writeSmall(MaxPopulation);
msg << float(Population);
msg.writeSignedSmall(prevIncome);
msg << Contestion;
if(growthRate != 1.0) {
msg.write1();
msg << float(growthRate);
}
else {
msg.write0();
}
msg.writeBit(needsPopulationForLevel);
msg.writeLimited(ResourceLevel,maxLevel);
msg.writeBit(isSendingColonizers);
}
void _writeRes(Message& msg) {
int maxLevel = getLevelChain(LevelChainId).levels.length-1;
msg.writeLimited(LevelChainId,getLevelChainCount());
msg.writeLimited(Level,maxLevel);
if(DecayLevel < Level) {
msg.write1();
msg.writeLimited(DecayLevel,Level-1);
msg << float(DecayTimer);
}
else {
msg.write0();
}
if(maxPlanetLevel != -1) {
msg.write1();
msg.writeSmall(maxPlanetLevel);
}
else {
msg.write0();
}
msg.writeBit(ColonizingMask != 0);
if(ColonizingMask != 0)
msg << ColonizingMask;
msg << disableProtection;
//TODO: This probably belongs somewhere else, but currently only changes when resources change
if(abs(colonyshipAccel - 1.0) > 0.0001) {
msg.writeBit(true);
msg << float(colonyshipAccel);
}
else {
msg.writeBit(false);
}
if(gfxFlags != 0) {
msg.write1();
msg.writeSmall(gfxFlags);
}
else {
msg.write0();
}
}
void _writeAff(Message& msg) {
uint cnt = affinities.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg.writeSmall(affinities[i]);
msg.writeSmall(Quarantined);
}
void clearProtectedFrom(uint mask = ~0) {
uint prev = protectedFromMask;
protectedFromMask &= ~mask;
if(prev != protectedFromMask)
deltaLoy = true;
}
void protectFrom(uint mask) {
uint prev = protectedFromMask;
protectedFromMask |= mask;
if(prev != protectedFromMask)
deltaLoy = true;
}
void _writeLoy(const Object& obj, Message& msg) {
double base = double(BaseLoyalty + obj.owner.GlobalLoyalty.value);
msg.writeSignedSmall(BaseLoyalty);
msg.writeSignedSmall(LoyaltyBonus);
msg.writeSmall(protectedFromMask);
msg.writeSmall(orbitsMask);
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
double loy = LoyaltyEffect[i];
if(loy == 0) {
msg.write0();
}
else {
msg.write1();
msg.writeFixed(loy, -base, 0, 12);
}
}
}
void _writeColonization(Message& msg) {
uint8 cnt = uint8(colonization.length);
msg.writeBit(cnt != 0);
if(cnt != 0) {
msg << cnt;
for(uint8 i = 0; i < cnt; ++i)
msg << colonization[i].target;
}
}
void _writeVis(Message& msg) {
uint cnt = getEmpireCount();
for(uint i = 0; i < cnt; ++i)
msg << iconMemory[i];
}
bool writeSurfaceDelta(const Object& obj, Message& msg) {
if(!deltaRes && !deltaAff && !deltaPop && !grid.delta && !deltaCol && !deltaLoy)
return false;
msg.write1();
if(deltaRes) {
deltaRes = false;
msg.write1();
_writeRes(msg);
}
else {
msg.write0();
}
if(deltaAff) {
deltaAff = false;
msg.write1();
_writeAff(msg);
}
else {
msg.write0();
}
if(deltaPop) {
deltaPop = false;
msg.write1();
_writePop(msg);
}
else {
msg.write0();
}
if(grid.delta) {
grid.delta = false;
msg.write1();
grid.write(msg, true);
}
else {
msg.write0();
}
if(deltaCol) {
deltaCol = false;
msg.write1();
_writeColonization(msg);
}
else {
msg.write0();
}
if(deltaLoy) {
deltaLoy = false;
msg.write1();
_writeLoy(obj, msg);
}
else {
msg.write0();
}
return true;
}
void writeSurface(const Object& obj, Message& msg) {
_writePop(msg);
_writeRes(msg);
_writeAff(msg);
_writeLoy(obj, msg);
_writeVis(msg);
_writeColonization(msg);
msg << Quarantined;
msg << float(tileDevelopRate);
msg << float(bldConstructRate);
msg << float(undevelopedMaint);
msg.writeSmall(originalSurfaceSize.x);
msg.writeSmall(originalSurfaceSize.y);
msg.writeSmall(biome0);
msg.writeSmall(biome1);
msg.writeSmall(biome2);
grid.write(msg);
}
}