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

463 lines
11 KiB
ActionScript

#priority init 2000
import hooks;
import saving;
export AnomalyType, AnomalyOption, AnomalyState;
export getAnomalyType, getAnomalyTypeCount;
export getDistributedAnomalyType;
export Targets;
tidy final class AnomalyOption {
uint id = 0;
Sprite icon;
string ident, desc;
string blurb;
bool isSafe = true;
double chance = 1.0;
array<IAnomalyHook@> hooks;
array<AnomalyResult@> results;
double resultTotal = 0.0;
Targets targets;
bool checkTargets(Empire@ emp, const Targets@ check) const {
if(check is null) {
if(targets.targets.length == 0)
return true;
else
return false;
}
if(check.targets.length != targets.targets.length)
return false;
for(uint i = 0, cnt = check.targets.length; i < cnt; ++i) {
if(!isValidTarget(emp, i, check.targets[i]))
return false;
}
return true;
}
bool isValidTarget(Empire@ emp, uint index, const Target@ targ) const {
if(index >= targets.targets.length)
return false;
if(targ.type != targets.targets[index].type)
return false;
if(!targ.filled)
return false;
if(targets.targets[index].filled && targets.targets[index] != targ)
return false;
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
if(!hooks[i].isValidTarget(emp, index, targ))
return false;
}
return true;
}
#section server
void choose(Anomaly@ anomaly, Empire@ emp, Targets@ targets) const {
if(!checkTargets(emp, targets))
return;
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
hooks[i].choose(anomaly, emp, targets);
double roll = randomd(0.0, resultTotal);
for(uint i = 0, cnt = results.length; i < cnt; ++i) {
if(roll <= results[i].chance) {
results[i].choose(anomaly, emp, targets);
break;
}
roll -= results[i].chance;
}
}
bool giveOption(Anomaly@ anomaly, Empire@ emp) const {
for(uint i = 0, cnt = hooks.length; i < cnt; ++i) {
if(!hooks[i].giveOption(anomaly, emp))
return false;
}
return true;
}
#section all
};
tidy class AnomalyResult {
double chance = 1.0;
array<IAnomalyHook@> hooks;
void choose(Anomaly@ anomaly, Empire@ emp, Targets@ targets) const {
for(uint i = 0, cnt = hooks.length; i < cnt; ++i)
hooks[i].choose(anomaly, emp, targets);
}
};
interface IAnomalyHook {
void init(AnomalyType@ type);
void choose(Anomaly@ obj, Empire@ emp, Targets@ targets) const;
bool giveOption(Anomaly@ obj, Empire@ emp) const;
bool isValidTarget(Empire@ emp, uint index, const Target@ targ) const;
};
tidy class AnomalyHook : Hook, IAnomalyHook {
void init(AnomalyType@ type) {}
void choose(Anomaly@ obj, Empire@ emp, Targets@ targets) const {}
bool giveOption(Anomaly@ obj, Empire@ emp) const { return true; }
bool isValidTarget(Empire@ emp, uint index, const Target@ targ) const { return true; }
};
tidy class AnomalyType {
uint id = 0;
string ident, name = locale::ANOMALY, desc, narrative;
string modelName = "Debris", matName = "Asteroid";
double frequency = 1.0;
double scanTime = 60.0;
bool unique = false;
double stateTotal = 0.0;
array<AnomalyState@> states;
array<AnomalyOption@> options;
const AnomalyState@ getState() const {
if(states.length == 0)
return null;
double value = randomd(0.0, stateTotal);
for(uint i = 0, cnt = states.length; i < cnt; ++i) {
if(value <= states[i].frequency)
return states[i];
value -= states[i].frequency;
}
return states[states.length-1];
}
const AnomalyOption@ getOption(const string& ident) const {
for(uint i = 0, cnt = options.length; i < cnt; ++i) {
if(options[i].ident == ident)
return options[i];
}
return null;
}
const AnomalyState@ getState(const string& ident) const {
for(uint i = 0, cnt = states.length; i < cnt; ++i) {
if(states[i].ident == ident)
return states[i];
}
return null;
}
};
tidy class AnomalyState {
uint id = 0;
string ident;
string narrative;
string modelName = "Debris", matName = "Asteroid";
array<const AnomalyOption@> options;
array<double> option_chances;
array<string> def_options;
double frequency = 1.0;
};
void parseLine(string& line, AnomalyType@ anomaly, AnomalyResult@ result, ReadFile@ file) {
//Try to find the design
if(line.findFirst("(") == -1) {
error("Invalid line during " + anomaly.ident+": "+escape(line));
}
else {
if(anomaly.options.length == 0) {
error("Missing 'Pickup:' line for: "+escape(line));
return;
}
//Hook line
auto@ hook = cast<IAnomalyHook>(parseHook(line, "anomaly_effects::", instantiate=false, file=file));
if(hook !is null) {
if(result !is null)
result.hooks.insertLast(hook);
else
anomaly.options[anomaly.options.length-1].hooks.insertLast(hook);
}
}
}
void loadAnomaly(const string& filename) {
ReadFile file(filename, true);
string key, value;
AnomalyType@ anomaly;
AnomalyOption@ opt;
AnomalyState@ state;
AnomalyResult@ result;
uint index = 0;
while(file++) {
key = file.key;
value = file.value;
if(file.fullLine) {
if(anomaly is null) {
error("Missing 'Anomaly: ID' line in " + filename);
continue;
}
if(opt is null) {
error("Missing 'Option: ID' line in " + filename);
continue;
}
string line = file.line;
parseLine(line, anomaly, result, file);
}
else if(key == "Anomaly") {
if(anomaly !is null)
addAnomalyType(anomaly);
@state = null;
@opt = null;
@result = null;
@anomaly = AnomalyType();
anomaly.ident = value;
if(anomaly.ident.length == 0)
anomaly.ident = filename+"__"+index;
@opt = null;
++index;
}
else if(anomaly is null) {
error("Missing 'Anomaly: ID' line in " + filename);
}
else if(key == "Name") {
anomaly.name = localize(value);
}
else if(key == "Scan Time") {
anomaly.scanTime = toDouble(value);
}
else if(key == "Unique") {
anomaly.unique = toBool(value);
}
else if(key == "Model") {
if(state !is null)
state.modelName = value;
else
anomaly.modelName = value;
}
else if(key == "Material") {
if(state !is null)
state.matName = value;
else
anomaly.matName = value;
}
else if(key == "Description") {
if(anomaly.desc.length == 0)
anomaly.desc = localize(value);
else if(opt !is null)
opt.desc = localize(value);
else
error("Duplicate Description for " + anomaly.ident + ". Missing Option line?");
}
else if(key == "Blurb") {
if(opt !is null)
opt.blurb = localize(value);
}
else if(key == "Narrative") {
if(state !is null)
state.narrative = localize(value);
else
anomaly.narrative = localize(value);
}
else if(key == "Frequency") {
if(state !is null)
state.frequency = toDouble(value);
else
anomaly.frequency = toDouble(value);
}
else if(key == "Icon") {
if(opt !is null)
opt.icon = getSprite(value);
else
file.error("Icon outside option block.");
}
else if(key == "Option") {
@state = null;
@result = null;
@opt = AnomalyOption();
opt.id = anomaly.options.length;
opt.ident = value;
if(opt.ident.length == 0)
opt.ident = anomaly.ident+"__opt__"+anomaly.options.length;
anomaly.options.insertLast(opt);
}
else if(key == "Safe") {
if(opt !is null)
opt.isSafe = toBool(value);
}
else if(key == "State") {
@opt = null;
@result = null;
@state = AnomalyState();
state.id = anomaly.states.length;
state.ident = value;
state.modelName = anomaly.modelName;
state.matName = anomaly.matName;
if(state.ident.length == 0)
state.ident = anomaly.ident+"__state__"+anomaly.states.length;
anomaly.states.insertLast(state);
}
else if(key == "Result") {
if(opt is null) {
file.error("Result outside option block.");
continue;
}
@state = null;
@result = AnomalyResult();
if(value.length == 0)
result.chance = 1.0;
else
result.chance = toDouble(value);
opt.resultTotal += result.chance;
opt.results.insertLast(result);
}
else if(key == "Chance") {
if(opt is null) {
file.error("Chance outside option block.");
continue;
}
double chance = 0.0;
if(value.findFirst("%") == -1)
chance = toDouble(value);
else
chance = toDouble(value) / 100.0;
opt.chance = chance;
}
else if(key == "Choice") {
if(state is null) {
file.error("Choice outside state block.");
continue;
}
int pos = value.findFirst("=");
if(pos == -1) {
state.def_options.insertLast(value);
state.option_chances.insertLast(1.0);
}
else {
state.def_options.insertLast(value.substr(0, pos).trimmed());
state.option_chances.insertLast(toDouble(value.substr(pos+1)) / 100.0);
}
}
else if(file.key == "Target") {
if(opt is null) {
file.error("Target outside option block.");
continue;
}
parseTarget(opt.targets, file.value);
}
else {
string line = file.line;
parseLine(line, anomaly, result, file);
}
}
if(anomaly !is null)
addAnomalyType(anomaly);
}
void preInit() {
//Load anomaly types
FileList list("data/anomalies", "*.txt", true);
for(uint i = 0, cnt = list.length; i < cnt; ++i)
loadAnomaly(list.path[i]);
}
void init() {
auto@ list = anomalyTypes;
for(uint i = 0, cnt = list.length; i < cnt; ++i) {
auto@ type = list[i];
type.stateTotal = 0;
for(uint o = 0, ocnt = type.options.length; o < ocnt; ++o) {
auto@ opt = type.options[o];
for(uint n = 0, ncnt = opt.hooks.length; n < ncnt; ++n) {
if(!cast<Hook>(opt.hooks[n]).instantiate())
error("Could not instantiate hook: "+addrstr(opt.hooks[n])+" in "+type.ident);
cast<Hook>(opt.hooks[n]).initTargets(opt.targets);
opt.hooks[n].init(type);
}
for(uint n = 0, ncnt = opt.results.length; n < ncnt; ++n) {
auto@ res = opt.results[n];
for(uint j = 0, jcnt = res.hooks.length; j < jcnt; ++j) {
if(!cast<Hook>(res.hooks[j]).instantiate())
error("Could not instantiate hook: "+addrstr(res.hooks[j])+" in "+type.ident);
cast<Hook>(res.hooks[j]).initTargets(opt.targets);
res.hooks[j].init(type);
}
}
}
for(uint o = 0, ocnt = type.states.length; o < ocnt; ++o) {
auto@ state = type.states[o];
type.stateTotal += state.frequency;
for(uint n = 0, ncnt = state.def_options.length; n < ncnt; ++n) {
auto@ opt = type.getOption(state.def_options[n]);
if(opt !is null)
state.options.insertLast(opt);
else
error("Could not find option: "+state.def_options[n]);
}
}
}
}
AnomalyType@[] anomalyTypes;
dictionary idents;
double totalFrequency = 0;
const AnomalyType@ getAnomalyType(uint id) {
if(id >= anomalyTypes.length)
return null;
return anomalyTypes[id];
}
const AnomalyType@ getAnomalyType(const string& ident) {
AnomalyType@ anomaly;
if(idents.get(ident, @anomaly))
return anomaly;
return null;
}
uint getAnomalyTypeCount() {
return anomalyTypes.length;
}
const AnomalyType@ getDistributedAnomalyType() {
uint count = anomalyTypes.length;
double num = randomd(0, totalFrequency);
for(uint i = 0, cnt = anomalyTypes.length; i < cnt; ++i) {
AnomalyType@ type = anomalyTypes[i];
double freq = type.frequency;
if(num <= freq) {
if(type.unique) {
totalFrequency -= type.frequency;
type.frequency = 0;
}
return type;
}
num -= freq;
}
return anomalyTypes[anomalyTypes.length-1];
}
void addAnomalyType(AnomalyType@ type) {
type.id = anomalyTypes.length;
anomalyTypes.insertLast(type);
idents.set(type.ident, @type);
totalFrequency += type.frequency;
}
void saveIdentifiers(SaveFile& file) {
for(uint i = 0, cnt = anomalyTypes.length; i < cnt; ++i) {
auto type = anomalyTypes[i];
file.addIdentifier(SI_AnomalyType, type.id, type.ident);
}
}