1670 lines
39 KiB
ActionScript
1670 lines
39 KiB
ActionScript
import tile_resources;
|
|
import util.formatting;
|
|
import int getResourceID(const string&) from "resources";
|
|
import string getResourceIdent(int id) from "resources";
|
|
import int getAbilityID(const string&) from "abilities";
|
|
import string getAbilityIdent(int id) from "abilities";
|
|
import int getStatusID(const string&) from "statuses";
|
|
import string getStatusIdent(int id) from "statuses";
|
|
import int getAttitudeID(const string&) from "attitudes";
|
|
import string getAttitudeIdent(int id) from "attitudes";
|
|
import int getCargoID(const string&) from "cargo";
|
|
import string getCargoIdent(int id) from "cargo";
|
|
import int getTraitID(const string&) from "traits";
|
|
import string getTraitIdent(int id) from "traits";
|
|
import int getRandomEventID(const string&) from "random_events";
|
|
import string getRandomEventIdent(int id) from "random_events";
|
|
import int getBiomeID(const string&) from "biomes";
|
|
import string getBiomeIdent(int id) from "biomes";
|
|
import int getOrbitalModuleID(const string&) from "orbitals";
|
|
import string getOrbitalModuleIdent(int id) from "orbitals";
|
|
import int getConstructionID(const string&) from "constructions";
|
|
import string getConstructionIdent(int id) from "constructions";
|
|
import int getArtifactID(const string&) from "artifacts";
|
|
import string getArtifactIdent(int id) from "artifacts";
|
|
import int getBuildingID(const string&) from "buildings";
|
|
import string getBuildingIdent(int id) from "buildings";
|
|
import int getTechnologyID(const string&) from "research";
|
|
import string getTechnologyIdent(int id) from "research";
|
|
import int getUnlockTag(const string& ident, bool create = true) from "unlock_tags";
|
|
import string getUnlockTagIdent(int id) from "unlock_tags";
|
|
import int getObjectStatId(const string& ident, bool create = true) from "object_stats";
|
|
import string getObjectStatIdent(int id) from "object_stats";
|
|
import string getObjectStatMode(int id) from "object_stats";
|
|
import int getObjectStatMode(const string& ident) from "object_stats";
|
|
import int getSystemFlag(const string& ident, bool create = true) from "system_flags";
|
|
import string getSystemFlagIdent(int id) from "system_flags";
|
|
import int getInfluenceCardID(const string& ident) from "influence";
|
|
import int getInfluenceVoteID(const string& ident) from "influence";
|
|
import int getInfluenceEffectID(const string& ident) from "influence";
|
|
import string getInfluenceCardIdent(int id) from "influence";
|
|
import string getInfluenceVoteIdent(int id) from "influence";
|
|
import string getInfluenceEffectIdent(int id) from "influence";
|
|
import hook_globals;
|
|
import attributes;
|
|
import saving;
|
|
|
|
export ArgumentType, Argument, REF_ARG;
|
|
export Document, EMPTY_DEFAULT;
|
|
export Hook, BlockHook, parseHook;
|
|
export TargetType, Targets, Target;
|
|
export parseTarget;
|
|
export makeHookInstance;
|
|
export EmpireResource, empResources;
|
|
|
|
// {{{ Arguments
|
|
enum ArgumentType {
|
|
AT_Integer,
|
|
AT_Decimal,
|
|
AT_Boolean,
|
|
AT_TileResource,
|
|
AT_PlanetResource,
|
|
AT_Ability,
|
|
AT_Range,
|
|
AT_Position,
|
|
AT_Position2D,
|
|
AT_Locale,
|
|
AT_PassFail,
|
|
AT_Target,
|
|
AT_TargetSpec,
|
|
AT_EmpAttribute,
|
|
AT_AttributeMode,
|
|
AT_Status,
|
|
AT_Cargo,
|
|
AT_Subsystem,
|
|
AT_Trait,
|
|
AT_OrbitalModule,
|
|
AT_Artifact,
|
|
AT_Technology,
|
|
AT_Global,
|
|
AT_SysVar,
|
|
AT_Sprite,
|
|
AT_Color,
|
|
AT_Hook,
|
|
AT_Model,
|
|
AT_Material,
|
|
AT_Selection,
|
|
AT_PlanetBiome,
|
|
AT_Construction,
|
|
AT_TileResourceSpec,
|
|
AT_Building,
|
|
AT_InfluenceCard,
|
|
AT_InfluenceVote,
|
|
AT_InfluenceEffect,
|
|
AT_CreepCamp,
|
|
AT_RandomEvent,
|
|
AT_Anomaly,
|
|
AT_ObjectType,
|
|
AT_VariableDef,
|
|
AT_ValueDef,
|
|
AT_File,
|
|
AT_UnlockTag,
|
|
AT_ObjectStat,
|
|
AT_ObjectStatMode,
|
|
AT_SystemFlag,
|
|
AT_Custom,
|
|
AT_VarArgs,
|
|
AT_EmpireResource,
|
|
AT_Attitude,
|
|
};
|
|
|
|
enum EmpireResource {
|
|
ER_Money,
|
|
ER_Influence,
|
|
ER_Energy,
|
|
ER_Research,
|
|
ER_Defense,
|
|
ER_FTL,
|
|
ER_COUNT
|
|
};
|
|
|
|
string[] empResources = {
|
|
"Money",
|
|
"Influence",
|
|
"Energy",
|
|
"Research",
|
|
"Defense",
|
|
"FTL"
|
|
};
|
|
|
|
const string EMPTY_DEFAULT = "-";
|
|
const string SEP_UNDER = "_";
|
|
Argument REF_ARG;
|
|
final class Argument {
|
|
ArgumentType type = AT_Custom;
|
|
string argName;
|
|
string str;
|
|
string doc;
|
|
int integer;
|
|
int integer2;
|
|
double decimal;
|
|
double decimal2;
|
|
vec3d position;
|
|
vec3d position2;
|
|
bool boolean;
|
|
bool isRange = false;
|
|
bool filled = false;
|
|
|
|
Argument() {
|
|
}
|
|
|
|
Argument(ArgumentType Type, const string& def = "", const string& doc = "") {
|
|
type = Type;
|
|
if(def.length != 0) {
|
|
if(def == EMPTY_DEFAULT)
|
|
parse("");
|
|
else
|
|
parse(def);
|
|
}
|
|
if(doc.length != 0)
|
|
this.doc = doc;
|
|
}
|
|
|
|
Argument(ArgumentType AType, ArgumentType Type, const string& doc = "", bool required = false) {
|
|
type = AType;
|
|
integer = int(Type);
|
|
boolean = required;
|
|
if(doc.length != 0)
|
|
this.doc = doc;
|
|
}
|
|
|
|
Argument(const string& name, ArgumentType Type, const string& defaultValue = "", const string& doc = "") {
|
|
type = Type;
|
|
argName = name;
|
|
boolean = defaultValue.length != 0;
|
|
if(boolean)
|
|
parse(defaultValue);
|
|
if(doc.length != 0)
|
|
this.doc = doc;
|
|
}
|
|
|
|
Argument(TargetType Type, const string& def = "", const string& doc = "") {
|
|
type = AT_Target;
|
|
integer2 = int(Type);
|
|
boolean = def.length != 0;
|
|
filled = boolean;
|
|
if(def != EMPTY_DEFAULT)
|
|
str = def;
|
|
if(doc.length != 0)
|
|
this.doc = doc;
|
|
}
|
|
|
|
Argument(const string& name, TargetType Type, const string& def = "", const string& doc = "") {
|
|
type = AT_Target;
|
|
argName = name;
|
|
integer2 = int(Type);
|
|
boolean = def.length != 0;
|
|
filled = boolean;
|
|
if(def != EMPTY_DEFAULT)
|
|
str = def;
|
|
if(doc.length != 0)
|
|
this.doc = doc;
|
|
}
|
|
|
|
double fromRange() const {
|
|
if(!isRange)
|
|
return decimal;
|
|
return randomd(decimal, decimal2);
|
|
}
|
|
|
|
double fromSys(const Subsystem@ sys = null, Object@ efficiencyObj = null) const {
|
|
if(sys is null || integer < 0)
|
|
return decimal;
|
|
double value = decimal;
|
|
if(sys.has(SubsystemVariable(integer)))
|
|
value = sys.variable(SubsystemVariable(integer));
|
|
if(efficiencyObj !is null && efficiencyObj.isShip) {
|
|
Ship@ ship = cast<Ship>(efficiencyObj);
|
|
auto@ status = ship.blueprint.getSysStatus(sys.index);
|
|
if(status !is null)
|
|
value *= double(status.workingHexes) / double(sys.hexCount);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
double fromShipEfficiencySum(Object@ efficiencyObj = null) const {
|
|
if(integer < 0)
|
|
return decimal;
|
|
double value = decimal;
|
|
if(efficiencyObj !is null && efficiencyObj.isShip) {
|
|
Ship@ ship = cast<Ship>(efficiencyObj);
|
|
value = ship.blueprint.getEfficiencySum(SubsystemVariable(integer));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void set(double value) {
|
|
decimal = value;
|
|
if(isRange)
|
|
decimal2 = value;
|
|
}
|
|
|
|
void set(double value, double value2) {
|
|
decimal = value;
|
|
decimal2 = value2;
|
|
}
|
|
|
|
vec3d fromPosition() const {
|
|
if(!isRange)
|
|
return position;
|
|
return vec3d(randomd(position.x, position2.x),
|
|
randomd(position.y, position2.y),
|
|
randomd(position.z, position2.z));
|
|
}
|
|
|
|
void set(const vec3d& pos) {
|
|
position = pos;
|
|
if(isRange)
|
|
position2 = pos;
|
|
}
|
|
|
|
void set(const vec2d& pos) {
|
|
position = vec3d(pos.x, pos.y, 0);
|
|
if(isRange)
|
|
position2 = position;
|
|
}
|
|
|
|
vec2d fromPosition2D() const {
|
|
if(!isRange)
|
|
return vec2d(position.x, position.y);
|
|
return vec2d(randomd(position.x, position2.x),
|
|
randomd(position.y, position2.y));
|
|
}
|
|
|
|
Target@ fromTarget(Targets@ targets) const {
|
|
if(uint(integer) >= targets.targets.length)
|
|
return null;
|
|
return targets.targets[integer];
|
|
}
|
|
|
|
const Target@ fromConstTarget(const Targets@ targets) const {
|
|
if(uint(integer) >= targets.targets.length)
|
|
return null;
|
|
return targets.targets[integer];
|
|
}
|
|
|
|
bool parseRange(double& value, double& value2, const string& str) {
|
|
if(str[0] == '$') {
|
|
value = config::get(str.substr(1));
|
|
value2 = value;
|
|
return false;
|
|
}
|
|
int pos = str.findFirst(":");
|
|
if(pos == -1) {
|
|
value = toDouble(str);
|
|
value2 = value;
|
|
return false;
|
|
}
|
|
else {
|
|
value = toDouble(str.substr(0, pos));
|
|
value2 = toDouble(str.substr(pos+1));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool instantiate() {
|
|
switch(type) {
|
|
case AT_PlanetResource:
|
|
if(str.length != 0) {
|
|
integer = getResourceID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown planetary resource: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Status:
|
|
if(str.length != 0) {
|
|
integer = getStatusID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown status: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Attitude:
|
|
if(str.length != 0) {
|
|
integer = getAttitudeID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown attitude: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Cargo:
|
|
if(str.length != 0) {
|
|
integer = getCargoID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown cargo: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Trait:
|
|
if(str.length != 0) {
|
|
integer = getTraitID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown trait: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_RandomEvent:
|
|
if(str.length != 0) {
|
|
integer = getRandomEventID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown random event: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_PlanetBiome:
|
|
if(str.length != 0) {
|
|
integer = getBiomeID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown biome: "+str);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Construction:
|
|
if(str.length != 0) {
|
|
integer = getConstructionID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown construction: "+str);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Artifact:
|
|
if(str.length != 0) {
|
|
integer = getArtifactID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown artifact: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Building:
|
|
if(str.length != 0) {
|
|
integer = getBuildingID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown building: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Technology:
|
|
if(str.length != 0) {
|
|
integer = getTechnologyID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown technology: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_OrbitalModule:
|
|
if(str.length != 0) {
|
|
integer = getOrbitalModuleID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown orbital: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_Ability:
|
|
if(str.length != 0) {
|
|
integer = getAbilityID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown ability: "+str);
|
|
return false;
|
|
}
|
|
str = "";
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_InfluenceCard:
|
|
if(str.length != 0) {
|
|
integer = getInfluenceCardID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown influence card type: "+str);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_InfluenceVote:
|
|
if(str.length != 0) {
|
|
integer = getInfluenceVoteID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown influence vote type: "+str);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
case AT_InfluenceEffect:
|
|
if(str.length != 0) {
|
|
integer = getInfluenceEffectID(str);
|
|
if(integer == -1) {
|
|
error(" Error: Unknown influence effect type: "+str);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
integer = -1;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parse(const string& value) {
|
|
filled = true;
|
|
str = value;
|
|
switch(type) {
|
|
case AT_Integer:
|
|
integer = toInt(value);
|
|
return true;
|
|
case AT_Boolean:
|
|
if(value[0] == '$')
|
|
boolean = config::get(value.substr(1)) != 0.0;
|
|
else if(value[0] == '!' && value[1] == '$')
|
|
boolean = config::get(value.substr(2)) == 0.0;
|
|
else
|
|
boolean = toBool(value);
|
|
return true;
|
|
case AT_PassFail:
|
|
boolean = value.equals_nocase("pass");
|
|
return true;
|
|
case AT_Decimal:
|
|
if(value[0] == '$')
|
|
decimal = config::get(value.substr(1));
|
|
else
|
|
decimal = toDouble(value);
|
|
return true;
|
|
case AT_Global:
|
|
integer = int(getGlobal(value).id);
|
|
return true;
|
|
case AT_UnlockTag:
|
|
integer = getUnlockTag(value);
|
|
return true;
|
|
case AT_ObjectStat:
|
|
integer = getObjectStatId(value);
|
|
return true;
|
|
case AT_SystemFlag:
|
|
integer = getSystemFlag(value);
|
|
return true;
|
|
case AT_TileResource:
|
|
integer = getTileResource(value);
|
|
if(integer == TR_NULL) {
|
|
error(" Error: Unknown tile resource: "+value);
|
|
return false;
|
|
}
|
|
return true;
|
|
case AT_EmpAttribute:
|
|
if(value.length == 0)
|
|
integer = -1;
|
|
else
|
|
integer = int(getEmpAttribute(value, true));
|
|
str = value;
|
|
return true;
|
|
case AT_AttributeMode:
|
|
integer = int(getAttributeMode(value));
|
|
if(integer == int(AC_INVALID)) {
|
|
error(" Error: Unknown empire attribute mode: "+value);
|
|
return false;
|
|
}
|
|
return true;
|
|
case AT_ObjectStatMode:
|
|
integer = int(getObjectStatMode(value));
|
|
return true;
|
|
case AT_PlanetResource:
|
|
if(value.equals_nocase("null")) {
|
|
integer = -1;
|
|
return true;
|
|
}
|
|
str = value;
|
|
return true;
|
|
case AT_Status:
|
|
case AT_Attitude:
|
|
case AT_Cargo:
|
|
case AT_Trait:
|
|
case AT_RandomEvent:
|
|
case AT_PlanetBiome:
|
|
case AT_Construction:
|
|
case AT_Artifact:
|
|
case AT_Building:
|
|
case AT_Technology:
|
|
case AT_OrbitalModule:
|
|
case AT_Ability:
|
|
str = value;
|
|
return true;
|
|
case AT_Subsystem:
|
|
{
|
|
auto@ def = getSubsystemDef(value);
|
|
if(def is null)
|
|
return false;
|
|
integer = int(def.index);
|
|
return true;
|
|
}
|
|
case AT_Range:
|
|
isRange = parseRange(decimal, decimal2, value);
|
|
return true;
|
|
case AT_Position: {
|
|
string fname;
|
|
array<string> args;
|
|
if(!funcSplit(value, fname, args, true)
|
|
|| args.length != 3 || fname.length != 0) {
|
|
error(" Error: Invalid position spec: "+value);
|
|
return false;
|
|
}
|
|
if(parseRange(position.x, position2.x, args[0]))
|
|
isRange = true;
|
|
if(parseRange(position.y, position2.y, args[1]))
|
|
isRange = true;
|
|
if(parseRange(position.z, position2.z, args[2]))
|
|
isRange = true;
|
|
} return true;
|
|
case AT_Position2D: {
|
|
string fname;
|
|
array<string> args;
|
|
if(!funcSplit(value, fname, args, true)
|
|
|| args.length != 2 || fname.length != 0) {
|
|
error(" Error: Invalid 2d position spec: "+value);
|
|
return false;
|
|
}
|
|
if(parseRange(position.x, position2.x, args[0]))
|
|
isRange = true;
|
|
if(parseRange(position.y, position2.y, args[1]))
|
|
isRange = true;
|
|
} return true;
|
|
case AT_VarArgs:
|
|
error("Invalid use of variable arguments.");
|
|
return false;
|
|
case AT_Locale:
|
|
str = localize(value);
|
|
return true;
|
|
case AT_Target:
|
|
str = value;
|
|
return true;
|
|
case AT_SysVar: {
|
|
integer = -1;
|
|
decimal = 0;
|
|
string val = value;
|
|
int colon = val.findLast(":");
|
|
if(colon != -1) {
|
|
decimal = toDouble(val.substr(colon+1));
|
|
val = val.substr(0, colon).trimmed();
|
|
}
|
|
if(val.startswith_nocase("sys.")) {
|
|
integer = getSubsystemVariable(val.substr(4));
|
|
if(integer == -1) {
|
|
error("Unknown subsystem variable: "+val);
|
|
return false;
|
|
}
|
|
}
|
|
else if(val[0] == '$')
|
|
decimal = config::get(val.substr(1));
|
|
else
|
|
decimal = toDouble(val);
|
|
} return true;
|
|
case AT_EmpireResource:
|
|
integer = empResources.find(value);
|
|
return integer >= 0;
|
|
case AT_Custom:
|
|
default:
|
|
str = value;
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
string output() const {
|
|
switch(type) {
|
|
case AT_Integer:
|
|
return toString(integer, 0);
|
|
case AT_Global:
|
|
return getGlobal(integer).ident;
|
|
case AT_UnlockTag:
|
|
return getUnlockTagIdent(integer);
|
|
case AT_ObjectStat:
|
|
return getObjectStatIdent(integer);
|
|
case AT_SystemFlag:
|
|
return getSystemFlagIdent(integer);
|
|
case AT_Boolean:
|
|
return boolean ? "True" : "False";
|
|
case AT_PassFail:
|
|
return boolean ? "Pass" : "Fail";
|
|
case AT_Decimal:
|
|
return toString(decimal);
|
|
case AT_SysVar:
|
|
return toString(decimal);
|
|
case AT_TileResource:
|
|
return getTileResourceIdent(integer);
|
|
case AT_PlanetResource:
|
|
return getResourceIdent(integer);
|
|
case AT_Status:
|
|
return getStatusIdent(integer);
|
|
case AT_Attitude:
|
|
return getAttitudeIdent(integer);
|
|
case AT_Cargo:
|
|
return getCargoIdent(integer);
|
|
case AT_Trait:
|
|
return getTraitIdent(integer);
|
|
case AT_RandomEvent:
|
|
return getRandomEventIdent(integer);
|
|
case AT_PlanetBiome:
|
|
return getBiomeIdent(integer);
|
|
case AT_Construction:
|
|
return getConstructionIdent(integer);
|
|
case AT_Artifact:
|
|
return getArtifactIdent(integer);
|
|
case AT_Building:
|
|
return getBuildingIdent(integer);
|
|
case AT_Technology:
|
|
return getTechnologyIdent(integer);
|
|
case AT_OrbitalModule:
|
|
return getOrbitalModuleIdent(integer);
|
|
case AT_Ability:
|
|
return getAbilityIdent(integer);
|
|
case AT_InfluenceCard:
|
|
return getInfluenceCardIdent(integer);
|
|
case AT_InfluenceVote:
|
|
return getInfluenceVoteIdent(integer);
|
|
case AT_InfluenceEffect:
|
|
return getInfluenceEffectIdent(integer);
|
|
case AT_VarArgs:
|
|
return "...";
|
|
case AT_Range:
|
|
if(isRange)
|
|
return toString(decimal)+":"+toString(decimal2);
|
|
else
|
|
return toString(decimal);
|
|
case AT_Position: {
|
|
string output = "(";
|
|
if(position.x == position2.x)
|
|
output += toString(position.x);
|
|
else
|
|
output += toString(position.x)+":"+toString(position2.x);
|
|
output += ", ";
|
|
if(position.y == position2.y)
|
|
output += toString(position.y);
|
|
else
|
|
output += toString(position.y)+":"+toString(position2.y);
|
|
output += ", ";
|
|
if(position.z == position2.z)
|
|
output += toString(position.z);
|
|
else
|
|
output += toString(position.z)+":"+toString(position2.z);
|
|
output += ")";
|
|
return output;
|
|
}
|
|
case AT_Position2D: {
|
|
string output = "(";
|
|
if(position.x == position2.x)
|
|
output += toString(position.x);
|
|
else
|
|
output += toString(position.x)+":"+toString(position2.x);
|
|
output += ", ";
|
|
if(position.y == position2.y)
|
|
output += toString(position.y);
|
|
else
|
|
output += toString(position.y)+":"+toString(position2.y);
|
|
output += ")";
|
|
return output;
|
|
}
|
|
case AT_Target:
|
|
return str;
|
|
case AT_Locale:
|
|
return str;
|
|
case AT_EmpAttribute:
|
|
if(integer == -1)
|
|
return "";
|
|
return getEmpAttributeIdent(integer);
|
|
case AT_AttributeMode:
|
|
return getAttributeModeIdent(integer);
|
|
case AT_ObjectStatMode:
|
|
return getObjectStatMode(integer);
|
|
case AT_EmpireResource:
|
|
if(integer >= 0 && uint(integer) < empResources.length)
|
|
return empResources[integer];
|
|
else
|
|
return empResources[0];
|
|
case AT_Custom:
|
|
default:
|
|
return str;
|
|
}
|
|
return "";
|
|
}
|
|
};
|
|
|
|
final class Document {
|
|
string text;
|
|
bool hidden = false;
|
|
|
|
Document(const string& txt) {
|
|
this.text = txt;
|
|
}
|
|
|
|
Document(const string& txt, bool hidden) {
|
|
this.text = txt;
|
|
this.hidden = hidden;
|
|
}
|
|
};
|
|
|
|
// }}}
|
|
// {{{ Hooks
|
|
class Hook {
|
|
array<Argument@> arguments;
|
|
Document@ documentation;
|
|
uint requiredArguments = 0;
|
|
string filePosition;
|
|
|
|
//Adding arguments to this hook
|
|
void argument(ArgumentType type) {
|
|
arguments.insertLast(Argument(type));
|
|
++requiredArguments;
|
|
}
|
|
|
|
void argument(ArgumentType type, const string& defaultValue) {
|
|
arguments.insertLast(Argument(type, defaultValue, ""));
|
|
}
|
|
|
|
void argument(const string& argName, ArgumentType type) {
|
|
arguments.insertLast(Argument(argName, type));
|
|
++requiredArguments;
|
|
}
|
|
|
|
void argument(const string& argName, ArgumentType type, const string& defaultValue) {
|
|
arguments.insertLast(Argument(argName, type, defaultValue));
|
|
}
|
|
|
|
void varargs(ArgumentType type, bool required = false) {
|
|
Argument arg(AT_VarArgs);
|
|
arg.integer = int(type);
|
|
arg.boolean = required;
|
|
arguments.insertLast(arg);
|
|
if(required)
|
|
++requiredArguments;
|
|
}
|
|
|
|
void target(const string& argName, TargetType type) {
|
|
Argument arg(AT_Target);
|
|
arg.integer2 = int(type);
|
|
arg.boolean = false;
|
|
arguments.insertLast(arg);
|
|
++requiredArguments;
|
|
}
|
|
|
|
void target(const string& argName, TargetType type, const string& defaultValue) {
|
|
Argument arg(AT_Target);
|
|
arg.integer2 = int(type);
|
|
arg.boolean = true;
|
|
arg.str = defaultValue;
|
|
arguments.insertLast(arg);
|
|
}
|
|
|
|
string formatHook() {
|
|
string txt;
|
|
txt = getClass(this).name;
|
|
txt += "(";
|
|
for(uint i = 0, cnt = arguments.length; i < cnt; ++i) {
|
|
if(i != 0)
|
|
txt += ", ";
|
|
txt += arguments[i].output();
|
|
}
|
|
txt += ")";
|
|
return txt;
|
|
}
|
|
|
|
string formatDeclaration() {
|
|
string txt;
|
|
txt = getClass(this).name;
|
|
txt += "(";
|
|
for(uint i = 0, cnt = arguments.length; i < cnt; ++i) {
|
|
if(i != 0)
|
|
txt += ", ";
|
|
auto@ arg = arguments[i];
|
|
txt += "<"+arg.argName+">";
|
|
if(arg.filled && arg.type != AT_Hook)
|
|
txt += " = "+arg.output();
|
|
}
|
|
txt += ")";
|
|
return txt;
|
|
}
|
|
|
|
//Parse arguments from strings
|
|
#section server
|
|
bool init(Design& design, Subsystem& subsystem, StringList& arglist, DoubleList& values) const {
|
|
array<string> args;
|
|
for(uint i = 0, cnt = arglist.length; i < cnt; ++i)
|
|
args.insertLast(arglist[i]);
|
|
initClass();
|
|
if(!parse("--", args)) {
|
|
printError("Error in hook "+formatHook());
|
|
return false;
|
|
}
|
|
for(uint i = 0, cnt = min(values.length, arguments.length); i < cnt; ++i) {
|
|
switch(arguments[i].type) {
|
|
case AT_Decimal: arguments[i].decimal = values[i]; break;
|
|
case AT_Integer: arguments[i].integer = values[i]; break;
|
|
case AT_Range: arguments[i].decimal = values[i]; arguments[i].decimal2 = values[i]; break;
|
|
}
|
|
}
|
|
if(!instantiate()) {
|
|
printError("Error in hook "+formatHook());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#section all
|
|
|
|
void printError(const string& message) {
|
|
if(filePosition.length != 0)
|
|
error(filePosition);
|
|
error(message);
|
|
}
|
|
|
|
bool parse(const string& name, array<string>& args) {
|
|
if(args.length < requiredArguments) {
|
|
printError(" Error: mismatched argument count, expected "+arguments.length+" arguments.");
|
|
return false;
|
|
}
|
|
bool inNamed = false;
|
|
uint i = 0;
|
|
for(uint cnt = arguments.length, acnt = args.length; i < cnt && i < acnt; ++i) {
|
|
int eqPos = args[i].findFirst("=");
|
|
int brkPos = args[i].findFirst("(");
|
|
if(eqPos != -1 && (brkPos == -1 || eqPos < brkPos)) {
|
|
//Named argument
|
|
uint index = uint(-1);
|
|
inNamed = true;
|
|
string name = args[i].substr(0, eqPos).trimmed();
|
|
string value = args[i].substr(eqPos+1).trimmed();
|
|
for(uint n = 0; n < cnt; ++n) {
|
|
if(arguments[n].argName.equals_nocase(name)) {
|
|
index = n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(index == uint(-1)) {
|
|
printError(" Error: Could not find argument with name: "+name);
|
|
return false;
|
|
}
|
|
else {
|
|
if(!arguments[index].parse(value))
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
//Positional argument
|
|
if(inNamed) {
|
|
printError(" Error: positional arguments cannot follow named ones.");
|
|
return false;
|
|
}
|
|
//Variable arguments
|
|
if(arguments[i].type == AT_VarArgs) {
|
|
auto varType = ArgumentType(arguments[i].integer);
|
|
arguments.removeAt(i);
|
|
for(uint n = i; n < acnt; ++n) {
|
|
Argument arg(varType);
|
|
if(!arg.parse(args[n]))
|
|
return false;
|
|
arguments.insertLast(arg);
|
|
}
|
|
}
|
|
//Normal arguments
|
|
else if(!arguments[i].parse(args[i])) {
|
|
printError("Error in arguments.");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if(i < requiredArguments) {
|
|
if(arguments[i].type != AT_VarArgs || arguments[i].boolean) {
|
|
printError(" Error: mismatched argument count, expected "+arguments.length+" arguments.");
|
|
return false;
|
|
}
|
|
}
|
|
for(uint i = 0, cnt = requiredArguments; i < cnt; ++i) {
|
|
if(!arguments[i].filled) {
|
|
printError(" Error: no value for argument "+i+" ("+arguments[i].argName+")");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//Pluck arguments from class
|
|
void initClass() {
|
|
auto@ argClass = getClass(REF_ARG);
|
|
auto@ thisClass = getClass(this);
|
|
for(uint i = 0, cnt = thisClass.memberCount; i < cnt; ++i) {
|
|
auto@ ptr = thisClass.getMember(this, i);
|
|
auto@ mem = cast<Argument>(ptr);
|
|
if(mem is null) {
|
|
if(cast<Document>(ptr) !is null)
|
|
@documentation = cast<Document>(ptr);
|
|
continue;
|
|
}
|
|
if(mem.argName.length == 0) {
|
|
mem.argName = thisClass.memberName[i];
|
|
if(mem.argName.findFirst(SEP_UNDER) != -1) {
|
|
array<string>@ splt = mem.argName.split(SEP_UNDER);
|
|
mem.argName = "";
|
|
for(uint i = 0, cnt = splt.length; i < cnt; ++i) {
|
|
splt[i][0] = uppercase(splt[i][0]);
|
|
if(i != 0)
|
|
mem.argName += " ";
|
|
mem.argName += splt[i];
|
|
}
|
|
}
|
|
else {
|
|
mem.argName[0] = uppercase(mem.argName[0]);
|
|
}
|
|
}
|
|
arguments.insertLast(mem);
|
|
if(mem.type == AT_VarArgs) {
|
|
if(mem.boolean)
|
|
++requiredArguments;
|
|
}
|
|
else if(!mem.filled) {
|
|
++requiredArguments;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Called when the hook is first created in its type
|
|
bool instantiate() {
|
|
for(uint i = 0, cnt = arguments.length; i < cnt; ++i)
|
|
if(!arguments[i].instantiate())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//Figure out target arguments
|
|
bool initTargets(Targets@ targets) {
|
|
uint targCnt = targets.targets.length;
|
|
for(uint i = 0, cnt = arguments.length; i < cnt; ++i) {
|
|
auto@ arg = arguments[i];
|
|
if(arg.type != AT_Target)
|
|
continue;
|
|
|
|
//Find the target index
|
|
uint index = uint(-1);
|
|
for(uint n = 0; n < targCnt; ++n) {
|
|
if(targets.targets[n].name.equals_nocase(arg.str)) {
|
|
index = n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(index >= targets.targets.length) {
|
|
if(arg.boolean) {
|
|
arg.integer = -1;
|
|
continue;
|
|
}
|
|
printError("Could not find target: "+arg.str);
|
|
return false;
|
|
}
|
|
|
|
//Check for type
|
|
auto@ targ = targets.targets[index];
|
|
if(arg.integer2 != int(TT_Any)) {
|
|
if(targ.type != TargetType(arg.integer2)) {
|
|
printError("Target "+targ.name+" is not of appropriate type.");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
arg.integer2 = int(targ.type);
|
|
}
|
|
|
|
//Record index
|
|
arg.integer = int(index);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class BlockHook : Hook {
|
|
int indent = 0;
|
|
array<Hook@> inner;
|
|
|
|
BlockHook@ getInside(int indent, const string& line) {
|
|
if(inner.length > 0) {
|
|
BlockHook@ block = cast<BlockHook@>(inner[inner.length - 1]);
|
|
if(block !is null) {
|
|
@block = block.getInside(indent, line);
|
|
if(block !is null)
|
|
return block;
|
|
}
|
|
}
|
|
if(indent > this.indent)
|
|
return this;
|
|
return null;
|
|
}
|
|
|
|
void addHook(Hook@ hook) {
|
|
inner.insertLast(hook);
|
|
}
|
|
|
|
bool prepare(Argument@& arg) const {
|
|
return true;
|
|
}
|
|
|
|
bool feed(Argument@& arg, Hook@& hook, uint& num) const {
|
|
if(num >= inner.length)
|
|
return false;
|
|
|
|
@hook = inner[num];
|
|
num += 1;
|
|
return true;
|
|
}
|
|
};
|
|
// }}}
|
|
// {{{ Targets
|
|
enum TargetType {
|
|
TT_Empire,
|
|
TT_Object,
|
|
TT_Side,
|
|
TT_Card,
|
|
TT_Vote,
|
|
TT_Effect,
|
|
TT_String,
|
|
TT_Point,
|
|
TT_ID,
|
|
TT_Any,
|
|
TT_Custom,
|
|
|
|
TT_COUNT
|
|
};
|
|
|
|
TargetType getTargetType(const string& str) {
|
|
if(str.equals_nocase("empire"))
|
|
return TT_Empire;
|
|
if(str.equals_nocase("object"))
|
|
return TT_Object;
|
|
if(str.equals_nocase("side"))
|
|
return TT_Side;
|
|
if(str.equals_nocase("card"))
|
|
return TT_Card;
|
|
if(str.equals_nocase("vote"))
|
|
return TT_Vote;
|
|
if(str.equals_nocase("id"))
|
|
return TT_ID;
|
|
if(str.equals_nocase("effect"))
|
|
return TT_Effect;
|
|
if(str.equals_nocase("text"))
|
|
return TT_String;
|
|
if(str.equals_nocase("point"))
|
|
return TT_Point;
|
|
return TT_Custom;
|
|
}
|
|
|
|
const array<string> TARGET_TYPE_NAMES = {
|
|
"Empire", "Object", "Side", "Card", "Vote",
|
|
"Effect", "Text", "Point", "ID", "Any", "Custom"};
|
|
|
|
final class Target : Serializable, Savable {
|
|
TargetType type = TT_Custom;
|
|
bool filled = false;
|
|
string name;
|
|
|
|
Empire@ emp;
|
|
Object@ obj;
|
|
string str;
|
|
bool side;
|
|
int id;
|
|
vec3d point;
|
|
|
|
Target() {
|
|
}
|
|
|
|
Target(TargetType Type) {
|
|
type = Type;
|
|
}
|
|
|
|
string format(bool pretty = true) const {
|
|
if(!filled)
|
|
return locale::CARD_GENERIC;
|
|
switch(type) {
|
|
case TT_Empire:
|
|
if(emp !is null && playerEmpire !is null && playerEmpire.valid && playerEmpire.ContactMask & emp.mask == 0)
|
|
return "???";
|
|
if(pretty)
|
|
return formatEmpireName(emp);
|
|
else if(emp is null)
|
|
return locale::CARD_GENERIC;
|
|
else
|
|
return emp.name;
|
|
case TT_Object:
|
|
if(pretty)
|
|
return formatObject(obj);
|
|
else if(obj is null)
|
|
return locale::CARD_GENERIC;
|
|
else
|
|
return obj.name;
|
|
case TT_Side: return side ? locale::SUPPORT : locale::OPPOSE;
|
|
case TT_String: case TT_Custom: return str;
|
|
case TT_Point: return ""+point;
|
|
}
|
|
return "...";
|
|
}
|
|
|
|
bool opEquals(const Target& other) const {
|
|
if(type != other.type)
|
|
return false;
|
|
if(filled != other.filled)
|
|
return false;
|
|
if(filled) {
|
|
switch(type) {
|
|
case TT_Empire: return emp is other.emp;
|
|
case TT_Object: return obj is other.obj;
|
|
case TT_Side: return side == other.side;
|
|
case TT_String: case TT_Custom: return str == other.str;
|
|
case TT_Vote: case TT_Card: case TT_Effect: case TT_ID:
|
|
return id == other.id;
|
|
case TT_Point: return point == other.point;
|
|
}
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//Networking
|
|
void writeData(Message& msg, TargetType Type) {
|
|
msg << filled;
|
|
if(filled) {
|
|
switch(Type) {
|
|
case TT_Empire: msg << emp; break;
|
|
case TT_Object: msg << obj; break;
|
|
case TT_Side: msg << side; break;
|
|
case TT_Point: msg << point; break;
|
|
case TT_String: case TT_Custom: msg << str; break;
|
|
case TT_Vote: case TT_Card: case TT_Effect: case TT_ID:
|
|
msg << id; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void write(Message& msg) {
|
|
uint Type = type;
|
|
msg.writeLimited(type, TT_COUNT-1);
|
|
msg << name;
|
|
writeData(msg, type);
|
|
}
|
|
|
|
void readData(Message& msg, TargetType Type) {
|
|
msg >> filled;
|
|
if(filled) {
|
|
switch(Type) {
|
|
case TT_Empire: msg >> emp; break;
|
|
case TT_Object: msg >> obj; break;
|
|
case TT_Side: msg >> side;break;
|
|
case TT_Point: msg >> point; break;
|
|
case TT_String: case TT_Custom: msg >> str; break;
|
|
case TT_Vote: case TT_Card: case TT_Effect: case TT_ID:
|
|
msg >> id; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void read(Message& msg) {
|
|
type = TargetType(msg.readLimited(TT_COUNT-1));
|
|
msg >> name;
|
|
readData(msg, type);
|
|
}
|
|
|
|
//Saving
|
|
void saveData(SaveFile& file, TargetType Type) {
|
|
file << filled;
|
|
switch(Type) {
|
|
case TT_Empire: file << emp; break;
|
|
case TT_Object: file << obj; break;
|
|
case TT_Side: file << side; break;
|
|
case TT_Point: file << point; break;
|
|
case TT_String: case TT_Custom: file << str; break;
|
|
case TT_Vote: case TT_Card: case TT_Effect: case TT_ID:
|
|
file << id; break;
|
|
}
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
uint Type = type;
|
|
file << Type;
|
|
file << name;
|
|
saveData(file, type);
|
|
}
|
|
|
|
void loadData(SaveFile& file, TargetType Type) {
|
|
file >> filled;
|
|
switch(Type) {
|
|
case TT_Empire: file >> emp; break;
|
|
case TT_Object: file >> obj; break;
|
|
case TT_Side: file >> side; break;
|
|
case TT_Point: file >> point; break;
|
|
case TT_String: case TT_Custom: file >> str; break;
|
|
case TT_Vote: case TT_Card: case TT_Effect: case TT_ID:
|
|
file >> id; break;
|
|
}
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
uint Type = 0;
|
|
file >> Type;
|
|
type = TargetType(Type);
|
|
file >> name;
|
|
loadData(file, type);
|
|
}
|
|
};
|
|
|
|
final class Targets : Serializable, Savable {
|
|
array<Target> targets;
|
|
|
|
//Empty list of targets
|
|
Targets() {
|
|
}
|
|
|
|
//Add a new target
|
|
Target@ add(const string& name, TargetType type) {
|
|
targets.length = targets.length+1;
|
|
Target@ targ = targets[targets.length-1];
|
|
targ.type = type;
|
|
targ.name = name;
|
|
return targ;
|
|
}
|
|
|
|
Target@ add(TargetType type, bool fill = false) {
|
|
targets.length = targets.length+1;
|
|
Target@ targ = targets[targets.length-1];
|
|
targ.type = type;
|
|
targ.filled = fill;
|
|
return targ;
|
|
}
|
|
|
|
//Copy existing targets
|
|
Targets(const Targets@ other) {
|
|
targets = other.targets;
|
|
}
|
|
|
|
void set(const Targets@ other) {
|
|
targets = other.targets;
|
|
}
|
|
|
|
//Get a target variable
|
|
Target@ get(const string& name) {
|
|
for(uint i = 0, cnt = targets.length; i < cnt; ++i) {
|
|
if(targets[i].name == name)
|
|
return targets[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Target@ fill(const string& name) {
|
|
auto@ targ = get(name);
|
|
if(targ !is null)
|
|
targ.filled = true;
|
|
return targ;
|
|
}
|
|
|
|
Target@ fill(uint index) {
|
|
auto@ targ = targets[index];
|
|
if(targ !is null)
|
|
targ.filled = true;
|
|
return targ;
|
|
}
|
|
|
|
int getIndex(const string& name) {
|
|
for(uint i = 0, cnt = targets.length; i < cnt; ++i) {
|
|
if(targets[i].name == name)
|
|
return int(i);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
string format(const string& text, bool pretty = true) const {
|
|
array<string> args;
|
|
formatInto(args, pretty);
|
|
return ::format(text, args);
|
|
}
|
|
|
|
void formatInto(array<string>& args, bool pretty = true) const {
|
|
for(uint i = 0, cnt = targets.length; i < cnt; ++i)
|
|
args.insertLast(targets[i].format(pretty));
|
|
}
|
|
|
|
string format(bool pretty = true) const {
|
|
string targs = "";
|
|
for(uint i = 0, cnt = targets.length; i < cnt; ++i) {
|
|
if(i != 0)
|
|
targs += ", ";
|
|
targs += targets[i].format(pretty);
|
|
}
|
|
return targs;
|
|
}
|
|
|
|
//Check equality of target values
|
|
bool opEquals(const Targets& other) const {
|
|
if(other.targets.length != targets.length)
|
|
return false;
|
|
for(uint i = 0, cnt = targets.length; i < cnt; ++i) {
|
|
if(targets[i] != other.targets[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//Quick access
|
|
const Target@ opIndex(uint index) const {
|
|
if(index >= targets.length)
|
|
return null;
|
|
return targets[index];
|
|
}
|
|
|
|
Target@ opIndex(uint index) {
|
|
if(index >= targets.length)
|
|
return null;
|
|
return targets[index];
|
|
}
|
|
|
|
Target@ opIndex(const string& name) {
|
|
return get(name);
|
|
}
|
|
|
|
const Target@ opIndex(const string& name) const {
|
|
return get(name);
|
|
}
|
|
|
|
uint get_length() const {
|
|
return targets.length;
|
|
}
|
|
|
|
//Networking
|
|
Targets(Message& msg) {
|
|
read(msg);
|
|
}
|
|
|
|
Targets(Message& msg, const Targets@ def) {
|
|
readData(msg, def);
|
|
}
|
|
|
|
void write(Message& msg) {
|
|
uint cnt = targets.length;
|
|
msg.writeSmall(cnt);
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << targets[i];
|
|
}
|
|
|
|
void read(Message& msg) {
|
|
uint cnt = msg.readSmall();
|
|
targets.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg >> targets[i];
|
|
}
|
|
|
|
void writeData(Message& msg, const Targets@ def) {
|
|
for(uint i = 0, cnt = def.targets.length; i < cnt; ++i)
|
|
targets[i].writeData(msg, def.targets[i].type);
|
|
}
|
|
|
|
void readData(Message& msg, const Targets@ def) {
|
|
set(def);
|
|
for(uint i = 0, cnt = def.targets.length; i < cnt; ++i)
|
|
targets[i].readData(msg, def.targets[i].type);
|
|
}
|
|
|
|
//Saving
|
|
Targets(SaveFile& file) {
|
|
load(file);
|
|
}
|
|
|
|
Targets(SaveFile& file, const Targets@ def) {
|
|
loadData(file, def);
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
uint cnt = targets.length;
|
|
file << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file << targets[i];
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
uint cnt = 0;
|
|
file >> cnt;
|
|
targets.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
file >> targets[i];
|
|
}
|
|
|
|
void saveData(SaveFile& file, const Targets@ def) {
|
|
for(uint i = 0, cnt = def.targets.length; i < cnt; ++i)
|
|
targets[i].saveData(file, def.targets[i].type);
|
|
}
|
|
|
|
void loadData(SaveFile& file, const Targets@ def) {
|
|
set(def);
|
|
for(uint i = 0, cnt = def.targets.length; i < cnt; ++i)
|
|
targets[i].loadData(file, def.targets[i].type);
|
|
}
|
|
};
|
|
//}}}
|
|
//{{{ Parser
|
|
Hook@ parseHook(array<Hook@>& list, int indent, const string& line, const string& defaultNamespace) {
|
|
BlockHook@ inBlock;
|
|
if(list.length != 0) {
|
|
@inBlock = cast<BlockHook>(list[list.length-1]);
|
|
if(inBlock !is null)
|
|
@inBlock = inBlock.getInside(indent, line);
|
|
}
|
|
|
|
Hook@ hook = parseHook(line, defaultNamespace);
|
|
BlockHook@ newBlock = cast<BlockHook>(hook);
|
|
if(newBlock !is null)
|
|
newBlock.indent = indent;
|
|
|
|
if(hook !is null) {
|
|
if(inBlock !is null)
|
|
inBlock.addHook(hook);
|
|
else
|
|
list.insertLast(hook);
|
|
}
|
|
return hook;
|
|
}
|
|
|
|
void error(ReadFile@ file, const string& message) {
|
|
if(file !is null)
|
|
file.error(message);
|
|
else
|
|
error(message);
|
|
}
|
|
|
|
Hook@ parseHook(const string& line, const string& defaultNamespace, bool required = true, bool instantiate = true, ReadFile@ file = null) {
|
|
array<string> args;
|
|
string name;
|
|
|
|
//Deal with version handling
|
|
string parseLine = line;
|
|
int lastBrkt = line.findLast(")");
|
|
int hashPos = line.findLast("#");
|
|
if(hashPos != -1 && hashPos > lastBrkt) {
|
|
int ltPos = line.findLast("#version<");
|
|
if(ltPos != -1) {
|
|
if(line.length <= uint(ltPos+9) || line[ltPos+9] == '=') {
|
|
error(file, "ERROR: Unknown hook tag: "+parseLine.substr(hashPos));
|
|
}
|
|
else {
|
|
int cmpVersion = toInt(line.substr(ltPos+9))-1;
|
|
if(isLoadedSave) {
|
|
if(START_VERSION >= cmpVersion)
|
|
return null;
|
|
}
|
|
else {
|
|
if(SV_CURRENT >= cmpVersion)
|
|
return null;
|
|
}
|
|
parseLine = parseLine.substr(0, hashPos);
|
|
}
|
|
}
|
|
else {
|
|
int gtePos = line.findLast("#version>=");
|
|
if(gtePos != -1) {
|
|
int cmpVersion = toInt(line.substr(gtePos+10))-1;
|
|
if(isLoadedSave && START_VERSION < cmpVersion)
|
|
return null;
|
|
parseLine = parseLine.substr(0, hashPos);
|
|
}
|
|
else {
|
|
error(file, "ERROR: Unknown hook tag: "+parseLine.substr(hashPos));
|
|
}
|
|
}
|
|
}
|
|
|
|
//Parse the actual hook
|
|
if(!funcSplit(parseLine, name, args)) {
|
|
error(file, "Invalid hook: "+escape(parseLine.trimmed()));
|
|
return null;
|
|
}
|
|
|
|
bool hasNamespace = true;
|
|
if(name.findFirst("::") == -1) {
|
|
name = defaultNamespace + name;
|
|
hasNamespace = false;
|
|
}
|
|
AnyClass@ cls = getClass(name.trimmed());
|
|
if(!hasNamespace)
|
|
name = name.substr(defaultNamespace.length);
|
|
if(cls is null) {
|
|
if(required && (!hasNamespace || (isServer && !isShadow)) && !isScriptDebug)
|
|
error(file, "Could not find hook class: "+escape(name.trimmed())+" in "+escape(line.trimmed()));
|
|
return null;
|
|
}
|
|
|
|
Hook@ hook = cast<Hook>(cls.create());
|
|
if(hook is null) {
|
|
if(required && (!hasNamespace || (isServer && !isShadow)) && !isScriptDebug)
|
|
error(file, "Could not find hook class: "+escape(name.trimmed())+" in "+escape(line.trimmed()));
|
|
return null;
|
|
}
|
|
hook.initClass();
|
|
if(!hook.parse(name, args)) {
|
|
error(file, "Invalid arguments to hook: "+escape(line.trimmed()));
|
|
return null;
|
|
}
|
|
if(instantiate && !hook.instantiate()) {
|
|
error(file, "Could not instantiate hook: "+escape(line.trimmed()));
|
|
return null;
|
|
}
|
|
|
|
if(file !is null)
|
|
hook.filePosition = file.position();
|
|
return hook;
|
|
}
|
|
|
|
Hook@ makeHookInstance(const string& line, const string& defaultNamespace, const string& enforceType = "") {
|
|
string name;
|
|
|
|
int pos = line.findFirst("(");
|
|
if(pos == -1)
|
|
name = line;
|
|
else
|
|
name = line.substr(0, pos);
|
|
|
|
if(name.findFirst("::") == -1)
|
|
name = defaultNamespace + name;
|
|
|
|
AnyClass@ cls = getClass(name.trimmed());
|
|
if(cls is null)
|
|
return null;
|
|
|
|
if(enforceType.length != 0) {
|
|
AnyClass@ checkType = getClass(enforceType);
|
|
if(checkType !is null && !cls.implements(checkType))
|
|
return null;
|
|
}
|
|
|
|
Hook@ hook = cast<Hook>(cls.create());
|
|
if(hook is null)
|
|
return null;
|
|
|
|
hook.initClass();
|
|
return hook;
|
|
}
|
|
|
|
Target@ parseTarget(Targets@ targets, const string& line, bool allowCustom = false) {
|
|
int pos = line.findFirst("=");
|
|
if(pos == -1) {
|
|
error("Invalid target spec: "+escape(line));
|
|
return null;
|
|
}
|
|
|
|
string name = line.substr(0, pos).trimmed();
|
|
string type = line.substr(pos+1).trimmed();
|
|
|
|
TargetType tt = getTargetType(type);
|
|
if(!allowCustom && tt == TT_Custom) {
|
|
error("Invalid target type: "+escape(type));
|
|
return null;
|
|
}
|
|
|
|
return targets.add(name, tt);
|
|
}
|
|
//}}}
|