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

1307 lines
30 KiB
ActionScript

import construction.Constructible;
import construction.ShipConstructible;
from construction.OrbitalConstructible import OrbitalConstructible;
from construction.AsteroidConstructible import AsteroidConstructible;
from construction.TerraformConstructible import TerraformConstructible;
from construction.RetrofitConstructible import RetrofitConstructible;
from construction.DryDockConstructible import DryDockConstructible;
from construction.ExportConstructible import ExportConstructible;
from construction.StationConstructible import StationConstructible;
from construction.BuildingConstructible import BuildingConstructible;
from construction.ConstructionConstructible import ConstructionConstructible;
from constructions import ConstructionType, getConstructionType;
import object_creation;
import resources;
import systems;
import buildings;
import bool getCheatsEverOn() from "cheats";
from regions.regions import getRegion;
#include "include/resource_constants.as"
enum ConstructionCapability {
CC_Ship = 0x1,
CC_Orbital = 0x2,
CC_Asteroid = 0x4,
CC_Terraform = 0x8,
CC_Supports = 0x10,
};
bool allowConstructFrom(Object& obj, Object& constructFrom) {
if(obj is constructFrom)
return true;
if(obj.isOrbital && constructFrom.isOrbital) {
if(cast<Orbital>(constructFrom).isMaster(obj))
return true;
}
return false;
}
tidy class Construction : Component_Construction, Savable {
Constructible@[] queue;
uint capabilities = 0;
bool deltaConstruction = false;
bool buildingSupport = false;
bool deltaStored = false;
bool curUsingLabor = false;
int nextID = 0;
bool canExport = true;
bool canImport = false;
double LaborIncome = 0;
double DistributedLabor = 0;
double LaborFactor = 1.0;
double hpBonus = 0.0;
int supportSpeed = 100;
int shipCost = 100;
int orbitalCost = 100;
double orbitalMaint = 1.0;
double terraformCost = 1.0;
double constructionCost = 1.0;
double laborStorage = 0;
double storedLabor = 0;
uint supportId = 0;
const Design@ supportDesign;
Object@ supportFor;
double supportLabor = -1;
int consType = -1;
bool repeating = false;
bool rally = false;
Object@ rallyObj;
vec3d rallyPoint;
Construction() {
}
void load(SaveFile& msg) {
msg >> capabilities;
msg >> LaborIncome;
msg >> LaborFactor;
msg >> DistributedLabor;
msg >> supportSpeed;
msg >> shipCost;
msg >> orbitalCost;
msg >> orbitalMaint;
msg >> nextID;
msg >> terraformCost;
msg >> canExport;
msg >> canImport;
if(msg >= SV_0035)
msg >> constructionCost;
if(msg >= SV_0090) {
msg >> laborStorage;
msg >> storedLabor;
}
if(msg > SV_0021) {
msg >> rally;
msg >> rallyObj;
msg >> rallyPoint;
}
if(msg >= SV_0117)
msg >> repeating;
uint cnt = 0;
msg >> cnt;
for(uint i = 0; i < cnt; ++i) {
Constructible@ cons;
uint8 type = 0;
msg >> type;
switch(type) {
case CT_Orbital:
@cons = OrbitalConstructible(msg);
break;
case CT_Flagship:
@cons = ShipConstructible(msg);
break;
case CT_Station:
@cons = StationConstructible(msg);
break;
case CT_Asteroid:
@cons = AsteroidConstructible(msg);
break;
case CT_Terraform:
@cons = TerraformConstructible(msg);
break;
case CT_Retrofit:
@cons = RetrofitConstructible(msg);
break;
case CT_DryDock:
@cons = DryDockConstructible(msg);
break;
case CT_Export:
@cons = ExportConstructible(msg);
break;
case CT_Building:
@cons = BuildingConstructible(msg);
break;
case CT_Construction:
@cons = ConstructionConstructible(msg);
break;
}
if(cons !is null)
queue.insertLast(cons);
}
msg >> supportId;
msg >> supportDesign;
msg >> supportFor;
msg >> supportLabor;
if(msg >= SV_0147)
msg >> hpBonus;
}
void save(SaveFile& msg) {
msg << capabilities;
msg << LaborIncome;
msg << LaborFactor;
msg << DistributedLabor;
msg << supportSpeed;
msg << shipCost;
msg << orbitalCost;
msg << orbitalMaint;
msg << nextID;
msg << terraformCost;
msg << canExport;
msg << canImport;
msg << constructionCost;
msg << laborStorage;
msg << storedLabor;
msg << rally;
msg << rallyObj;
msg << rallyPoint;
msg << repeating;
uint cnt = queue.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
queue[i].save(msg);
msg << supportId;
msg << supportDesign;
msg << supportFor;
msg << supportLabor;
msg << hpBonus;
}
double get_constructionCostMod() const {
return constructionCost;
}
double get_laborIncome() const {
return LaborIncome * LaborFactor;
}
double get_baseLaborIncome() const {
return LaborIncome;
}
double get_laborFactor() const {
return LaborFactor;
}
double get_laborStorageCapacity() const {
return laborStorage;
}
double get_currentLaborStored() const {
return storedLabor;
}
bool get_isRepeating() const {
return repeating;
}
void modLaborIncome(Object& obj, double mod) {
LaborIncome += mod;
deltaConstruction = true;
if(laborIncome >= LABOR_ACHIEVE_THRESH && obj.owner is playerEmpire && !getCheatsEverOn())
unlockAchievement("ACH_LABOR200");
}
void modLaborFactor(Object& obj, double mod) {
LaborFactor += mod;
deltaConstruction = true;
if(laborIncome >= LABOR_ACHIEVE_THRESH && obj.owner is playerEmpire && !getCheatsEverOn())
unlockAchievement("ACH_LABOR200");
}
void setDistributedLabor(Object& obj, double val) {
if(val != DistributedLabor) {
LaborIncome += val - DistributedLabor;
DistributedLabor = val;
deltaConstruction = true;
if(laborIncome >= LABOR_ACHIEVE_THRESH && obj.owner is playerEmpire && !getCheatsEverOn())
unlockAchievement("ACH_LABOR200");
}
}
void setRepeating(bool value) {
repeating = value;
deltaConstruction = true;
}
double get_distributedLabor() {
return DistributedLabor;
}
float get_constructionProgress() const {
if(queue.length == 0)
return -1.f;
if(queue[0].totalLabor <= 0)
return 1.f;
return queue[0].curLabor / queue[0].totalLabor;
}
const Design@ get_constructionDesign() const {
if(queue.length == 0)
return null;
Constructible@ top = queue[0];
ShipConstructible@ flagship = cast<ShipConstructible@>(top);
if(flagship !is null)
return flagship.design;
return null;
}
bool hasConstructionUnder(double eta) {
if(queue.length == 0)
return false;
if(LaborIncome == 0)
return false;
return (queue[0].totalLabor - queue[0].curLabor) / (LaborIncome * LaborFactor) < eta;
}
bool get_canBuildSupports() {
return capabilities & CC_Supports != 0;
}
void set_canBuildSupports(bool value) {
if(value == get_canBuildSupports())
return;
if(value)
capabilities |= CC_Supports;
else
capabilities &= ~CC_Supports;
deltaConstruction = true;
}
bool get_canBuildShips() {
return capabilities & CC_Ship != 0;
}
void set_canBuildShips(bool value) {
if(value == get_canBuildShips())
return;
if(value)
capabilities |= CC_Ship | CC_Supports;
else
capabilities &= ~(CC_Ship | CC_Supports);
deltaConstruction = true;
}
bool get_canBuildOrbitals() {
return capabilities & CC_Orbital != 0;
}
void set_canBuildOrbitals(bool value) {
if(value == get_canBuildOrbitals())
return;
if(value)
capabilities |= CC_Orbital;
else
capabilities &= ~CC_Orbital;
deltaConstruction = true;
}
bool get_canBuildAsteroids() {
return capabilities & CC_Asteroid != 0;
}
void set_canBuildAsteroids(bool value) {
if(value == get_canBuildAsteroids())
return;
if(value)
capabilities |= CC_Asteroid;
else
capabilities &= ~CC_Asteroid;
deltaConstruction = true;
}
bool get_canTerraform() {
return capabilities & CC_Terraform != 0;
}
void set_canTerraform(bool value) {
if(value == get_canTerraform())
return;
if(value)
capabilities |= CC_Terraform;
else
capabilities &= ~CC_Terraform;
deltaConstruction = true;
}
bool get_canExportLabor() {
return canExport;
}
void set_canExportLabor(bool value) {
if(value == canExport)
return;
canExport = value;
deltaConstruction = true;
}
bool get_canImportLabor() {
return canImport;
}
void set_canImportLabor(bool value) {
if(value == canImport)
return;
canImport = value;
deltaConstruction = true;
}
uint get_constructionCount() const {
return queue.length;
}
uint get_constructionType() const {
return consType;
}
string get_constructionName(uint num) const {
if(num >= queue.length)
return "(null)";
return queue[num].name;
}
int get_constructionID(uint num) const {
if(num >= queue.length)
return -1;
return queue[num].id;
}
void getConstructionQueue() {
for(uint i = 0, cnt = queue.length; i < cnt; ++i)
yield(queue[i]);
}
void getConstructionQueue(uint limit) {
for(uint i = 0, cnt = min(queue.length, limit); i < cnt; ++i)
yield(queue[i]);
}
bool queueConstructible(Object& obj, Constructible@ cons) {
if(!cons.pay(obj))
return false;
cons.id = nextID++;
queue.insertLast(cons);
deltaConstruction = true;
if(queue.length == 1)
startConstruction(obj);
return true;
}
int constructibleIndex(int id) {
for(uint i = 0, cnt = queue.length; i < cnt; ++i)
if(queue[i].id == id)
return i;
return -1;
}
int shipConsIndex(int id) {
if(id == -1) {
for(int i = queue.length - 1; i >= 0; --i)
if(queue[i].type == CT_Flagship || queue[i].type == CT_Station)
return i;
}
else {
for(uint i = 0, cnt = queue.length; i < cnt; ++i)
if(queue[i].id == id)
return i;
}
return -1;
}
void startConstruction(Object& obj) {
if(queue.length == 0)
return;
Constructible@ active = queue[0];
active.start(obj);
}
int get_supportBuildSpeed(const Object& obj) const {
int cost = 100;
cost = max(10, cost + (supportSpeed - 100));
return cost;
}
int get_shipBuildCost(const Object& obj) const {
int cost = 100;
cost = max(10, cost + (shipCost - 100));
return cost;
}
int get_orbitalBuildCost(const Object& obj) const {
int cost = 100;
cost = max(10, cost + (orbitalCost - 100));
return cost;
}
void modSupportBuildSpeed(int amt) {
supportSpeed += amt;
deltaConstruction = true;
}
void modShipBuildCost(int amt) {
shipCost += amt;
deltaConstruction = true;
}
void multConstructionCostMod(double multFactor) {
constructionCost *= multFactor;
deltaConstruction = true;
}
void modOrbitalBuildCost(int amt) {
orbitalCost += amt;
deltaConstruction = true;
}
double get_orbitalMaintenanceMod(const Object& obj) const {
double cost = 1.0;
cost *= clamp(orbitalMaint, 0.01f, 1.f);
return cost;
}
void modOrbitalMaintenanceMod(double amt) {
orbitalMaint *= amt;
deltaConstruction = true;
}
double get_terraformCostMod() const {
return terraformCost;
}
void modTerraformCostMod(double amt) {
terraformCost += amt;
deltaConstruction = true;
}
bool get_constructingSupport() const {
return supportDesign !is null;
}
bool get_isUsingLabor() const {
return curUsingLabor;
}
void clearRally() {
rally = false;
@rallyObj = null;
deltaConstruction = true;
}
void rallyTo(Object& obj, Object@ dest) {
if(dest is null || !dest.valid || !dest.isVisibleTo(obj.owner))
clearRally();
rally = true;
@rallyObj = dest;
rallyPoint = dest.position;
deltaConstruction = true;
}
void rallyTo(vec3d position) {
rally = true;
rallyPoint = position;
deltaConstruction = true;
}
bool get_isRallying() {
return rally;
}
vec3d get_rallyPosition() {
return rallyPoint;
}
Object@ get_rallyObject() {
return rallyObj;
}
void doRally(Object& obj, Object@ orderObj) {
if(!rally || obj.owner !is orderObj.owner || !orderObj.hasLeaderAI)
return;
if(rallyObj !is null && rallyObj.isVisibleTo(obj.owner)) {
switch(rallyObj.type) {
case OT_Planet:
{
Planet@ pl = cast<Planet>(rallyObj);
vec2d off = random2d(pl.radius + 0.5 + orderObj.radius, pl.OrbitSize - 0.1);
orderObj.addMoveOrder(pl.position + vec3d(off.x, 0.0, off.y));
break;
}
case OT_Star:
{
Region@ reg = rallyObj.region;
orderObj.addMoveOrder(reg.position + (orderObj.position - reg.position).normalize(reg.radius * 0.9));
break;
}
case OT_Region:
{
Region@ reg = cast<Region>(rallyObj);
orderObj.addMoveOrder(reg.position + (orderObj.position - reg.position).normalize(reg.radius * 0.9));
break;
}
default:
orderObj.addGotoOrder(rallyObj); break;
}
}
else {
orderObj.addMoveOrder(rallyPoint);
}
}
bool laborFlag = false;
int labObj = 0;
bool flagUsingLabor(Object@ obj) {
if(obj !is null) {
int id = obj.id;
if(labObj == id)
return true;
if(labObj != 0)
return false;
labObj = id;
}
else {
if(labObj != 0)
return false;
}
laborFlag = true;
return true;
}
void constructionTick(Object& obj, double time) {
bool usingLabor = laborFlag;
if(rally && rallyObj !is null && rallyObj.isVisibleTo(obj.owner))
rallyPoint = rallyObj.position;
//Handle support construction
double subLabor = time * LaborIncome * LaborFactor * double(get_supportBuildSpeed(obj)) / 100.0;
double resvLabor = 0.0;
if(supportDesign !is null) {
subLabor *= 0.5;
resvLabor = subLabor;
usingLabor = true;
}
for(uint i = 0, cnt = queue.length; i < cnt && subLabor > 0; ++i) {
ShipConstructible@ cons = cast<ShipConstructible>(queue[i]);
if(cons !is null && cons.hasSupportsBuilding) {
subLabor = cons.addSupportLabor(subLabor);
usingLabor = true;
}
}
if(supportDesign !is null) {
subLabor += resvLabor;
if(subLabor > 0) {
if(supportLabor < 0) {
supportLabor = getLaborCost(supportDesign, 1);
}
else {
supportLabor -= subLabor;
if(supportLabor <= 0) {
Object@ at = obj;
if(supportDesign.hasTag(ST_Satellite) && supportFor.isPlanet)
@at = supportFor;
Ship@ ship = createShip(at, supportDesign, obj.owner, null, false);
supportFor.supportBuildFinished(supportId, supportDesign, obj, ship);
if(hpBonus != 0)
ship.modHPFactor(+hpBonus);
@supportDesign = null;
@supportFor = null;
deltaConstruction = true;
}
}
}
}
//Handle the queue
double tick = time * LaborIncome * LaborFactor;
double prevMaximumStored = max(storedLabor, laborStorage);
if(queue.length != 0 && storedLabor > 0) {
double takeLabor = min(storedLabor, max(prevMaximumStored, 1.0) * time / config::LABOR_STORAGE_DUMP_TIME);
tick += takeLabor;
storedLabor -= takeLabor;
}
uint index = 0;
while(index < queue.length) {
Constructible@ active = queue[index];
if(!active.paid) {
if(!active.pay(obj))
break;
}
if(!active.start(obj))
break;
double remain = active.totalLabor - active.curLabor;
if(remain <= tick && active.canComplete) {
deltaConstruction = true;
tick -= remain;
active.complete(obj);
active.remove(obj);
queue.removeAt(index);
if(repeating) {
if(active.repeat(obj)) {
active.id = nextID++;
queue.insertLast(active);
}
}
usingLabor = true;
deltaConstruction = true;
}
else {
active.curLabor += tick;
uint result = active.tick(obj, time);
switch(result) {
case TR_Remove:
active.remove(obj);
queue.removeAt(index);
deltaConstruction = true;
break;
case TR_UsedLabor:
usingLabor = true;
tick = 0;
break;
case TR_VanishLabor:
tick = 0;
break;
case TR_UnusedLabor:
//Fall through the tick labor
break;
}
if(tick <= 0)
break;
else
index += 1;
}
}
if(tick > 0 && storedLabor < prevMaximumStored) {
storedLabor = min(prevMaximumStored, storedLabor + tick);
deltaStored = true;
usingLabor = true;
}
laborFlag = false;
labObj = 0;
curUsingLabor = usingLabor;
if(queue.length == 0)
consType = -1;
else
consType = queue[0].type;
}
uint queuePosition(int id) {
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
if(queue[i].id == id)
return i;
}
return uint(-1);
}
void modLaborStorage(double mod) {
laborStorage += mod;
deltaStored = true;
}
void modStoredLabor(double mod, bool obeyCap = false) {
if(obeyCap) {
if(storedLabor < laborStorage)
storedLabor = min(laborStorage, storedLabor + mod);
}
else {
storedLabor += mod;
}
deltaStored = true;
}
void cancelConstruction(Object& obj, int id) {
int index = constructibleIndex(id);
if(index == -1)
return;
queue[index].cancel(obj);
queue[index].remove(obj);
queue.removeAt(index);
deltaConstruction = true;
}
void moveConstruction(Object& obj, int id, int beforeId = -1) {
int myIndex = constructibleIndex(id);
if(myIndex == -1)
return;
int dropIndex = constructibleIndex(beforeId);
if(dropIndex == -1) {
queue.insertLast(queue[myIndex]);
queue.removeAt(myIndex);
}
else {
Constructible@ copy = queue[myIndex];
queue.removeAt(myIndex);
if(myIndex < dropIndex)
--dropIndex;
queue.insertAt(dropIndex, copy);
}
for(uint i = 0, cnt = queue.length; i < cnt; ++i)
queue[i].move(obj, i);
deltaConstruction = true;
}
void buildOrbital(Object& obj, int type, vec3d position, Object@ frame = null, Object@ constructFrom = null) {
if(capabilities & CC_Orbital == 0)
return;
Object@ pathFrom = obj;
if(constructFrom !is null) {
if(!allowConstructFrom(obj, constructFrom))
return;
@pathFrom = constructFrom;
}
auto@ def = getOrbitalModule(type);
if(def is null)
return;
if(!def.canBuildBy(obj))
return;
if(!def.canBuildAt(obj, position))
return;
if(!canBuildOrbital(pathFrom, position, true))
return;
Orbital@ orbFrame = cast<Orbital>(frame);
if(orbFrame !is null && orbFrame.getValue(OV_FRAME_Usable) == 0.0)
@orbFrame = null;
if(orbFrame !is null && orbFrame.owner !is obj.owner)
@orbFrame = null;
OrbitalConstructible cons(obj, def, position);
cons.buildCost = ceil(double(cons.buildCost) * double(get_orbitalBuildCost(obj)) / 100.0);
cons.buildCost *= constructionCost;
double penFact = 1.0;
if(orbFrame !is null) {
cons.buildCost *= orbFrame.getValue(OV_FRAME_CostFactor);
cons.totalLabor *= orbFrame.getValue(OV_FRAME_LaborFactor);
penFact = orbFrame.getValue(OV_FRAME_LaborPenaltyFactor);
}
TradePath path(obj.owner);
Region@ target = getRegion(position);
path.generate(getSystem(pathFrom.region), getSystem(target));
if(!path.isUsablePath)
return;
cons.totalLabor *= 1.0 + config::ORBITAL_LABOR_COST_STEP * penFact * double(path.pathSize - 1);
if(queueConstructible(obj, cons)) {
if(orbFrame !is null)
orbFrame.sendObject(OV_FRAME_Target, cons.target);
}
}
void buildStation(Object& obj, const Design@ design, vec3d position, Object@ frame = null, Object@ constructFrom = null) {
if(capabilities & CC_Orbital == 0)
return;
if(design is null || design.hull is null || !design.hasTag(ST_Station)) {
error("Invalid design for station construction at " + obj.name);
return;
}
Object@ pathFrom = obj;
if(constructFrom !is null) {
if(!allowConstructFrom(obj, constructFrom))
return;
@pathFrom = constructFrom;
}
if(!canBuildOrbital(pathFrom, position))
return;
Orbital@ orbFrame = cast<Orbital>(frame);
if(orbFrame !is null && orbFrame.getValue(OV_FRAME_Usable) == 0.0)
@orbFrame = null;
if(orbFrame !is null && orbFrame.owner !is obj.owner)
@orbFrame = null;
TradePath path(obj.owner);
Region@ target = getRegion(position);
Region@ reg = pathFrom.region;
if(reg is null)
return;
auto@ fromSys = getSystem(reg);
auto@ toSys = getSystem(target);
if(fromSys is null || toSys is null)
return;
path.generate(fromSys, toSys);
if(!path.isUsablePath)
return;
double penFact = 1.0;
if(orbFrame !is null)
penFact = orbFrame.getValue(OV_FRAME_LaborPenaltyFactor);
double penalty = 1.0 + config::ORBITAL_LABOR_COST_STEP * penFact * double(path.pathSize - 1);
StationConstructible cons(design, position, penalty);
cons.totalLabor *= obj.owner.OrbitalLaborCostFactor;
cons.buildCost *= obj.owner.OrbitalBuildCostFactor;
cons.buildCost *= constructionCost;
if(orbFrame !is null) {
cons.buildCost *= orbFrame.getValue(OV_FRAME_CostFactor);
cons.totalLabor *= orbFrame.getValue(OV_FRAME_LaborFactor);
}
if(queueConstructible(obj, cons)) {
if(orbFrame !is null)
orbFrame.sendObject(OV_FRAME_Target, cons.target);
}
}
void buildFlagship(Object& obj, const Design@ design, Object@ constructFrom) {
if(capabilities & CC_Ship == 0)
return;
if(constructFrom !is null) {
if(!allowConstructFrom(obj, constructFrom))
return;
}
if(design is null || design.hull is null || !design.hasTag(ST_Flagship)) {
error("Invalid design for ship construction at " + obj.name);
return;
}
ShipConstructible cons(design);
@cons.constructFrom = constructFrom;
cons.buildCost = ceil(double(cons.buildCost) * double(get_shipBuildCost(obj)) / 100.0);
cons.buildCost *= constructionCost;
queueConstructible(obj, cons);
}
void buildDryDock(Object& obj, const Design@ forDesign, float pct) {
if(capabilities & CC_Ship == 0 || capabilities & CC_Orbital == 0)
return;
if(pct < 0.01f)
return;
auto@ module = getOrbitalModule("DryDock");
if(module is null) {
error("Could not find 'DryDock' orbital module for drydock construction.");
return;
}
if(!module.canBuildBy(obj))
return;
if(!payDesignCosts(obj, forDesign))
return;
//Pay for all this*/
int cost = getBuildCost(forDesign);
cost = 100 + ceil(float(cost) * pct);
cost *= constructionCost;
cost = double(cost) * config::DRYDOCK_BUILDCOST_FACTOR * obj.owner.DrydockCostFactor;
if(obj.owner.consumeBudget(cost) == -1)
return;
ObjectDesc oDesc;
oDesc.type = OT_Orbital;
@oDesc.owner = obj.owner;
oDesc.name = format(locale::DRY_DOCK_NAME, forDesign.name);
oDesc.radius = pow(forDesign.size, 1.0/2.5);
//Figure out a good position to put this dry dock
double minRad = obj.radius * 2.5, maxRad = minRad * 2.0;
Planet@ pl = cast<Planet>(obj);
if(pl !is null)
maxRad = pl.OrbitSize;
vec2d pos = random2d(minRad, maxRad);
vec3d offset(pos.x, 0, pos.y);
oDesc.position = obj.position + offset;
Orbital@ orb = cast<Orbital>(makeObject(oDesc));
orb.addSection(module.id);
orb.sendDesign(OV_DRY_Design, forDesign);
orb.sendValue(OV_DRY_SetFinanced, pct);
//Start building on it
DryDockConstructible cons(obj, orb);
queueConstructible(obj, cons);
}
void workDryDock(Object& obj, Orbital@ dryDock) {
if(capabilities & CC_Ship == 0)
return;
if(dryDock is null || dryDock.owner !is obj.owner)
return;
DryDockConstructible cons(obj, dryDock);
cons.buildCost *= constructionCost;
queueConstructible(obj, cons);
}
void exportLaborTo(Object& obj, Object@ exportTo) {
if(!canExport)
return;
if(!exportTo.hasConstruction || !exportTo.canImportLabor)
return;
ExportConstructible cons(obj, exportTo);
cons.buildCost *= constructionCost;
queueConstructible(obj, cons);
}
void addSupportShipConstruction(Object& obj, int id, const Design@ dsg, uint amount) {
int index = shipConsIndex(id);
if(index == -1)
return;
ShipConstructible@ cons = cast<ShipConstructible>(queue[index]);
if(cons is null)
return;
uint maxAmount = floor(cons.getSupportSupplyFree() / dsg.size);
amount = min(amount, maxAmount);
if(amount == 0)
return;
if(!payDesignCosts(obj, dsg, amount))
return;
int cost = getBuildCost(dsg, amount);
int take = obj.owner.consumeBudget(cost, true);
if(take == -1) {
reverseDesignCosts(obj, dsg, amount);
return;
}
if(!cons.addSupports(obj, dsg, amount, cycle=take))
obj.owner.refundBudget(cost, take);
int maint = getMaintenanceCost(dsg, amount);
cons.maintainCost += maint;
cons.buildCost += cost;
if(cons.started)
obj.owner.modMaintenance(maint, MoT_Construction);
deltaConstruction = true;
}
void removeSupportShipConstruction(Object& obj, int id, const Design@ dsg, uint amount) {
int index = shipConsIndex(id);
if(index == -1)
return;
ShipConstructible@ cons = cast<ShipConstructible>(queue[index]);
if(cons is null)
return;
cons.removeSupports(dsg, amount, refund = obj);
deltaConstruction = true;
}
void buildAsteroid(Object& obj, Asteroid@ asteroid, uint resId, Object@ constructFrom) {
if(capabilities & CC_Asteroid == 0)
return;
Object@ pathFrom = obj;
if(constructFrom !is null) {
if(!allowConstructFrom(obj, constructFrom))
return;
@pathFrom = constructFrom;
}
const ResourceType@ res = getResource(resId);
if(res is null)
return;
double cost = asteroid.getAvailableCostFor(resId);
if(cost < 0.0)
return;
if(!asteroid.canDevelop(obj.owner))
return;
TradePath path(obj.owner);
path.generate(getSystem(pathFrom.region), getSystem(asteroid.region));
if(!path.isUsablePath)
return;
double costFactor = 1.0 + config::ASTEROID_COST_STEP * double(path.pathSize - 1);
AsteroidConstructible cons(obj, asteroid, res, cost * costFactor);
queueConstructible(obj, cons);
}
void buildConstruction(Object& obj, uint consId, Object@ objTarg = null, vec3d pointTarg = vec3d()) {
const ConstructionType@ type = getConstructionType(consId);
if(type is null)
return;
Targets targs(type.targets);
if(targs.length != 0) {
if(targs[0].type == TT_Object) {
targs[0].filled = true;
@targs[0].obj = objTarg;
}
else if(targs[0].type == TT_Point) {
targs[0].filled = true;
targs[0].point = pointTarg;
}
}
if(!type.canBuild(obj, targs))
return;
ConstructionConstructible cons(obj, type, targs);
queueConstructible(obj, cons);
}
void startTerraform(Object& obj, Planet@ planet, uint resId) {
if(capabilities & CC_Terraform == 0)
return;
if(config::ENABLE_TERRAFORMING == 0)
return;
const ResourceType@ res = getResource(resId);
if(res is null || !res.canTerraform(obj, planet))
return;
if(planet.owner !is obj.owner || obj is planet)
return;
if(planet.isTerraforming())
return;
double cost = res.terraformCost * terraformCost * constructionCost;
double labor = res.terraformLabor;
if(cost < 0.0)
return;
TradePath path(obj.owner);
path.generate(getSystem(obj.region), getSystem(planet.region));
if(!path.isUsablePath)
return;
double costFactor = 1.0 + config::TERRAFORM_COST_STEP * double(path.pathSize - 1);
cost *= costFactor;
labor *= costFactor;
if(cost < 0.0)
return;
TerraformConstructible cons(obj, planet, res, cost, labor);
queueConstructible(obj, cons);
}
void startBuildingConstruction(Object& obj, uint id, vec2i position) {
auto@ type = getBuildingType(id);
if(type is null)
return;
queueConstructible(obj, BuildingConstructible(obj, position, type));
}
void cancelBuildingConstruction(Object& obj, uint id, vec2i position) {
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
auto@ cons = cast<BuildingConstructible>(queue[i]);
if(cons is null)
continue;
if(cons.position != position || cons.building.id != id)
continue;
cancelConstruction(obj, cons.id);
break;
}
}
void startRetrofitConstruction(Object& obj, Object@ fleet, int buildCost, double laborCost, int extraMaint, Object@ constructFrom) {
if(capabilities & CC_Ship == 0) {
fleet.stopFleetRetrofit(obj);
return;
}
if(constructFrom !is null) {
if(!allowConstructFrom(obj, constructFrom)) {
fleet.stopFleetRetrofit(obj);
return;
}
}
RetrofitConstructible cons(obj, fleet, buildCost, laborCost, extraMaint);
@cons.constructFrom = constructFrom;
cons.buildCost *= constructionCost;
if(!queueConstructible(obj, cons))
fleet.stopFleetRetrofit(obj);
}
array<const Design@> retrofitCosts;
void retrofitDesignCost(Object& obj, Object@ fleet, const Design@ dsg) {
retrofitCosts.insertLast(dsg);
}
void retrofitDesignCostFinish(Object& obj, Object@ fleet) {
for(uint i = 0, cnt = retrofitCosts.length; i < cnt; ++i) {
auto@ dsg = retrofitCosts[i];
if(!payDesignCosts(obj, dsg)) {
for(uint n = 0; n < i; ++n)
reverseDesignCosts(obj, dsg, cancel=false);
for(uint i = 0, cnt = queue.length; i < cnt; ++i) {
auto@ cons = cast<RetrofitConstructible>(queue[i]);
if(cons is null)
continue;
if(cons.fleet !is fleet)
continue;
cancelConstruction(obj, cons.id);
}
break;
}
}
retrofitCosts.length = 0;
}
void buildSupport(Object& obj, uint id, const Design@ design, Object@ buildFor) {
if(capabilities & CC_Supports == 0)
return;
if(supportDesign !is null)
return;
if(!payDesignCosts(obj, design))
return;
@supportDesign = design;
@supportFor = buildFor;
supportId = id;
supportFor.supportBuildStarted(supportId, design, obj);
deltaConstruction = true;
}
void transferBuildSupport(uint id, Object@ buildFor) {
if(capabilities & CC_Supports == 0)
return;
if(supportId != id)
return;
@supportFor = buildFor;
}
void cancelBuildSupport(Object& obj, uint id) {
if(capabilities & CC_Supports == 0)
return;
if(supportId != id)
return;
if(supportDesign !is null)
reverseDesignCosts(obj, supportDesign, cancel=true);
@supportDesign = null;
@supportFor = null;
deltaConstruction = true;
}
void modConstructionHPBonus(double mod) {
hpBonus += mod;
}
double get_constructionHPBonus() {
return hpBonus;
}
bool writeConstructionDelta(Message& msg) {
if(!deltaConstruction) {
if(queue.length > 0) {
msg.write1();
msg.write0();
if(deltaStored) {
msg.write1();
msg << float(laborStorage);
msg << float(storedLabor);
msg.write0();
}
else {
msg.write0();
}
queue[0].write(msg);
return true;
}
if(deltaStored) {
msg.write1();
msg.write0();
msg.write1();
msg << float(laborStorage);
msg << float(storedLabor);
msg.write1();
return true;
}
return false;
}
msg.write1();
msg.write1();
writeConstruction(msg);
deltaConstruction = false;
return true;
}
void destroyConstruction(Object& obj) {
for(uint i = 0, cnt = queue.length; i < cnt; ++i)
queue[i].remove(obj);
queue.length = 0;
}
void writeConstruction(Message& msg) {
uint cnt = queue.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
queue[i].write(msg);
msg << rally;
if(rally) {
msg.writeBit(rallyObj !is null);
if(rallyObj !is null)
msg << rallyObj;
else
msg.writeMedVec3(rallyPoint);
}
msg << capabilities;
msg << repeating;
msg.writeBit(supportDesign !is null);
msg << float(LaborIncome);
msg << float(LaborFactor);
msg << float(DistributedLabor);
msg << float(constructionCost);
msg << canExport;
msg << canImport;
msg << supportSpeed;
msg << shipCost;
msg << orbitalCost;
msg << float(terraformCost);
msg << float(orbitalMaint);
msg << float(laborStorage);
msg << float(storedLabor);
}
};