Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user