Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
final class ConVar : ConsoleCommand {
string name;
double value;
ConVar(const string& Name, double initial = 0) {
name = Name;
value = initial;
addConsoleCommand(Name, this);
}
void execute(const string& args) {
if(args.length != 0)
value = toDouble(args);
print(name + " = " + value);
}
};
File diff suppressed because it is too large Load Diff
+383
View File
@@ -0,0 +1,383 @@
import designs;
import design_settings;
const int DESIGN_SERIALIZE_VERSION = 1;
JSONTree@ serialize_design(const Design@ dsg, const DesignClass@ cls = null) {
JSONTree tree;
JSONNode@ root = tree.root.makeObject();
root["__VERSION__"] = DESIGN_SERIALIZE_VERSION;
root["name"] = dsg.name;
root["size"] = int(dsg.size);
root["hull"] = dsg.hull.baseHull.ident;
root["type"] = getHullTypeTag(dsg.hull);
root["gridWidth"] = dsg.hull.active.width;
root["gridHeight"] = dsg.hull.active.height;
if(dsg.forceHull)
root["forceHull"].setBool(true);
if(cls !is null)
root["class"] = cls.name;
else if(dsg.cls !is null)
root["class"] = dsg.cls.name;
JSONNode@ sysList = root["subsystems"].makeArray();
auto@ settings = cast<const DesignSettings>(dsg.settings);
if(settings !is null) {
JSONNode@ node = root["settings"].makeObject();
settings.write(node);
}
uint sysCnt = dsg.subsystemCount;
for(uint i = 0; i < sysCnt; ++i) {
const Subsystem@ sys = dsg.subsystems[i];
if(sys.type.isHull)
continue;
if(sys.type.isApplied)
continue;
JSONNode@ node = sysList.pushBack().makeObject();
node["type"] = sys.type.id;
if(sys.direction != vec3d_front()) {
JSONNode@ dir = node["direction"].makeArray();
dir.pushBack() = sys.direction.x;
dir.pushBack() = sys.direction.y;
dir.pushBack() = sys.direction.z;
}
JSONNode@ hexes = node["hexes"].makeArray();
uint hexCnt = sys.hexCount;
for(uint j = 0; j < hexCnt; ++j) {
vec2u hex = sys.hexagon(j);
JSONNode@ coord = hexes.pushBack().makeArray();
coord.pushBack() = hex.x;
coord.pushBack() = hex.y;
if(sys.module(j) !is sys.type.defaultModule)
coord.pushBack() = sys.module(j).id;
}
}
@sysList = null;
for(uint i = 0; i < sysCnt; ++i) {
const Subsystem@ sys = dsg.subsystems[i];
if(!sys.type.isApplied)
continue;
if(sysList is null)
@sysList = root["appliedSubsystems"].makeArray();
sysList.pushBack() = sys.type.id;
}
return tree;
}
void write_design(const Design@ dsg, const string& filename, const DesignClass@ cls = null, bool pretty = true) {
serialize_design(dsg, cls).writeFile(filename, pretty);
}
bool unserialize_design(JSONTree@ tree, DesignDescriptor& desc) {
JSONNode@ root = tree.root;
if(!root.isObject() || !root["name"].isString() || !root["size"].isInt() || !root["hull"].isString())
return false;
desc.name = root["name"].getString();
desc.size = root["size"].getInt();
if(desc.size < 1)
return false;
desc.forceHull = false;
auto@ force = root.findMember("forceHull");
if(force !is null && force.isBool())
desc.forceHull = force.getBool();
JSONNode@ cls = root.findMember("class");
if(cls !is null && cls.isString())
desc.className = cls.getString();
desc.hullName = root["hull"].getString();
@desc.hull = getHullDefinition(desc.hullName);
JSONNode@ sysList = root["subsystems"];
if(!sysList.isArray())
return false;
uint sysCnt = sysList.size();
for(uint i = 0; i < sysCnt; ++i) {
JSONNode@ node = sysList[i];
if(!node["type"].isString())
return false;
const SubsystemDef@ def = getSubsystemDef(node["type"].getString());
if(def is null)
continue;
desc.addSystem(def);
JSONNode@ dir = node["direction"];
if(dir.isArray() && dir.size() == 3)
desc.setDirection(vec3d(dir[0].getNumber(), dir[1].getNumber(), dir[2].getNumber()));
JSONNode@ hexes = node["hexes"];
if(!hexes.isArray())
return false;
uint hexCnt = hexes.size();
for(uint j = 0; j < hexCnt; ++j) {
JSONNode@ coord = hexes[j];
if(!coord.isArray() || coord.size() < 2)
return false;
vec2u pos(coord[0].getUint(), coord[1].getUint());
if(coord.size() == 2) {
desc.addHex(pos);
}
else {
const ModuleDef@ mod = def.module(coord[2].getString());
if(mod !is null)
desc.addHex(pos, mod);
else
desc.addHex(pos);
}
}
}
@sysList = root["appliedSubsystems"];
if(sysList !is null && sysList.isArray()) {
uint sysCnt = sysList.size();
for(uint i = 0; i < sysCnt; ++i) {
JSONNode@ node = sysList[i];
if(node is null || !node.isString())
continue;
const SubsystemDef@ def = getSubsystemDef(node.getString());
if(def !is null)
desc.applySubsystem(def);
}
}
if(desc.hull is null) {
JSONNode@ type = root.findMember("type");
if(type !is null && type.isString())
@desc.hull = getBestHull(desc, type.getString());
if(desc.hull is null)
return false;
}
JSONNode@ stNode = root.findMember("settings");
if(stNode !is null && stNode.isObject()) {
DesignSettings settings;
settings.read(stNode);
@desc.settings = settings;
}
else {
@desc.settings = null;
}
JSONNode@ w = root.findMember("gridWidth");
JSONNode@ h = root.findMember("gridHeight");
if(w !is null && w.isUint() && h !is null && h.isUint())
desc.gridSize = vec2u(w.getUint(), h.getUint());
else
desc.gridSize = getDesignGridSize(desc.hull, desc.size);
return true;
}
int upload_design(const Design@ design, const string& description = "", bool waitId = false) {
#section client
WebData dat;
dat.addPost("name", design.name);
dat.addPost("size", toString(design.size, 0));
dat.addPost("author", settings::sNickname);
dat.addPost("description", description);
dat.addPost("color", toString(design.color));
dat.addPost("data", serialize_design(design).toString());
webAPICall("designs/submit", dat);
if(waitId) {
while(!dat.completed)
sleep(100);
return toInt(dat.result);
}
#section all
return -1;
}
bool read_design(const string& filename, DesignDescriptor& desc) {
JSONTree tree;
tree.readFile(filename);
return unserialize_design(tree, desc);
}
string uniqueDesignName(string name, Empire@ emp) {
int num = 1;
string oldName = name;
int pos = oldName.findLast(" Mk");
if(pos != -1)
oldName = oldName.substr(0, pos);
while(emp.getDesign(name) !is null) {
string newName = oldName;
newName += " Mk";
appendRoman(num, newName);
name = newName;
++num;
}
return name;
}
string getHullTypeTag(const Hull@ hull) {
if(hull is null)
return "";
if(hull.hasTag("Flagship"))
return "Flagship";
if(hull.hasTag("Support"))
return "Support";
if(hull.hasTag("Satellite"))
return "Satellite";
if(hull.hasTag("Station"))
return "Station";
return "";
}
const Hull@ getBestHull(DesignDescriptor& desc, const string& hullTag, Empire@ emp = playerEmpire) {
const Shipset@ shipset;
if(emp is null || emp.shipset is null)
@shipset = getShipset("Volkur");
else
@shipset = emp.shipset;
if(shipset is null)
return null;
const Hull@ bestHull;
double bestHullDist = INFINITY;
for(uint i = 0, cnt = shipset.hullCount; i < cnt; ++i) {
const Hull@ hull = shipset.hulls[i];
//Check if it matches the tag
if(!hull.hasTag(hullTag))
continue;
if(bestHull is null)
@bestHull = hull;
//Make sure we can use this hull
if(hull.minSize >= 0 && hull.minSize > desc.size)
continue;
if(hull.maxSize >= 0 && hull.maxSize < desc.size)
continue;
//Check distance
double d = hull.getMatchDistance(desc);
if(hull is desc.hull)
d -= 0.1;
if(d < bestHullDist) {
bestHullDist = d;
@bestHull = hull;
}
}
return bestHull;
}
void describeDesign(const Design@ orig, DesignDescriptor& desc) {
desc.name = orig.name;
desc.className = orig.cls.name;
desc.gridSize = vec2u(orig.hull.gridSize);
desc.size = orig.size;
@desc.hull = orig.hull;
@desc.owner = orig.owner;
uint sysCnt = orig.subsystemCount;
for(uint i = 0; i < sysCnt; ++i) {
const Subsystem@ sys = orig.subsystems[i];
if(sys.type.isHull)
continue;
if(sys.type.isApplied) {
desc.applySubsystem(sys.type);
continue;
}
desc.addSystem(sys.type);
desc.setDirection(sys.direction);
uint hexCnt = sys.hexCount;
for(uint j = 0; j < hexCnt; ++j) {
vec2u hex = sys.hexagon(j);
desc.addHex(hex, sys.module(j));
}
}
}
void resizeDesign(const Design@ orig, int newSize, DesignDescriptor& desc) {
describeDesign(orig, desc);
desc.size = newSize;
}
class DesignSet {
DesignDescriptor[] designs;
bool limitShipset = false;
bool softLimitRetry = false;
bool log = false;
void readDirectory(const string& directory) {
FileList list(directory, "*.design", true);
uint cnt = list.length;
designs.resize(cnt);
for(uint i = 0; i < cnt; ++i)
read_design(list.path[i], designs[i]);
}
void createFor(Empire@ emp, bool overrideLimit = false) const {
bool foundAny = false;
for(uint i = 0, cnt = designs.length; i < cnt; ++i) {
DesignDescriptor desc = designs[i];
@desc.owner = emp;
string hullName = format(desc.hullName, emp.shipset.ident);
@desc.hull = getHullDefinition(hullName);
if(desc.hull is null) {
hullName = format("$1FlagTiny", emp.shipset.ident);
@desc.hull = getHullDefinition(hullName);
if(desc.hull is null) {
if(!limitShipset || overrideLimit) {
hullName = format(desc.hullName, "Volkur");
@desc.hull = getHullDefinition(hullName);
if(desc.hull is null) {
if(desc.hull is null)
@desc.hull = getHullDefinition("VolkurFlagTiny");
}
}
if(desc.hull is null)
continue;
}
}
if(limitShipset && !overrideLimit) {
if(emp.shipset is null || !emp.shipset.hasHull(desc.hull))
continue;
}
else {
if(emp.shipset !is null && !emp.shipset.hasHull(desc.hull))
@desc.hull = getBestHull(desc, getHullTypeTag(desc.hull), emp);
}
if(desc.hull is null)
continue;
if(desc.className.length == 0)
desc.className = "Default";
const Design@ dsg = makeDesign(desc);
if(log && dsg !is null && dsg.hasFatalErrors()) {
print(emp.name+" Importing "+desc.name+":");
for(uint i = 0, cnt = dsg.errorCount; i < cnt; ++i)
print(" "+dsg.errors[i].text);
}
if(dsg is null || dsg.hasFatalErrors())
continue;
if(emp.getDesign(dsg.name) !is null)
continue;
if(desc.settings !is null)
dsg.setSettings(desc.settings);
const DesignClass@ cls = emp.getDesignClass(desc.className);
emp.addDesign(cls, dsg);
foundAny = true;
}
if(softLimitRetry && !foundAny && !overrideLimit)
createFor(emp, overrideLimit=true);
}
};
+300
View File
@@ -0,0 +1,300 @@
string formatEmpireName(Empire@ emp, Empire@ contactCheck = null) {
if(emp is null)
return "(n/a)";
if(emp is defaultEmpire)
return locale::EMPIRE_UNIVERSE;
if(contactCheck !is null) {
if(contactCheck.ContactMask & emp.mask == 0)
return "[color=#aaa]???[/color]";
}
return format("[color=$1]$2[/color]", toString(emp.color), bbescape(emp.name));
}
string formatObject(Object@ obj, bool showOwner = true, bool showIcon = false) {
if(obj is null)
return "(n/a)";
string text;
if(showIcon)
text += format("[obj_icon=$1/] ", toString(obj.id));
if(showOwner) {
if(obj.isRegion) {
Empire@ primary = obj.visiblePrimaryEmpire;
if(primary !is null)
text += format("[color=$1]$2[/color]", toString(primary.color), formatObjectName(obj));
}
else if(obj.owner !is null)
text += format("[color=$1]$2[/color]", toString(obj.owner.color), formatObjectName(obj));
}
else {
text += formatObjectName(obj);
}
return text;
}
string formatGameTime(double time, bool dispSeconds = true) {
int hours = time / 60.0 / 60.0;
int minutes = (time - (hours * 60.0 * 60.0)) / 60.0;
int seconds = (time - (hours * 60.0 * 60.0 + minutes * 60.0));
string text;
if(hours < 10) {
text += "0";
text += toString(hours);
}
else {
text += toString(hours);
}
text += ":";
if(minutes < 10) {
text += "0";
text += toString(minutes);
}
else {
text += toString(minutes);
}
if(dispSeconds) {
text += ":";
if(seconds < 10) {
text += "0";
text += toString(seconds);
}
else {
text += toString(seconds);
}
}
return text;
}
string formatTimeStamp(double time, bool dispSeconds = true) {
return format("[color=#888]$1[/color]", formatGameTime(time, dispSeconds));
}
string formatInfluenceCost(string option, int influence) {
return format(locale::OPTION_INFLUENCE_COST, option, toString(influence));
}
string formatMoney(int money, bool colored = false, bool roundUp = true) {
string text;
if(money < 0) {
if(colored)
text += "[color=#f00]";
text += "-";
}
text += "§";
int am = abs(money);
if(am == 0)
text += "0";
else if(am < 1000)
text += toString(am)+"k";
else if(am < 1000000)
text += standardize(double(am) / 1000.0, true, true)+"M";
else
text += standardize(double(am) / 1000000.0, true, true)+"B";
if(money < 0 && colored)
text += "[/color]";
return text;
}
string formatMoneyChange(int money, bool colored = false) {
string text;
if(money < 0) {
if(colored)
text += "[color=#f00]";
text += "-";
}
else {
if(colored)
text += "[color=#0f0]";
text += "+";
}
text += "§";
int am = abs(money);
if(am == 0)
text += "0";
else if(am < 1000)
text += toString(am)+"k";
else if(am < 1000000)
text += toString(double(am) / 1000.0, 2)+"M";
else
text += toString(double(am) / 1000000.0, 1)+"B";
if(colored)
text += "[/color]";
return text;
}
string formatMoney(int build, int maintain, bool hideZeroMaintenance = true) {
if(maintain == 0 && hideZeroMaintenance)
return formatMoney(build);
return formatMoney(build)+" / "+formatMoney(maintain);
}
string formatTimeRate(double time, double atRate, bool tenthPrecision = false) {
if(atRate == 0)
return locale::NEVER;
return formatTime(time / atRate, tenthPrecision);
}
string formatTime(double time, bool tenthPrecision = false) {
if(time == INFINITY) {
return locale::NEVER;
}
else if(time <= 0) {
return "";
}
else if(time > 60) {
int mins = floor(time / 60.0);
int secs = time % 60;
if(secs == 0)
return format(locale::TIME_M, toString(mins));
else
return format(locale::TIME_MS, toString(mins), toString(secs));
}
else if(tenthPrecision) {
return format(locale::TIME_S,
toString(time, time < 10 ? 1 : 0));
}
else {
return format(locale::TIME_S,
toString(time, 0));
}
}
string formatShortTime(double time) {
if(time == INFINITY || time <= 0)
return format(locale::TIME_MS_SHORT, "--", "--");
double mins = floor(time / 60.0);
double secs = time % 60;
if(secs < 10.0)
return format(locale::TIME_MS_SHORT, toString(mins, 0), "0"+toString(floor(secs), 0));
else
return format(locale::TIME_MS_SHORT, toString(mins, 0), toString(floor(secs), 0));
}
string formatEstTime(double time) {
if(time == INFINITY) {
return "-";
}
else if(time <= 0) {
return "";
}
else if(time > 60) {
return format(locale::TIME_M, toString(round(time / 60.0), 0));
}
else {
return format(locale::TIME_S, toString(time, 0));
}
}
string formatMinuteRate(double rate) {
rate *= 60.0;
return standardize(rate, true)+locale::PER_MINUTE;
}
string formatMinuteRate(double rate, const string& unit) {
rate *= 60.0;
return standardize(rate, true)+unit+locale::PER_MINUTE;
}
string formatIncomeRate(double rate, bool perMinute = false) {
string unit = locale::PER_SECOND;
if(perMinute) {
rate *= 60.0;
unit = locale::PER_MINUTE;
}
if(rate < 0)
return format("[color=#f88]$1$2[/color]", standardize(rate, true), unit);
else if(rate == 0)
return format("[color=#bbb]±0$1[/color]", unit);
else
return format("[color=#8f8]+$1$2[/color]", standardize(rate, true), unit);
}
string formatRate(double rate) {
if(rate < 0.2) {
rate *= 60.0;
return standardize(rate, true)+locale::PER_MINUTE;
}
else {
return standardize(rate, true)+locale::PER_SECOND;
}
}
string formatRate(double rate, const string& unit) {
if(rate < 0.2) {
rate *= 60.0;
return standardize(rate, true)+unit+locale::PER_MINUTE;
}
else {
return standardize(rate, true)+unit+locale::PER_SECOND;
}
}
string formatEffect(const string& effect, const string& magnitude) {
return format("$1\n[right][b]$2[/b][/right]", effect, magnitude);
}
string formatPosEffect(const string& effect, const string& magnitude) {
return format("$1\n[right][b][color=#0f0]$2[/color][/b][/right]", effect, magnitude);
}
string formatNegEffect(const string& effect, const string& magnitude) {
return format("$1\n[right][b][color=#f00]$2[/color][/b][/right]", effect, magnitude);
}
string formatMagEffect(const string& effect, double amt) {
string magnitude;
if(amt < 0.0)
magnitude = "[color=#f00]-"+standardize(amt, true)+"[/color]";
else
magnitude = "[color=#0f0]+"+standardize(amt, true)+"[/color]";
return formatEffect(effect, magnitude);
}
string formatPctEffect(const string& effect, float pct) {
string magnitude;
if(pct < 0.f)
magnitude = "[color=#f00]-"+toString(pct*-100.f, 0)+"%[/color]";
else
magnitude = "[color=#0f0]+"+toString(pct*100.f, 0)+"%[/color]";
return formatEffect(effect, magnitude);
}
string formatPctEffect(const string& effect, float pct, const string& mod) {
string magnitude;
if(pct < 0.f)
magnitude = mod+" [color=#f00]-"+toString(pct*-100.f, 0)+"%[/color]";
else
magnitude = mod+" [color=#0f0]+"+toString(pct*100.f, 0)+"%[/color]";
return formatEffect(effect, magnitude);
}
string formatObjectName(Object& obj) {
if(obj.isShip)
return formatShipName(cast<Ship>(obj));
else
return obj.name;
}
string formatShipName(Ship& ship) {
if(ship.named)
return format("$1 ($2)", ship.name, standardize(ship.blueprint.design.size, true));
return formatShipName(ship.blueprint.design);
}
string formatShipName(const Design@ dsg) {
if(dsg is null)
return "-";
string name = dsg.name;
if(dsg.next() !is null)
name += format(locale::REV_SPEC, toString(dsg.revision));
name = format("$1 ($2)", name, standardize(dsg.size, true));
return name;
}
+111
View File
@@ -0,0 +1,111 @@
class Poisson2D {
array<vec2d> points;
array<vec2d> grid;
array<vec2d> output;
vec2u gridSize;
double cell;
double circleRadius = INFINITY;
//TODO: Use a dequeue (list?)
array<vec2d> queue;
Poisson2D() {
}
Poisson2D(double width, double height, double distance, uint order = 30) {
generate(width, height, distance, order);
}
void generate(double width, double height, double distance, uint order = 30) {
cell = distance / sqrt(2.0);
gridSize = vec2u(ceil(width/cell), ceil(height)/cell);
grid.length = gridSize.x * gridSize.y;
for(uint i = 0, cnt = grid.length; i < cnt; ++i)
grid[i] = vec2d(INFINITY, INFINITY);
queue.reserve(order * gridSize.x);
//Generate a first point
vec2d start = vec2d(randomd(width*0.2, width*0.8), randomd(height*0.2, height*0.8));
queue.insertLast(start);
points.insertLast(start);
grid[gridIndex(start)] = start;
//Process the grid
while(queue.length != 0) {
uint index = randomi(0, queue.length-1);
vec2d point = queue[index];
queue.removeAt(index);
for(uint n = 0; n < order; ++n) {
vec2d other = point + random2d(distance, distance*2.0);
if(!validPosition(other))
continue;
if(circleRadius != INFINITY && other.distanceToSQ(vec2d(width/2, height/2)) > circleRadius * circleRadius)
continue;
if(!checkDistance(other, distance * distance))
continue;
queue.insertLast(other);
grid[gridIndex(other)] = other;
points.insertLast(other);
}
}
//Shuffle the points
for(int i = points.length - 1; i >= 0; --i) {
int swapIndex = randomi(0, i);
auto first = points[i];
auto second = points[swapIndex];
points[i] = second;
points[swapIndex] = first;
}
}
bool checkDistance(const vec2d& pos, double distSQ) {
vec2u coords = gridCoords(pos);
for(int x = -2; x <= 2; ++x) {
for(int y = -2; y <= 2; ++y) {
if(pointDistance(pos, vec2u(vec2i(coords) + vec2i(x, y))) < distSQ)
return false;
}
}
return true;
}
double pointDistance(const vec2d& pos, const vec2u& coords) {
if(coords.x >= gridSize.x || coords.y >= gridSize.y)
return INFINITY;
uint index = coords.x + coords.y * gridSize.x;
return grid[index].distanceToSQ(pos);
}
bool validPosition(const vec2d& pos) {
if(pos.x < 0 || pos.y < 0)
return false;
vec2u coords = gridCoords(pos);
return coords.x < gridSize.x && coords.y < gridSize.y;
}
vec2u gridCoords(const vec2d& pos) {
return vec2u(pos.x / cell, pos.y / cell);
}
uint gridIndex(const vec2d& pos) {
return int(pos.x / cell) + int(pos.y / cell) * gridSize.x;
}
uint gridIndex(const vec2u& coords) {
return coords.x + coords.y* gridSize.x;
}
uint get_length() {
return points.length;
}
vec2d opIndex(uint index) {
return points[index];
}
};
File diff suppressed because it is too large Load Diff