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
+103
View File
@@ -0,0 +1,103 @@
#priority init 2000
import biomes;
void loadBiomes(const string& filename) {
ReadFile file(filename);
string key, value;
Biome@ biome;
while(file++) {
key = file.key;
value = file.value;
if(key == "Biome") {
if(biome !is null)
addBiome(biome);
@biome = Biome();
biome.ident = value;
}
else if(biome is null) {
error("Missing 'Biome: ID' line in " + filename);
}
else if(key == "Name") {
biome.name = localize(value);
}
else if(key == "Description") {
biome.description = localize(value);
}
else if(key == "Color") {
biome.color = toColor(value);
}
else if(key == "Sprite") {
biome.tile = getSprite(value);
}
else if(key == "Frequency") {
biome.frequency = toUInt(value);
}
else if(key == "UseWeight") {
biome.useWeight = toFloat(value);
}
else if(key == "Humidity") {
biome.humidity = toFloat(value);
}
else if(key == "Temperature") {
biome.temp = toFloat(value);
}
else if(key == "IsCrystallic") {
biome.isCrystallic = toBool(value);
}
else if(key == "IsVoid") {
biome.isVoid = toBool(value);
}
else if(key == "IsWater") {
biome.isWater = toBool(value);
}
else if(key == "IsMoon") {
biome.isMoon = toBool(value);
}
else if(key == "Buildable") {
biome.buildable = toBool(value);
}
else if(key == "BuildCost") {
biome.buildCost = toFloat(value);
}
else if(key == "BuildTime") {
biome.buildTime = toFloat(value);
}
else if(key == "Picks") {
array<string>@ split = value.split(",");
if(split.length >= 1)
biome.picks.x = toFloat(split[0]);
if(split.length >= 2)
biome.picks.y = toFloat(split[1]);
if(split.length >= 3)
biome.picks.z = toFloat(split[2]);
if(split.length >= 4)
biome.picks.w = toFloat(split[3]);
}
else if(key == "Lookup Range") {
array<string>@ split = value.split(",");
if(split.length >= 1) {
biome.lookupRange.x = toFloat(split[0]);
biome.lookupRange.y = biome.lookupRange.x;
}
if(split.length >= 2)
biome.lookupRange.y = toFloat(split[1]);
}
else {
error("Unrecognized line in biome " + biome.ident + ": " + key + ": " + value);
}
}
if(biome !is null)
addBiome(biome);
}
void preInit() {
FileList list("data/biomes", "*.txt");
for(uint i = 0, cnt = list.length; i < cnt; ++i)
loadBiomes(list.path[i]);
}
+75
View File
@@ -0,0 +1,75 @@
#section client
int uploadStage = 0;
uint uploadPercent = 0;
string title, description, contentFolder, imagePath, changelog;
array<string> tags;
void tick(double t) {
switch(uploadStage) {
case 1:
if(cloud::isActive) {
print("Attempting to create or update cloud item");
cloud::prepItem(contentFolder);
uploadStage = 2;
}
break;
case 2:
if(cloud::itemReady) {
uploadStage = 3;
print("Updating cloud item " + cloud::itemID);
cloud::itemTitle = title;
cloud::itemDescription = description;
cloud::setItemContent(contentFolder);
cloud::setItemImage(imagePath);
cloud::setItemTags(tags);
cloud::setItemPublic();
cloud::commitItem(changelog);
uploadPercent = 0;
}
break;
case 3: {
uint pct = uint(100.0 * cloud::uploadProgress);
if(pct >= 100 && cloud::isUploading)
pct = 99;
if(pct > uploadPercent) {
print("Upload " + pct + "%");
uploadPercent = pct;
}
if(!cloud::isUploading) {
cloud::closeItem();
uploadStage = 0;
}
} break;
}
}
void uploadMod(const string& name, const string& changenote = "", const array<string>& modtags = array<string>()) {
auto@ mod = getMod(name);
if(mod is null || uploadStage != 0)
return;
title = mod.name;
description = mod.description;
if(isLinux)
contentFolder = mod.abspath+"/";
else
contentFolder = mod.abspath;
imagePath = mod.abspath + "/logo.png";
tags = modtags;
if(tags.length == 0)
tags.insertLast("Mod");
changelog = changenote;
uploadStage = 1;
}
#section menu
class UploadMod : ConsoleCommand {
void execute(const string& args) {
uploadMod(args);
}
};
void init() {
addConsoleCommand("upload_mod", UploadMod());
}
+262
View File
@@ -0,0 +1,262 @@
from orbitals import OrbitalModule, getOrbitalModule, OrbitalValues;
import buildings;
import ship_groups;
import resources;
import string getConstructionName(int id) from "constructions";
import Sprite getConstructionIcon(int id) from "constructions";
enum ConstructibleType {
CT_Invalid,
CT_Flagship,
CT_Orbital,
CT_Asteroid,
CT_Terraform,
CT_Retrofit,
CT_DryDock,
CT_Export,
CT_Station,
CT_Building,
CT_Construction
};
tidy class Constructible : Serializable {
ConstructibleType type;
double curLabor = 0;
double totalLabor = 1;
int id = -1;
int buildCost = 0;
int maintainCost = 0;
bool started = false;
float pct = 0.f;
float prog = 0.f;
bool isTimed = false;
Object@ obj;
const OrbitalModule@ orbital;
const Design@ dsg;
const ResourceType@ resource;
const BuildingType@ building;
int constructionId = -1;
array<GroupData> groups;
Constructible() {
type = CT_Invalid;
}
Constructible(const Design@ Design) {
type = CT_Flagship;
@dsg = Design;
}
string get_name() {
switch(type) {
case CT_Flagship:
case CT_Station:
return dsg !is null ? dsg.name : "Unknown";
case CT_Orbital:
return orbital.name;
case CT_Asteroid:
return format(locale::BUILD_ASTEROID, resource.name);
case CT_Building:
return building.name;
case CT_Terraform:
return format(locale::BUILD_TERRAFORM, obj.name, resource.name);
case CT_Retrofit:
return format(locale::BUILD_RETROFIT, obj.name);
case CT_DryDock:
if(id == -1)
return dsg is null ? "Unknown" : dsg.name;
else
return format(locale::BUILD_DRY_DOCK, dsg is null ? "Unknown" : dsg.name);
case CT_Export:
return format(locale::EXPORT_LABOR, obj.name);
case CT_Construction:
return getConstructionName(constructionId);
}
return "(null)";
}
Sprite get_icon() {
switch(type) {
case CT_Flagship:
case CT_Station:
return dsg !is null ? dsg.icon : Sprite();
case CT_Orbital:
return orbital.icon;
case CT_Asteroid:
return icons::Asteroid;
case CT_Building:
return building.sprite;
case CT_Terraform:
return resource.smallIcon;
case CT_Retrofit:
return Sprite();
case CT_DryDock:
return Sprite(spritesheet::GuiOrbitalIcons, 3);
case CT_Export:
return icons::Labor;
case CT_Construction:
return getConstructionIcon(constructionId);
}
return Sprite();
}
float get_percentage() {
return pct;
}
float get_progress() {
if(type == CT_Export)
return 0.f;
if(type == CT_DryDock)
return prog;
return curLabor / totalLabor;
}
double getETA(Object& obj) {
if(isTimed)
return totalLabor - curLabor;
if(type == CT_Export)
return INFINITY;
#section server-side
if(type == CT_DryDock)
return INFINITY;
#section client
if(type == CT_DryDock)
return cast<Orbital>(this.obj).getValue(OV_DRY_ETA);
#section all
double income = obj.laborIncome;
if(income == 0)
return INFINITY;
return (totalLabor - curLabor) / income;
}
void read(Message& msg) {
uint8 ctype = CT_Invalid;
msg >> ctype;
msg >> id;
msg >> started;
type = ConstructibleType(ctype);
msg >> curLabor;
msg >> totalLabor;
msg >> maintainCost;
msg >> buildCost;
@dsg = null;
@orbital = null;
@resource = null;
@building = null;
constructionId = -1;
isTimed = false;
switch(type) {
case CT_Station:
case CT_Flagship: {
msg >> dsg;
uint cnt = 0;
msg >> cnt;
groups.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> groups[i];
} break;
case CT_Orbital: {
uint id = 0;
msg >> id;
@orbital = getOrbitalModule(id);
groups.length = 0;
} break;
case CT_Asteroid: {
uint id = 0;
msg >> id;
@resource = getResource(id);
groups.length = 0;
} break;
case CT_Building: {
uint id = 0;
msg >> id;
msg >> isTimed;
@building = getBuildingType(id);
groups.length = 0;
} break;
case CT_Terraform: {
uint id = 0;
msg >> id;
@resource = getResource(id);
msg >> obj;
groups.length = 0;
} break;
case CT_Construction: {
msg >> constructionId;
msg >> isTimed;
groups.length = 0;
} break;
case CT_Retrofit: {
msg >> obj;
} break;
case CT_DryDock: {
msg >> obj;
Orbital@ orb = cast<Orbital>(obj);
#section server-side
@dsg = null;
prog = 0.f;
pct = 0.f;
#section client
@dsg = orb.getDesign(OV_DRY_Design);
prog = orb.getValue(OV_DRY_Progress);
pct = orb.getValue(OV_DRY_Financed);
#section all
} break;
case CT_Export: {
msg >> obj;
} break;
}
}
void write(Message& msg) {
uint8 tp = type;
msg << tp;
msg << id;
msg << started;
msg << curLabor;
msg << totalLabor;
msg << maintainCost;
msg << buildCost;
switch(type) {
case CT_Station:
case CT_Flagship:
msg << dsg;
msg << groups.length;
for(uint i = 0, cnt = groups.length; i < cnt; ++i)
msg << groups[i];
break;
case CT_Orbital:
msg << orbital.id;
break;
case CT_Asteroid:
msg << resource.id;
break;
case CT_Building:
msg << building.id;
break;
case CT_Terraform:
msg << resource.id;
msg << obj;
break;
case CT_Retrofit:
msg << obj;
break;
case CT_Export:
msg << obj;
break;
case CT_DryDock:
msg << obj;
break;
case CT_Construction:
msg << constructionId;
msg << isTimed;
break;
}
}
};
+99
View File
@@ -0,0 +1,99 @@
enum SupportBehavior {
SG_Brawler,
SG_Shield,
SG_Cavalry,
SG_Artillery,
SG_Cannon,
SG_Support,
SG_COUNT,
SG_Satellite,
};
const array<string> SUPPORT_BEHAVIOR_NAMES = {
"Brawler",
"Shield",
"Cavalry",
"Artillery",
"Cannon",
"Support",
};
const array<Sprite> SUPPORT_BEHAVIOR_ICONS = {
Sprite(spritesheet::AttributeIcons, 3),
Sprite(spritesheet::ResourceIcon, 5),
Sprite(material::StatusWar),
Sprite(spritesheet::CardCategoryIcons, 5),
Sprite(spritesheet::AttributeIcons, 4),
Sprite(material::SupplyIcon),
};
enum SupportRange {
SR_Auto,
SR_Far,
SR_Close,
SR_COUNT
};
const array<string> SUPPORT_RANGE_NAMES = {
"Auto", "Far", "Close",
};
final class DesignSettings : Serializable, Savable {
uint behavior = SG_Cannon;
uint range = SR_Auto;
DesignSettings() {
}
void write(Message& msg) {
msg.writeSmall(behavior);
msg.writeSmall(range);
}
void read(Message& msg) {
behavior = msg.readSmall();
range = msg.readSmall();
}
void save(SaveFile& file) {
file << behavior;
file << range;
}
void load(SaveFile& file) {
file >> behavior;
file >> range;
}
void write(JSONNode@ node) const {
node["behavior"] = SUPPORT_BEHAVIOR_NAMES[clamp(behavior, 0, SUPPORT_BEHAVIOR_NAMES.length-1)];
node["range"] = SUPPORT_RANGE_NAMES[clamp(range, 0, SUPPORT_RANGE_NAMES.length-1)];
}
void read(JSONNode@ node) {
auto@ field = node.findMember("behavior");
if(field !is null && field.isString()) {
string value = field.getString();
for(uint i = 0, cnt = SUPPORT_BEHAVIOR_NAMES.length; i < cnt; ++i) {
if(SUPPORT_BEHAVIOR_NAMES[i].equals_nocase(value)) {
behavior = i;
break;
}
}
if(value == "Bomber")
behavior = SG_Cavalry;
}
@field = node.findMember("range");
if(field !is null && field.isString()) {
string value = field.getString();
for(uint i = 0, cnt = SUPPORT_RANGE_NAMES.length; i < cnt; ++i) {
if(SUPPORT_RANGE_NAMES[i].equals_nocase(value)) {
range = i;
break;
}
}
}
}
};
+259
View File
@@ -0,0 +1,259 @@
bool checkCoreFacingBackwards(Design& design, Subsystem& sys) {
if(!design.hull.active.valid(sys.core, HEX_UpLeft) || design.hull.isExteriorInDirection(sys.core, HEX_UpLeft))
return false;
if(!design.hull.active.valid(sys.core, HEX_DownLeft) || design.hull.isExteriorInDirection(sys.core, HEX_DownLeft))
return false;
design.addError(true, format(locale::ERROR_FACE_BACKWARDS, sys.type.name), sys, null, sys.core);
return true;
}
vec2u getTargetGridSize(const Design@ dsg) {
return vec2u(max(1, uint(dsg.total(SV_GridWidth))), max(1, uint(dsg.total(SV_GridHeight))));
}
const vec2u FLAGSHIP_GRID_SIZE(28, 23);
const vec2u SUPPORT_GRID_SIZE(19, 16);
const vec2u SATELLITE_GRID_SIZE(21, 17);
vec2u getDesignGridSize(const Hull@ hull, double size) {
if(hull.hasTag("Support"))
return SUPPORT_GRID_SIZE;
if(hull.hasTag("Satellite"))
return SATELLITE_GRID_SIZE;
return FLAGSHIP_GRID_SIZE;
}
vec2u getDesignGridSize(const string& type, double size) {
if(type == "Support")
return SUPPORT_GRID_SIZE;
if(type == "Satellite")
return SATELLITE_GRID_SIZE;
return FLAGSHIP_GRID_SIZE;
}
bool checkGlobalDesign(Design& design, Subsystem& sys) {
//Check for the correct grid size
vec2u target = getTargetGridSize(design);
if(design.hull.gridSize.x > int(target.x) || design.hull.gridSize.y > int(target.y)) {
design.addError(true, locale::ERROR_GRID_SIZE, null, null, vec2u());
return true;
}
return false;
}
bool checkCoversAllDirections(Design& design, Subsystem& sys) {
auto gridSize = design.hull.gridSize;
//Top & Bottom Lines
for(uint i = 0, cnt = gridSize.x; i < cnt; ++i) {
if(checkCovers(sys.type, design, vec2u(i, 0), HEX_DownLeft))
return true;
if(checkCovers(sys.type, design, vec2u(i, 0), HEX_Down))
return true;
if(checkCovers(sys.type, design, vec2u(i, 0), HEX_DownRight))
return true;
if(checkCovers(sys.type, design, vec2u(i, gridSize.y-1), HEX_UpLeft))
return true;
if(checkCovers(sys.type, design, vec2u(i, gridSize.y-1), HEX_Up))
return true;
if(checkCovers(sys.type, design, vec2u(i, gridSize.y-1), HEX_UpRight))
return true;
}
//Right & Left Lines
for(uint i = 0, cnt = gridSize.y; i < cnt; ++i) {
if(checkCovers(sys.type, design, vec2u(0, i), HEX_DownRight))
return true;
if(checkCovers(sys.type, design, vec2u(0, i), HEX_UpRight))
return true;
if(checkCovers(sys.type, design, vec2u(gridSize.x-1, i), HEX_DownLeft))
return true;
if(checkCovers(sys.type, design, vec2u(gridSize.x-1, i), HEX_UpLeft))
return true;
}
return false;
}
bool checkCovers(const SubsystemDef@ def, Design& design, vec2u& pos, HexGridAdjacency direction) {
while(design.hull.active.valid(pos)) {
auto@ sys = design.subsystem(pos);
if(sys !is null) {
if(sys.type is def)
return false;
design.addError(true, format(locale::ERROR_MUST_COVER, def.name), null, null, vec2u());
return true;
}
if(!design.hull.active.advance(pos, direction))
break;
}
return false;
}
bool checkAdjacentToEverything(Design& design, Subsystem& checkSys) {
bool failed = false;
for(uint i = 0, cnt = design.subsystemCount; i < cnt; ++i) {
auto@ sys = design.subsystems[i];
if(sys is checkSys)
continue;
if(sys.type.hasTag(ST_Ephemeral))
continue;
for(uint n = 0, ncnt = sys.hexCount; n < ncnt; ++n) {
vec2u hex = sys.hexagon(n);
if(!design.hull.active.valid(hex))
continue;
bool found = false;
for(uint d = 0; d < 6; ++d) {
vec2u other = hex;
if(design.hull.active.advance(other, HexGridAdjacency(d))) {
auto@ otherSys = design.subsystem(other);
if(otherSys !is null && otherSys is checkSys) {
found = true;
break;
}
}
}
if(!found) {
if(!failed) {
design.addError(true, format(locale::ERROR_MUST_ADJACENT, checkSys.type.name), null, null, vec2u());
failed = true;
}
design.addErrorHex(hex);
}
}
}
return failed;
}
bool checkAdjacentToAllInterior(Design& design, Subsystem& checkSys) {
bool failed = false;
for(uint i = 0, cnt = design.subsystemCount; i < cnt; ++i) {
auto@ sys = design.subsystems[i];
if(sys is checkSys)
continue;
if(sys.type.hasTag(ST_Ephemeral))
continue;
if(sys.type.hasTag(ST_ExternalSpace))
continue;
for(uint n = 0, ncnt = sys.hexCount; n < ncnt; ++n) {
vec2u hex = sys.hexagon(n);
if(!design.hull.active.valid(hex))
continue;
bool found = false;
for(uint d = 0; d < 6; ++d) {
vec2u other = hex;
if(design.hull.active.advance(other, HexGridAdjacency(d))) {
auto@ otherSys = design.subsystem(other);
if(otherSys !is null && otherSys is checkSys) {
found = true;
break;
}
}
}
if(!found) {
if(!failed) {
design.addError(true, format(locale::ERROR_MUST_ADJACENT_INTERIOR, checkSys.type.name), null, null, vec2u());
failed = true;
}
design.addErrorHex(hex);
}
}
}
return failed;
}
bool checkContiguous(Design& design, Subsystem& sys) {
HexGridb checked(design.hull.active.width, design.hull.active.height);
checked.clear(false);
if(sys.hexCount > 0)
markContiguous(design, sys, checked, sys.hexagon(0));
for(uint i = 0, cnt = sys.hexCount; i < cnt; ++i) {
if(!checked[sys.hexagon(i)]) {
design.addError(true, format(locale::ERROR_CONTIGUOUS, sys.type.name), null, null, vec2u());
return true;
}
}
return false;
}
void markContiguous(Design& design, Subsystem& sys, HexGridb& grid, const vec2u& hex) {
if(grid[hex])
return;
grid[hex] = true;
for(uint d = 0; d < 6; ++d) {
vec2u other = hex;
if(design.hull.active.advance(other, HexGridAdjacency(d))) {
auto@ otherSys = design.subsystem(other);
if(otherSys is sys)
markContiguous(design, sys, grid, other);
}
}
}
bool checkSinew(Design& design, Subsystem& sys) {
bool errors = false;
if(checkAdjacentToAllInterior(design, sys))
errors = true;
if(checkContiguous(design, sys))
errors = true;
return errors;
}
bool checkExposedLeftRight(Design& design, Subsystem& sys) {
array<bool> hasExposed(6, false);
for(uint i = 0, cnt = sys.hexCount; i < cnt; ++i) {
vec2u hex = sys.hexagon(i);
for(uint n = 0; n < 6; ++n) {
if(design.hull.isExteriorInDirection(hex, HexGridAdjacency(n)))
hasExposed[n] = true;
}
}
if(!hasExposed[HEX_UpLeft] && !hasExposed[HEX_DownLeft]) {
design.addError(true, format(locale::ERROR_EXPOSE_LEFT_RIGHT, sys.type.name), sys, null, vec2u());
return true;
}
if(!hasExposed[HEX_UpRight] && !hasExposed[HEX_DownRight]) {
design.addError(true, format(locale::ERROR_EXPOSE_LEFT_RIGHT, sys.type.name), sys, null, vec2u());
return true;
}
return false;
}
#section server-side
void getDesignMesh(Empire@ owner, const Design& design, MeshDesc& mesh) {
const Shipset@ ss;
const ShipSkin@ skin;
if(owner !is null)
@ss = owner.shipset;
if(ss !is null) {
bool isCivilian = !design.hasTag(ST_Weapon) && !design.hasTag(ST_SupportCap);
if(isCivilian) {
if(design.hasSubsystem(subsystem::TractorBeam))
@skin = ss.getSkin("Tractor");
else if(design.hasSubsystem(subsystem::MiningLaser))
@skin = ss.getSkin("Miner");
}
if(design.hasTag(ST_Gate) && design.hasTag(ST_Station))
@skin = ss.getSkin("Gate");
}
if(skin !is null) {
@mesh.model = skin.model;
@mesh.material = skin.material;
}
else if(design.hasTag(ST_Gate) && design.hasTag(ST_Station)) {
@mesh.model = model::Warpgate;
@mesh.material = material::GenericPBR_Gate;
}
else {
@mesh.model = design.hull.model;
@mesh.material = design.hull.material;
}
@mesh.iconSheet = design.distantIcon.sheet;
mesh.iconIndex = design.distantIcon.index;
}
+310
View File
@@ -0,0 +1,310 @@
#section server
import void addDialogue(Dialogue@ diag) from "scenario";
#section all
class Dialogue : Serializable {
string title;
string text;
string proceedText;
Sprite icon;
array<Objective@> objectives;
//pass is called at the start of the page, even if skipping/loading through it
DialogueAction@ pass;
//complete is called when the page is first completed or skipped, not when loading.
DialogueAction@ complete;
//start is called when the page is trying to appear
DialogueAction@ start;
Dialogue() {
_add();
}
Dialogue(const string& title, const string& text, const Sprite& icon = Sprite()) {
this.title = title;
this.text = text;
this.icon = icon;
_add();
}
Dialogue(const string& ident) {
this.title = localize("#"+ident+"_TITLE");
this.text = localize("#"+ident+"_TEXT");
addObjectives(ident);
_add();
}
Dialogue& proceedWith(const string& text) {
proceedText = localize(text);
return this;
}
Dialogue& onPass(DialogueAction@ act) {
@pass = act;
return this;
}
Dialogue& onComplete(DialogueAction@ act) {
@complete = act;
return this;
}
Dialogue& onStart(DialogueAction@ act) {
@start = act;
return this;
}
Dialogue& get_newObjective() {
objectives.insertLast(Objective(empty=true));
return this;
}
Dialogue& addObjectives(const string& ident) {
for(uint i = 1; true; ++i) {
string title = localize("#"+ident+"_ACT"+i+"_TITLE");
if(title[0] == '#')
break;
objectives.insertLast(Objective(ident+"_ACT"+i));
}
return this;
}
Dialogue& checker(uint index, ObjectiveCheck@ check, bool skippable = false) {
if(index > objectives.length) {
error("Invalid objective "+index+" on "+title);
return this;
}
@objectives[index-1].check = check;
objectives[index-1].skippable = skippable;
return this;
}
Dialogue& objectiveKeybind(uint index, uint keybind) {
if(index > objectives.length) {
error("Invalid objective "+index+" on "+title);
return this;
}
auto@ obj = objectives[index-1];
int key = keybinds::Global.getCurrentKey(Keybind(keybind), 0);
string keyname = getKeyDisplayName(key);
obj.text = format(obj.text, keyname);
return this;
}
void _add() {
#section server
addDialogue(this);
#section all
}
void write(Message& msg) {
msg << title << text << proceedText;
msg << getSpriteDesc(icon);
msg.writeSmall(objectives.length);
for(uint i = 0, cnt = objectives.length; i < cnt; ++i)
msg << objectives[i];
}
void read(Message& msg) {
msg >> title >> text >> proceedText;
string desc;
msg >> desc;
icon = getSprite(desc);
uint cnt = msg.readSmall();
objectives.length = cnt;
for(uint i = 0, cnt = objectives.length; i < cnt; ++i) {
if(objectives[i] !is null) {
msg >> objectives[i];
}
else {
Objective obj;
msg >> obj;
@objectives[i] = obj;
}
}
}
};
class DialogueAction {
void call() {
}
bool check() {
return true;
}
};
class GUIAction : DialogueAction {
string id;
GUIAction(const string& id) {
this.id = id;
}
void call() {
#section server
guiDialogueAction(CURRENT_PLAYER, id);
#section all
}
};
void DialogueRemoteAction(string id) {
auto@ cls = getClass(id);
if(cls is null)
return;
Lock lck(actMtx);
actions.insertLast(cast<DialogueAction>(cls.create()));
}
Mutex actMtx;
array<DialogueAction@> actions;
array<GuiObjectiveCheck@> guiCheckers;
array<GUIChecker@> remoteCheckers;
void tick(double time) {
Lock lck(actMtx);
for(uint i = 0, cnt = actions.length; i < cnt; ++i)
actions[i].call();
actions.length = 0;
#section gui
for(uint i = 0, cnt = guiCheckers.length; i < cnt; ++i) {
if(guiCheckers[i].check()) {
srvObjectiveComplete(guiCheckers[i].id);
guiCheckers[i].end();
guiCheckers.removeAt(i);
--i; --cnt;
}
}
#section all
}
void RemoteObjectiveStart(string id, Object@ obj) {
auto@ cls = getClass(id);
if(cls is null)
return;
Lock lck(actMtx);
auto@ chk = cast<GuiObjectiveCheck>(cls.create());
chk.id = id;
@chk.obj = obj;
chk.start();
guiCheckers.insertLast(chk);
}
void RemoteObjectiveEnd(string id) {
Lock lck(actMtx);
for(uint i = 0, cnt = guiCheckers.length; i < cnt; ++i) {
if(guiCheckers[i].id == id) {
guiCheckers[i].end();
guiCheckers.removeAt(i);
--i; --cnt;
}
}
}
void RemoteObjectiveComplete(string id) {
Lock lck(actMtx);
for(uint i = 0, cnt = remoteCheckers.length; i < cnt; ++i) {
if(remoteCheckers[i].id == id)
remoteCheckers[i].completed = true;
}
}
int nextObjectiveId = 0;
class Objective : Serializable {
int id;
string title;
string text;
Sprite icon;
bool skippable = true;
ObjectiveCheck@ check;
Objective() {
id = -1;
}
Objective(bool empty) {
if(!empty)
error("Empty objectives must be empty");
id = nextObjectiveId++;
}
Objective(const string& ident) {
id = nextObjectiveId++;
this.title = localize("#"+ident+"_TITLE");
this.text = localize("#"+ident+"_TEXT");
}
void write(Message& msg) {
msg << id;
msg << title << text;
msg << getSpriteDesc(icon);
msg << skippable;
}
void read(Message& msg) {
msg >> id;
msg >> title >> text;
string desc;
msg >> desc;
icon = getSprite(desc);
msg >> skippable;
}
};
class ObjectiveCheck {
bool start() {
return true;
}
bool check() {
return false;
}
void end() {
}
};
class GuiObjectiveCheck : ObjectiveCheck {
string id;
Object@ obj;
};
class GUIChecker : ObjectiveCheck {
string id;
bool completed;
Object@ obj;
GUIChecker(const string& id, Object@ obj = null) {
this.id = id;
@this.obj = obj;
}
#section server
bool start() {
guiObjectiveStart(CURRENT_PLAYER, id, obj);
completed = false;
{
Lock lck(actMtx);
remoteCheckers.insertLast(this);
}
return true;
}
bool check() {
return completed;
}
void end() {
guiObjectiveEnd(CURRENT_PLAYER, id);
{
Lock lck(actMtx);
remoteCheckers.remove(this);
}
}
#section all
};
+50
View File
@@ -0,0 +1,50 @@
from designs import checkCoreFacingBackwards;
bool checkRamjet(Design& design, Subsystem& sys) {
if(checkCoreFacingBackwards(design, sys))
return true;
//Check all the scoops
auto@ scoop = sys.type.module("Scoop");
bool failed = false;
for(uint i = 0, cnt = sys.hexCount; i < cnt; ++i) {
vec2u hex = sys.hexagon(i);
if(sys.module(i) is scoop) {
if(!design.hull.active.valid(hex, HEX_UpRight) || design.hull.isExteriorInDirection(hex, HEX_UpRight))
continue;
if(!design.hull.active.valid(hex, HEX_DownRight) || design.hull.isExteriorInDirection(hex, HEX_DownRight))
continue;
design.addErrorHex(hex);
failed = true;
}
}
if(failed) {
design.addError(true, locale::ERROR_SCOOP_FACE_FRONT, null, null, vec2u());
return true;
}
return false;
}
bool checkSurroundedInSystem(Design& design, Subsystem& sys, const vec2u& hex) {
bool valid = true;
for(uint d = 0; d < 6; ++d) {
vec2u other = hex;
if(design.hull.active.advance(other, HexGridAdjacency(d))) {
auto@ otherSys = design.subsystem(other);
auto@ otherMod = design.module(other);
if(otherSys !is sys || (otherMod !is sys.type.defaultModule && otherMod !is sys.type.coreModule)) {
design.addErrorHex(other);
valid = false;
}
}
}
if(!valid) {
auto@ mod = design.module(hex.x, hex.y);
design.addErrorHex(hex);
design.addError(true, format(locale::ERROR_MUST_SURROUND, mod.name, sys.type.name), null, mod, hex);
return true;
}
return false;
}
+123
View File
@@ -0,0 +1,123 @@
import saving;
export getGlobal;
Mutex globMutex;
class HookGlobal {
uint id = 0;
string ident;
double value = 0.0;
bool delta = true;
void add(double amount) {
Lock lock(globMutex);
value += amount;
delta = true;
}
};
array<HookGlobal> globals;
dictionary idents;
HookGlobal@ getGlobal(uint id) {
if(id >= globals.length)
return null;
#section gui
globals[id].value = getGlobalValue(id);
#section all
return globals[id];
}
HookGlobal@ getGlobal(const string& ident) {
HookGlobal@ glob;
if(idents.get(ident, @glob)) {
#section gui
glob.value = getGlobalValue(glob.id);
#section all
return glob;
}
@glob = HookGlobal();
glob.id = globals.length;
glob.ident = ident;
globals.insertLast(glob);
idents.set(ident, @glob);
return glob;
}
#section server-side
double getGlobalValue_client(uint id) {
auto@ glob = getGlobal(id);
if(glob !is null)
return glob.value;
else
return 0.0;
}
#section shadow
void syncInitial(Message& msg) {
for(uint i = 0, cnt = globals.length; i < cnt; ++i)
msg >> globals[i].value;
}
void recvPeriodic(Message& msg) {
uint deltas = msg.readSmall();
for(uint i = 0; i < deltas; ++i) {
uint id = msg.readSmall();
msg >> globals[id].value;
}
}
#section server
void save(SaveFile& file) {
uint cnt = globals.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
file.writeIdentifier(SI_Global, globals[i].id);
file << globals[i].value;
}
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
uint id = file.readIdentifier(SI_Global);
if(id == uint(-1))
continue;
file >> globals[id].value;
}
}
void saveIdentifiers(SaveFile& file) {
for(uint i = 0, cnt = globals.length; i < cnt; ++i) {
auto type = globals[i];
file.addIdentifier(SI_Global, type.id, type.ident);
}
}
bool sendPeriodic(Message& msg) {
uint deltas = 0;
for(uint i = 0, cnt = globals.length; i < cnt; ++i) {
if(globals[i].delta)
deltas += 1;
}
if(deltas == 0)
return false;
Lock lock(globMutex);
msg.writeSmall(deltas);
for(uint i = 0, cnt = globals.length; i < cnt && deltas > 0; ++i) {
if(globals[i].delta) {
msg.writeSmall(i);
msg << globals[i].value;
globals[i].delta = false;
--deltas;
}
}
return true;
}
void syncInitial(Message& msg) {
for(uint i = 0, cnt = globals.length; i < cnt; ++i)
msg << globals[i].value;
}
+15
View File
@@ -0,0 +1,15 @@
#priority init 1501
import maps;
const int DEFAULT_SYSTEM_COUNT = 60;
const double DEFAULT_SPACING = 6500.0;
const double MIN_SPACING = 6500.0;
void init() {
auto@ mapClass = getClass("Map");
for(uint i = 0, cnt = THIS_MODULE.classCount; i < cnt; ++i) {
auto@ cls = THIS_MODULE.classes[i];
if(cls !is mapClass && cls.implements(mapClass))
cls.create();
}
}
@@ -0,0 +1,16 @@
//Amount of money generated by one money tile-resource
const double TILE_MONEY_RATE = 75.0;
//Amount of energy generated by one energy tile-resource per second
const double TILE_ENERGY_RATE = 0.5;
//Amount of research progress points generated by one
//research tile-resource per second
const double TILE_RESEARCH_RATE = 0.75;
//Amount of labor generation per second by one labor tile-resource
const double TILE_LABOR_RATE = 1.0 / 60.0;
const double LABOR_ACHIEVE_THRESH = 200.0 * TILE_LABOR_RATE;
//Labor's worth of support ships to build per minute with defense
const double DEFENSE_LABOR_PM = 2.0;
+96
View File
@@ -0,0 +1,96 @@
#priority init 1500
#section server
import settings.map_lib;
import map_generation;
#section shadow
import settings.map_lib;
class MapGeneration {
void initDefs() {}
void init() {}
void tick(double time) {}
};
#section client-side
import util.settings_page;
class MapGeneration : SettingsPage {}
#section all
bool mapsInitialized = false;
class Map : MapGeneration {
uint index;
string id;
string name;
string description;
string dlc;
AnyClass@ mapClass;
int sortIndex = 0;
bool isListed = true;
bool isScenario = false;
bool allowHomeworlds = true;
bool eatsPlayers = false;
bool isUnique = false;
string icon;
Color color;
Map() {
id = __module__;
@mapClass = getClass(this);
if(!mapsInitialized)
addMap(this);
}
Map@ create() {
Map@ other = cast<Map>(mapClass.create());
other.id = id;
other.index = index;
return other;
}
int opCmp(const Map@ other) const {
if(sortIndex < other.sortIndex)
return -1;
if(sortIndex > other.sortIndex)
return 1;
return 0;
}
};
Map@[] Maps;
void addMap(Map@ map) {
Maps.insertLast(map);
}
Map@ getMap(uint index) {
return Maps[index];
}
Map@ getMap(const string& id) {
uint cnt = Maps.length;
for(uint i = 0; i < cnt; ++i)
if(Maps[i].id == id)
return Maps[i];
return null;
}
uint get_mapCount() {
return Maps.length;
}
Map@ get_maps(uint num) {
return Maps[num];
}
void setupPhysics(double size, double fuzz, uint cells) {
@physicsWorld = PhysicsWorld(size, fuzz, cells);
@nodePhysicsWorld = PhysicsWorld(size, fuzz, cells);
}
void init() {
Maps.sortDesc();
for(uint i = 0, cnt = Maps.length; i < cnt; ++i)
Maps[i].index = i;
mapsInitialized = true;
}
+122
View File
@@ -0,0 +1,122 @@
bool canMove(Object& obj) {
if(!obj.hasMover || (obj.hasOrbit && obj.maxAcceleration == 0))
return false;
return obj.hasLeaderAI || obj.hasSupportAI;
}
bool canMoveIndependently(Object& obj, bool isFTL = false) {
if(!obj.hasMover || (!isFTL && obj.hasOrbit && obj.maxAcceleration == 0))
return false;
return obj.hasLeaderAI;
}
void orderMove(Object& obj, const vec3d& point, bool queued = false) {
if(!obj.hasMover)
return;
if(obj.hasLeaderAI)
obj.addMoveOrder(point, queued);
}
void orderMove(Object& obj, const vec3d& point, const quaterniond& facing, bool queued = false) {
if(!obj.hasMover)
return;
if(obj.hasLeaderAI)
obj.addMoveOrder(point, facing, queued);
}
array<vec3d>@ getFleetTargetPositions(array<Object@>& fleets, vec3d targetPos, quaterniond& facing = quaterniond(), bool calculateFacing = true, bool checkMovement = true, bool isFTL = false) {
//Remove things that can't move
if(checkMovement) {
for(int i = fleets.length - 1; i >= 0; --i) {
if(!canMoveIndependently(fleets[i], isFTL))
fleets.removeAt(i);
}
if(fleets.length == 0)
return array<vec3d>();
}
//Get facing from center of gravity.
if(calculateFacing) {
vec3d centerPos;
double totalRadius = 0.0;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
centerPos += fleets[i].position * fleets[i].radius;
totalRadius += fleets[i].radius;
}
centerPos /= totalRadius;
facing = quaterniond_fromVecToVec(vec3d_front(), targetPos - centerPos);
}
//Calculate positions
array<vec3d> positions(fleets.length);
int width = ceil(sqrt(double(fleets.length)));
if(width % 2 != 0)
width += 1;
int depth = ceil(double(fleets.length) / double(width));
vec3d xoff = facing * vec3d_right();
vec3d yoff = facing * vec3d_front();
int x = 0, y = 0;
bool right = true;
double xPos = 0.0;
double yPos = 0.0;
double startOff = 0.0;
positions[0] = targetPos;
if(fleets[0].hasLeaderAI && fleets[0].SupplyCapacity > 0)
startOff = fleets[0].getFormationRadius();
else
startOff = fleets[0].radius;
double maxRad = 0;
xPos = -startOff;
for(uint i = 0, cnt = fleets.length; i < cnt; ++i) {
double rad = 0.0;
if(fleets[i].hasLeaderAI && fleets[i].SupplyCapacity > 0)
rad = fleets[i].getFormationRadius();
else
rad = fleets[i].radius;
if(x >= width/2) {
if(right) {
x = 1;
xPos = -startOff;
right = false;
}
else {
x = 0;
y += 1;
xPos = -rad;
yPos -= y == 1 ? maxRad : (maxRad * 2.0);
maxRad = rad;
right = true;
startOff = rad;
}
}
if(rad > maxRad)
maxRad = rad;
if(right)
xPos += rad;
else
xPos -= rad;
double yEx = 0.0;
if(y > 0)
yEx += rad;
positions[i] = targetPos + (xoff * xPos) + (yoff * (yPos - yEx));
++x;
if(right)
xPos += rad;
else
xPos -= rad;
}
return positions;
}
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
enum OddityType {
Odd_Slipstream,
Odd_Wormhole,
Odd_Nebula,
};
#section server-side
StrategicIconNode@ makeOddityVisuals(Oddity& obj, uint type, bool fromCreation = true, bool isServer = true, uint color = 0xffffffff) {
StrategicIconNode@ icon;
if(type == Odd_Slipstream) {
auto@ gfx = PersistentGfx();
gfx.establish(obj, "Tear", 1.0/3.0);
gfx.rotate(quaterniond_fromAxisAngle(vec3d_up(), pi * 0.5) * obj.rotation);
@icon = StrategicIconNode();
icon.establish(obj, 0.0325, spritesheet::OrbitalIcons, 4);
icon.memorable = true;
icon.setColor(0xffafffff);
if(obj.region !is null)
obj.region.addStrategicIcon(-1, obj, icon);
#section server
if(fromCreation && (obj.region is null || obj.region.VisionMask & playerEmpire.mask != 0))
sound::open_slipstream.play(obj.position, priority=true);
addAmbientSource(CURRENT_PLAYER, "tear", obj.id, obj.position, obj.radius);
#section shadow
if(!inGalaxyCreation && (obj.region is null || obj.region.VisionMask & playerEmpire.mask != 0))
sound::open_slipstream.play(obj.position, priority=true);
addAmbientSource("tear", obj.id, obj.position, obj.radius);
#section server-side
}
else if(type == Odd_Wormhole) {
auto@ gfx = PersistentGfx();
gfx.establish(obj, "Wormhole", 1.0/3.0);
@icon = StrategicIconNode();
icon.establish(obj, 0.0325, spritesheet::OrbitalIcons, 4);
icon.memorable = true;
icon.setColor(0x66f4ffff);
if(obj.region !is null)
obj.region.addStrategicIcon(-1, obj, icon);
#section server
addAmbientSource(CURRENT_PLAYER, "tear", obj.id, obj.position, obj.radius);
#section shadow
addAmbientSource("tear", obj.id, obj.position, obj.radius);
#section server-side
}
else if(type == Odd_Nebula) {
auto@ node = GalaxyGas();
node.position = obj.position;
node.scale = obj.radius;
node.rebuildTransform();
Colorf fCol(Color(color));
float h = fCol.hue, s = fCol.saturation, v = fCol.value;
for(uint i = 0; i < 40; ++i) {
vec2d off = (random2d(0, 0.75) * obj.radius);
vec3d pos = obj.position;
pos.x += off.x;
pos.z += off.y;
double rad = obj.radius * 0.65;
Colorf hsv;
hsv.fromHSV(h + normald(-70.0,70.0), clamp(s * normald(0.6,1.4), 0.0, 1.0), v);
Color col(hsv);
col.a = randomi(0x18,0x22);
node.addSprite(pos, rad, col.rgba, true, baseAlpha=col.a);
}
}
return icon;
}
+340
View File
@@ -0,0 +1,340 @@
export PathNode;
import saving;
final class PathNode : Serializable, Savable {
Object@ pathEntry;
Object@ pathExit;
vec3d pathTo;
float dist = 0;
void write(Message& msg) {
msg.writeBit(pathEntry !is null);
if(pathEntry !is null) {
msg << pathEntry;
msg << pathExit;
}
else {
msg.writeMedVec3(pathTo);
msg << dist;
}
}
void read(Message& msg) {
if(msg.readBit()) {
msg >> pathEntry;
msg >> pathExit;
}
else {
@pathEntry = null;
@pathExit = null;
pathTo = msg.readMedVec3();
msg >> dist;
}
}
void save(SaveFile& file) {
file << pathEntry;
file << pathExit;
file << pathTo;
file << dist;
}
void load(SaveFile& file) {
file >> pathEntry;
file >> pathExit;
file >> pathTo;
if(file >= SV_0086)
file >> dist;
}
vec3d pathOut() {
if(pathExit !is null)
return pathExit.position;
return pathTo;
}
#section server-side
uint get_visionMask() const {
if(pathExit !is null)
return pathExit.visibleMask;
return 0;
}
#section all
bool valid(Object& obj) {
if(pathEntry !is null && !pathEntry.valid)
return false;
if(pathExit !is null && !pathExit.valid)
return false;
/*Orbital@ orb = cast<Orbital>(pathEntry);*/
/*if(orb !is null && orb.Disabled)*/
/* return false;*/
/*if(pathExit !is null) {*/
/* if(!pathExit.valid)*/
/* return false;*/
/* @orb = cast<Orbital>(pathExit);*/
/* if(orb !is null && orb.Disabled)*/
/* return false;*/
/*}*/
return true;
}
};
#section server-side
ReadWriteMutex mutex;
array<Oddity@> gates;
export addOddityGate;
void addOddityGate(Oddity& gate) {
WriteLock lock(mutex);
gates.insertLast(gate);
}
export removeOddityGate;
void removeOddityGate(Oddity& gate) {
WriteLock lock(mutex);
gates.remove(gate);
}
export pathOddityGates;
double pathOddityGates(Empire@ emp, array<PathNode@>@ path, const vec3d& from, const vec3d& to, double accel = 0.0) {
if(path !is null)
path.length = 0;
ReadLock lock(mutex);
double eta = 0.0;
doPathing(gates, emp, from, to, 0, path, eta, accel);
return eta;
}
double pathOddityGates(array<Oddity@>& gates, Empire@ emp, array<PathNode@>@ path, const vec3d& from, const vec3d& to, double accel = 0.0) {
if(path !is null)
path.length = 0;
double eta = 0.0;
doPathing(gates, emp, from, to, 0, path, eta, accel);
return eta;
}
export getOddityGates;
void getOddityGates(array<Oddity@>& output) {
ReadLock lock(mutex);
output = gates;
}
export grantOddityGateVision;
void grantOddityGateVision(Empire& emp) {
ReadLock lck(mutex);
for(uint i = 0, cnt = gates.length; i < cnt; ++i)
gates[i].donatedVision |= emp.mask;
}
double dumbETA(double dist, double accel) {
return sqrt(4.0 * (dist / accel));
}
export hasOddityLink;
bool hasOddityLink(Region@ fromRegion, Region@ toRegion, double minDuration = 0.0) {
if(fromRegion is null || toRegion is null)
return false;
ReadLock lck(mutex);
for(uint i = 0, cnt = gates.length; i < cnt; ++i) {
auto@ obj = gates[i];
if(obj.region !is fromRegion)
continue;
if(minDuration > 0 && obj.getTimer() < minDuration)
continue;
vec3d dest = obj.getGateDest();
if(dest.distanceTo(toRegion.position) < toRegion.radius)
return true;
}
return false;
}
bool hasOddityLink(array<Oddity@>& gates, Region@ fromRegion, Region@ toRegion, double minDuration = 0.0) {
if(fromRegion is null || toRegion is null)
return false;
ReadLock lck(mutex);
for(uint i = 0, cnt = gates.length; i < cnt; ++i) {
auto@ obj = gates[i];
if(obj.region !is fromRegion)
continue;
if(minDuration > 0 && obj.getTimer() < minDuration)
continue;
vec3d dest = obj.getGateDest();
if(dest.distanceTo(toRegion.position) < toRegion.radius)
return true;
}
return false;
}
bool hasOddityLink(Region@ fromRegion, const vec3d& nearPosition, double maxDistance, double minDuration = 0.0) {
if(fromRegion is null)
return false;
ReadLock lck(mutex);
for(uint i = 0, cnt = gates.length; i < cnt; ++i) {
auto@ obj = gates[i];
if(obj.region !is fromRegion)
continue;
if(minDuration > 0 && obj.getTimer() < minDuration)
continue;
vec3d dest = obj.getGateDest();
if(dest.distanceTo(nearPosition) < maxDistance)
return true;
}
return false;
}
export getPathDistance;
double getPathDistance_client(Player& pl, vec3d from, vec3d to) {
Empire@ emp = pl.emp;
if(emp is null || !emp.valid)
return from.distanceTo(to);
return getPathDistance(emp, from, to);
}
double getPathDistance(Empire@ emp, const vec3d& from, const vec3d& to) {
array<PathNode@> path;
pathOddityGates(emp, path, from, to, 1.0);
return getPathDistance(from, to, path);
}
double getPathDistance(const vec3d& startPos, const vec3d& endPos, array<PathNode@>@ path = null) {
double distance = 0.0;
vec3d pos = startPos;
if(path !is null) {
for(uint i = 0, cnt = path.length; i < cnt; ++i) {
auto@ node = path[i];
if(node.pathEntry !is null)
distance += node.pathEntry.position.distanceTo(pos);
else
distance += node.pathTo.distanceTo(pos);
if(node.pathExit !is null)
pos = node.pathExit.position;
else
pos = node.pathTo;
}
}
distance += pos.distanceTo(endPos);
return distance;
}
double getPathDistance(array<Oddity@>& gates, Empire@ emp, const vec3d& from, const vec3d& to) {
array<PathNode@> path;
pathOddityGates(gates, emp, path, from, to, 1.0);
return getPathDistance(from, to, path);
}
export getPathETA;
double getPathETA(const vec3d& startPos, const vec3d& endPos, double accel, array<PathNode@>@ path = null) {
double eta = 0.0;
vec3d pos = startPos;
if(path !is null) {
for(uint i = 0, cnt = path.length; i < cnt; ++i) {
auto@ node = path[i];
if(node.pathEntry !is null)
eta += dumbETA(node.pathEntry.position.distanceTo(pos), accel);
else
eta += dumbETA(node.pathTo.distanceTo(pos), accel);
if(node.pathExit !is null)
pos = node.pathExit.position;
else
pos = node.pathTo;
}
}
eta += dumbETA(pos.distanceTo(endPos), accel);
return eta;
}
const double SQRT_2 = sqrt(2.0);
uint doPathing(array<Oddity@>& gates, Empire@ emp, const vec3d& from, const vec3d& to, uint index, array<PathNode@>@ path, double& eta, double accel) {
//Check which gate jump makes this journey shorter by the most
Oddity@ shortest;
Object@ shortestGateIn; Object@ shortestGateOut;
double shortestDist = from.distanceTo(to);
if(emp.hasStargates()) {
Object@ entryGate = emp.getStargate(from);
Object@ exitGate = emp.getStargate(to);
if(entryGate !is null && exitGate !is null && entryGate !is exitGate) {
double gateDist = from.distanceTo(entryGate.position);
gateDist += exitGate.position.distanceTo(to);
gateDist *= SQRT_2;
if(gateDist < shortestDist) {
shortestDist = gateDist;
@shortestGateIn = entryGate;
@shortestGateOut = exitGate;
}
}
}
for(uint i = 0, cnt = gates.length; i < cnt; ++i) {
Oddity@ gate = gates[i];
if(emp !is null && !gate.isKnownTo(emp))
continue;
vec3d enter = gate.position;
vec3d exit = gate.getGateDest();
double enterDist = enter.distanceTo(from);
double exitDist = exit.distanceTo(to);
double dist = (enterDist + exitDist) * SQRT_2;
if(accel > 0) {
double timer = gate.getTimer();
if(timer >= 0) {
double curETA = eta + dumbETA(enterDist, accel);
if(curETA >= timer - 20.0)
continue;
}
}
if(dist < shortestDist) {
shortestDist = dist;
@shortest = gate;
}
}
if(shortest !is null) {
//Add to path
PathNode node;
@node.pathEntry = shortest;
@node.pathExit = shortest.getLink();
if(index > path.length)
path.length = index+1;
path.insertAt(index, node);
//Recurse into more paths
uint amount = 1;
amount += doPathing(gates, emp, from, shortest.position, index, path, eta, accel);
eta += dumbETA(path[index+amount-1].pathOut().distanceTo(shortest.position), accel);
amount += doPathing(gates, emp, shortest.getGateDest(), to, index+amount, path, eta, accel);
return amount;
}
else if(shortestGateIn !is null) {
//Add to path
PathNode node;
@node.pathEntry = shortestGateIn;
@node.pathExit = shortestGateOut;
if(index > path.length)
path.length = index+1;
path.insertAt(index, node);
//Recurse into more paths
uint amount = 1;
amount += doPathing(gates, emp, from, shortestGateIn.position, index, path, eta, accel);
eta += dumbETA(path[index+amount-1].pathOut().distanceTo(shortestGateIn.position), accel);
amount += doPathing(gates, emp, shortestGateOut.position, to, index+amount, path, eta, accel);
return amount;
}
return 0;
}
+46
View File
@@ -0,0 +1,46 @@
enum OrderType {
OT_Attack,
OT_Goto,
OT_Hyperdrive,
OT_Move,
OT_PickupOrder,
OT_Capture,
OT_Scan,
OT_Refresh,
OT_Fling,
OT_OddityGate,
OT_Slipstream,
OT_Ability,
OT_AutoExplore,
OT_Wait,
OT_Jumpdrive,
OT_INVALID
};
bool isFTLOrder(uint type) {
return type == OT_Hyperdrive || type == OT_Slipstream || type == OT_Fling || type == OT_Jumpdrive;
}
class OrderDesc : Serializable {
uint type;
bool hasMovement;
vec3d moveDestination;
void write(Message& msg) {
msg << uint(type);
if(hasMovement) {
msg.write1();
msg << moveDestination;
}
else {
msg.write0();
}
}
void read(Message& msg) {
msg >> type;
hasMovement = msg.readBit();
if(hasMovement)
msg >> moveDestination;
}
};
+286
View File
@@ -0,0 +1,286 @@
#priority init 2500
from resources import ResourceRequirements;
import saving;
class PlanetLevelChain {
uint id;
string ident;
array<PlanetLevel@> levels;
void inherit(const PlanetLevelChain& other) {
levels.length = other.levels.length;
for(uint i = 0, cnt = levels.length; i < cnt; ++i) {
if(levels[i] is null)
@levels[i] = PlanetLevel();
levels[i] = other.levels[i];
}
}
};
class PlanetLevel {
uint level = 0;
string name;
ResourceRequirements reqs;
uint population = 1;
double popGrowth = 0.3;
double requiredPop = 0.0;
int baseIncome = 100;
int resourceIncome = 0;
int baseLoyalty = 10;
int baseSupport = 0;
uint basePressure = 0;
uint exportPressurePenalty = 0;
double neighbourLoyalty = 0.0;
int points = 10;
Sprite icon;
};
array<PlanetLevelChain@> levelChains;
dictionary levelChainIdents;
PlanetLevelChain baseLevelChain;
int getLevelChainID(const string& ident) {
auto@ type = getLevelChain(ident);
if(type is null)
return -1;
return int(type.id);
}
string getLevelChainIdent(int id) {
auto@ type = getLevelChain(id);
if(type is null)
return "";
return type.ident;
}
const PlanetLevelChain@ getLevelChain(uint id) {
if(id >= levelChains.length)
return null;
return levelChains[id];
}
const PlanetLevelChain@ getLevelChain(const string& ident) {
PlanetLevelChain@ def;
if(levelChainIdents.get(ident, @def))
return def;
return null;
}
uint getLevelChainCount() {
return levelChains.length;
}
const PlanetLevel@ getPlanetLevel(const Object& planet) {
auto@ chain = getLevelChain(planet.levelChain);
if(chain is null)
return null;
uint level = planet.level;
if(level >= chain.levels.length)
return null;
return chain.levels[level];
}
const PlanetLevel@ getPlanetLevel(uint chainId, uint level) {
auto@ chain = getLevelChain(chainId);
if(chain is null)
return null;
if(level >= chain.levels.length)
return null;
return chain.levels[level];
}
const PlanetLevel@ getPlanetLevel(const Object& planet, uint level) {
auto@ chain = getLevelChain(planet.levelChain);
if(chain is null)
return null;
if(level >= chain.levels.length)
return null;
return chain.levels[level];
}
double getPlanetLevelRequiredPop(const Object& planet, uint level) {
auto@ lvl = getPlanetLevel(planet, level);
if(lvl is null)
return 0.0;
return lvl.requiredPop;
}
double getPlanetLevelRequiredPop(uint chainId, uint level) {
auto@ lvl = getPlanetLevel(chainId, level);
if(lvl is null)
return 0.0;
return lvl.requiredPop;
}
int getMaxPlanetLevel(uint chainId) {
auto@ chain = getLevelChain(chainId);
if(chain is null)
return 0;
return chain.levels.length-1;
}
int getMaxPlanetLevel(const Object& planet) {
auto@ chain = getLevelChain(planet.levelChain);
if(chain is null)
return 0;
return chain.levels.length-1;
}
bool readLevelChain(ReadFile& file) {
PlanetLevelChain chain;
chain.ident = file.value;
chain.id = levelChains.length;
levelChains.insertLast(chain);
levelChainIdents.set(chain.ident, @chain);
chain.inherit(baseLevelChain);
int indent = file.indent;
bool advance = true;
string key, value;
uint nextLevel = 0;
while(!advance || file++) {
key = file.key;
value = file.value;
if(file.indent <= indent) {
chain.levels.length = nextLevel;
return true;
}
if(key == "Level") {
PlanetLevel@ lvl;
if(nextLevel < chain.levels.length) {
@lvl = chain.levels[nextLevel];
}
else {
@lvl = PlanetLevel();
lvl.level = nextLevel;
chain.levels.insertLast(lvl);
}
nextLevel += 1;
advance = !readLevel(file, lvl);
}
else {
advance = true;
}
}
chain.levels.length = nextLevel;
return false;
}
bool readLevel(ReadFile& file, PlanetLevel& lvl) {
string key, value;
int indent = file.indent;
while(file++) {
key = file.key;
value = file.value;
if(file.indent <= indent)
return true;
if(key == "Required") {
if(lvl.reqs !is null)
lvl.reqs.parse(value);
}
else if(key == "Population") {
if(lvl !is null)
lvl.population = toUInt(value);
}
else if(key == "PopGrowth") {
if(lvl !is null)
lvl.popGrowth = toDouble(value);
}
else if(key == "RequiredPop") {
if(lvl !is null)
lvl.requiredPop = toDouble(value);
}
else if(key == "BaseIncome") {
if(lvl !is null)
lvl.baseIncome = toInt(value);
}
else if(key == "ResourceIncome") {
if(lvl !is null)
lvl.resourceIncome = toInt(value);
}
else if(key == "BasePressure") {
if(lvl !is null)
lvl.basePressure = toUInt(value);
}
else if(key == "BaseLoyalty") {
if(lvl !is null)
lvl.baseLoyalty = toInt(value);
}
else if(key == "NeighbourLoyalty") {
if(lvl !is null)
lvl.neighbourLoyalty = toDouble(value);
}
else if(key == "ExportPressurePenalty") {
if(lvl !is null)
lvl.exportPressurePenalty = toUInt(value);
}
else if(key == "Name") {
if(lvl !is null)
lvl.name = localize(value);
}
else if(key == "Icon") {
lvl.icon = getSprite(value);
}
else if(key == "Points") {
lvl.points = toInt(value);
}
else if(key == "BaseSupport") {
lvl.baseSupport = toInt(value);
}
else {
file.error("Unknown level property: "+file.line);
}
}
return false;
}
void preInit() {
baseLevelChain.id = 0;
baseLevelChain.ident = "base";
levelChains.insertLast(baseLevelChain);
levelChainIdents.set(baseLevelChain.ident, @baseLevelChain);
}
void init() {
ReadFile file(resolve("data/planet_levels.txt"));
bool advance = true;
string key, value;
while(!advance || file++) {
key = file.key;
value = file.value;
if(key == "Level") {
PlanetLevel@ lvl = PlanetLevel();
lvl.level = baseLevelChain.levels.length;
baseLevelChain.levels.insertLast(lvl);
advance = !readLevel(file, lvl);
}
else if(key == "Level Chain") {
advance = !readLevelChain(file);
}
else {
advance = true;
}
}
}
void saveIdentifiers(SaveFile& file) {
for(uint i = 0, cnt = levelChains.length; i < cnt; ++i) {
auto type = levelChains[i];
file.addIdentifier(SI_PlanetLevelChain, type.id, type.ident);
}
}
+17
View File
@@ -0,0 +1,17 @@
enum ContestedMode {
CM_None,
CM_Contested,
CM_GainingLoyalty,
CM_LosingLoyalty,
CM_Protected,
CM_Zealot,
};
const array<Color> ContestedColors = {
Color(),
Color(0xffc600ff),
Color(0x38ff00ff),
Color(0xff3800ff),
Color(0x00c0ffff),
Color(0xff00bfff)
};
+169
View File
@@ -0,0 +1,169 @@
#priority init 2000
from biomes import Biome, getBiome;
from saving import SaveIdentifier;
tidy final class PlanetType {
string ident;
int id;
//Model used to render it
const Model@ model = model::Planet_Sphere_max;
//Material used for empty planets
const Material@ emptyMat = material::ProceduralPlanet;
//Material used for colonized planets
const Material@ colonyMat = material::ProceduralPlanet;
//Material used for planets undergoing imminent death
const Material@ dyingMat = material::ProceduralPlanet;
//Has an atmosphere?
const Material@ atmosMat;
//Small icon sprite
Sprite icon;
//Distant icon sprite
Sprite distantIcon;
//Whether this can naturally occur
bool artificial = false;
map biomeWeights;
int getBiomeWeight(int biomeID) const {
int64 weight = 0;
biomeWeights.get(biomeID, weight);
return weight;
}
};
enum PlanetGfxAddon {
PGA_SpaceElevator = 0x1,
PGA_Ringworld = 0x2,
}
array<string> PlanetGfxNames = {"SpaceElevator", "Ringworld"};
array<PlanetType@> _planetTypes;
uint getPlanetTypeCount() {
return _planetTypes.length;
}
const PlanetType@ getPlanetType(int id) {
return _planetTypes[id];
}
const PlanetType@ getPlanetType(Planet& pl) {
return _planetTypes[pl.PlanetType];
}
const PlanetType@ getPlanetType(const string& ident) {
for(uint i = 0, cnt = _planetTypes.length; i < cnt; ++i)
if(_planetTypes[i].ident == ident)
return _planetTypes[i];
return null;
}
const PlanetType@ getBestPlanetType(const Biome@ biome1, const Biome@ biome2, const Biome@ biome3) {
array<const PlanetType@> choices;
int bestWeight = -10000;
int b1 = -1, b2 = -1, b3 = -1;
if(biome1 !is null)
b1 = biome1.id;
if(biome2 !is null)
b2 = biome2.id;
if(biome3 !is null)
b3 = biome3.id;
for(uint i = 0, cnt = _planetTypes.length; i < cnt; ++i) {
const PlanetType@ type = _planetTypes[i];
int totalWeight = type.getBiomeWeight(b1) + type.getBiomeWeight(b2) + type.getBiomeWeight(b3);
if(type.artificial && totalWeight <= 0)
continue;
if(totalWeight > bestWeight) {
choices.length = 1;
@choices[0] = type;
bestWeight = totalWeight;
}
else if(totalWeight == bestWeight) {
choices.insertLast(type);
}
}
if(choices.length == 1)
return choices[0];
else
return choices[randomi(0,choices.length-1)];
}
void loadPlanetType(ReadFile& file) {
PlanetType@ type;
int biomeIndent = -1;
string key, value;
while(file++) {
key = file.key;
value = file.value;
if(file.indent != biomeIndent)
biomeIndent = -1;
if(key == "PlanetType") {
@type = PlanetType();
type.id = _planetTypes.length;
type.ident = value;
_planetTypes.insertLast(type);
}
else if(type !is null) {
if(biomeIndent != -1) {
const Biome@ biome = getBiome(key);
if(biome !is null) {
int64 weight = toInt(value);
type.biomeWeights.set(biome.id, weight);
}
else {
error(format("'$1' is not a biome", key));
}
}
else {
if(key == "EmptyMat") {
@type.emptyMat = getMaterial(value);
}
else if(key == "ColonyMat") {
@type.colonyMat = getMaterial(value);
}
else if(key == "DyingMat") {
@type.dyingMat = getMaterial(value);
}
else if(key == "BiomeWeights") {
biomeIndent = file.indent + 1;
}
else if(key == "Atmosphere") {
@type.atmosMat = getMaterial(value);
}
else if(key == "Icon") {
type.icon = getSprite(value);
}
else if(key == "Artificial") {
type.artificial = toBool(value);
}
else if(key == "DistantIcon") {
type.distantIcon = getSprite(value);
}
}
}
else {
error("Missing 'PlanetType: Name' line");
}
}
}
void preInit() {
FileList list("data/planet_types", "*.txt");
for(uint i = 0, cnt = list.length; i < cnt; ++i)
loadPlanetType(ReadFile(list.path[i]));
}
void saveIdentifiers(SaveFile& file) {
for(uint i = 0, cnt = _planetTypes.length; i < cnt; ++i) {
PlanetType@ type = _planetTypes[i];
file.addIdentifier(SI_PlanetType, type.id, type.ident);
}
}
+426
View File
@@ -0,0 +1,426 @@
import biomes;
import buildings;
enum SurfaceFlags {
SuF_Usable = 1,
};
void preparePlanetShader(Object& obj) {
#section client
vec4f picks;
getBiomePicks(obj, getBiome(obj.Biome0), picks);
shader::BIOME_PICKS[0] = picks;
getBiomePicks(obj, getBiome(obj.Biome1), picks);
shader::BIOME_PICKS[1] = picks;
getBiomePicks(obj, getBiome(obj.Biome2), picks);
shader::BIOME_PICKS[2] = picks;
shader::PLANET_FULL_GRID_SIZE = vec2f(obj.surfaceGridSize);
shader::PLANET_SURFACE_GRID_SIZE = vec2f(obj.originalGridSize);
#section all
}
void getBiomePicks(Object& obj, const Biome@ biome, vec4f& picks) {
if(biome is null)
return;
picks = biome.picks;
picks.z += (biome.lookupRange.y - biome.lookupRange.x) * double((obj.id * 2654435761) % 127) / 127.0 + biome.lookupRange.x;
}
void renderSurfaceData(Object& obj, PlanetSurface& surface, Image& output, const vec2u& sizeLimit = vec2u(0,0), bool citiesMode = false) {
Image@ img = output;
vec2u size = surface.size;
vec2u origSize = vec2u(obj.originalGridSize);
if(sizeLimit.x != 0 && sizeLimit.y != 0)
size = sizeLimit;
if(img.size != size)
@img = Image(size, 4);
const Biome@ biome0;
const Biome@ biome1;
const Biome@ biome2;
if(surface.biomes.length == 1) {
@biome0 = getBiome(obj.Biome0);
}
else if(surface.biomes.length == 2) {
@biome0 = getBiome(obj.Biome0);
@biome1 = getBiome(obj.Biome1);
}
else if(surface.biomes.length >= 3) {
@biome0 = getBiome(obj.Biome0);
@biome1 = getBiome(obj.Biome1);
@biome2 = getBiome(obj.Biome2);
}
for(uint y = 0; y < size.height; ++y) {
for(uint x = 0; x < size.width; ++x) {
const Biome@ biome = surface.getBiome(x, y);
Color output(0);
if(x >= origSize.x || y >= origSize.y) {
if(biome.isMoon) {
output.r = 0xff;
}
else if(biome.isVoid) {
//output.a = 0; //implied
}
else {
output.g = 0xff;
}
}
else {
output.b = 0x80;
if(citiesMode) {
if(surface.getBuilding(x, y) !is null)
output.a = 0xff;
else
output.a = 0x0;
}
else {
output.a = 0xff;
}
if(biome.isCrystallic)
output.b = 0;
if(biome.isVoid) {
output.a = 0;
}
else if(biome.isWater) {
output.b = 0xff;
}
else if(biome is biome0) {
//output.r = 0; //implied
//output.g = 0; //implied
}
else if(biome is biome1) {
output.r = 0xff;
//output.g = 0; //implied
}
else if(biome is biome2) {
//output.r = 0; //implied
output.g = 0xff;
}
}
img.set(x,y, output);
}
}
if(img !is output)
output = img;
}
class PlanetSurface : Serializable {
vec2u size;
//Data grid
array<uint8> biomes;
array<uint8> flags;
array<SurfaceBuilding@> tileBuildings;
const Biome@ baseBiome;
//Resources and pressures
double[] resources = double[](TR_COUNT, 0);
float[] saturates = float[](TR_COUNT, 0);
float[] pressures = float[](TR_COUNT, 0.f);
double totalResource = 0;
float totalSaturate = 0;
double totalPressure = 0.0;
//Improving tiles to usable status
vec2u nextReady;
double readyTimer = -1.0;
int Maintenance = 0;
uint usableTiles = 0;
uint citiesBuilt = 0;
uint civsBuilt = 0;
uint pressureCap = 0;
//Civilian building construction
array<SurfaceBuilding@> buildings;
SurfaceBuilding@ civConstructing;
PlanetSurface() {
}
uint get_dataSize() {
return size.width * size.height;
}
bool isValidPosition(const vec2i& pos) const {
return uint(pos.x) < size.width && uint(pos.y) < size.height;
}
bool isValidPosition(const vec2u& pos) const {
return pos.x < size.width && pos.y < size.height;
}
void clearState() {
for(uint i = 0, cnt = flags.length; i < cnt; ++i)
flags[i] = 0;
for(uint i = 0, cnt = flags.length; i < cnt; ++i)
@tileBuildings[i] = null;
buildings.length = 0;
}
void write(Message& msg) {
write(msg, false);
}
void write(Message& msg, bool delta) {
msg.writeSmall(size.width);
msg.writeSmall(size.height);
msg << baseBiome.id;
msg.writeSmall(Maintenance);
msg.writeSmall(pressureCap);
msg.writeSmall(civsBuilt);
uint maxBiomeID = getBiomeCount() - 1;
uint dsize = biomes.length;
uint8 prevFlags = 0, prevBiome = baseBiome.id;
for(uint i = 0; i < dsize; ++i) {
uint8 biome = biomes[i];
if(biome != prevBiome) {
msg.write0();
msg.writeLimited(biome,maxBiomeID);
prevBiome = biome;
}
else {
msg.write1();
}
uint8 _flags = flags[i];
if(_flags != prevFlags) {
msg.write0();
msg << _flags;
prevFlags = _flags;
}
else {
msg.write1();
}
}
uint bcnt = buildings.length;
msg.writeSmall(bcnt);
int civIndex = -1;
for(uint i = 0; i < bcnt; ++i) {
SurfaceBuilding@ bldg = buildings[i];
if(bldg is civConstructing)
civIndex = int(i);
if(delta) {
msg.writeBit(bldg.delta);
if(!bldg.delta)
continue;
bldg.delta = false;
}
bldg.write(msg);
}
if(civIndex > 0) {
msg.write1();
msg.writeSmall(uint(civIndex));
}
else {
msg.write0();
}
for(uint i = 0; i < TR_COUNT; ++i) {
if(resources[i] != 0) {
msg.write1();
msg << float(resources[i]);
}
else {
msg.write0();
}
if(pressures[i] != 0) {
msg.write1();
msg << pressures[i];
msg << saturates[i];
}
else {
msg.write0();
}
}
}
void read(Message& msg) {
read(msg, false);
}
bool read(Message& msg, bool delta) {
bool surfaceDelta = false;
size.width = msg.readSmall();
size.height = msg.readSmall();
uint8 baseId = 0;
msg >> baseId;
@baseBiome = ::getBiome(baseId);
Maintenance = msg.readSmall();
pressureCap = msg.readSmall();
civsBuilt = msg.readSmall();
uint maxBiomeID = getBiomeCount() - 1;
uint dsize = dataSize;
if(biomes.length != dsize)
surfaceDelta = true;
biomes.length = dsize;
flags.length = dsize;
tileBuildings.length = dsize;
uint8 prevFlags = 0, prevBiome = baseId;
for(uint i = 0; i < dsize; ++i) {
if(!msg.readBit())
prevBiome = msg.readLimited(maxBiomeID);
if(!surfaceDelta && prevBiome != biomes[i])
surfaceDelta = true;
biomes[i] = prevBiome;
if(!msg.readBit())
msg >> prevFlags;
flags[i] = prevFlags;
@tileBuildings[i] = null;
}
uint bcnt = msg.readSmall();
buildings.length = bcnt;
for(uint i = 0; i < bcnt; ++i) {
if(buildings[i] is null)
@buildings[i] = SurfaceBuilding();
SurfaceBuilding@ bld = buildings[i];
if(delta && !msg.readBit())
continue;
bld.read(msg);
vec2u pos = bld.position;
vec2u center = bld.type.getCenter();
for(uint x = 0; x < bld.type.size.x; ++x) {
for(uint y = 0; y < bld.type.size.y; ++y) {
vec2u rpos = (pos - center) + vec2u(x, y);
uint index = rpos.y * size.width + rpos.x;
@tileBuildings[index] = bld;
}
}
}
if(msg.readBit()) {
uint civIndex = msg.readSmall();
if(civIndex < buildings.length)
@civConstructing = buildings[civIndex];
}
else {
@civConstructing = null;
}
totalPressure = 0;
totalSaturate = 0;
totalResource = 0;
for(uint i = 0; i < TR_COUNT; ++i) {
if(msg.readBit()) {
float resource = 0;
msg >> resource;
resources[i] = resource;
totalResource += resource;
}
else {
resources[i] = 0;
}
if(msg.readBit()) {
msg >> pressures[i];
totalPressure += pressures[i];
msg >> saturates[i];
totalSaturate += saturates[i];
}
else {
pressures[i] = 0;
}
}
return surfaceDelta;
}
uint getIndex(int x, int y) {
return y * size.width + x;
}
const Biome@ getBiome(int x, int y) {
uint index = y * size.width + x;
if(index >= biomes.length)
return null;
return ::getBiome(biomes[index]);
}
uint8 getFlags(int x, int y) {
uint index = y * size.width + x;
if(index >= flags.length)
return 0;
return flags[index];
}
bool checkFlags(int x, int y, uint8 f) {
uint index = y * size.width + x;
if(index >= flags.length)
return false;
return (flags[index] & f) == f;
}
void setFlags(int x, int y, uint8 f) {
uint index = y * size.width + x;
if(index >= flags.length)
return;
flags[index] = f;
}
void addFlags(int x, int y, uint8 f) {
uint index = y * size.width + x;
if(index >= flags.length)
return;
flags[index] |= f;
}
void removeFlags(int x, int y, uint8 f) {
uint index = y * size.width + x;
if(index >= flags.length)
return;
flags[index] &= ~f;
}
SurfaceBuilding@ getBuilding(int x, int y) {
uint index = y * size.width + x;
if(index >= tileBuildings.length)
return null;
return tileBuildings[index];
}
float getBuildingBuildWeight(int x, int y) {
uint index = y * size.width + x;
if(index >= tileBuildings.length)
return 0;
SurfaceBuilding@ bld = tileBuildings[index];
if(bld is null)
return 0;
return bld.type.hubWeight;
}
void setBuilding(int x, int y, SurfaceBuilding@ bld) {
uint index = y * size.width + x;
if(index >= tileBuildings.length)
return;
@tileBuildings[index] = bld;
}
};
+458
View File
@@ -0,0 +1,458 @@
import traits;
import saving;
enum EmpireType {
ET_Player,
ET_BumAI,
ET_WeaselAI,
ET_NoAI,
};
enum AIFlags {
AIF_Aggressive = 0x1,
AIF_Passive = 0x2,
AIF_Biased = 0x4,
AIF_CheatPrivileged = 0x1000,
};
const string DEFAULT_SHIPSET = "Volkur";
const int STARTING_TRAIT_POINTS = 1;
class EmpireSettings : Serializable {
uint index = 0;
uint type;
string name;
string raceName;
string shipset;
string effectorSkin;
bool ready = false;
int handicap = 0;
int playerId = -1;
string portrait;
string flag;
Color color;
array<const Trait@> traits;
int delta = 0;
int difficulty = 1;
int team = -1;
int aiFlags = 0;
int cheatWealth = 0;
int cheatStrength = 0;
int cheatAbundance = 0;
EmpireSettings() {
type = ET_WeaselAI;
name = "Unknown Empire";
for(uint i = 0, cnt = getTraitCount(); i < cnt; ++i) {
auto@ trait = getTrait(i);
if(trait.defaultTrait)
traits.insertLast(trait);
}
}
bool hasTrait(const Trait@ trait) {
return traits.find(trait) != -1;
}
void addTrait(const Trait@ trait) {
if(trait is null)
return;
if(traits.find(trait) == -1)
traits.insertLast(trait);
}
void removeTrait(const Trait@ trait) {
traits.remove(trait);
}
void chooseTrait(const Trait@ trait) {
if(trait is null)
return;
for(int i = traits.length - 1; i >= 0; --i) {
if(traits[i].unique == trait.unique)
traits.removeAt(i);
}
traits.insertLast(trait);
}
void resetTraits() {
traits.length = 0;
for(uint i = 0, cnt = getTraitCount(); i < cnt; ++i) {
auto@ trait = getTrait(i);
if(trait.defaultTrait)
traits.insertLast(trait);
}
}
void read(Message& msg) {
msg >> index;
msg >> name;
msg >> raceName;
msg >> shipset;
msg >> color;
msg >> portrait;
msg >> flag;
msg >> type;
msg >> handicap;
msg >> playerId;
msg >> ready;
msg >> delta;
msg >> difficulty;
msg >> effectorSkin;
msg >> team;
msg >> aiFlags;
msg >> cheatWealth;
msg >> cheatStrength;
msg >> cheatAbundance;
uint cnt = 0;
msg >> cnt;
traits.length = 0;
traits.reserve(cnt);
for(uint i = 0; i < cnt; ++i) {
auto@ trait = getTrait(msg.readSmall());
if(trait !is null)
traits.insertLast(trait);
}
}
void write(Message& msg) {
msg << index;
msg << name;
msg << raceName;
msg << shipset;
msg << color;
msg << portrait;
msg << flag;
msg << type;
msg << handicap;
msg << playerId;
msg << ready;
msg << delta;
msg << difficulty;
msg << effectorSkin;
msg << team;
msg << aiFlags;
msg << cheatWealth;
msg << cheatStrength;
msg << cheatAbundance;
msg << traits.length;
for(uint i = 0, cnt = traits.length; i < cnt; ++i)
msg.writeSmall(traits[i].id);
}
int getTraitPoints() {
int points = STARTING_TRAIT_POINTS;
for(uint i = 0, cnt = traits.length; i < cnt; ++i) {
points += traits[i].gives;
points -= traits[i].cost;
}
return points;
}
bool hasTraitConflicts() {
for(uint i = 0, cnt = traits.length; i < cnt; ++i) {
if(traits[i].hasConflicts(traits))
return true;
}
return false;
}
void copyRaceFrom(const EmpireSettings& other) {
raceName = other.raceName;
shipset = other.shipset;
portrait = other.portrait;
traits = other.traits;
effectorSkin = other.effectorSkin;
}
};
class SettingsContainer : Savable {
double[] settings;
double get_opIndex(uint index) {
//Dynamically allocate for map settings, since
//we don't know how many there will be here
if(index >= settings.length) {
uint oldcnt = settings.length;
settings.length = index+1;
for(uint i = oldcnt; i <= index; ++i)
settings[i] = INFINITY;
}
return settings[index];
}
void set_opIndex(uint index, double val) {
//Dynamically allocate for map settings, since
//we don't know how many there will be here
if(index >= settings.length) {
uint oldcnt = settings.length;
settings.length = index+1;
for(uint i = oldcnt; i <= index; ++i)
settings[i] = INFINITY;
}
settings[index] = val;
}
double getSetting(uint index, double def = 0.0) {
if(index >= settings.length)
return def;
double val = settings[index];
if(val == INFINITY)
return def;
return val;
}
void setNamed(const string& name, double value) {
}
double getNamed(const string& name, double defaultValue = INFINITY) {
return defaultValue;
}
void clearNamed(const string& name) {
}
void save(SaveFile& file) {
uint cnt = settings.length;
file << cnt;
for(uint i = 0; i < cnt; ++i)
file << settings[i];
}
void load(SaveFile& file) {
uint cnt = 0;
file >> cnt;
settings.length = cnt;
for(uint i = 0; i < cnt; ++i)
file >> settings[i];
}
}
class MapSettings : SettingsContainer, Serializable {
string map_id;
GameSettings@ parent;
uint galaxyCount = 1;
bool allowHomeworlds = true;
void read(Message& msg) {
//Read global settings
msg >> map_id;
msg >> galaxyCount;
msg >> allowHomeworlds;
//Read global settings
uint setcnt = 0;
msg >> setcnt;
settings.length = setcnt;
for(uint i = 0; i < setcnt; ++i)
msg >> settings[i];
}
uint get_systemCount() {
if(settings.length == 0)
return 0;
return getSetting(0, 0.0);
}
void write(Message& msg) {
//Write global settings
msg << map_id;
msg << galaxyCount;
msg << allowHomeworlds;
//Write global settings
uint setcnt = settings.length;
msg << setcnt;
for(uint i = 0; i < setcnt; ++i)
msg << settings[i];
}
void setNamed(const string& name, double value) {
parent.setNamed(name, value);
}
};
class GameSettings : SettingsContainer, Serializable {
string map_id;
EmpireSettings[] empires;
MapSettings[] galaxies;
dictionary namedSettings;
GameSettings() {
}
void defaults() {
empires.length = 3;
empires[0].type = ET_Player;
empires[0].name = "Empire 1";
empires[0].raceName = "The First";
//empires[0].chooseTrait(getTrait("Flux"));
//empires[0].chooseTrait(getTrait("Empire"));
empires[1].name = "Empire 2";
empires[1].effectorSkin = "Skin1";
empires[1].shipset = "Gevron";
empires[1].raceName = "Terrakin";
empires[1].difficulty = 2;
empires[2].name = "Empire 3";
empires[2].effectorSkin = "Skin2";
empires[2].raceName = "Terrakin";
empires[2].difficulty = 2;
//empires[0].chooseTrait(getTrait("Ancient"));
//empires[0].chooseTrait(getTrait("Sublight"));
//empires[0].chooseTrait(getTrait("Extragalactic"));
//empires[0].chooseTrait(getTrait("Gate"));
//empires[0].chooseTrait(getTrait("Jumpdrive"));
//empires[0].chooseTrait(getTrait("Sublight"));
//empires[1].chooseTrait(getTrait("Extragalactic"));
//empires[2].chooseTrait(getTrait("Extragalactic"));
/*empires[1].aiFlags = AIF_CheatPrivileged;*/
/*empires[2].aiFlags = AIF_CheatPrivileged;*/
galaxies.length = 1;
//galaxies[0].map_id = "Invasion.InvasionMap";
galaxies[0].map_id = "Clusters.ClustersMap";
//galaxies[0].map_id = "Expanse.ExpanseMap";
//galaxies[0].map_id = "Rings.RingsMap";
galaxies[0].galaxyCount = 1;
//galaxies[0][0] = 40;
settings.length = 0;
namedSettings.deleteAll();
}
void setNamed(const string& name, double value) {
namedSettings.set(name, value);
}
double getNamed(const string& name, double defaultValue = INFINITY) {
double value = defaultValue;
if(!namedSettings.get(name, value))
value = defaultValue;
return value;
}
void clearNamed(const string& name) {
namedSettings.delete(name);
}
void read(Message& msg) {
if(msg.empty) {
defaults();
return;
}
//Read named settings
uint cnt = msg.readSmall();
namedSettings.deleteAll();
string name; float value = 0.f;
for(uint i = 0; i < cnt; ++i) {
msg >> name;
msg >> value;
namedSettings.set(name, double(value));
}
//Read empire settings
uint empcnt = 0;
msg >> empcnt;
empires.length = empcnt;
for(uint i = 0; i < empcnt; ++i)
empires[i].read(msg);
//Read galaxy settings
uint galaxycnt = 0;
msg >> galaxycnt;
galaxies.length = galaxycnt;
for(uint i = 0; i < galaxycnt; ++i)
galaxies[i].read(msg);
//Read global settings
uint setcnt = 0;
msg >> setcnt;
settings.length = setcnt;
for(uint i = 0; i < setcnt; ++i)
msg >> settings[i];
}
void write(Message& msg) {
//Write named settings
auto it = namedSettings.iterator();
string name; double value = 0.0;
msg.writeSmall(namedSettings.getSize());
while(it.iterate(name, value)) {
msg << name;
msg << float(value);
}
//Read empire settings
uint empcnt = empires.length;
msg << empcnt;
for(uint i = 0; i < empcnt; ++i)
empires[i].write(msg);
//Read galaxy settings
uint galaxycnt = galaxies.length;
msg << galaxycnt;
for(uint i = 0; i < galaxycnt; ++i)
galaxies[i].write(msg);
//Write global settings
uint setcnt = settings.length;
msg << setcnt;
for(uint i = 0; i < setcnt; ++i)
msg << settings[i];
}
};
GameSettings settings;
GameSettings@ get_gameSettings() {
return settings;
}
double getGameSetting(uint index) {
return settings.getSetting(index, 0.0);
}
double getGameSetting(uint index, double def) {
return settings.getSetting(index, def);
}
double modSpacing(double spacing) {
return (spacing - 2000.0) * config::SYSTEM_SIZE * config::PLANET_FREQUENCY + 2000.0;
}
void onGameSettings(Message& msg) {
initTraits();
settings.read(msg);
auto it = settings.namedSettings.iterator();
string name; double value = 0.0;
while(it.iterate(name, value))
config::set(name, value);
config::GFX_DISTANCE_MOD = 6500.0 / modSpacing(6500.0);
if(!hasDLC("Heralds"))
config::EXPERIENCE_GAIN_FACTOR = 0.0;
}
void save(SaveFile& file) {
file << settings;
}
void load(SaveFile& file) {
if(file >= SV_0048)
file >> settings;
}
+201
View File
@@ -0,0 +1,201 @@
from saving import SaveVersion;
from hooks import parseHook, Hook;
//System data is an intermediate description during map generation
final class SystemData {
uint index;
vec3d position;
uint sysIndex = 0;
int systemType = -1;
int quality = 0;
double contestation = 0;
int marked = -1;
int artifacts = 0;
bool canHaveHomeworld = true;
array<Empire@>@ homeworlds;
array<double>@ hwDistances;
SystemData@ mirrorSystem;
uint[] adjacent;
uint[] wormholes;
SystemData@[] adjacentData;
Star@ star;
Planet@[] planets;
Object@[] distributedResources;
Planet@[] distributedConditions;
const SystemCode@ systemCode;
bool autoGenerateLinks = true;
bool ignoreAdjacencies = false;
uint assignGroup = uint(-1);
void addHomeworld(Empire@ empire) {
if(homeworlds is null)
@homeworlds = array<Empire@>();
homeworlds.insertLast(empire);
}
};
final class SystemCode {
array<string> commands;
array<Hook@> hooks;
int indent = 0;
SystemCode& opShl(const string& code) {
commands.insertLast(code);
parseHook(hooks, indent, code, "map_effects::");
indent = 0;
return this;
}
SystemCode& opShl(int num) {
indent += num;
return this;
}
};
//Abstract description of a system
final class SystemDesc : Serializable, Savable {
uint index;
string name;
vec3d position;
double radius;
Region@ object;
uint[] adjacent;
double[] adjacentDist;
uint[] wormholes;
double contestation = 0;
bool donateVision = true;
uint assignGroup = uint(-1);
array<uint> territories(getEmpireCount());
array<uint> visibleTerritory(getEmpireCount());
void read(Message& msg) {
index = msg.readSmall();
position = msg.readMedVec3();
radius = msg.read_float();
msg >> object;
msg >> name;
msg >> donateVision;
uint cnt = msg.readSmall();
adjacent.length = cnt;
adjacentDist.length = cnt;
for(uint i = 0; i < cnt; ++i) {
adjacent[i] = msg.readSmall();
adjacentDist[i] = msg.read_float();
}
cnt = msg.readSmall();
wormholes.length = cnt;
for(uint i = 0; i < cnt; ++i)
wormholes[i] = msg.readSmall();
}
uint get_spatialAdjacentCount() {
return adjacent.length + wormholes.length;
}
uint get_spatialAdjacent(uint index) {
if(index < adjacent.length)
return adjacent[index];
index -= adjacent.length;
if(index < wormholes.length)
return wormholes[index];
return uint(-1);
}
bool isSpatialAdjacent(const SystemDesc& other) const {
for(uint i = 0, cnt = adjacent.length; i < cnt; ++i) {
if(adjacent[i] == other.index)
return true;
}
return false;
}
bool isAdjacent(const SystemDesc& other) const {
for(uint i = 0, cnt = adjacent.length; i < cnt; ++i) {
if(adjacent[i] == other.index)
return true;
}
for(uint i = 0, cnt = wormholes.length; i < cnt; ++i) {
if(wormholes[i] == other.index)
return true;
}
return false;
}
void write(Message& msg) {
msg.writeSmall(index);
msg.writeMedVec3(position);
msg << float(radius);
msg << object;
msg << name;
msg << donateVision;
uint cnt = adjacent.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i) {
msg.writeSmall(adjacent[i]);
msg << float(adjacentDist[i]);
}
cnt = wormholes.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg.writeSmall(wormholes[i]);
}
void load(SaveFile& msg) {
msg >> index;
msg >> name;
msg >> position;
msg >> radius;
msg >> object;
if(msg >= SV_0082)
msg >> contestation;
if(msg >= SV_0099)
msg >> donateVision;
if(msg >= SV_0152)
msg >> assignGroup;
uint cnt = 0;
msg >> cnt;
adjacent.length = cnt;
adjacentDist.length = cnt;
for(uint i = 0; i < cnt; ++i) {
msg >> adjacent[i];
msg >> adjacentDist[i];
}
if(msg >= SV_0020) {
cnt = 0;
msg >> cnt;
wormholes.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> wormholes[i];
}
}
void save(SaveFile& msg) {
msg << index;
msg << name;
msg << position;
msg << radius;
msg << object;
msg << contestation;
msg << donateVision;
msg << assignGroup;
uint cnt = adjacent.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i) {
msg << adjacent[i];
msg << adjacentDist[i];
}
cnt = wormholes.length;
msg << cnt;
for(uint i = 0; i < cnt; ++i)
msg << wormholes[i];
}
};
+87
View File
@@ -0,0 +1,87 @@
enum EngagementRange {
ER_FlagshipMin,
ER_FlagshipMax,
ER_SupportMin,
ER_RaidingOnly,
};
enum EngagementBehaviour {
EB_CloseIn,
EB_KeepDistance,
};
enum AutoMode {
AM_HoldPosition,
AM_AreaBound,
AM_Unbound,
AM_RegionBound,
AM_HoldFire,
};
enum AutoState {
AS_None,
AS_Attacking,
AS_Returning,
};
final class GroupData : Serializable, Savable {
const Design@ dsg;
//Ships that are currently alive
uint amount = 0;
//Ships that have died in the past
uint ghost = 0;
//Ships that have been paid for but
//need to be constructed on nearby shipyards
uint ordered = 0;
//Amount of ships that have already been
//ordered and are awaiting completion
uint waiting = 0;
//Ships ordered in a particular budget cycle for refunding
int orderCycle = -1;
uint orderAmount = 0;
uint get_totalSize() {
return amount + ordered + ghost;
}
void read(Message& msg) {
msg >> dsg;
amount = msg.readSmall();
ghost = msg.readSmall();
ordered = msg.readSmall();
waiting = msg.readSmall();
}
void write(Message& msg) {
msg << dsg;
msg.writeSmall(amount);
msg.writeSmall(ghost);
msg.writeSmall(ordered);
msg.writeSmall(waiting);
}
void load(SaveFile& msg) {
msg >> dsg;
msg >> amount;
msg >> ghost;
msg >> ordered;
msg >> waiting;
msg >> orderCycle;
msg >> orderAmount;
}
void save(SaveFile& msg) {
msg << dsg;
msg << amount;
msg << ghost;
msg << ordered;
msg << waiting;
msg << orderCycle;
msg << orderAmount;
}
};
+347
View File
@@ -0,0 +1,347 @@
from settings.map_lib import SystemDesc;
#section gui
from navigation.systems import get_systemCount, getSystem;
#section server-side
import SystemDesc@ getSystem(uint index) from "game_start";
import SystemDesc@ getSystem(Region@ region) from "game_start";
import uint get_systemCount() from "game_start";
#section menu
uint get_systemCount() { return 0; }
SystemDesc@ getSystem(uint index) { return null; }
SystemDesc@ getSystem(Region@ region) { return null; }
#section all
const double MAX_LINK_DISTANCE = INFINITY;
class SystemPath : Serializable {
int[] path;
SystemDesc@ goal;
SystemDesc@ origin;
double maxLinkDistance = MAX_LINK_DISTANCE;
priority_queue q;
double[] dist;
int[] previous;
bool[] visited;
SystemPath() {
}
bool get_valid() {
return path.length != 0;
}
void read(Message& msg) {
if(msg.readBit()) {
uint ind = 0;
msg >> ind;
@goal = getSystem(ind);
}
else {
@goal = null;
}
if(msg.readBit()) {
uint ind = 0;
msg >> ind;
@origin = getSystem(ind);
}
else {
@origin = null;
}
uint cnt = msg.readSmall();
path.length = cnt;
for(uint i = 0; i < cnt; ++i)
msg >> path[i];
maxLinkDistance = msg.read_float();
}
void write(Message& msg) {
if(goal !is null) {
msg.write1();
msg << goal.index;
}
else {
msg.write0();
}
if(origin !is null) {
msg.write1();
msg << origin.index;
}
else {
msg.write0();
}
uint cnt = path.length;
msg.writeSmall(cnt);
for(uint i = 0; i < cnt; ++i)
msg << path[i];
msg << float(maxLinkDistance);
}
void clear() {
@goal = null;
@origin = null;
path.length = 0;
}
uint get_pathSize() {
return path.length;
}
SystemDesc@ get_pathNode(uint index) {
if(index >= path.length)
return null;
int sysindex = path[index];
return getSystem(uint(sysindex));
}
void itLink(SystemDesc@ node, SystemDesc@ other, double distance) {
//Don't consider links over the maximum distance
if(distance > maxLinkDistance)
return;
//Don't consider already visited nodes
if(visited[other.index])
return;
//If the path through here is faster,
//run it through here instead
double pthlen = dist[node.index] + distance;
if(pthlen < dist[other.index]) {
dist[other.index] = pthlen;
previous[other.index] = int(node.index);
q.push(int(other.index), -dist[other.index]);
}
}
void itNodes(SystemDesc@ node) {
//Add adjacencies
uint ncnt = node.adjacent.length;
for(uint j = 0; j < ncnt; ++j) {
SystemDesc@ other = getSystem(node.adjacent[j]);
double dist = node.adjacentDist[j];
itLink(node, other, dist);
}
//Add wormholes
ncnt = node.wormholes.length;
for(uint j = 0; j < ncnt; ++j) {
SystemDesc@ other = getSystem(node.wormholes[j]);
itLink(node, other, 0.0);
}
}
//Run dijkstra and generate a path
bool generate(SystemDesc@ from, SystemDesc@ to, bool keepCache = false) {
@origin = from;
@goal = to;
return generate(keepCache);
}
bool generate(bool keepCache = false) {
uint cnt = systemCount;
while(!q.empty())
q.pop();
path.length = 0;
if(origin is null || goal is null)
return false;
if(origin is goal) {
path.insertLast(goal.index);
return true;
}
dist.length = cnt;
previous.length = cnt;
visited.length = cnt;
for(uint i = 0; i < cnt; ++i) {
dist[i] = INFINITY;
previous[i] = -1;
visited[i] = false;
}
dist[origin.index] = 0;
q.push(int(origin.index), 0);
//Run dijkstra
while(!q.empty()) {
//Retrieve the highest priority node
uint index = uint(q.top());
SystemDesc@ node = getSystem(index);
//Stop if all the nodes are unreachable
if(dist[index] == INFINITY)
break;
q.pop();
//Only visit nodes once
if(visited[index])
continue;
visited[index] = true;
//Check all neighbours
itNodes(node);
}
//Check if a path was found
if(previous[goal.index] == -1)
return false;
//Generate the path in reverse form
uint current = goal.index;
uint orig = origin.index;
while(current != orig) {
path.insertLast(current);
current = previous[current];
}
path.insertLast(current);
path.reverse();
if(!keepCache) {
dist.length = 0;
previous.length = 0;
visited.length = 0;
}
return true;
}
void printPath() {
if(goal is null || origin is null) {
print("Uninitialized path.");
return;
}
print(origin.name+" --> "+goal.name);
if(path.length == 0) {
print("Invalid path.");
return;
}
for(uint i = 0, cnt = path.length; i < cnt; ++i)
print(" . "+pathNode[i].name);
}
};
class TradePath : SystemPath {
Empire@ forEmpire;
bool onlyValid = true;
bool foundGate = false;
TradePath() {
}
TradePath(Empire@ emp) {
@forEmpire = emp;
}
void read(Message& msg) {
msg >> forEmpire;
msg >> onlyValid;
SystemPath::read(msg);
}
void write(Message& msg) {
msg << forEmpire;
msg << onlyValid;
SystemPath::write(msg);
}
bool canLink(SystemDesc@ node, SystemDesc@ other) {
if(node is other || forEmpire is null)
return true;
if(node.isAdjacent(other)) {
if(forEmpire.GlobalTrade)
return true;
if(node !is origin && node.object.TradeMask & forEmpire.TradeMask.value == 0)
return false;
if(other !is goal && other.object.TradeMask & forEmpire.TradeMask.value == 0)
return false;
}
else {
if(node.object.GateMask.value & forEmpire.mask == 0)
return false;
if(other.object.GateMask.value & forEmpire.mask == 0)
return false;
}
return true;
}
bool get_isUsablePath() {
if(!valid)
return false;
if(forEmpire !is null) {
uint cnt = path.length;
SystemDesc@ prev = origin;
for(uint i = 0, cnt = path.length; i < cnt; ++i) {
SystemDesc@ desc = getSystem(path[i]);
if(!canLink(prev, desc))
return false;
@prev = desc;
}
}
return true;
}
bool generate(bool keepCache = false) override {
onlyValid = true;
foundGate = false;
return SystemPath::generate(keepCache=keepCache);
}
bool generate(bool OnlyValid, bool keepCache) {
onlyValid = OnlyValid;
foundGate = false;
return SystemPath::generate(keepCache=keepCache);
}
void itLink(SystemDesc@ node, SystemDesc@ other, double distance) override {
if(forEmpire !is null && onlyValid && !canLink(node, other))
return;
SystemPath::itLink(node, other, distance);
}
void itNodes(SystemDesc@ node) override {
if(forEmpire !is null && !foundGate) {
if(node.object.GateMask.value & forEmpire.mask != 0) {
foundGate = true;
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
auto@ other = getSystem(i);
if(other is node)
continue;
if(other.object.GateMask.value & forEmpire.mask != 0)
SystemPath::itLink(node, other, 0.05);
}
}
}
SystemPath::itNodes(node);
}
};
const SystemDesc@ getClosestSystem(const vec3d& point, Empire& presence, bool trade = false) {
const SystemDesc@ best;
double bestDist = INFINITY;
for(uint i = 0, cnt = systemCount; i < cnt; ++i) {
auto@ sys = getSystem(i);
if(trade) {
if(sys.object.TradeMask & presence.TradeMask.value == 0)
continue;
}
else {
if(sys.object.PlanetsMask & presence.mask == 0)
continue;
}
double d = sys.object.position.distanceToSQ(point);
if(d < bestDist) {
@best = sys;
bestDist = d;
}
}
return best;
}
+83
View File
@@ -0,0 +1,83 @@
#section game
from settings.map_lib import SystemDesc;
#section gui
from navigation.systems import get_systemCount, getSystem;
#section server-side
from game_start import get_systemCount, getSystem;
#section menu
uint get_systemCount() { return 0; }
#section game
import system_pathing;
enum RegionEffectType {
RET_TaxIncome,
RET_Null,
};
class RegionEffect {
int id = -1;
Empire@ forEmpire;
RegionEffectType type = RET_Null;
void save(SaveFile& msg) {
msg << uint(type);
msg << id;
msg << forEmpire;
}
void enable(Object& obj) {
}
void disable(Object& obj) {
}
void ownerChange(Object& obj, Empire@ prevOwner, Empire@ newOwner) {
}
};
bool hasTradeAdjacent(Empire@ emp, Region@ region) {
if(region is null)
return false;
if(region.TradeMask & emp.TradeMask.value != 0)
return true;
auto@ sys = getSystem(region);
if(sys is null)
return false;
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
auto@ other = getSystem(sys.adjacent[i]);
if(other.object.TradeMask & emp.TradeMask.value != 0)
return true;
}
for(uint i = 0, cnt = sys.wormholes.length; i < cnt; ++i) {
auto@ other = getSystem(sys.wormholes[i]);
if(other.object.TradeMask & emp.TradeMask.value != 0)
return true;
}
return false;
}
bool hasPlanetsAdjacent(Empire@ emp, Region@ region) {
if(region is null)
return false;
if(region.TradeMask & emp.TradeMask.value != 0)
return true;
auto@ sys = getSystem(region);
if(sys is null)
return false;
for(uint i = 0, cnt = sys.adjacent.length; i < cnt; ++i) {
auto@ other = getSystem(sys.adjacent[i]);
if(other.object.PlanetsMask & emp.mask != 0)
return true;
}
for(uint i = 0, cnt = sys.wormholes.length; i < cnt; ++i) {
auto@ other = getSystem(sys.wormholes[i]);
if(other.object.PlanetsMask & emp.mask != 0)
return true;
}
return false;
}
+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