Open source Star Ruler 2 source code!
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user