1767 lines
43 KiB
ActionScript
1767 lines
43 KiB
ActionScript
#priority init 2000
|
|
import tile_resources;
|
|
import util.formatting;
|
|
import hooks;
|
|
from saving import SaveIdentifier, SaveVersion;
|
|
import bool readLevelChain(ReadFile& file) from "planet_levels";
|
|
|
|
const double[] LEVEL_DISTRIBUTION = {0.76, 0.15, 0.06, 0.03};
|
|
const double[] RARITY_DISTRIBUTION = {
|
|
0.88, 0.09, 0.02, 0.01, 0.05, 0.00002, //Level 0
|
|
0.73, 0.20, 0.05, 0.02, 0.05, 0.00002, //Level 1
|
|
0.70, 0.20, 0.06, 0.04, 0.05, 0.00002, //Level 2
|
|
0.70, 0.18, 0.08, 0.04, 0.05, 0.00002, //Level 3
|
|
};
|
|
const int CARGO_WORTH_LEVEL = 10;
|
|
|
|
//Various ways excess budget may be exchanged into resources
|
|
enum WelfareMode {
|
|
WM_Influence,
|
|
WM_Energy,
|
|
WM_Research,
|
|
WM_HW_Labor,
|
|
WM_Defense,
|
|
|
|
WM_COUNT
|
|
};
|
|
|
|
tidy final class ResourceClass {
|
|
uint id = 0;
|
|
string ident;
|
|
string name;
|
|
array<const ResourceType@> types;
|
|
};
|
|
|
|
enum ResourceMode {
|
|
RM_Normal,
|
|
RM_Universal,
|
|
RM_UniversalUnique,
|
|
RM_NonRequirement,
|
|
};
|
|
|
|
enum ResourceRarity {
|
|
RR_Common,
|
|
RR_Uncommon,
|
|
RR_Rare,
|
|
RR_Epic,
|
|
RR_Unique,
|
|
RR_Mythical,
|
|
|
|
RR_COUNT
|
|
};
|
|
|
|
enum VanishMode {
|
|
VM_Never,
|
|
VM_WhenExported,
|
|
VM_Always,
|
|
VM_ExportedInCombat,
|
|
VM_Custom,
|
|
};
|
|
|
|
enum MoneyType {
|
|
MoT_Misc,
|
|
MoT_Construction,
|
|
MoT_Orbitals,
|
|
MoT_Buildings,
|
|
MoT_Ships,
|
|
MoT_Planet_Upkeep,
|
|
MoT_Planet_Income,
|
|
MoT_Colonizers,
|
|
MoT_Handicap,
|
|
MoT_Trade,
|
|
MoT_Vassals,
|
|
//Update save version when adding
|
|
// See: components.ResourceManager
|
|
|
|
MoT_COUNT
|
|
};
|
|
|
|
tidy final class ResourceType {
|
|
const ResourceClass@ cls;
|
|
uint id = 0;
|
|
string ident;
|
|
string name;
|
|
string description;
|
|
string blurb;
|
|
string nativeBiome;
|
|
string className;
|
|
string dlc;
|
|
uint level = 0;
|
|
Sprite icon;
|
|
Sprite smallIcon;
|
|
ResourceSheet@ distantSheet;
|
|
uint distantIndex = 0;
|
|
double frequency = 1;
|
|
double distribution = 1;
|
|
double rarityScore = 1;
|
|
double vanishTime = -1.0;
|
|
VanishMode vanishMode = VM_Never;
|
|
bool exportable = true;
|
|
ResourceMode mode = RM_Normal;
|
|
int[] tilePressure = array<int>(TR_COUNT, 0);
|
|
int totalPressure = 0;
|
|
int displayWeight = 0;
|
|
int cargoWorth = 0;
|
|
array<uint> affinities;
|
|
ResourceRarity rarity = RR_Common;
|
|
bool artificial = false;
|
|
bool requirementDisplay = true;
|
|
bool willLock = false;
|
|
bool unique = false;
|
|
double requireContestation = -INFINITY;
|
|
int rarityLevel = -1;
|
|
bool limitlessLevel = false;
|
|
bool canBeTerraformed = true;
|
|
|
|
bool get_hasEffect() const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
|
|
if(hooks[i].hasEffect)
|
|
return true;
|
|
}
|
|
if(!exportable)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
double asteroidFrequency = 0.0;
|
|
double asteroidCost = 0.0;
|
|
|
|
int terraformCost = 0;
|
|
double terraformLabor = 0.0;
|
|
|
|
array<IResourceHook@> hooks;
|
|
array<Hook@> ai;
|
|
|
|
void addAffinity(uint v) {
|
|
affinities.insertLast(v);
|
|
}
|
|
|
|
bool isMaterial(uint onLevel) const {
|
|
if(mode == RM_NonRequirement)
|
|
return true;
|
|
if(!exportable)
|
|
return true;
|
|
if(level < onLevel)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool canTerraform(Object@ from, Object@ to) const {
|
|
auto@ curType = getResource(to.primaryResourceType);
|
|
if(curType !is null && !curType.canBeTerraformed)
|
|
return false;
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
|
|
if(!hooks[i].canTerraform(from, to))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
double get_totalRarity() const {
|
|
return 1.0 / rarityScore;
|
|
}
|
|
|
|
string formatEffect(Object& obj, array<const IResourceHook@>& hooks) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
|
|
if(hooks[i].hasEffect)
|
|
return hooks[i].formatEffect(obj, hooks);
|
|
}
|
|
if(!exportable)
|
|
return locale::CANNOT_EXPORT;
|
|
return "---";
|
|
}
|
|
|
|
bool shouldVanish(Object& obj, Resource@ native) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
|
|
if(hooks[i].shouldVanish(obj, native))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void onAdd(Object& obj, Resource@ r) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onAdd(obj, r);
|
|
}
|
|
|
|
void onRemove(Object& obj, Resource@ r) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onRemove(obj, r);
|
|
}
|
|
|
|
void onTick(Object& obj, Resource@ r, double tick) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onTick(obj, r, tick);
|
|
}
|
|
|
|
void onOwnerChange(Object& obj, Resource@ r, Empire@ prevOwner, Empire@ newOwner) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onOwnerChange(obj, r, prevOwner, newOwner);
|
|
}
|
|
|
|
void onRegionChange(Object& obj, Resource@ r, Region@ fromRegion, Region@ toRegion) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onRegionChange(obj, r, fromRegion, toRegion);
|
|
}
|
|
|
|
void save(Resource@ r, SaveFile& file) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].save(r, file);
|
|
}
|
|
|
|
void load(Resource@ r, SaveFile& file) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].load(r, file);
|
|
}
|
|
|
|
void applyGraphics(Object& obj, Node& node) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].applyGraphics(obj, node);
|
|
}
|
|
|
|
void onTerritoryAdd(Object& obj, Resource@ r, Territory@ terr) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onTerritoryAdd(obj, r, terr);
|
|
}
|
|
|
|
void onTerritoryRemove(Object& obj, Resource@ r, Territory@ terr) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onTerritoryRemove(obj, r, terr);
|
|
}
|
|
|
|
void onGenerate(Object& obj, Resource@ native) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onGenerate(obj, native);
|
|
}
|
|
|
|
void onDestroy(Object& obj, Resource@ native) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].onDestroy(obj, native);
|
|
}
|
|
|
|
void nativeTick(Object& obj, Resource@ native, double time) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].nativeTick(obj, native, time);
|
|
}
|
|
|
|
void nativeSave(Resource@ r, SaveFile& file) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].nativeSave(r, file);
|
|
}
|
|
|
|
void nativeLoad(Resource@ r, SaveFile& file) const {
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
hooks[i].nativeLoad(r, file);
|
|
}
|
|
};
|
|
|
|
interface IResourceHook {
|
|
void initialize(ResourceType@ type, uint index);
|
|
bool canTerraform(Object@ from, Object@ to) const;
|
|
bool get_hasEffect() const;
|
|
bool mergesEffect(Object& obj, const IResourceHook@ other) const;
|
|
string formatEffect(Object& obj, array<const IResourceHook@>& hooks) const;
|
|
const IResourceHook@ get_displayHook() const;
|
|
const IResourceHook@ get_carriedHook() const;
|
|
bool shouldVanish(Object& obj, Resource@ native) const;
|
|
void onAdd(Object& obj, Resource@ r) const;
|
|
void onRemove(Object& obj, Resource@ r) const;
|
|
void onTick(Object& obj, Resource@ r, double tick) const;
|
|
void onOwnerChange(Object& obj, Resource@ r, Empire@ prevOwner, Empire@ newOwner) const;
|
|
void onRegionChange(Object& obj, Resource@ r, Region@ fromRegion, Region@ toRegion) const;
|
|
void save(Resource@ r, SaveFile& file) const;
|
|
void load(Resource@ r, SaveFile& file) const;
|
|
void applyGraphics(Object& obj, Node& node) const;
|
|
void onTerritoryAdd(Object& obj, Resource@ r, Territory@ terr) const;
|
|
void onTerritoryRemove(Object& obj, Resource@ r, Territory@ terr) const;
|
|
void onTradeDeliver(Civilian& civ, Object@ origin, Object@ target) const;
|
|
void onTradeDestroy(Civilian& civ, Object@ origin, Object@ target, Object@ destroyer) const;
|
|
|
|
void onGenerate(Object& obj, Resource@ native) const;
|
|
void nativeTick(Object&, Resource@ native, double time) const;
|
|
void onDestroy(Object&, Resource@ native) const;
|
|
void nativeSave(Resource@ native, SaveFile& file) const;
|
|
void nativeLoad(Resource@ native, SaveFile& file) const;
|
|
};
|
|
|
|
class ResourceHook : Hook, IResourceHook {
|
|
uint hookIndex = 0;
|
|
void initialize(ResourceType@ type, uint index) { hookIndex = index; }
|
|
bool canTerraform(Object@ from, Object@ to) const { return true; }
|
|
bool get_hasEffect() const { return false; }
|
|
bool mergesEffect(Object& obj, const IResourceHook@ other) const { return getClass(other) is getClass(this); }
|
|
string formatEffect(Object& obj, array<const IResourceHook@>& hooks) const { return "---"; }
|
|
const IResourceHook@ get_displayHook() const { return this; }
|
|
const IResourceHook@ get_carriedHook() const { return null; }
|
|
bool shouldVanish(Object& obj, Resource@ native) const { return false; }
|
|
void onAdd(Object& obj, Resource@ r) const {}
|
|
void onRemove(Object& obj, Resource@ r) const {}
|
|
void onTick(Object& obj, Resource@ r, double tick) const {}
|
|
void onOwnerChange(Object& obj, Resource@ r, Empire@ prevOwner, Empire@ newOwner) const {}
|
|
void onRegionChange(Object& obj, Resource@ r, Region@ fromRegion, Region@ toRegion) const {}
|
|
void save(Resource@ r, SaveFile& file) const {}
|
|
void load(Resource@ r, SaveFile& file) const {}
|
|
void applyGraphics(Object& obj, Node& node) const {}
|
|
void onTerritoryAdd(Object& obj, Resource@ r, Territory@ terr) const {};
|
|
void onTerritoryRemove(Object& obj, Resource@ r, Territory@ terr) const {};
|
|
void onGenerate(Object& obj, Resource@ native) const {}
|
|
void nativeTick(Object&, Resource@ native, double time) const {}
|
|
void onDestroy(Object&, Resource@ native) const {}
|
|
void onTradeDeliver(Civilian& civ, Object@ origin, Object@ target) const {}
|
|
void onTradeDestroy(Civilian& civ, Object@ origin, Object@ target, Object@ destroyer) const {}
|
|
void nativeSave(Resource@ native, SaveFile& file) const {}
|
|
void nativeLoad(Resource@ native, SaveFile& file) const {}
|
|
};
|
|
|
|
int integerSum(array<const IResourceHook@>& hooks, int argument) {
|
|
int amount = 0;
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
amount += cast<const Hook>(hooks[i]).arguments[argument].integer;
|
|
return amount;
|
|
}
|
|
|
|
double decimalSum(array<const IResourceHook@>& hooks, int argument) {
|
|
double amount = 0.0;
|
|
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
|
|
amount += cast<const Hook>(hooks[i]).arguments[argument].decimal;
|
|
return amount;
|
|
}
|
|
|
|
Color getResourceRarityColor(uint rarity) {
|
|
switch(rarity) {
|
|
case RR_Common: return Color(0xffffffff);
|
|
case RR_Uncommon: return Color(0x0062d6ff);
|
|
case RR_Rare: return Color(0xab00d6ff);
|
|
case RR_Epic: return Color(0xd6ac00ff);
|
|
case RR_Unique: return Color(0xf5ff00ff);
|
|
case RR_Mythical: return Color(0x00ffffff);
|
|
}
|
|
return Color(0xffffffff);
|
|
}
|
|
|
|
string getResourceTooltip(const ResourceType@ type, const Resource@ r = null, Object@ drawFrom = null, bool showName = true) {
|
|
if(r !is null && drawFrom !is null && r.origin is null)
|
|
return "[color=#aaa]"+locale::QUEUED_AUTO_IMPORT+"[/color]";
|
|
|
|
string text;
|
|
int curLevel = -1;
|
|
if(r !is null && r.origin !is null && r.origin.hasSurfaceComponent)
|
|
curLevel = r.origin.level;
|
|
|
|
if(showName)
|
|
text = format("[font=Medium][color=$2]$1[/color] [img=$3;24/][/font]\n", type.name,
|
|
toString(getResourceRarityColor(type.rarity)), getSpriteDesc(type.smallIcon));
|
|
if(type.level > 0) {
|
|
text += locale::LEVEL+" "+type.level;
|
|
if(type.affinities.length != 0)
|
|
text += " - ";
|
|
}
|
|
if(type.affinities.length != 0) {
|
|
uint affCnt = type.affinities.length;
|
|
for(uint i = 0; i < affCnt; ++i) {
|
|
uint a = type.affinities[i];
|
|
text += format("[img=$1;20/]", getSpriteDesc(getAffinitySprite(a)));
|
|
}
|
|
text += "\n";
|
|
}
|
|
else if(type.level > 0) {
|
|
text += "\n";
|
|
}
|
|
|
|
//Pressure
|
|
if(type.totalPressure > 0) {
|
|
string prs;
|
|
for(uint i = 0; i < TR_COUNT; ++i) {
|
|
int val = type.tilePressure[i];
|
|
if(val > 0) {
|
|
if(prs.length != 0)
|
|
prs += ", ";
|
|
int prod = val;
|
|
if(r !is null && r.origin !is null && r.origin.owner is playerEmpire)
|
|
prod = max(round(float(val) * r.efficiency), 0.f);
|
|
if(prod != val)
|
|
prs += format("$1/$2 [img=$3;20/]", toString(prod), toString(val), getTileResourceSpriteSpec(i));
|
|
else
|
|
prs += format("$1 [img=$2;20/]", toString(val), getTileResourceSpriteSpec(i));
|
|
}
|
|
}
|
|
text += format("$1[offset=80][b]$2[/b][/offset]\n[vspace=8/]", locale::RES_PROVIDES_PRESSURE, prs);
|
|
}
|
|
|
|
if(type.description.length == 0)
|
|
text += format(type.blurb, toString(curLevel, 0));
|
|
else
|
|
text += format(type.description, toString(curLevel, 0));
|
|
|
|
if(type.totalPressure > 0) {
|
|
uint types = 0;
|
|
for(uint i = 0; i < TR_COUNT; ++i)
|
|
if(type.tilePressure[i] > 0)
|
|
types += 1;
|
|
|
|
if(types <= 2) {
|
|
if(type.description.length > 0 || type.blurb.length > 0)
|
|
text += "\n\n";
|
|
|
|
bool first = true;
|
|
for(uint i = 0; i < TR_COUNT; ++i) {
|
|
if(type.tilePressure[i] > 0) {
|
|
if(!first)
|
|
text += "\n\n";
|
|
first = false;
|
|
text += format("[img=$1;20/] ", getTileResourceSpriteSpec(i)) + localize("PRESSURE_" + getTileResourceIdent(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Import marker
|
|
if(r !is null) {
|
|
if(drawFrom !is null) {
|
|
if(r.origin is null) {
|
|
text += "\n\n[color=#aaa]"+locale::QUEUED_AUTO_IMPORT+"[/color]";
|
|
}
|
|
else if(r.origin is drawFrom && r.exportedTo !is null) {
|
|
text += "\n\n[color=#aaa]"+format(locale::RESOURCE_EXPORTED_TO, r.exportedTo.name)+"[/color]";
|
|
}
|
|
else if(r.exportedTo is drawFrom) {
|
|
text += "\n\n[color=#aaa]"+format(locale::RESOURCE_IMPORTED_FROM, r.origin.name)+"[/color]";
|
|
}
|
|
}
|
|
}
|
|
|
|
//Warnings
|
|
if(r !is null) {
|
|
if(r.type.vanishMode != VM_Never) {
|
|
double timeLeft = r.type.vanishTime - r.vanishTime;
|
|
if(r.exportedTo !is null)
|
|
timeLeft /= r.exportedTo.resourceVanishRate;
|
|
else if(r.origin !is null)
|
|
timeLeft /= r.origin.resourceVanishRate;
|
|
|
|
text += "\n\n";
|
|
if(r.usable && r.origin !is null && r.origin.owner is playerEmpire && (type.vanishMode == VM_Always || r.exportedTo !is null))
|
|
text += "[color=#fb0]"+format(locale::VANISH_TIP, formatTime(timeLeft))+"[/color]";
|
|
else
|
|
text += format(locale::VANISH_TIP_NOUSE, formatTime(timeLeft));
|
|
}
|
|
|
|
if(!r.usable) {
|
|
string err;
|
|
if(r.origin !is null)
|
|
err = r.origin.getDisabledReason(r.id);
|
|
else if(drawFrom !is null)
|
|
err = drawFrom.getDisabledReason(r.id);
|
|
|
|
if(err.length > 0) {
|
|
text += "\n\n";
|
|
string base;
|
|
if(r.origin is drawFrom) {
|
|
if(r.exportedTo is null)
|
|
base = locale::EXPBLOCK_USE;
|
|
else
|
|
base = locale::EXPBLOCK_EXPORT;
|
|
}
|
|
else {
|
|
base = locale::EXPBLOCK_IMPORT;
|
|
}
|
|
text += format(base, err);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if(type.vanishMode != VM_Never) {
|
|
text += "\n\n";
|
|
text += format(locale::VANISH_TIP_NOUSE, formatTime(type.vanishTime));
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
|
|
string formatBuildCost(const Design@ dsg, Object@ obj = null, string fmt = "$1 / $2, $3") {
|
|
int buildCost = 0;
|
|
int maintainCost = 0;
|
|
double laborCost = 0;
|
|
|
|
getBuildCost(dsg, buildCost, maintainCost, laborCost, -1, obj);
|
|
return format(fmt, formatMoney(buildCost), formatMoney(maintainCost), standardize(laborCost)+" "+locale::RESOURCE_LABOR);
|
|
}
|
|
|
|
void getBuildCost(const Design@ dsg, int&out buildCost, int&out maintainCost, double&out laborCost, int count = 1, Object@ obj = null) {
|
|
double build = 0;
|
|
double maintain = 0;
|
|
double labor = 0;
|
|
|
|
uint cnt = dsg.subsystemCount;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
const Subsystem@ sys = dsg.subsystems[i];
|
|
for(uint j = 0, hexCnt = sys.hexCount; j < hexCnt; ++j) {
|
|
if(sys.has(HV_BuildCost))
|
|
build += sys.hexVariable(HV_BuildCost, j);
|
|
if(sys.has(HV_MaintainCost))
|
|
maintain += sys.hexVariable(HV_MaintainCost, j);
|
|
if(sys.has(HV_LaborCost))
|
|
labor += sys.hexVariable(HV_LaborCost, j);
|
|
}
|
|
}
|
|
if(obj !is null) {
|
|
build *= double(obj.shipBuildCost) / 100.0;
|
|
build *= obj.constructionCostMod;
|
|
}
|
|
|
|
buildCost = max(ceil(build), 0.0);
|
|
maintainCost = max(ceil(maintain), 0.0);
|
|
|
|
if(count >= 0) {
|
|
buildCost *= count;
|
|
maintainCost *= count;
|
|
labor *= count;
|
|
}
|
|
|
|
if(buildCost < 0)
|
|
buildCost = INT_MAX>>1;
|
|
if(maintainCost < 0)
|
|
maintainCost = INT_MAX>>1;
|
|
laborCost = labor;
|
|
}
|
|
|
|
int getBuildCost(const Design@ dsg, int count = 1, Object@ buildAt = null) {
|
|
double build = 0;
|
|
uint cnt = dsg.subsystemCount;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
const Subsystem@ sys = dsg.subsystems[i];
|
|
if(!sys.has(HV_BuildCost))
|
|
continue;
|
|
for(uint j = 0, hexCnt = sys.hexCount; j < hexCnt; ++j)
|
|
build += sys.hexVariable(HV_BuildCost, j);
|
|
}
|
|
if(buildAt !is null) {
|
|
build *= double(buildAt.shipBuildCost) / 100.0;
|
|
build *= buildAt.constructionCostMod;
|
|
}
|
|
|
|
int v = ceil(build);
|
|
if(count >= 0)
|
|
v *= count;
|
|
if(v < 0)
|
|
v = INT_MAX>>1;
|
|
return v;
|
|
}
|
|
|
|
int getMaintenanceCost(const Design@ dsg, int count = 1) {
|
|
double maintain = 0;
|
|
uint cnt = dsg.subsystemCount;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
const Subsystem@ sys = dsg.subsystems[i];
|
|
if(!sys.has(HV_MaintainCost))
|
|
continue;
|
|
for(uint j = 0, hexCnt = sys.hexCount; j < hexCnt; ++j)
|
|
maintain += sys.hexVariable(HV_MaintainCost, j);
|
|
}
|
|
|
|
if(count >= 0)
|
|
maintain *= count;
|
|
int v = max(ceil(maintain), 0.0);
|
|
if(v < 0)
|
|
v = INT_MAX>>1;
|
|
return v;
|
|
}
|
|
|
|
double getLaborCost(const Design@ dsg, int count = 1) {
|
|
double time = 0;
|
|
uint cnt = dsg.subsystemCount;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
const Subsystem@ sys = dsg.subsystems[i];
|
|
if(!sys.has(HV_LaborCost))
|
|
continue;
|
|
for(uint j = 0, hexCnt = sys.hexCount; j < hexCnt; ++j)
|
|
time += sys.hexVariable(HV_LaborCost, j);
|
|
}
|
|
|
|
if(count >= 0)
|
|
time *= count;
|
|
return time;
|
|
}
|
|
|
|
tidy final class Resources : Serializable, Savable {
|
|
array<uint> types;
|
|
int[] amounts;
|
|
|
|
Resources() {
|
|
}
|
|
|
|
int modAmount(const ResourceType@ type, int mod, bool remove = true) {
|
|
int index = types.find(type.id);
|
|
if(index == -1) {
|
|
types.insertLast(type.id);
|
|
amounts.insertLast(mod);
|
|
return mod;
|
|
}
|
|
else {
|
|
amounts[index] += mod;
|
|
int amt = amounts[index];
|
|
if(remove && amt == 0) {
|
|
types.removeAt(index);
|
|
amounts.removeAt(index);
|
|
}
|
|
return amt;
|
|
}
|
|
}
|
|
|
|
int getAmount(const ResourceType@ type) const {
|
|
int index = types.find(type.id);
|
|
if(index == -1)
|
|
return 0;
|
|
return amounts[index];
|
|
}
|
|
|
|
void clear() {
|
|
types.length = 0;
|
|
amounts.length = 0;
|
|
}
|
|
|
|
uint get_length() const {
|
|
return types.length;
|
|
}
|
|
|
|
int get_universalCount() const {
|
|
int amt = 0;
|
|
uint cnt = types.length;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ type = getResource(types[i]);
|
|
if(type.mode == RM_Universal)
|
|
amt += amounts[i];
|
|
else if(type.mode == RM_UniversalUnique)
|
|
amt += 1;
|
|
}
|
|
return amt;
|
|
}
|
|
|
|
bool get_empty() const {
|
|
return types.length == 0;
|
|
}
|
|
|
|
void read(Message& msg) {
|
|
uint allTypeCount = getResourceCount();
|
|
uint cnt = msg.readSmall();
|
|
types.length = cnt;
|
|
amounts.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
types[i] = msg.readLimited(allTypeCount-1);
|
|
amounts[i] = msg.readSignedSmall();
|
|
}
|
|
}
|
|
|
|
void write(Message& msg) {
|
|
uint allTypeCount = getResourceCount();
|
|
uint cnt = types.length;
|
|
msg.writeSmall(cnt);
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
msg.writeLimited(types[i], allTypeCount-1);
|
|
msg.writeSignedSmall(amounts[i]);
|
|
}
|
|
}
|
|
|
|
void load(SaveFile& msg) {
|
|
uint cnt = 0;
|
|
msg >> cnt;
|
|
types.length = cnt;
|
|
amounts.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
types[i] = msg.readIdentifier(SI_Resource);
|
|
msg >> amounts[i];
|
|
}
|
|
}
|
|
|
|
void save(SaveFile& msg) {
|
|
uint cnt = types.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
msg.writeIdentifier(SI_Resource, types[i]);
|
|
msg << amounts[i];
|
|
}
|
|
}
|
|
|
|
void print() const {
|
|
uint cnt = types.length;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
::print(getResource(types[i]).name+": "+amounts[i]);
|
|
}
|
|
};
|
|
|
|
enum ResourceRequirementType {
|
|
RRT_Resource,
|
|
RRT_Class,
|
|
RRT_Class_Types,
|
|
RRT_Level,
|
|
RRT_Level_Types,
|
|
};
|
|
|
|
tidy final class ResourceRequirement {
|
|
ResourceRequirementType type = RRT_Resource;
|
|
const ResourceClass@ cls;
|
|
const ResourceType@ resource;
|
|
uint level = 0;
|
|
uint amount = 0;
|
|
|
|
bool equivalent(const ResourceRequirement@ other) const {
|
|
if(type != other.type)
|
|
return false;
|
|
if(cls !is other.cls)
|
|
return false;
|
|
if(resource !is other.resource)
|
|
return false;
|
|
if(level != other.level)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
int meetQuality(const ResourceType@ check, bool doneUniversal) const {
|
|
if(check.mode == RM_UniversalUnique) {
|
|
if(doneUniversal)
|
|
return 0;
|
|
return 10;
|
|
}
|
|
if(check.mode == RM_Universal)
|
|
return 11;
|
|
|
|
switch(type) {
|
|
case RRT_Resource:
|
|
if(resource is check)
|
|
return 1;
|
|
break;
|
|
case RRT_Class:
|
|
if(check.cls is cls)
|
|
return 2;
|
|
break;
|
|
case RRT_Level:
|
|
if(check.level == level)
|
|
return 3;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int opCmp(const ResourceRequirement@ other) const {
|
|
if(type < other.type)
|
|
return -1;
|
|
else if(type > other.type)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
tidy final class ResourceRequirements {
|
|
ResourceRequirement@[] reqs;
|
|
|
|
void addRequirement(ResourceRequirement@ req) {
|
|
reqs.insertLast(req);
|
|
reqs.sortAsc();
|
|
}
|
|
|
|
bool satisfiedBy(const Resources@ res, array<int>@ remaining = null,
|
|
bool allowUniversal = true, array<int>@ reqRemaining = null) const {
|
|
int universal = res.universalCount;
|
|
int unsatisfied = 0;
|
|
if(remaining is null)
|
|
@remaining = array<int>();
|
|
remaining = res.amounts;
|
|
|
|
uint reqCnt = reqs.length;
|
|
uint resCnt = res.length;
|
|
if(reqRemaining !is null)
|
|
reqRemaining.length = reqCnt;
|
|
for(uint i = 0; i < reqCnt; ++i) {
|
|
ResourceRequirement@ r = reqs[i];
|
|
uint total = r.amount;
|
|
|
|
switch(r.type) {
|
|
case RRT_Resource: {
|
|
if(r.resource.mode == RM_Normal) {
|
|
for(uint j = 0; j < resCnt; ++j) {
|
|
if(res.types[j] == r.resource.id) {
|
|
uint take = min(remaining[j], total);
|
|
remaining[j] -= take;
|
|
total -= take;
|
|
if(total <= 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(reqRemaining !is null)
|
|
reqRemaining[i] = total;
|
|
unsatisfied += total;
|
|
} break;
|
|
case RRT_Class:
|
|
for(uint j = 0; j < resCnt; ++j) {
|
|
auto@ type = getResource(res.types[j]);
|
|
if(type.mode != RM_Normal)
|
|
continue;
|
|
const ResourceClass@ cls = type.cls;
|
|
if(cls is r.cls) {
|
|
uint take = min(remaining[j], total);
|
|
remaining[j] -= take;
|
|
total -= take;
|
|
if(total <= 0)
|
|
break;
|
|
}
|
|
}
|
|
if(reqRemaining !is null)
|
|
reqRemaining[i] = total;
|
|
unsatisfied += total;
|
|
break;
|
|
case RRT_Class_Types:
|
|
for(uint j = 0; j < resCnt; ++j) {
|
|
auto@ type = getResource(res.types[j]);
|
|
if(type.mode != RM_Normal)
|
|
continue;
|
|
const ResourceClass@ cls = type.cls;
|
|
if(cls is r.cls) {
|
|
total -= 1;
|
|
remaining[j] -= 1;
|
|
if(total <= 0)
|
|
break;
|
|
}
|
|
}
|
|
if(reqRemaining !is null)
|
|
reqRemaining[i] = total;
|
|
unsatisfied += total;
|
|
break;
|
|
case RRT_Level:
|
|
for(uint j = 0; j < resCnt; ++j) {
|
|
auto@ type = getResource(res.types[j]);
|
|
if(type.mode != RM_Normal)
|
|
continue;
|
|
if(type.level == r.level) {
|
|
uint take = min(remaining[j], total);
|
|
remaining[j] -= take;
|
|
total -= take;
|
|
if(total <= 0)
|
|
break;
|
|
}
|
|
}
|
|
if(reqRemaining !is null)
|
|
reqRemaining[i] = total;
|
|
unsatisfied += total;
|
|
break;
|
|
case RRT_Level_Types:
|
|
for(uint j = 0; j < resCnt; ++j) {
|
|
auto@ type = getResource(res.types[j]);
|
|
if(type.mode != RM_Normal)
|
|
continue;
|
|
if(type.level == r.level) {
|
|
total -= 1;
|
|
remaining[j] -= 1;
|
|
if(total <= 0)
|
|
break;
|
|
}
|
|
}
|
|
if(reqRemaining !is null)
|
|
reqRemaining[i] = total;
|
|
unsatisfied += total;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(allowUniversal)
|
|
return unsatisfied <= universal;
|
|
else
|
|
return unsatisfied == 0;
|
|
}
|
|
|
|
void parse(string text) {
|
|
string[] parts = text.split(",");
|
|
for(uint i = 0, cnt = parts.length; i < cnt; ++i) {
|
|
string spec = parts[i].trimmed();
|
|
int space = spec.findFirst(" ");
|
|
if(space == -1) {
|
|
error("Invalid resource specifier: "+parts[i]);
|
|
continue;
|
|
}
|
|
|
|
ResourceRequirement req;
|
|
req.amount = toUInt(spec.substr(0, space));
|
|
string resource = spec.substr(space+1);
|
|
|
|
//Detect types of modifier
|
|
bool typesOf = false;
|
|
if(resource.startswith_nocase("types of ")) {
|
|
resource = resource.substr(9);
|
|
typesOf = true;
|
|
}
|
|
|
|
//Detect resource level
|
|
if(resource.startswith_nocase("level")) {
|
|
req.level = toUInt(resource.substr(5));
|
|
if(typesOf)
|
|
req.type = RRT_Level_Types;
|
|
else
|
|
req.type = RRT_Level;
|
|
|
|
reqs.insertLast(req);
|
|
continue;
|
|
}
|
|
|
|
//Detect single resource amount
|
|
const ResourceType@ res = getResource(resource);
|
|
if(res !is null) {
|
|
if(typesOf) {
|
|
error("Invalid resource specifier: "+parts[i]);
|
|
error(" Cannot take types of specific resource.");
|
|
continue;
|
|
}
|
|
|
|
req.type = RRT_Resource;
|
|
@req.resource = res;
|
|
|
|
reqs.insertLast(req);
|
|
continue;
|
|
}
|
|
|
|
//Detect resource class
|
|
const ResourceClass@ cls = getResourceClass(resource);
|
|
if(cls !is null) {
|
|
if(typesOf)
|
|
req.type = RRT_Class_Types;
|
|
else
|
|
req.type = RRT_Class;
|
|
@req.cls = cls;
|
|
|
|
reqs.insertLast(req);
|
|
continue;
|
|
}
|
|
|
|
error("Invalid resource specifier: "+parts[i]);
|
|
error(" Unknown resource '"+resource+"'.");
|
|
}
|
|
|
|
reqs.sortAsc();
|
|
}
|
|
};
|
|
|
|
tidy class Resource : Serializable, Savable {
|
|
int id = -1;
|
|
const ResourceType@ type;
|
|
Object@ origin;
|
|
Object@ exportedTo;
|
|
bool usable = true;
|
|
bool locked = false;
|
|
uint8 disabled = 0;
|
|
double vanishTime;
|
|
float efficiency = 1.f;
|
|
any[] data;
|
|
|
|
void write(Message& msg) {
|
|
msg.writeLimited(type.id, resources::resources.length-1);
|
|
msg.writeSmall(id);
|
|
msg << origin;
|
|
msg << exportedTo;
|
|
msg << usable;
|
|
msg << disabled;
|
|
msg << efficiency;
|
|
msg << locked;
|
|
if(type.vanishMode != VM_Never)
|
|
msg << float(vanishTime);
|
|
}
|
|
|
|
void read(Message& msg) {
|
|
@type = getResource(msg.readLimited(resources::resources.length-1));
|
|
id = msg.readSmall();
|
|
msg >> origin;
|
|
msg >> exportedTo;
|
|
msg >> usable;
|
|
msg >> disabled;
|
|
msg >> efficiency;
|
|
msg >> locked;
|
|
if(type.vanishMode != VM_Never)
|
|
vanishTime = msg.read_float();
|
|
}
|
|
|
|
void save(SaveFile& msg) {
|
|
msg.writeIdentifier(SI_Resource, type.id);
|
|
msg << id;
|
|
msg << origin;
|
|
msg << exportedTo;
|
|
msg << usable;
|
|
msg << disabled;
|
|
msg << vanishTime;
|
|
msg << efficiency;
|
|
msg << locked;
|
|
}
|
|
|
|
void load(SaveFile& msg) {
|
|
uint res = msg.readIdentifier(SI_Resource);
|
|
@type = getResource(res);
|
|
data.length = type.hooks.length;
|
|
|
|
msg >> id;
|
|
msg >> origin;
|
|
msg >> exportedTo;
|
|
msg >> usable;
|
|
msg >> disabled;
|
|
msg >> vanishTime;
|
|
msg >> efficiency;
|
|
|
|
if(msg >= SV_0027)
|
|
msg >> locked;
|
|
}
|
|
|
|
void descFrom(const Resource@ other) {
|
|
if(other is null) {
|
|
id = -1;
|
|
@type = null;
|
|
@exportedTo = null;
|
|
return;
|
|
}
|
|
id = other.id;
|
|
@type = other.type;
|
|
@origin = other.origin;
|
|
@exportedTo = other.exportedTo;
|
|
usable = other.usable;
|
|
locked = other.locked;
|
|
disabled = other.disabled;
|
|
vanishTime = other.vanishTime;
|
|
efficiency = other.efficiency;
|
|
}
|
|
|
|
int opCmp(const Resource& other) const {
|
|
#section client
|
|
if(exportedTo is null && other.exportedTo !is null)
|
|
return 1;
|
|
if(exportedTo !is null && other.exportedTo is null)
|
|
return -1;
|
|
if(other.exportedTo is origin)
|
|
return 1;
|
|
if(exportedTo is other.origin)
|
|
return -1;
|
|
#section all
|
|
if(type is null || other.type is null)
|
|
return 0;
|
|
if(type.displayWeight > other.type.displayWeight)
|
|
return 1;
|
|
else if(type.displayWeight < other.type.displayWeight)
|
|
return -1;
|
|
if(type.level < other.type.level)
|
|
return -1;
|
|
if(type.level > other.type.level)
|
|
return 1;
|
|
if(type.rarityScore > other.type.rarityScore)
|
|
return -1;
|
|
if(type.rarityScore < other.type.rarityScore)
|
|
return 1;
|
|
if(type.id < other.type.id)
|
|
return -1;
|
|
if(type.id > other.type.id)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
tidy final class QueuedImport : Resource {
|
|
Empire@ forEmpire;
|
|
|
|
void save(SaveFile& msg) {
|
|
Resource::save(msg);
|
|
msg << forEmpire;
|
|
}
|
|
|
|
void load(SaveFile& msg) {
|
|
Resource::load(msg);
|
|
msg >> forEmpire;
|
|
}
|
|
|
|
void writeQueued(Message& msg) {
|
|
Resource::write(msg);
|
|
msg << forEmpire;
|
|
}
|
|
|
|
void readQueued(Message& msg) {
|
|
Resource::read(msg);
|
|
msg >> forEmpire;
|
|
}
|
|
};
|
|
|
|
tidy final class QueuedResource : Serializable, Savable {
|
|
Empire@ forEmpire;
|
|
int id = -1;
|
|
Object@ to;
|
|
bool locked;
|
|
|
|
void write(Message& msg) {
|
|
msg << forEmpire;
|
|
msg << id;
|
|
msg << to;
|
|
msg << locked;
|
|
}
|
|
|
|
void read(Message& msg) {
|
|
msg >> forEmpire;
|
|
msg >> id;
|
|
msg >> to;
|
|
msg >> locked;
|
|
}
|
|
|
|
void save(SaveFile& msg) {
|
|
msg << forEmpire;
|
|
msg << id;
|
|
msg << to;
|
|
msg << locked;
|
|
}
|
|
|
|
void load(SaveFile& msg) {
|
|
msg >> forEmpire;
|
|
msg >> id;
|
|
msg >> to;
|
|
if(msg >= SV_0027)
|
|
msg >> locked;
|
|
}
|
|
};
|
|
|
|
tidy class AutoImportDesc : Serializable {
|
|
Object@ to;
|
|
const ResourceType@ type;
|
|
const ResourceClass@ cls;
|
|
int level = -1;
|
|
bool handled = false;
|
|
|
|
void write(Message& msg) {
|
|
msg << to;
|
|
if(type !is null) {
|
|
msg.write1();
|
|
msg.writeSmall(type.id);
|
|
}
|
|
else {
|
|
msg.write0();
|
|
}
|
|
if(cls !is null) {
|
|
msg.write1();
|
|
msg.writeSmall(cls.id);
|
|
}
|
|
else {
|
|
msg.write0();
|
|
}
|
|
msg.writeSignedSmall(level);
|
|
msg << handled;
|
|
}
|
|
|
|
void read(Message& msg) {
|
|
msg >> to;
|
|
if(msg.readBit())
|
|
@type = getResource(msg.readSmall());
|
|
else
|
|
@type = null;
|
|
if(msg.readBit())
|
|
@cls = getResourceClass(msg.readSmall());
|
|
else
|
|
@cls = null;
|
|
level = msg.readSignedSmall();
|
|
msg >> handled;
|
|
}
|
|
|
|
bool equivalent(const AutoImportDesc& other) const {
|
|
return type is other.type && cls is other.cls && level == other.level;
|
|
}
|
|
|
|
bool satisfies(const ResourceType@ type, bool alreadyPresent = false) {
|
|
if(type.mode != RM_Normal)
|
|
return false;
|
|
if(!alreadyPresent && !type.exportable)
|
|
return false;
|
|
if(level != -1 && type.level != uint(level))
|
|
return false;
|
|
if(cls !is null && type.cls !is cls)
|
|
return false;
|
|
if(this.type !is null && type !is this.type)
|
|
return false;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
int getGroupSize(const Design@ dsg) {
|
|
return ceil(40.0 / sqrt(dsg.size));
|
|
}
|
|
|
|
namespace resources {
|
|
::dictionary resourceIdents;
|
|
::ResourceType@[] resources;
|
|
double totalFrequency = 0.0;
|
|
double asteroidFrequency = 0.0;
|
|
|
|
::dictionary classIdents;
|
|
::ResourceClass@[] resClasses;
|
|
};
|
|
|
|
tidy final class RarityFrequency {
|
|
double totalDistribution = 0.0;
|
|
array<ResourceType@> types;
|
|
};
|
|
tidy final class LevelFrequency {
|
|
double totalDistribution = 0.0;
|
|
array<ResourceType@> types;
|
|
array<RarityFrequency> rarities(6);
|
|
};
|
|
array<LevelFrequency> LevelFrequencies(4);
|
|
|
|
uint getResourceCount() {
|
|
return resources::resources.length;
|
|
}
|
|
|
|
const ResourceType@ getResource(uint id) {
|
|
if(id >= resources::resources.length)
|
|
return null;
|
|
return resources::resources[id];
|
|
}
|
|
|
|
int getResourceID(const string& ident) {
|
|
ResourceType@ type;
|
|
resources::resourceIdents.get(ident, @type);
|
|
if(type !is null)
|
|
return int(type.id);
|
|
return -1;
|
|
}
|
|
|
|
string getResourceIdent(int id) {
|
|
if(id < 0 || id >= int(resources::resources.length))
|
|
return "-";
|
|
return resources::resources[id].ident;
|
|
}
|
|
|
|
const ResourceType@ getResource(const string& ident) {
|
|
ResourceType@ type;
|
|
resources::resourceIdents.get(ident, @type);
|
|
return type;
|
|
}
|
|
|
|
const ResourceType@ getDistributedResourceContest(double contestation) {
|
|
uint tries = 0;
|
|
const ResourceType@ type;
|
|
do {
|
|
@type = getDistributedResource();
|
|
++tries;
|
|
}
|
|
while(tries < 10 && type.requireContestation > contestation);
|
|
return type;
|
|
}
|
|
|
|
const ResourceType@ getDistributedResource() {
|
|
double num = randomd(0, resources::totalFrequency);
|
|
double orig = num;
|
|
for(uint i = 0, cnt = resources::resources.length; i < cnt; ++i) {
|
|
const ResourceType@ type = resources::resources[i];
|
|
double freq = type.rarityScore;
|
|
|
|
if(num <= freq)
|
|
return type;
|
|
num -= freq;
|
|
}
|
|
return resources::resources[resources::resources.length-1];
|
|
}
|
|
|
|
const ResourceType@ getDistributedResource(uint level) {
|
|
auto@ lFreq = LevelFrequencies[level];
|
|
double num = randomd(0, lFreq.totalDistribution);
|
|
double orig = num;
|
|
for(uint i = 0, cnt = lFreq.types.length; i < cnt; ++i) {
|
|
const ResourceType@ type = lFreq.types[i];
|
|
double freq = type.distribution;
|
|
|
|
if(num <= freq)
|
|
return type;
|
|
num -= freq;
|
|
}
|
|
return lFreq.types[lFreq.types.length-1];
|
|
}
|
|
|
|
const ResourceType@ getRandomResource(uint level) {
|
|
auto@ lFreq = LevelFrequencies[level];
|
|
if(lFreq.types.length == 0)
|
|
return null;
|
|
return lFreq.types[randomi(0,lFreq.types.length-1)];
|
|
}
|
|
|
|
const ResourceType@ getDistributedAsteroidResource() {
|
|
double num = randomd(0, resources::asteroidFrequency);
|
|
const ResourceType@ type;
|
|
for(uint i = 0, cnt = resources::resources.length; i < cnt; ++i) {
|
|
@type = resources::resources[i];
|
|
if(type.asteroidFrequency <= 0.0)
|
|
continue;
|
|
|
|
double freq = type.asteroidFrequency;
|
|
if(num <= freq)
|
|
return type;
|
|
num -= freq;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
void markResourceUsed(const ResourceType@ type) {
|
|
if(type.unique) {
|
|
ResourceType@ mut = resources::resources[type.id];
|
|
|
|
uint lev = clamp(type.level, 0, 3);
|
|
if(type.rarityLevel != -1)
|
|
lev = clamp(type.rarityLevel, 0, 3);
|
|
|
|
uint rar = clamp(uint(type.rarity), 0, 3);
|
|
LevelFrequencies[lev].totalDistribution -= type.distribution;
|
|
LevelFrequencies[lev].rarities[rar].totalDistribution -= type.distribution;
|
|
|
|
resources::totalFrequency -= mut.rarityScore;
|
|
resources::asteroidFrequency -= mut.asteroidFrequency;
|
|
mut.rarityScore = 0;
|
|
mut.distribution = 0;
|
|
mut.asteroidFrequency = 0;
|
|
}
|
|
}
|
|
|
|
const ResourceClass@ getResourceClass(uint id) {
|
|
if(id >= resources::resClasses.length)
|
|
return null;
|
|
return resources::resClasses[id];
|
|
}
|
|
|
|
const ResourceClass@ getResourceClass(string ident) {
|
|
ResourceClass@ type;
|
|
resources::classIdents.get(ident, @type);
|
|
return type;
|
|
}
|
|
|
|
uint getResourceClassCount() {
|
|
return resources::resClasses.length;
|
|
}
|
|
|
|
void parseLine(string& line, ResourceType@ r, ReadFile@ file) {
|
|
if(line.findFirst("(") == -1) {
|
|
//Pressure line
|
|
array<string>@ decls = line.split(",");
|
|
for(uint i = 0, cnt = decls.length; i < cnt; ++i) {
|
|
array<string>@ parts = decls[i].split(" ");
|
|
if(parts.length != 3 || !parts[2].trimmed().equals_nocase("pressure")) {
|
|
error("Invalid pressure spec: "+escape(decls[i]));
|
|
continue;
|
|
}
|
|
|
|
uint resource = getTileResource(parts[1]);
|
|
if(resource == TR_INVALID) {
|
|
error("Invalid pressure spec: "+escape(decls[i]));
|
|
continue;
|
|
}
|
|
|
|
int amt = toInt(parts[0]);
|
|
r.tilePressure[resource] += amt;
|
|
}
|
|
}
|
|
else {
|
|
//Hook line
|
|
auto@ hook = cast<IResourceHook>(parseHook(line, "resource_effects::", instantiate=false, file=file));
|
|
if(hook !is null)
|
|
r.hooks.insertLast(hook);
|
|
}
|
|
}
|
|
|
|
void loadResources(const string& filename) {
|
|
ReadFile file(filename, true);
|
|
|
|
string key, value;
|
|
ResourceType@ r;
|
|
bool advance = true;
|
|
while(!advance || file++) {
|
|
key = file.key;
|
|
value = file.value;
|
|
advance = true;
|
|
|
|
if(file.fullLine) {
|
|
if(r is null) {
|
|
error("Missing 'Resource: ID' line in " + filename);
|
|
continue;
|
|
}
|
|
|
|
string line = file.line;
|
|
parseLine(line, r, file);
|
|
}
|
|
else if(key == "Resource") {
|
|
if(r !is null)
|
|
addResourceType(r);
|
|
@r = ResourceType();
|
|
r.ident = value;
|
|
}
|
|
else if(key == "Level Chain") {
|
|
advance = !readLevelChain(file);
|
|
}
|
|
else if(r is null) {
|
|
error("Missing 'Resource: ID' line in " + filename);
|
|
}
|
|
else if(key == "Name") {
|
|
r.name = localize(value);
|
|
}
|
|
else if(key == "Description") {
|
|
r.description = localize(value);
|
|
}
|
|
else if(key == "Blurb") {
|
|
r.blurb = localize(value);
|
|
}
|
|
else if(key == "Native Biome") {
|
|
r.nativeBiome = value;
|
|
}
|
|
else if(key == "Class") {
|
|
r.className = value;
|
|
}
|
|
else if(key == "DLC") {
|
|
r.dlc = value;
|
|
}
|
|
else if(key == "Level") {
|
|
r.level = toUInt(value);
|
|
if(r.cargoWorth == 0)
|
|
r.cargoWorth = (r.level+1) * CARGO_WORTH_LEVEL;
|
|
}
|
|
else if(key == "Rarity Level") {
|
|
r.rarityLevel = toInt(value);
|
|
}
|
|
else if(key == "Limitless Level") {
|
|
r.limitlessLevel = toBool(value);
|
|
}
|
|
else if(key == "Can Be Terraformed") {
|
|
r.canBeTerraformed = toBool(value);
|
|
}
|
|
else if(key == "Frequency") {
|
|
r.frequency = toDouble(value);
|
|
}
|
|
else if(key == "Distribution") {
|
|
r.distribution = toDouble(value);
|
|
}
|
|
else if(key == "Vanish Time") {
|
|
r.vanishTime = toDouble(value);
|
|
}
|
|
else if(key == "Cargo Worth") {
|
|
r.cargoWorth = toInt(value);
|
|
}
|
|
else if(key == "Vanish Mode") {
|
|
if(value == "Never")
|
|
r.vanishMode = VM_Never;
|
|
else if(value == "When Exported")
|
|
r.vanishMode = VM_WhenExported;
|
|
else if(value == "Always")
|
|
r.vanishMode = VM_Always;
|
|
else if(value == "Exported In Combat")
|
|
r.vanishMode = VM_ExportedInCombat;
|
|
else if(value == "Custom")
|
|
r.vanishMode = VM_Custom;
|
|
else
|
|
error("Invalid vanish mode: "+value);
|
|
}
|
|
else if(key == "Artificial") {
|
|
r.artificial = toBool(value);
|
|
if(r.artificial)
|
|
r.distribution = 0.0;
|
|
}
|
|
else if(key == "Exportable") {
|
|
r.exportable = toBool(value);
|
|
}
|
|
else if(key == "Unique") {
|
|
r.unique = toBool(value);
|
|
}
|
|
else if(key == "Require Contestation") {
|
|
r.requireContestation = toDouble(value);
|
|
}
|
|
else if(key == "Mode") {
|
|
if(value == "Normal")
|
|
r.mode = RM_Normal;
|
|
else if(value == "Universal")
|
|
r.mode = RM_Normal;
|
|
else if(value == "Universal Unique")
|
|
r.mode = RM_UniversalUnique;
|
|
else if(value == "Non Requirement")
|
|
r.mode = RM_NonRequirement;
|
|
else
|
|
error("Invalid resource mode: "+value);
|
|
}
|
|
else if(key == "Affinity") {
|
|
uint aff = getAffinityFromDesc(value);
|
|
if(aff != A_NULL)
|
|
r.addAffinity(aff);
|
|
}
|
|
else if(key == "Rarity") {
|
|
if(value == "Common")
|
|
r.rarity = RR_Common;
|
|
else if(value == "Uncommon")
|
|
r.rarity = RR_Uncommon;
|
|
else if(value == "Rare")
|
|
r.rarity = RR_Rare;
|
|
else if(value == "Epic")
|
|
r.rarity = RR_Epic;
|
|
else if(value == "Mythical")
|
|
r.rarity = RR_Mythical;
|
|
else if(value == "Unique") {
|
|
r.rarity = RR_Unique;
|
|
r.unique = true;
|
|
}
|
|
else
|
|
error("Invalid resource rarity: "+value);
|
|
}
|
|
else if(key == "Display Requirement") {
|
|
r.requirementDisplay = toBool(value);
|
|
}
|
|
else if(key == "Will Lock") {
|
|
r.willLock = toBool(value);
|
|
}
|
|
else if(key == "Display Weight") {
|
|
r.displayWeight = toDouble(value);
|
|
}
|
|
else if(key == "Asteroid Frequency") {
|
|
r.asteroidFrequency = toDouble(value);
|
|
}
|
|
else if(key == "Asteroid Labor") {
|
|
r.asteroidCost = toDouble(value);
|
|
}
|
|
else if(key == "Terraform Cost") {
|
|
r.terraformCost = toInt(value);
|
|
}
|
|
else if(key == "Terraform Labor") {
|
|
r.terraformLabor = toDouble(value);
|
|
}
|
|
else if(key == "Icon") {
|
|
r.icon = getSprite(value);
|
|
}
|
|
else if(key == "Small Icon") {
|
|
r.smallIcon = getSprite(value);
|
|
}
|
|
else if(key == "Distant Icon") {
|
|
//TODO
|
|
}
|
|
else if(key == "Pressure") {
|
|
array<string>@ decls = value.split(",");
|
|
for(uint i = 0, cnt = decls.length; i < cnt; ++i) {
|
|
array<string>@ parts = decls[i].split(" ");
|
|
if(parts.length != 2) {
|
|
error("Invalid pressure spec: "+escape(decls[i]));
|
|
continue;
|
|
}
|
|
|
|
uint resource = getTileResource(parts[1]);
|
|
if(resource == TR_INVALID) {
|
|
error("Invalid pressure spec: "+escape(decls[i]));
|
|
continue;
|
|
}
|
|
|
|
int amt = toInt(parts[0]);
|
|
r.tilePressure[resource] += amt;
|
|
}
|
|
}
|
|
else if(key.equals_nocase("AI")) {
|
|
auto@ hook = parseHook(value, "ai.resources::", instantiate=false, file=file);
|
|
if(hook !is null)
|
|
r.ai.insertLast(hook);
|
|
else
|
|
file.error("Could not find AI hook "+value);
|
|
}
|
|
else {
|
|
string line = file.line;
|
|
parseLine(line, r, file);
|
|
}
|
|
}
|
|
|
|
if(r !is null)
|
|
addResourceType(r);
|
|
}
|
|
|
|
void preInit() {
|
|
//Load resources
|
|
FileList list("data/resources", "*.txt", true);
|
|
for(uint i = 0, cnt = list.length; i < cnt; ++i)
|
|
loadResources(list.path[i]);
|
|
|
|
//Calculate resource frequencies
|
|
double rarTotal = 0;
|
|
resources::totalFrequency = 0.0;
|
|
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
|
|
ResourceType@ type = resources::resources[i];
|
|
for(uint n = 0, ncnt = type.hooks.length; n < ncnt; ++n)
|
|
type.hooks[n].initialize(type, n);
|
|
|
|
uint lev = clamp(type.level, 0, 3);
|
|
if(type.rarityLevel != -1)
|
|
lev = clamp(type.rarityLevel, 0, 3);
|
|
uint rar = clamp(uint(type.rarity), 0, 5);
|
|
|
|
LevelFrequency@ lFreq = LevelFrequencies[lev];
|
|
RarityFrequency@ rFreq = lFreq.rarities[rar];
|
|
|
|
double freq = 0.0;
|
|
if(type.distribution > 0 && rFreq.totalDistribution > 0) {
|
|
freq = LEVEL_DISTRIBUTION[lev] * RARITY_DISTRIBUTION[rar+lev*6] *
|
|
(type.distribution / rFreq.totalDistribution) * type.frequency;
|
|
|
|
}
|
|
if(type.unique)
|
|
freq *= config::UNIQUE_RESOURCE_OCCURANCE / 0.3;
|
|
|
|
resources::totalFrequency += freq;
|
|
type.rarityScore = freq;
|
|
}
|
|
}
|
|
|
|
tidy final class ResourceSheet {
|
|
#section gui
|
|
Sprite sprt;
|
|
array<Material> mats;
|
|
|
|
ResourceSheet(const Sprite& sprt) {
|
|
this.sprt = sprt;
|
|
|
|
mats.length = 6;
|
|
mats[0] = material::PlanetLevel0;
|
|
mats[1] = material::PlanetLevel1;
|
|
mats[2] = material::PlanetLevel2;
|
|
mats[3] = material::PlanetLevel3;
|
|
mats[4] = material::PlanetLevel4;
|
|
mats[5] = material::PlanetLevel5;
|
|
for(uint i = 0; i < 6; ++i) {
|
|
if(sprt.mat !is null)
|
|
@mats[i].texture2 = sprt.mat.texture0;
|
|
else if(sprt.sheet !is null)
|
|
@mats[i].texture2 = sprt.sheet.material.texture0;
|
|
mats[i].constant = true;
|
|
}
|
|
}
|
|
|
|
~ResourceSheet() {
|
|
for(uint i = 0; i < 6; ++i)
|
|
mats[i].constant = false;
|
|
}
|
|
|
|
void getUV(uint resourceIcon, vec4f& uvs) {
|
|
if(resourceIcon < 0xffffffff) {
|
|
if(sprt.sheet !is null)
|
|
sprt.sheet.getSourceUV(resourceIcon, uvs);
|
|
else
|
|
uvs = vec4f(0.f, 0.f, 1.f, 1.f);
|
|
}
|
|
else
|
|
uvs = vec4f();
|
|
}
|
|
|
|
Material@ getMaterial(uint level) {
|
|
return mats[clamp(level, 0, 5)];
|
|
}
|
|
#section all
|
|
};
|
|
|
|
array<ResourceSheet@> sheets;
|
|
void init() {
|
|
auto@ list = resources::resources;
|
|
for(uint i = 0, cnt = list.length; i < cnt; ++i) {
|
|
auto@ type = list[i];
|
|
for(uint n = 0, ncnt = type.hooks.length; n < ncnt; ++n)
|
|
if(!cast<Hook>(type.hooks[n]).instantiate())
|
|
error("Could not instantiate hook: "+addrstr(type.hooks[n])+" in "+type.ident);
|
|
if(type.smallIcon.valid) {
|
|
if(!type.icon.valid)
|
|
type.icon = type.smallIcon;
|
|
}
|
|
else if(type.icon.valid) {
|
|
type.smallIcon = type.icon;
|
|
}
|
|
for(uint n = 0, ncnt = type.ai.length; n < ncnt; ++n) {
|
|
if(!type.ai[n].instantiate())
|
|
error("Could not instantiate AI hook: "+addrstr(type.ai[n])+" in resource "+type.ident);
|
|
}
|
|
}
|
|
|
|
#section gui
|
|
for(uint i = 0, cnt = list.length; i < cnt; ++i) {
|
|
auto@ r = list[i];
|
|
if(!r.smallIcon.valid)
|
|
continue;
|
|
|
|
//Find associated resource sheet
|
|
ResourceSheet@ rs;
|
|
for(uint n = 0, ncnt = sheets.length; n < ncnt; ++n) {
|
|
if((sheets[n].sprt.sheet is r.smallIcon.sheet && r.smallIcon.sheet !is null)
|
|
|| (sheets[n].sprt.mat is r.smallIcon.mat && r.smallIcon.mat !is null)) {
|
|
@rs = sheets[n];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(rs is null) {
|
|
@rs = ResourceSheet(r.smallIcon);
|
|
sheets.insertLast(rs);
|
|
}
|
|
|
|
@r.distantSheet = rs;
|
|
}
|
|
#section all
|
|
}
|
|
|
|
ThreadLocal<Resource@> res;
|
|
Resource@ _tempResource() {
|
|
Resource@ r = res.get();
|
|
if(r is null) {
|
|
@r = Resource();
|
|
res.set(r);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void addResourceType(ResourceType@ type) {
|
|
//Add resource
|
|
type.id = resources::resources.length;
|
|
resources::resources.insertLast(@type);
|
|
resources::resourceIdents.set(type.ident, @type);
|
|
|
|
if(type.dlc.length != 0 && !hasDLC(type.dlc)) {
|
|
type.distribution = 0.0;
|
|
type.frequency = 0.0;
|
|
}
|
|
|
|
if(type.asteroidFrequency > 0) {
|
|
if(type.vanishMode == VM_Never)
|
|
type.asteroidFrequency *= config::ASTEROID_PERMANENT_FREQ;
|
|
resources::asteroidFrequency += type.asteroidFrequency;
|
|
}
|
|
|
|
uint lev = type.level;
|
|
if(type.rarityLevel != -1)
|
|
lev = clamp(type.rarityLevel, 0, 3);
|
|
|
|
LevelFrequency@ lFreq = LevelFrequencies[clamp(lev, 0, 3)];
|
|
RarityFrequency@ rFreq = lFreq.rarities[clamp(uint(type.rarity), 0, 5)];
|
|
|
|
lFreq.totalDistribution += type.distribution;
|
|
lFreq.types.insertLast(type);
|
|
rFreq.totalDistribution += type.distribution;
|
|
rFreq.types.insertLast(type);
|
|
|
|
//Figure out class
|
|
if(type.className.length != 0) {
|
|
ResourceClass@ cls;
|
|
resources::classIdents.get(type.className, @cls);
|
|
|
|
if(cls is null) {
|
|
@cls = ResourceClass();
|
|
cls.id = resources::resClasses.length;
|
|
cls.ident = type.className;
|
|
cls.name = localize("RESOURCE_CLASS_"+cls.ident);
|
|
|
|
resources::resClasses.insertLast(cls);
|
|
resources::classIdents.set(cls.ident, @cls);
|
|
}
|
|
|
|
@type.cls = cls;
|
|
cls.types.insertLast(type);
|
|
}
|
|
|
|
//Calculate total pressure
|
|
for(uint i = 0; i < TR_COUNT; ++i)
|
|
type.totalPressure += type.tilePressure[i];
|
|
}
|
|
|
|
void saveIdentifiers(SaveFile& file) {
|
|
for(uint i = 0, cnt = resources::resources.length; i < cnt; ++i) {
|
|
ResourceType type = resources::resources[i];
|
|
file.addIdentifier(SI_Resource, type.id, type.ident);
|
|
}
|
|
}
|