Files
2018-07-17 14:15:37 +02:00

1804 lines
44 KiB
ActionScript

import saving;
import systems;
import resources;
import planet_levels;
import statuses;
from resources import _tempResource;
import bool getCheatsEverOn() from "cheats";
tidy class NativeResource : Resource {
TradePath@ path;
int opCmp(const NativeResource@ other) const {
return Resource::opCmp(other);
}
};
const ResourceClass@ foodCls;
void init() {
@foodCls = getResourceClass("Food");
}
tidy class ObjectResources : Component_Resources, Savable {
int nextResourceID = 0;
NativeResource@[] nativeResources;
Resource@[] resources;
Resource primaryResource;
array<QueuedImport@>@ queuedImports;
Resources availableResources;
array<QueuedResource@>@ queuedExports;
int[] pressures = array<int>(TR_COUNT, 0);
int totalPressure = 0;
bool deltaRes = false;
bool deltaPath = false;
bool resourcesEnabled = true;
bool terraforming = false;
double ResourceCheck = 1.0;
float resEfficiency = 1.f;
double resEfficiencyBonus = 0.0;
double resVanishBonus = 0.0;
int ExportDisabled = 0;
int ImportDisabled = 0;
uint ResourceModId = 0;
uint ResourceLevel = 999;
locked_Civilian civilian;
double civilianTimer = 200.0;
ObjectResources() {}
void save(SaveFile& file) {
file << ExportDisabled;
file << ImportDisabled;
uint cnt = nativeResources.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
nativeResources[i].save(file);
nativeResources[i].type.nativeSave(nativeResources[i], file);
}
cnt = resources.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
resources[i].save(file);
if(resources[i].usable)
resources[i].type.save(resources[i], file);
}
cnt = 0;
if(queuedExports !is null)
cnt = queuedExports.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << queuedExports[i];
cnt = 0;
if(queuedImports !is null)
cnt = queuedImports.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << queuedImports[i];
availableResources.save(file);
file << nextResourceID;
file << ResourceModId;
file << ResourceLevel;
file << resourcesEnabled;
file << deltaPath;
file << deltaRes;
file << terraforming;
file << resEfficiency;
file << resEfficiencyBonus;
file << resVanishBonus;
for(uint i = 0; i < TR_COUNT; ++i)
file << pressures[i];
file << totalPressure;
file << civilian.get();
file << civilianTimer;
}
void load(SaveFile& file) {
file >> ExportDisabled;
file >> ImportDisabled;
uint cnt = 0;
file >> cnt;
nativeResources.length = cnt;
for(uint i = 0; i < cnt; ++i) {
NativeResource r;
r.load(file);
@nativeResources[i] = r;
@r.path = TradePath(r.origin.owner);
r.type.nativeLoad(r, file);
}
if(cnt != 0)
primaryResource.descFrom(nativeResources[0]);
else
primaryResource.descFrom(null);
file >> cnt;
resources.length = cnt;
for(uint i = 0; i < cnt; ++i) {
Resource r;
r.load(file);
if(r.usable || file < SV_0074)
r.type.load(r, file);
@resources[i] = r;
}
file >> cnt;
if(cnt > 0) {
@queuedExports = array<QueuedResource@>(cnt);
for(uint i = 0; i < cnt; ++i) {
@queuedExports[i] = QueuedResource();
file >> queuedExports[i];
}
}
file >> cnt;
if(cnt > 0) {
@queuedImports = array<QueuedImport@>(cnt);
for(uint i = 0; i < cnt; ++i) {
@queuedImports[i] = QueuedImport();
file >> queuedImports[i];
}
}
availableResources.load(file);
file >> nextResourceID;
file >> ResourceModId;
file >> ResourceLevel;
file >> resourcesEnabled;
file >> deltaPath;
file >> deltaRes;
file >> terraforming;
file >> resEfficiency;
file >> resEfficiencyBonus;
file >> resVanishBonus;
for(uint i = 0; i < TR_COUNT; ++i)
file >> pressures[i];
file >> totalPressure;
if(file >= SV_0048) {
civilian.set(cast<Civilian>(file.readObject()));
file >> civilianTimer;
}
else
civilianTimer = randomd(0.0, 180.0);
}
void resourcesPostLoad(Object& obj) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
TradePath@ path = nativeResources[i].path;
Resource@ r = nativeResources[i];
if(r.exportedTo !is null) {
@path.origin = getSystem(obj.region);
@path.goal = getSystem(r.exportedTo.region);
}
}
}
void setResourceLevel(Object& obj, uint level, bool wasManual) {
ResourceLevel = level;
checkResources(obj, wasManual);
++ResourceModId;
}
void modPressure(uint resource, int amount) {
if(resource >= TR_COUNT)
return;
pressures[resource] += amount;
totalPressure += amount;
}
int get_resourcePressure(uint resource) const {
if(resource >= TR_COUNT)
return 0;
return pressures[resource];
}
uint get_resourcesProducing(uint resource) {
if(resource >= TR_COUNT)
return 0;
uint amount = 0;
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
if(resources[i].type.tilePressure[resource] > 0.001f)
++amount;
}
return amount;
}
int get_totalResourcePressure() const {
return totalPressure;
}
uint get_resourceModID() {
return ResourceModId;
}
void bumpResourceModId() {
ResourceModId++;
}
void getNativeResources(Player& pl, const Object& obj) {
Empire@ plEmp = pl.emp;
if(plEmp is obj.owner || pl == SERVER_PLAYER) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
yield(nativeResources[i]);
}
else {
Resource@ res = _tempResource();
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
res = nativeResources[i];
@res.exportedTo = null;
if(queuedExports !is null) {
for(uint n = 0, ncnt = queuedExports.length; n < ncnt; ++n) {
QueuedResource@ q = queuedExports[n];
if(q.forEmpire is plEmp && res.id == q.id)
@res.exportedTo = q.to;
}
}
yield(res);
}
}
}
void getAvailableResources() {
for(uint i = 0, cnt = resources.length; i < cnt; ++i)
yield(resources[i]);
}
Civilian@ getAssignedCivilian() {
return civilian.get();
}
void setAssignedCivilian(Civilian@ civ) {
civilian.set(civ);
}
double getCivilianTimer() {
return civilianTimer;
}
void setCivilianTimer(double time) {
civilianTimer = time;
}
uint getUniqueFoodCount(int modBy = 0) {
uint uniques = 0;
for(uint i = 0, cnt = availableResources.length; i < cnt; ++i) {
if(getResource(availableResources.types[i]).cls is foodCls) {
int amt = availableResources.amounts[i] + modBy;
if(amt > 0)
uniques += 1;
}
}
return uniques;
}
uint getFoodCount() {
uint count = 0;
for(uint i = 0, cnt = availableResources.length; i < cnt; ++i) {
if(getResource(availableResources.types[i]).cls is foodCls)
count += availableResources.amounts[i];
}
return count;
}
uint getAvailableOfTier(uint tier) {
uint count = 0;
for(uint i = 0, cnt = availableResources.length; i < cnt; ++i) {
auto@ res = getResource(availableResources.types[i]);
if(res.mode == RM_NonRequirement)
continue;
if(res.level == tier)
count += availableResources.amounts[i];
}
return count;
}
Object@ get_availableResourceOrigin(uint index) const {
if(index >= resources.length)
return null;
return resources[index].origin;
}
void redirectAllImports(Object& obj, Object@ toObject) {
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
auto@ res = resources[i];
if(res.origin is null)
continue;
if(res.origin is obj)
continue;
if(res.origin.owner !is obj.owner)
continue;
res.origin.exportResourceByID(res.id, toObject);
}
}
double pressureFromAsteroids(Object& obj, uint type) {
double pres = 0.0;
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
Object@ source = resources[i].origin;
if(source !is null && source.isAsteroid)
pres += resources[i].type.tilePressure[type];
}
return pres;
}
void getImportedResources(const Object& obj) {
for(uint i = 0, cnt = resources.length; i < cnt; ++i)
if(resources[i].origin is null || obj !is resources[i].origin)
yield(resources[i]);
}
bool get_hasAutoImports(Player& pl, const Object& obj) {
if(queuedImports is null)
return false;
Empire@ plEmp = pl.emp;
if(pl == SERVER_PLAYER)
@plEmp = obj.owner;
for(uint i = 0, cnt = queuedImports.length; i < cnt; ++i)
if(queuedImports[i].origin is null && (plEmp is null || plEmp is queuedImports[i].forEmpire))
return true;
return false;
}
uint getTradedResourceCount() const {
uint tradedNative = 0;
uint usableNative = 0;
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(!nativeResources[i].usable)
continue;
++usableNative;
if(nativeResources[i].exportedTo !is null)
++tradedNative;
}
return (resources.length - (usableNative - tradedNative)) + tradedNative;
}
void getAllResources(Player& pl, const Object& obj) {
Empire@ emp = pl.emp;
if(emp is obj.owner || pl == SERVER_PLAYER) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
yield(nativeResources[i]);
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
if(obj !is resources[i].origin)
yield(resources[i]);
}
}
else {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
yield(nativeResources[i]);
}
if(queuedImports !is null) {
for(uint i = 0, cnt = queuedImports.length; i < cnt; ++i) {
if(queuedImports[i].forEmpire is emp)
yield(queuedImports[i]);
}
}
}
void getQueuedImports(Player& pl, const Object& obj) {
Empire@ emp = pl.emp;
if(queuedImports !is null) {
for(uint i = 0, cnt = queuedImports.length; i < cnt; ++i) {
if(queuedImports[i].forEmpire is emp)
yield(queuedImports[i]);
}
}
}
void getQueuedImportsFor(const Object& obj, Empire@ emp) {
if(queuedImports !is null) {
for(uint i = 0, cnt = queuedImports.length; i < cnt; ++i) {
if(queuedImports[i].forEmpire is emp)
yield(queuedImports[i]);
}
}
}
void getResourcesFor(const Object& obj, Empire@ emp) {
if(emp is obj.owner) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
yield(nativeResources[i]);
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
if(obj !is resources[i].origin)
yield(resources[i]);
}
}
else {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
yield(nativeResources[i]);
}
if(queuedImports !is null) {
for(uint i = 0, cnt = queuedImports.length; i < cnt; ++i) {
if(queuedImports[i].forEmpire is emp)
yield(queuedImports[i]);
}
}
}
void getResourceAmounts() {
yield(availableResources);
}
void modDummyResource(Object& obj, uint resource, int amount, bool manual = false) {
const ResourceType@ type = getResource(resource);
if(type is null)
return;
availableResources.modAmount(type, amount);
++ResourceModId;
deltaRes = true;
checkResources(obj, manual);
}
void createResource(Object& obj, uint resource) {
addResource(obj, resource);
}
int addResource(Object& obj, uint resource) {
auto@ type = getResource(resource);
if(type is null)
return -1;
NativeResource r;
r.id = nextResourceID++;
@r.type = type;
@r.origin = obj;
r.data.length = r.type.hooks.length;
r.usable = resourcesEnabled && ResourceLevel >= r.type.level;
@r.path = TradePath(obj.owner);
nativeResources.insertLast(r);
r.type.onGenerate(obj, r);
if(r.usable) {
obj.addAvailableResource(obj, r.id, r.type.id, true);
if(r.type.vanishMode != VM_Never)
obj.setAvailableResourceVanish(obj, r.id, r.vanishTime);
}
deltaRes = true;
++ResourceModId;
checkResources(obj);
nativeResources.sortDesc();
primaryResource.descFrom(nativeResources[0]);
return r.id;
}
uint getNativeIndex(int id) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(nativeResources[i].id == id)
return i;
}
return uint(-1);
}
void startTerraform(Object& obj) {
terraforming = true;
deltaRes = true;
checkResources(obj, true);
}
void stopTerraform(Object& obj) {
terraforming = false;
deltaRes = true;
checkResources(obj, true);
}
bool isTerraforming() {
return terraforming;
}
void terraformTo(Object& obj, uint resId) {
Empire@ owner = obj.owner;
if(owner.valid && !getCheatsEverOn()) {
if(owner is playerEmpire)
unlockAchievement("ACH_TERRAFORM");
if(mpServer && owner.player !is null)
clientAchievement(owner.player, "ACH_TERRAFORM");
}
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(!nativeResources[i].type.artificial)
obj.removeResource(nativeResources[i].id);
}
obj.addResource(resId);
auto@ barrenStatus = getStatusType("Barren");
if(barrenStatus !is null)
obj.removeStatusType(barrenStatus.id);
terraforming = false;
}
void removeResource(Object& obj, int id, bool wasManual = false) {
NativeResource@ r;
uint index = 0;
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(nativeResources[i].id == id) {
@r = nativeResources[i];
index = i;
break;
}
}
if(r is null)
return;
if(r.exportedTo !is null) {
clearLines(r, r.path, obj, r.exportedTo);
r.exportedTo.removeAvailableResource(obj, r.id, wasManual);
}
else {
if(r.usable)
obj.removeAvailableResource(obj, r.id, wasManual);
}
if(queuedExports !is null) {
for(int i = queuedExports.length - 1; i >= 0; --i) {
auto@ q = queuedExports[i];
if(q.to !is null)
q.to.removeQueuedImport(q.forEmpire, obj, q.id);
queuedExports.removeAt(i);
}
if(queuedExports.length == 0)
@queuedExports = null;
}
r.type.onDestroy(obj, r);
nativeResources.removeAt(index);
checkResources(obj);
++ResourceModId;
deltaRes = true;
deltaPath = true;
}
void setResourceDisabled(Object& obj, int nativeId, bool disabled, bool wasManual) {
Resource@ r;
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(nativeResources[i].id == nativeId) {
@r = nativeResources[i];
break;
}
}
if(r !is null) {
if(disabled)
++r.disabled;
else if(r.disabled > 0)
--r.disabled;
checkResources(obj, wasManual);
deltaRes = true;
}
}
void setResourceLocked(int nativeId, bool locked) {
Resource@ r;
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(nativeResources[i].id == nativeId) {
@r = nativeResources[i];
break;
}
}
if(r !is null)
r.locked = locked;
}
void disableResources(Object& obj) {
resourcesEnabled = false;
checkResources(obj);
}
void enableResources(Object& obj) {
if(!resourcesEnabled) {
resourcesEnabled = true;
checkResources(obj);
if(obj.owner !is null)
obj.owner.checkAutoImport(obj);
}
}
uint getResourceTargetLevel(Object& obj) {
//Check planet levels
int maxPlanetLevel= getMaxPlanetLevel(obj);
for(uint i = 1, cnt = maxPlanetLevel; i <= cnt; ++i) {
const PlanetLevel@ lvl = getPlanetLevel(obj, i);
if(lvl !is null && !lvl.reqs.satisfiedBy(availableResources))
return i-1;
}
return maxPlanetLevel;
}
bool get_areResourcesEnabled() {
return resourcesEnabled;
}
uint get_nativeResourceCount() {
return nativeResources.length;
}
uint get_nativeResourceType(uint i) {
if(i >= nativeResources.length)
return uint(-1);
return nativeResources[i].type.id;
}
int get_nativeResourceId(uint i) {
if(i >= nativeResources.length)
return -1;
return nativeResources[i].id;
}
uint get_nativeResourceTotalLevel() const {
uint level = 0;
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
level += nativeResources[i].type.level;
return level;
}
uint get_nativeResourceByID(int id) const {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(nativeResources[i].id == id)
return nativeResources[i].type.id;
}
return uint(-1);
}
bool isPrimaryDestination(Object& obj, Object@ dest) {
if(primaryResource.type is null)
return false;
if(primaryResource.exportedTo is dest)
return true;
if(obj is dest && primaryResource.exportedTo is null)
return true;
return false;
}
uint get_primaryResourceType() const {
if(primaryResource.type is null)
return uint(-1);
return primaryResource.type.id;
}
uint get_primaryResourceLevel() const {
if(primaryResource.type is null)
return 0;
return primaryResource.type.level;
}
uint get_primaryResourceLimitLevel(const Object& obj) const {
if(primaryResource.type is null)
return 0;
if(primaryResource.type.limitlessLevel)
return getMaxPlanetLevel(obj.levelChain);
return primaryResource.type.level;
}
int get_primaryResourceId() const {
return primaryResource.id;
}
bool get_primaryResourceUsable() const {
return primaryResource.usable;
}
bool get_primaryResourceLocked() const {
return primaryResource.locked;
}
bool get_primaryResourceExported() const {
return primaryResource.exportedTo !is null;
}
Object@ get_nativeResourceDestination(Player& pl, const Object& obj, uint i) {
if(i >= nativeResources.length)
return null;
Resource@ r = nativeResources[i];
//Find the current export
Empire@ plEmp = pl.emp;
if(r.exportedTo !is null && (plEmp is obj.owner || pl == SERVER_PLAYER))
return nativeResources[i].exportedTo;
//Try to find a queued export
if(queuedExports !is null) {
for(uint i = 0, cnt = queuedExports.length; i < cnt; ++i) {
QueuedResource@ q = queuedExports[i];
if(q.forEmpire is plEmp && r.id == q.id)
return q.to;
}
}
return null;
}
Object@ getNativeResourceDestinationByID(const Object& obj, Empire@ emp, int id) {
uint index = getNativeIndex(id);
return getNativeResourceDestination(obj, emp, index);
}
Object@ getNativeResourceDestination(const Object& obj, Empire@ emp, uint i) {
if(i >= nativeResources.length)
return null;
Resource@ r = nativeResources[i];
//Find the current export
if(r.exportedTo !is null && emp is obj.owner)
return nativeResources[i].exportedTo;
//Try to find a queued export
if(queuedExports !is null) {
for(uint i = 0, cnt = queuedExports.length; i < cnt; ++i) {
QueuedResource@ q = queuedExports[i];
if(q.forEmpire is emp && r.id == q.id)
return q.to;
}
}
return null;
}
bool get_nativeResourceUsable(uint i) {
if(i >= nativeResources.length)
return false;
return nativeResources[i].usable;
}
bool getNativeResourceUsableByID(int id) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(nativeResources[i].id == id)
return nativeResources[i].usable;
}
return false;
}
bool get_nativeResourceLocked(Player& pl, Object& obj, uint i) {
Empire@ forEmp = pl.emp;
if(pl == SERVER_PLAYER)
@forEmp = obj.owner;
if(i >= nativeResources.length)
return false;
auto@ r = nativeResources[i];
if(forEmp is obj.owner)
return r.locked;
if(queuedExports !is null) {
for(uint i = 0, cnt = queuedExports.length; i < cnt; ++i) {
QueuedResource@ q = queuedExports[i];
if(q.forEmpire is forEmp && r.id == q.id)
return q.locked;
}
}
return false;
}
bool hasImportedResources(const Object& obj) const {
if(resources.length == 0)
return false;
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
if(obj !is resources[i].origin)
return true;
}
return false;
}
uint getImportsOfClass(Player& pl, const Object& obj, uint clsId) const {
const ResourceClass@ cls = getResourceClass(clsId);
if(cls is null)
return 0;
uint count = 0;
Empire@ emp = pl.emp;
if(emp is obj.owner) {
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
if(resources[i].type.cls is cls)
count += 1;
}
}
if(queuedImports !is null) {
for(uint i = 0, cnt = queuedImports.length; i < cnt; ++i) {
const QueuedImport@ imp = queuedImports[i];
if(imp.forEmpire is emp && imp.type.cls is cls)
count += 1;
}
}
return count;
}
uint get_availableResourceCount() const {
return resources.length;
}
uint get_usableResourceCount() const {
uint amt = 0;
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
if(resources[i].usable)
amt += 1;
}
return amt;
}
uint get_availableResourceType(uint index) const {
if(index >= resources.length)
return uint(-1);
return resources[index].type.id;
}
bool get_availableResourceUsable(uint index) const {
if(index >= resources.length)
return false;
return resources[index].usable;
}
bool isResourceAvailable(uint id) const {
return availableResources.getAmount(getResource(id)) != 0;
}
uint getAvailableResourceAmount(uint id) const {
return availableResources.getAmount(getResource(id));
}
void exportResource(Player& pl, Object& obj, uint index, Object@ to) {
if(pl == SERVER_PLAYER)
exportResource(obj, obj.owner, index, to);
else
exportResource(obj, pl.emp, index, to);
}
void exportResourceByID(Player& pl, Object& obj, int id, Object@ to) {
uint index = getNativeIndex(id);
if(pl == SERVER_PLAYER)
exportResource(obj, obj.owner, index, to);
else
exportResource(obj, pl.emp, index, to);
}
void exportResource(Object& obj, Empire@ forEmpire, uint index, Object@ to) {
if(to !is null && !to.hasResources)
return;
if(index >= nativeResources.length)
return;
if(to !is null && (to.region is null || obj.region is null))
return;
//If this is ordered by not our owner, we queue it
if(forEmpire !is obj.owner) {
queueExportResource(forEmpire, obj, index, to);
return;
}
else {
NativeResource@ r = nativeResources[index];
if(r.locked)
return;
}
//If this is ordered to something by a different owner, we queue it
if(to !is null && obj.owner !is to.owner) {
queueExportResource(forEmpire, obj, index, to);
@to = null;
}
else {
//Remove old queues
queueExportResource(forEmpire, obj, index, null);
}
_exportResource(obj, index, to, true);
}
string getDisabledReason(Object& obj, int id) {
uint index = getNativeIndex(id);
if(index >= nativeResources.length)
return "Not present";
auto@ r = nativeResources[index];
if(!r.type.exportable)
return "";
auto@ to = r.exportedTo;
if(ExportDisabled != 0)
return locale::EXPBLOCK_DISABLED;
if((to !is null && to.region is null) || obj.region is null)
return locale::EXPBLOCK_DEEPSPACE;
if(!obj.owner.valid) {
if(obj.isAsteroid)
return locale::EXPBLOCK_UNMINED;
else if(obj.isPlanet)
return locale::EXPBLOCK_UNCOLONIZED;
else
return locale::EXPBLOCK_UNOWNED;
}
if(to !is null && obj.owner !is to.owner)
return locale::EXPBLOCK_UNOWNED;
if(obj.hasSurfaceComponent) {
if(obj.population < 1.0)
return format(locale::EXPBLOCK_POP, uint(1));
else if(obj.resourceLevel < r.type.level)
return format(locale::EXPBLOCK_LOWLEVEL, r.type.level);
else if(obj.population < getPlanetLevelRequiredPop(obj, r.type.level))
return format(locale::EXPBLOCK_POP, uint(getPlanetLevelRequiredPop(obj, r.type.level)));
}
TradePath@ path = r.path;
if(path.goal !is null && (!path.valid || !path.isUsablePath))
return locale::EXPBLOCK_DISCONNECTED;
if(!r.usable)
return locale::EXPBLOCK_UNUSABLE;
return "";
}
void _exportResource(Object& obj, uint index, Object@ to, bool wasManual = false) {
//Remove from our current export
NativeResource@ r = nativeResources[index];
TradePath@ path = r.path;
if((!r.type.exportable || ExportDisabled != 0) && to !is null)
return;
if(r.exportedTo is null && to is null)
return;
if(r.exportedTo !is null) {
r.exportedTo.removeAvailableResource(obj, r.id, wasManual);
if(r.usable)
clearLines(r, path, obj, r.exportedTo);
}
else {
if(r.usable)
obj.removeAvailableResource(obj, r.id, wasManual);
}
//Add to our new export
if(to !is null) {
if(!to.valid)
return;
//Must export to things in regions
Region@ fromRegion = obj.region, toRegion = to.region;
if(fromRegion is null || toRegion is null)
return;
to.addAvailableResource(obj, r.id, r.type.id, r.usable);
if(r.type.vanishMode != VM_Never)
to.setAvailableResourceVanish(obj, r.id, r.vanishTime);
@r.exportedTo = to;
path.clear();
@path.origin = getSystem(fromRegion);
@path.goal = getSystem(toRegion);
civilianTimer = randomd(0.0, 3.0 * 60.0);
}
else {
if(r.usable) {
obj.addAvailableResource(obj, r.id, r.type.id, true);
if(r.type.vanishMode != VM_Never)
obj.setAvailableResourceVanish(obj, r.id, r.vanishTime);
}
@r.exportedTo = null;
path.clear();
}
//Check resource enabled state
checkResources(obj);
++ResourceModId;
deltaRes = true;
deltaPath = true;
}
void queueExportResource(Empire@ forEmpire, Object& obj, uint index, Object@ to, bool locked = false) {
//Find existing
Resource@ r = nativeResources[index];
QueuedResource@ q;
if(queuedExports !is null) {
for(uint i = 0, cnt = queuedExports.length; i < cnt; ++i) {
if(queuedExports[i].forEmpire is forEmpire && queuedExports[i].id == r.id) {
@q = queuedExports[i];
break;
}
}
}
if(to !is null) {
if(q is null) {
@q = QueuedResource();
q.id = r.id;
@q.forEmpire = forEmpire;
if(queuedExports is null)
@queuedExports = array<QueuedResource@>();
queuedExports.insertLast(q);
}
else {
if(q.locked)
return;
if(q.to !is null)
q.to.removeQueuedImport(forEmpire, obj, r.id);
}
@q.to = to;
q.locked = locked;
q.to.addQueuedImport(forEmpire, obj, r.id, r.type.id);
deltaRes = true;
++ResourceModId;
}
else if(q !is null) {
if(q.locked)
return;
if(q.to !is null)
q.to.removeQueuedImport(forEmpire, obj, r.id);
if(queuedExports !is null) {
queuedExports.remove(q);
if(queuedExports.length == 0)
@queuedExports = null;
}
deltaRes = true;
++ResourceModId;
}
}
void addQueuedImport(Object& obj, Empire@ fromEmpire, Object@ from, int id, uint resource) {
QueuedImport r;
@r.forEmpire = fromEmpire;
r.id = id;
@r.type = getResource(resource);
@r.origin = from;
@r.exportedTo = obj;
r.usable = false;
if(queuedImports is null)
@queuedImports = array<QueuedImport@>();
queuedImports.insertLast(r);
++ResourceModId;
if(r.type is null)
return;
deltaRes = true;
}
void removeQueuedImport(Object& obj, Empire@ fromEmpire, Object@ from, int id) {
_removeQueuedImport(obj, fromEmpire, from, id);
}
bool _removeQueuedImport(Object& obj, Empire@ fromEmpire, Object@ from, int id) {
if(queuedImports is null)
return false;
for(uint i = 0, cnt = queuedImports.length; i < cnt; ++i) {
QueuedImport@ r = queuedImports[i];
if(r.forEmpire is fromEmpire && r.origin is from && r.id == id) {
queuedImports.removeAt(i);
deltaRes = true;
if(queuedImports.length == 0)
@queuedImports = null;
++ResourceModId;
return true;
}
}
return false;
}
uint get_queuedImportCount() {
return queuedImports is null ? 0 : queuedImports.length;
}
uint get_queuedImportType(Player& pl, Object& obj, uint i) {
if(queuedImports is null || i >= queuedImports.length || pl.emp !is queuedImports[i].forEmpire)
return uint(-1);
return queuedImports[i].type.id;
}
Object@ get_queuedImportOrigin(Player& pl, Object& obj, uint i) {
if(queuedImports is null || i >= queuedImports.length || pl.emp !is queuedImports[i].forEmpire)
return null;
return queuedImports[i].origin;
}
void addAvailableResource(Object& obj, Object@ from, int id, uint resource, bool usable) {
if(from !is null && obj !is from && ImportDisabled != 0) {
from.clearExportResource(id);
return;
}
deltaRes = true;
Resource r;
r.id = id;
@r.type = getResource(resource);
@r.origin = from;
r.data.length = r.type.hooks.length;
r.usable = usable;
if(obj is from)
@r.exportedTo = null;
else
@r.exportedTo = obj;
resources.insertLast(r);
++ResourceModId;
Empire@ fromOwner = from.owner;
_removeQueuedImport(obj, fromOwner, from, id);
if(usable)
_addedResource(obj, r);
if(obj.hasAutoImports)
obj.owner.gotImportFor(obj, r.type.id);
}
void clearExportResource(Object& obj, int id) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
if(nativeResources[i].id == id) {
if(nativeResources[i].exportedTo !is null)
_exportResource(obj, i, null, true);
break;
}
}
}
void setExportEnabled(Object& obj, bool value) {
if(value) {
ExportDisabled--;
}
else {
if(ExportDisabled == 0) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
if(nativeResources[i].exportedTo !is null)
_exportResource(obj, i, null);
}
ExportDisabled++;
}
++ResourceModId;
}
void setImportEnabled(Object& obj, bool value) {
if(value) {
ImportDisabled--;
}
else {
if(ImportDisabled == 0) {
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
Resource@ r = resources[i];
if(r.origin !is null && r.origin !is obj)
r.origin.clearExportResource(r.id);
}
}
ImportDisabled++;
}
++ResourceModId;
}
Resource@ resourceFrom(Object@ from, int id) {
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
Resource@ r = resources[i];
if(r.origin is from && r.id == id)
return r;
}
return null;
}
void enableAvailableResource(Object& obj, Object@ from, int id) {
Resource@ r = resourceFrom(from, id);
if(r !is null && !r.usable) {
++ResourceModId;
deltaRes = true;
r.usable = true;
_addedResource(obj, r);
}
}
void setAvailableResourceVanish(Object& obj, Object@ from, int id, double vanishTime) {
Resource@ r = resourceFrom(from, id);
if(r !is null) {
if(int(r.vanishTime) != int(vanishTime))
deltaRes = true;
r.vanishTime = vanishTime;
}
}
void _addedResource(Object& obj, Resource@ r) {
availableResources.modAmount(r.type, +1);
r.type.onAdd(obj, r);
r.efficiency = r.origin.resourceEfficiency;
deltaRes = true;
++ResourceModId;
Region@ reg = obj.region;
if(reg !is null) {
Territory@ terr = obj.region.getTerritory(obj.owner);
if(terr !is null)
r.type.onTerritoryAdd(obj, r, terr);
}
for(uint i = 0; i < TR_COUNT; ++i) {
if(r.type.tilePressure[i] != 0) {
int prs = max(round(float(r.type.tilePressure[i]) * float(r.efficiency)), 0.f);
pressures[i] += prs;
totalPressure += prs;
}
}
uint affCnt = r.type.affinities.length;
if(affCnt != 0 && obj.hasSurfaceComponent) {
for(uint i = 0; i < affCnt; ++i)
obj.addAffinity(r.type.affinities[i]);
}
}
void _removedResource(Object& obj, Resource@ r) {
availableResources.modAmount(r.type, -1);
r.type.onRemove(obj, r);
deltaRes = true;
Region@ reg = obj.region;
if(reg !is null) {
Territory@ terr = obj.region.getTerritory(obj.owner);
if(terr !is null)
r.type.onTerritoryRemove(obj, r, terr);
}
for(uint i = 0; i < TR_COUNT; ++i) {
if(r.type.tilePressure[i] != 0) {
int prs = max(round(float(r.type.tilePressure[i]) * float(r.efficiency)), 0.f);
pressures[i] -= prs;
totalPressure -= prs;
}
}
uint affCnt = r.type.affinities.length;
if(affCnt != 0 && obj.hasSurfaceComponent) {
for(uint i = 0; i < affCnt; ++i)
obj.removeAffinity(r.type.affinities[i]);
}
}
void destroyObjResources(Object& obj) {
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
NativeResource@ r = nativeResources[i];
TradePath@ path = r.path;
if(r.exportedTo !is null) {
r.exportedTo.removeAvailableResource(obj, r.id);
if(r.usable)
clearLines(r, path, obj, r.exportedTo);
}
else if(r.usable) {
removeAvailableResource(obj, obj, r.id);
}
}
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
Resource@ r = resources[i];
removeAvailableResource(obj, r.origin, r.id);
--i; --cnt;
}
}
void removeAvailableResource(Object& obj, Object@ from, int id, bool wasManual = false) {
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
Resource@ r = resources[i];
if(r.origin is from && r.id == id) {
++ResourceModId;
deltaRes = true;
if(r.usable) {
_removedResource(obj, r);
r.usable = false;
}
resources.removeAt(i);
if(wasManual)
checkResources(obj, true);
break;
}
}
}
void disableAvailableResource(Object& obj, Object@ from, int id, bool wasManual = false) {
Resource@ r = resourceFrom(from, id);
if(r !is null && r.usable) {
deltaRes = true;
++ResourceModId;
r.usable = false;
_removedResource(obj, r);
if(wasManual)
checkResources(obj, true);
}
}
void changeResourceTerritory(Object& obj, Territory@ prev, Territory@ next) {
//Trigger any owner change hooks on resources
uint resCnt = resources.length;
for(uint i = 0; i < resCnt; ++i) {
Resource@ r = resources[i];
if(r.usable) {
if(prev !is null)
r.type.onTerritoryRemove(obj, r, prev);
if(next !is null)
r.type.onTerritoryAdd(obj, r, next);
}
}
}
void clearLines(Resource@ res, TradePath@ path, Object@ from, Object@ to) {
uint cnt = path.pathSize;
if(cnt == 0) {
return;
}
else if(cnt == 1) {
path.origin.object.removeTradePathing(-1, from, res.id);
}
else {
for(uint i = 0; i < cnt-1; ++i) {
SystemDesc@ node = path.pathNode[i];
SystemDesc@ next = path.pathNode[i+1];
node.object.removeTradePathing(next.index, from, res.id);
}
}
}
void updateLines(Resource@ res, TradePath@ path, Object@ from, Object@ to) {
uint cnt = path.pathSize;
if(cnt == 0) {
return;
}
else if(cnt == 1) {
path.origin.object.addTradePathing(-1, from, to, res.id, res.type.id);
}
else {
for(uint i = 0; i < cnt-1; ++i) {
SystemDesc@ node = path.pathNode[i];
SystemDesc@ next = path.pathNode[i+1];
node.object.addTradePathing(next.index, from, to, res.id, res.type.id);
}
}
}
void checkResources(Object& obj, bool wasManual = false) {
//Unlock resources
bool haveLocalEnableds = false;
uint cnt = nativeResources.length;
for(uint i = 0; i < cnt; ++i) {
NativeResource@ r = nativeResources[i];
//Check usability
bool prev = r.usable;
r.usable = resourcesEnabled && ResourceLevel >= r.type.level
&& r.disabled == 0 && (!terraforming || r.type.artificial);
r.efficiency = obj.resourceEfficiency;
bool exporting = r.exportedTo !is null;
//Check pathing
if(exporting) {
TradePath@ path = r.path;
bool usablePath = path.isUsablePath;
if(path.valid && !usablePath && prev)
clearLines(r, path, obj, r.exportedTo);
if(r.usable) {
if(!usablePath) {
if(path.goal is null)
@path.goal = getSystem(r.exportedTo.region);
if(path.origin is null)
@path.origin = getSystem(obj.region);
if(path.goal !is null && path.origin !is null) {
path.generate();
deltaPath = true;
}
if(path.isUsablePath)
updateLines(r, path, obj, r.exportedTo);
}
if(!path.valid)
r.usable = false;
}
}
//Disable and enable
if(r.usable && !prev) {
++ResourceModId;
deltaRes = true;
if(r.exportedTo !is null) {
r.exportedTo.enableAvailableResource(obj, r.id);
}
else {
obj.addAvailableResource(obj, r.id, r.type.id, true);
if(r.type.vanishMode != VM_Never)
obj.setAvailableResourceVanish(obj, r.id, r.vanishTime);
haveLocalEnableds = true;
}
}
else if(prev && !r.usable) {
++ResourceModId;
deltaRes = true;
if(r.exportedTo !is null) {
r.exportedTo.disableAvailableResource(obj, r.id, wasManual);
}
else {
obj.removeAvailableResource(obj, r.id, wasManual);
}
}
if(exporting) {
Empire@ otherOwner = r.exportedTo.owner;
//Check if exporting to wrong owner
if(otherOwner !is obj.owner) {
//Cancel exports to foreign empires
queueExportResource(obj.owner, obj, i, r.exportedTo, r.locked);
_exportResource(obj, i, null);
r.locked = false;
}
//Check if exporting to a destroyed object
else if(!r.exportedTo.valid)
_exportResource(obj, i, null);
}
}
//Check if anything queued is now available again
if(queuedExports !is null) {
for(uint i = 0; queuedExports !is null && i < queuedExports.length; ++i) {
QueuedResource@ q = queuedExports[i];
if(q.forEmpire is obj.owner && q.to.owner is obj.owner) {
//Find native resource index from id
int rIndex = -1;
for(uint n = 0, rcnt = nativeResources.length; n < rcnt; ++n) {
if(nativeResources[n].id == q.id) {
rIndex = int(n);
break;
}
}
queuedExports.removeAt(i);
--i; --cnt;
if(rIndex != -1) {
_exportResource(obj, uint(rIndex), q.to);
break;
}
}
}
//queuedExports can become null as a result of _exportResource
if(queuedExports !is null && queuedExports.length == 0)
@queuedExports = null;
}
if(nativeResources.length != 0)
primaryResource.descFrom(nativeResources[0]);
else
primaryResource.descFrom(null);
//Inform the surface if we have one
if(wasManual && obj.hasSurfaceComponent)
obj.onManualResourceRemoved();
if(haveLocalEnableds)
obj.owner.checkAutoImport(obj);
}
void modResourceEfficiencyBonus(double amt) {
resEfficiencyBonus += amt;
}
float get_resourceEfficiency(const Object& obj) const {
float eff = resEfficiency * (1.0 + resEfficiencyBonus);
if(primaryResource.type !is null && primaryResource.type.level == 3)
eff *= obj.owner.Tier3PressureFactor;
return eff;
}
void set_resourceEfficiency(float val) {
resEfficiency = val;
}
float get_resourceVanishRate() const {
return 1.f / (1.f + resVanishBonus);
}
void modResourceVanishBonus(double val) {
resVanishBonus += val;
deltaRes = true;
}
void updateResourcePressures(Object& obj) {
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
Resource@ r = resources[i];
if(!r.usable)
continue;
float prevEff = max(r.efficiency, 0.f);
float newEff = max(r.origin.resourceEfficiency, 0.f);
if(prevEff != newEff) {
for(uint i = 0; i < TR_COUNT; ++i) {
float prs = r.type.tilePressure[i];
if(prs != 0.f) {
int prevPrs = round(prs * prevEff);
int newPrs = round(prs * newEff);
pressures[i] += newPrs - prevPrs;
totalPressure += newPrs - prevPrs;
}
}
r.efficiency = newEff;
if(abs(newEff - prevEff) >= 0.1)
deltaRes = true;
}
}
}
bool get_exportEnabled() {
return ExportDisabled == 0;
}
bool get_importEnabled() {
return ImportDisabled == 0;
}
void changeResourceOwner(Object& obj, Empire@ prevOwner) {
//Move path owners
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i)
@nativeResources[i].path.forEmpire = obj.owner;
//Remove any exports to other empires
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
NativeResource@ r = nativeResources[i];
@r.path.forEmpire = obj.owner;
if(r.exportedTo !is null && r.exportedTo.owner !is obj.owner) {
//Add previous export as queued
if(r.locked)
queueExportResource(prevOwner, obj, i, r.exportedTo, r.locked);
_exportResource(obj, i, null);
r.locked = false;
}
}
Territory@ prevTerr, newTerr;
Region@ region = obj.region;
if(region !is null) {
@prevTerr = region.getTerritory(prevOwner);
@newTerr = region.getTerritory(obj.owner);
}
uint resCnt = resources.length;
for(uint i = 0; i < resCnt; ++i) {
Resource@ r = resources[i];
if(r.usable) {
//Trigger owner change hooks on resources
r.type.onOwnerChange(obj, r, prevOwner, obj.owner);
//Trigger territory change hooks
if(prevTerr !is null)
r.type.onTerritoryRemove(obj, r, prevTerr);
if(newTerr !is null)
r.type.onTerritoryAdd(obj, r, newTerr);
}
}
//Check if we should engage any queued exports
if(queuedExports !is null) {
for(uint i = 0; queuedExports !is null && i < queuedExports.length; ++i) {
QueuedResource@ q = queuedExports[i];
if(q.forEmpire is obj.owner && q.to.owner is obj.owner) {
//Find native resource index from id
int rIndex = -1;
for(uint n = 0, rcnt = nativeResources.length; n < rcnt; ++n) {
if(nativeResources[n].id == q.id) {
rIndex = int(n);
break;
}
}
queuedExports.removeAt(i);
--i;
if(rIndex != -1) {
_exportResource(obj, uint(rIndex), q.to);
}
}
}
if(queuedExports !is null && queuedExports.length == 0)
@queuedExports = null;
}
if(obj.owner !is null)
obj.owner.checkAutoImport(obj);
}
void changeResourceRegion(Object& obj, Region@ prevRegion, Region@ newRegion) {
//Check territories
Territory@ prevTerr, newTerr;
Region@ region = obj.region;
if(region !is null)
@newTerr = region.getTerritory(obj.owner);
if(prevRegion !is null)
@prevTerr = prevRegion.getTerritory(obj.owner);
if(prevTerr !is newTerr) {
uint resCnt = resources.length;
for(uint i = 0; i < resCnt; ++i) {
Resource@ r = resources[i];
//Trigger territory change hooks
if(prevTerr !is null)
r.type.onTerritoryRemove(obj, r, prevTerr);
if(newTerr !is null)
r.type.onTerritoryAdd(obj, r, newTerr);
}
}
//Resource hooks
uint resCnt = resources.length;
for(uint i = 0; i < resCnt; ++i) {
Resource@ r = resources[i];
if(r.usable)
r.type.onRegionChange(obj, r, prevRegion, newRegion);
}
}
void resourceTick(Object& obj, double time) {
//Periodic resource checks
if(ResourceCheck <= 0.0) {
ResourceCheck = randomd(1.0, 2.0);
checkResources(obj);
updateResourcePressures(obj);
}
else
ResourceCheck -= time;
//Vanish any native resources
for(uint i = 0, cnt = nativeResources.length; i < cnt; ++i) {
Resource@ r = nativeResources[i];
if(obj.owner is null || !obj.owner.valid)
continue;
r.type.nativeTick(obj, r, time);
if(!r.usable)
continue;
switch(r.type.vanishMode) {
case VM_WhenExported:
if(r.exportedTo is null)
continue;
break;
case VM_ExportedInCombat:
if(r.exportedTo is null) {
if(r.origin is null || !r.origin.inCombat)
continue;
}
else {
if(!r.exportedTo.inCombat)
continue;
}
break;
case VM_Always:
break;
case VM_Custom:
if(!r.type.shouldVanish(obj, r))
continue;
break;
default:
continue;
}
int prevTime = r.vanishTime;
if(r.exportedTo !is null) {
float rate = r.exportedTo.resourceVanishRate;
r.vanishTime += time * rate;
r.exportedTo.setAvailableResourceVanish(obj, r.id, r.vanishTime);
}
else {
r.vanishTime += time * get_resourceVanishRate();
setAvailableResourceVanish(obj, obj, r.id, r.vanishTime);
}
if(prevTime != int(r.vanishTime))
deltaRes = true;
if(r.vanishTime >= r.type.vanishTime)
obj.removeResource(r.id);
}
//Tick any resources with tick hooks
uint resCnt = resources.length;
for(uint i = 0; i < resCnt; ++i) {
Resource@ r = resources[i];
if(r.usable)
r.type.onTick(obj, r, time);
}
//Deal with civilian trade
civilianTimer += time;
}
void _writeRes(Message& msg) {
msg << terraforming;
msg << float(resVanishBonus);
availableResources.write(msg);
{
uint cnt = nativeResources.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
nativeResources[i].write(msg);
}
{
uint cnt = resources.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
resources[i].write(msg);
}
{
uint cnt = queuedImports is null ? 0 : queuedImports.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
queuedImports[i].writeQueued(msg);
}
{
uint cnt = queuedExports is null ? 0 : queuedExports.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
queuedExports[i].write(msg);
}
}
void _writePath(Message& msg) {
uint cnt = nativeResources.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
nativeResources[i].path.write(msg);
}
bool writeResourceDelta(Message& msg) {
if(!deltaRes && !deltaPath)
return false;
msg.write1();
if(deltaRes) {
deltaRes = false;
msg.write1();
_writeRes(msg);
}
else {
msg.write0();
}
if(deltaPath) {
deltaPath = false;
msg.write1();
_writePath(msg);
}
else {
msg.write0();
}
return true;
}
void writeResources(Message& msg) {
_writeRes(msg);
_writePath(msg);
msg.writeBit(ImportDisabled != 0);
if(ImportDisabled != 0)
msg << ImportDisabled;
msg.writeBit(ExportDisabled != 0);
if(ExportDisabled != 0)
msg << ExportDisabled;
}
};