1449 lines
33 KiB
C++
1449 lines
33 KiB
C++
#include "empire.h"
|
|
#include "empire_stats.h"
|
|
#include "compat/misc.h"
|
|
#include "design/design.h"
|
|
#include "main/logging.h"
|
|
#include "network/network_manager.h"
|
|
#include "main/references.h"
|
|
#include "network.h"
|
|
#include "assert.h"
|
|
#include "util/stat_history.h"
|
|
#include <vector>
|
|
#include "util/save_file.h"
|
|
#include "processing.h"
|
|
|
|
Empire* defaultEmpire = 0;
|
|
Empire* spectatorEmpire = 0;
|
|
Empire* playerEmpire = 0;
|
|
static std::vector<Empire*> empires;
|
|
const StateDefinition* empStates = 0;
|
|
static EmpMask nextMask = 2;
|
|
static unsigned char nextID = 0;
|
|
|
|
extern std::vector<SubsystemDef::ModifyStage*> ModifiersByID;
|
|
|
|
static const vec2i ICON_SIZE(64, 64);
|
|
static const vec2i SHEET_PAGE_SIZE(256, 256);
|
|
static const unsigned ICONS_PER_SHEET = 9;
|
|
static const unsigned ICON_SPACING = 2;
|
|
|
|
unsigned validEmpireCount;
|
|
EmpMask currentVision[32];
|
|
|
|
Empire* Empire::getDefaultEmpire() {
|
|
return defaultEmpire;
|
|
}
|
|
|
|
Empire* Empire::getSpectatorEmpire() {
|
|
return spectatorEmpire;
|
|
}
|
|
|
|
Empire* Empire::getPlayerEmpire() {
|
|
return playerEmpire;
|
|
}
|
|
|
|
unsigned Empire::getEmpireCount() {
|
|
return empires.size();
|
|
}
|
|
|
|
Empire* Empire::getEmpireByIndex(unsigned index) {
|
|
if(index < empires.size())
|
|
return empires[index];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
Empire* Empire::getEmpireByID(unsigned char id) {
|
|
if(id == INVALID_EMPIRE)
|
|
return 0;
|
|
if(id == DEFAULT_EMPIRE)
|
|
return defaultEmpire;
|
|
if(id == SPECTATOR_EMPIRE)
|
|
return spectatorEmpire;
|
|
for(unsigned i = 0; i < empires.size(); ++i)
|
|
if(empires[i]->id == id)
|
|
return empires[i];
|
|
return 0;
|
|
}
|
|
|
|
void Empire::setPlayerEmpire(Empire* emp) {
|
|
playerEmpire = emp;
|
|
}
|
|
|
|
void* Empire::operator new(size_t size) {
|
|
if(empStates)
|
|
size += empStates->getSize(size);
|
|
return ::operator new(size);
|
|
}
|
|
|
|
void Empire::clearEmpires() {
|
|
for(auto emp = empires.begin(); emp != empires.end(); ++emp)
|
|
delete *emp;
|
|
empires.clear();
|
|
|
|
delete defaultEmpire;
|
|
defaultEmpire = 0;
|
|
delete spectatorEmpire;
|
|
spectatorEmpire = 0;
|
|
playerEmpire = 0;
|
|
|
|
nextMask = 2;
|
|
nextID = 0;
|
|
|
|
memset(currentVision, 0, sizeof(currentVision));
|
|
validEmpireCount = 0;
|
|
}
|
|
|
|
void Empire::initEmpires() {
|
|
if(!defaultEmpire)
|
|
defaultEmpire = new Empire(DEFAULT_EMPIRE);
|
|
if(!spectatorEmpire) {
|
|
spectatorEmpire = new Empire(SPECTATOR_EMPIRE);
|
|
spectatorEmpire->visionMask = ~0;
|
|
}
|
|
}
|
|
|
|
void Empire::registerObject(Object* obj) {
|
|
threads::WriteLock lock(objectLock);
|
|
objects.push_back(obj);
|
|
obj->grab();
|
|
}
|
|
|
|
void Empire::unregisterObject(Object* obj) {
|
|
//TODO: Improve
|
|
threads::WriteLock lock(objectLock);
|
|
for(auto i = objects.begin(), end = objects.end(); i != end; ++i) {
|
|
if(*i == obj) {
|
|
obj->drop();
|
|
objects.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned Empire::objectCount() {
|
|
return objects.size();
|
|
}
|
|
|
|
Object* Empire::findObject(unsigned i) {
|
|
threads::ReadLock lock(objectLock);
|
|
if(i < objects.size())
|
|
return objects[i];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void Empire::setEmpireStates(const StateDefinition* states) {
|
|
empStates = states;
|
|
}
|
|
|
|
const StateDefinition* Empire::getEmpireStates() {
|
|
return empStates;
|
|
}
|
|
|
|
Empire::Empire(unsigned char ID) : index(-1), background(0), portrait(0), flag(0), flagID(0), player(0), hullIconIndex(0), validEmpIndex(0) {
|
|
if(ID == INVALID_EMPIRE) {
|
|
objects.reserve(1250);
|
|
id = ++nextID;
|
|
mask = nextMask;
|
|
nextMask <<= 1;
|
|
}
|
|
else {
|
|
mask = ID == DEFAULT_EMPIRE ? 1 : 0;
|
|
id = ID;
|
|
}
|
|
visionMask = mask;
|
|
hostileMask = 0;
|
|
|
|
unsigned subsysCnt = getSubsystemDefCount();
|
|
subsysData.resize(subsysCnt);
|
|
for(unsigned i = 0; i < subsysCnt; ++i) {
|
|
auto& data = subsysData[i];
|
|
const SubsystemDef* def = getSubsystemDef(i);
|
|
if(def->defaultUnlock)
|
|
data.unlocked = true;
|
|
data.modulesUnlocked.resize(def->modules.size());
|
|
for(unsigned j = 0, jcnt = def->modules.size(); j < jcnt; ++j)
|
|
data.modulesUnlocked[j] = def->modules[j]->defaultUnlock;
|
|
}
|
|
|
|
if(valid()) {
|
|
statHistories.resize(getEmpireStatCount());
|
|
for(unsigned i = 0; i < statHistories.size(); ++i)
|
|
statHistories[i] = new StatHistory;
|
|
statLocks.resize(statHistories.size());
|
|
}
|
|
|
|
if(empStates) {
|
|
void* mixinMem = this + 1;
|
|
empStates->prepare(mixinMem);
|
|
}
|
|
|
|
if(valid()) {
|
|
empires.push_back(this);
|
|
index = empires.size() - 1;
|
|
validEmpIndex = validEmpireCount;
|
|
currentVision[validEmpIndex] = mask;
|
|
++validEmpireCount;
|
|
}
|
|
|
|
processing::startEmpireThread(this);
|
|
}
|
|
|
|
bool Empire::valid() {
|
|
return id < UNLISTED_EMPIRE;
|
|
}
|
|
|
|
Empire::~Empire() {
|
|
if(empStates) {
|
|
void* mixinMem = this + 1;
|
|
empStates->unprepare(mixinMem);
|
|
}
|
|
|
|
foreach(it, designIds)
|
|
(*it)->drop();
|
|
foreach(it, designClasses)
|
|
delete it->second;
|
|
foreach(it, objects)
|
|
(*it)->drop();
|
|
foreach(it, hullDistantIcons)
|
|
delete *it;
|
|
foreach(it, hullFleetIcons)
|
|
delete *it;
|
|
foreach(it, hullIcons) {
|
|
delete (*it)->material.textures[0];
|
|
delete *it;
|
|
}
|
|
foreach(it, hullImages)
|
|
delete *it;
|
|
}
|
|
|
|
void Empire::cacheVision() {
|
|
if(valid())
|
|
currentVision[validEmpIndex] = visionMask;
|
|
}
|
|
|
|
extern threads::atomic_int remainingMessages;
|
|
void Empire::processMessages(unsigned maxMessages) {
|
|
if(messages.empty())
|
|
return;
|
|
|
|
if(!processLock.try_lock())
|
|
return;
|
|
|
|
while(maxMessages-- && !messages.empty()) {
|
|
msgLock.lock();
|
|
EmpireMessage* msg = nullptr;
|
|
if(!messages.empty()) {
|
|
msg = messages.front();
|
|
messages.pop_front();
|
|
}
|
|
msgLock.release();
|
|
|
|
if(msg) {
|
|
msg->process(this);
|
|
delete msg;
|
|
}
|
|
--remainingMessages;
|
|
}
|
|
|
|
processLock.release();
|
|
}
|
|
|
|
void Empire::queueMessage(EmpireMessage* msg) {
|
|
if(!msg)
|
|
return;
|
|
|
|
msgLock.lock();
|
|
++remainingMessages;
|
|
messages.push_back(msg);
|
|
msgLock.release();
|
|
}
|
|
|
|
void Empire::recordStat(unsigned id, int value) {
|
|
if(id < statHistories.size()) {
|
|
threads::WriteLock lock(statLocks[id]);
|
|
|
|
StatHistory* history = statHistories[id];
|
|
|
|
StatEntry* entry = history->addStatEntry(unsigned(devices.driver->getGameTime()));
|
|
entry->asInt = value;
|
|
}
|
|
}
|
|
|
|
void Empire::recordStatDelta(unsigned id, int delta) {
|
|
if(id < statHistories.size()) {
|
|
threads::WriteLock lock(statLocks[id]);
|
|
|
|
StatHistory* history = statHistories[id];
|
|
|
|
unsigned time = unsigned(devices.driver->getGameTime());
|
|
|
|
StatEntry* tail = history->getTail();
|
|
if(tail && tail->time == time) {
|
|
tail->asInt += delta;
|
|
}
|
|
else {
|
|
StatEntry* entry = history->addStatEntry(time);
|
|
entry->asInt = entry->prev ? entry->prev->asInt + delta : entry->asInt + delta;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Empire::recordStat(unsigned id, float value) {
|
|
if(id < statHistories.size()) {
|
|
threads::WriteLock lock(statLocks[id]);
|
|
|
|
StatHistory* history = statHistories[id];
|
|
|
|
StatEntry* entry = history->addStatEntry(unsigned(devices.driver->getGameTime()));
|
|
entry->asFloat = value;
|
|
}
|
|
}
|
|
|
|
void Empire::recordStatDelta(unsigned id, float delta) {
|
|
if(id < statHistories.size()) {
|
|
threads::WriteLock lock(statLocks[id]);
|
|
|
|
StatHistory* history = statHistories[id];
|
|
|
|
unsigned time = unsigned(devices.driver->getGameTime());
|
|
|
|
StatEntry* tail = history->getTail();
|
|
if(tail && tail->time == time) {
|
|
tail->asFloat += delta;
|
|
}
|
|
else {
|
|
StatEntry* entry = history->addStatEntry(time);
|
|
entry->asFloat = entry->prev ? entry->prev->asFloat + delta : entry->asFloat + delta;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Empire::recordEvent(unsigned id, unsigned short type, const std::string& name) {
|
|
if(id < statHistories.size()) {
|
|
threads::WriteLock lock(statLocks[id]);
|
|
|
|
StatHistory* history = statHistories[id];
|
|
|
|
StatEntry* entry = history->getTail();
|
|
if(!entry)
|
|
entry = history->addStatEntry(unsigned(devices.driver->getGameTime()));
|
|
entry->addEvent(type, name);
|
|
}
|
|
}
|
|
|
|
const StatHistory* Empire::lockStatHistory(unsigned id) {
|
|
if(id < statHistories.size()) {
|
|
statLocks[id].readLock();
|
|
return statHistories[id];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Empire::unlockStatHistory(unsigned id) {
|
|
if(id < statHistories.size())
|
|
statLocks[id].release();
|
|
}
|
|
|
|
bool Empire::addDesign(DesignClass* cls, const Design* design) {
|
|
if(design->hasFatalErrors()) {
|
|
error("Could not add design '%s', design has fatal errors:",
|
|
design->name.c_str());
|
|
for(auto i = design->errors.begin(), end = design->errors.end(); i != end; ++i)
|
|
error(" %s", i->text.c_str());
|
|
return false;
|
|
}
|
|
|
|
threads::WriteLock lock(designMutex);
|
|
auto it = designs.find(design->name);
|
|
if(it != designs.end())
|
|
return false;
|
|
|
|
design->owner = this;
|
|
design->used = true;
|
|
design->cls = cls;
|
|
design->revision = 1;
|
|
designs.insert(it, std::pair<std::string, const Design*>(design->name, design));
|
|
cls->designs.push_back(design);
|
|
|
|
design->grab();
|
|
design->id = designIds.size();
|
|
designIds.push_back(design);
|
|
|
|
makeDesignIcon(design);
|
|
|
|
if(devices.network->isServer || devices.network->isClient)
|
|
devices.network->sendDesign(this, cls, design);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Empire::changeDesign(const Design* older, const Design* newer, DesignClass* cls) {
|
|
if(newer->hasFatalErrors()) {
|
|
error("Could not update design '%s', new design has fatal errors.",
|
|
newer->name.c_str());
|
|
return false;
|
|
}
|
|
|
|
//TODO: Print sensible error messages
|
|
if(older->owner != this || !older->used) {
|
|
error("Could not update design '%s', old design not owned by this empire.",
|
|
newer->name.c_str());
|
|
return false;
|
|
}
|
|
|
|
threads::WriteLock lock(designMutex);
|
|
setDesign(older, newer, cls);
|
|
|
|
if(devices.network->isServer || devices.network->isClient)
|
|
devices.network->sendDesignUpdate(this, older, newer, cls);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Empire::setDesign(const Design* older, const Design* newer, DesignClass* cls) {
|
|
if(older->owner != this)
|
|
return;
|
|
threads::WriteLock lock(designMutex);
|
|
if(older->original != nullptr)
|
|
older = older->original;
|
|
if(cls == nullptr)
|
|
cls = older->cls;
|
|
|
|
if(newer->id == (unsigned)-1) {
|
|
newer->grab();
|
|
newer->id = designIds.size();
|
|
designIds.push_back(newer);
|
|
}
|
|
|
|
newer->owner = this;
|
|
newer->used = true;
|
|
newer->cls = cls;
|
|
|
|
makeDesignIcon(newer);
|
|
|
|
//Replace indicated older
|
|
older->newer = newer;
|
|
newer->revision = older->revision + 1;
|
|
|
|
//Deal with changing name
|
|
if(older->name == newer->name) {
|
|
designs[older->name] = newer;
|
|
|
|
if(cls == older->cls) {
|
|
//Replace it in its class
|
|
for(size_t i = 0, cnt = older->cls->designs.size(); i < cnt; ++i) {
|
|
if(older->cls->designs[i]->base() == older) {
|
|
older->cls->designs[i] = newer;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//Remove from previous class
|
|
foreach(it, older->cls->designs) {
|
|
if((*it)->base() == older) {
|
|
older->cls->designs.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Add to new class
|
|
cls->designs.push_back(newer);
|
|
}
|
|
}
|
|
else {
|
|
//Remove previous from class
|
|
foreach(it, older->cls->designs) {
|
|
if((*it)->base() == older) {
|
|
older->cls->designs.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Also replace the design we're overwriting
|
|
auto it = designs.find(newer->name);
|
|
if(it != designs.end()) {
|
|
const Design* other = it->second->base();
|
|
other->newer = newer;
|
|
newer->revision = std::max(newer->revision, other->revision + 1);
|
|
|
|
designs[other->name] = newer;
|
|
|
|
if(cls == other->cls) {
|
|
//Replace it in its class too
|
|
bool found = false;
|
|
for(size_t i = 0, cnt = other->cls->designs.size(); i < cnt; ++i) {
|
|
if(other->cls->designs[i]->base() == other) {
|
|
other->cls->designs[i] = newer;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found) {
|
|
//Add to new class
|
|
cls->designs.push_back(newer);
|
|
}
|
|
}
|
|
else {
|
|
//Remove from previous class
|
|
foreach(it, other->cls->designs) {
|
|
if((*it)->base() == other) {
|
|
other->cls->designs.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Add to new class
|
|
cls->designs.push_back(newer);
|
|
}
|
|
}
|
|
else {
|
|
designs[newer->name] = newer;
|
|
|
|
//Add to class as new design
|
|
cls->designs.push_back(newer);
|
|
}
|
|
}
|
|
}
|
|
|
|
const Design* Empire::updateDesign(const Design* dsg, bool onlyOutdated) {
|
|
if(devices.network->isClient)
|
|
return 0;
|
|
if(dsg->owner != this)
|
|
return dsg;
|
|
if(!dsg->updated && !dsg->outdated && onlyOutdated)
|
|
return dsg;
|
|
|
|
threads::WriteLock lock(designMutex);
|
|
|
|
//Mark all designs in this design path as no longer outdated
|
|
while(true) {
|
|
if(dsg->updated) {
|
|
dsg->outdated = false;
|
|
dsg = dsg->updated;
|
|
}
|
|
else {
|
|
if(onlyOutdated && !dsg->outdated) {
|
|
dsg->grab();
|
|
return dsg;
|
|
}
|
|
dsg->outdated = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Design::Descriptor desc;
|
|
dsg->toDescriptor(desc);
|
|
|
|
const Design* newDesign = new Design(desc);
|
|
if(newDesign->hasFatalErrors()) {
|
|
newDesign->drop();
|
|
dsg->grab();
|
|
return dsg;
|
|
}
|
|
|
|
setDesignUpdate(dsg, newDesign);
|
|
|
|
if(devices.network->isServer)
|
|
devices.network->sendDesignUpdate(this, dsg, newDesign);
|
|
|
|
newDesign->grab();
|
|
return newDesign;
|
|
}
|
|
|
|
void Empire::setDesignUpdate(const Design* dsg, const Design* newDesign) {
|
|
dsg->updated = newDesign;
|
|
newDesign->data = dsg->data;
|
|
newDesign->clientData = dsg->clientData;
|
|
newDesign->serverData = dsg->serverData;
|
|
newDesign->original = dsg->original ? dsg->original.ptr : dsg;
|
|
newDesign->revision = dsg->revision;
|
|
newDesign->obsolete = dsg->obsolete;
|
|
newDesign->distantIcon = dsg->distantIcon;
|
|
newDesign->icon = dsg->icon;
|
|
newDesign->fleetIcon = dsg->fleetIcon;
|
|
|
|
if(newDesign->id == (unsigned)-1) {
|
|
newDesign->id = designIds.size();
|
|
designIds.push_back(newDesign); //transfer original ref
|
|
}
|
|
newDesign->owner = this;
|
|
newDesign->used = true;
|
|
newDesign->cls = dsg->cls;
|
|
|
|
for(size_t i = 0, cnt = newDesign->cls->designs.size(); i < cnt; ++i) {
|
|
if(newDesign->cls->designs[i]->base() == dsg->base()) {
|
|
newDesign->cls->designs[i] = newDesign;
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto it = designs.find(newDesign->name);
|
|
if(it != designs.end()) {
|
|
if(it->second->base() == newDesign->base())
|
|
designs[newDesign->name] = newDesign;
|
|
}
|
|
}
|
|
|
|
void Empire::flagDesignOld(const Design* design) {
|
|
if(devices.network->isClient)
|
|
return;
|
|
//NOTE: This can use a readlock as outdated is only used by updateDesign which can't be writing at the same time
|
|
threads::ReadLock lock(designMutex);
|
|
design->mostUpdated()->outdated = true;
|
|
}
|
|
|
|
const Design* Empire::getDesign(const std::string& name, bool grab) {
|
|
threads::ReadLock lock(designMutex);
|
|
auto it = designs.find(name);
|
|
if(it == designs.end())
|
|
return 0;
|
|
if(grab)
|
|
it->second->grab();
|
|
return it->second;
|
|
}
|
|
|
|
const Design* Empire::getDesign(unsigned id, bool grab) {
|
|
threads::ReadLock lock(designMutex);
|
|
if(id >= (unsigned)designIds.size())
|
|
return 0;
|
|
const Design* dsg = designIds[id];
|
|
if(grab && dsg)
|
|
dsg->grab();
|
|
return dsg;
|
|
}
|
|
|
|
Design* Empire::getDesignMake(unsigned id) {
|
|
threads::WriteLock lock(designMutex);
|
|
unsigned cnt = designIds.size();
|
|
if(id < cnt) {
|
|
if(!designIds[id]) {
|
|
Design* dsg = new Design();
|
|
dsg->id = id;
|
|
designIds[id] = dsg;
|
|
}
|
|
return (Design*)designIds[id];
|
|
}
|
|
else {
|
|
designIds.resize(id+1);
|
|
for(unsigned i = cnt; i < unsigned(id); ++i)
|
|
designIds[i] = 0;
|
|
Design* dsg = new Design();
|
|
dsg->id = id;
|
|
designIds[id] = dsg;
|
|
return dsg;
|
|
}
|
|
}
|
|
|
|
DesignClass* Empire::getDesignClass(int id) {
|
|
threads::ReadLock lock(designMutex);
|
|
if(id < (int)designClassIds.size())
|
|
return designClassIds[id];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
DesignClass* Empire::getDesignClass(const std::string& name, bool add) {
|
|
{
|
|
threads::ReadLock lock(designMutex);
|
|
auto it = designClasses.find(name);
|
|
if(it != designClasses.end())
|
|
return it->second;
|
|
}
|
|
|
|
if(add) {
|
|
threads::WriteLock lock(designMutex);
|
|
DesignClass* cls = new DesignClass;
|
|
cls->id = designClassIds.size();
|
|
designClassIds.push_back(cls);
|
|
cls->name = name;
|
|
designClasses[name] = cls;
|
|
return cls;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void Empire::makeDesignIcon(const Design* design) {
|
|
threads::WriteLock lock(designMutex);
|
|
|
|
Image* img;
|
|
render::SpriteSheet* sheet;
|
|
render::SpriteSheet* distantSheet;
|
|
render::SpriteSheet* fleetSheet;
|
|
|
|
if(hullIcons.empty() || hullIconIndex >= ICONS_PER_SHEET) {
|
|
hullIconIndex = 0;
|
|
|
|
auto* tex = render::RenderDriver::createTexture();
|
|
|
|
img = new Image(SHEET_PAGE_SIZE.x, SHEET_PAGE_SIZE.y, FMT_RGBA);
|
|
memset(img->rgba, 0x00, sizeof(Color) * img->width * img->height);
|
|
|
|
sheet = new render::SpriteSheet();
|
|
sheet->material = devices.library.getMaterial("ShipIcon");
|
|
sheet->width = ICON_SIZE.x;
|
|
sheet->height = ICON_SIZE.y;
|
|
sheet->spacing = ICON_SPACING;
|
|
sheet->material.textures[0] = tex;
|
|
hullIcons.push_back(sheet);
|
|
|
|
distantSheet = new render::SpriteSheet();
|
|
distantSheet->material = devices.library.getMaterial("ShipDistantIcon");
|
|
distantSheet->width = ICON_SIZE.x;
|
|
distantSheet->height = ICON_SIZE.y;
|
|
distantSheet->material.textures[0] = tex;
|
|
distantSheet->spacing = ICON_SPACING;
|
|
hullDistantIcons.push_back(distantSheet);
|
|
|
|
fleetSheet = new render::SpriteSheet();
|
|
fleetSheet->material = devices.library.getMaterial("FleetIcon");
|
|
fleetSheet->width = ICON_SIZE.x;
|
|
fleetSheet->height = ICON_SIZE.y;
|
|
fleetSheet->material.textures[0] = tex;
|
|
fleetSheet->spacing = ICON_SPACING;
|
|
hullFleetIcons.push_back(fleetSheet);
|
|
|
|
hullImages.push_back(img);
|
|
}
|
|
else {
|
|
sheet = hullIcons[hullIcons.size() - 1];
|
|
distantSheet = hullDistantIcons[hullIcons.size() - 1];
|
|
fleetSheet = hullFleetIcons[hullIcons.size() - 1];
|
|
img = hullImages[hullIcons.size() - 1];
|
|
}
|
|
|
|
unsigned perLine = (SHEET_PAGE_SIZE.x - sheet->spacing) / (sheet->width + sheet->spacing);
|
|
vec2i source(hullIconIndex % perLine, hullIconIndex / perLine);
|
|
source.x = source.x * (sheet->width + sheet->spacing) + sheet->spacing;
|
|
source.y = source.y * (sheet->height + sheet->spacing) + sheet->spacing;
|
|
|
|
design->makeDistanceMap(*img, source, ICON_SIZE);
|
|
|
|
int prior = -hullImages.size() * ICONS_PER_SHEET - hullIconIndex;
|
|
resource::queueTextureUpdate(sheet->material.textures[0], new Image(*img), prior, sheet->material.mipmap);
|
|
design->icon = render::Sprite(sheet, hullIconIndex);
|
|
design->distantIcon = render::Sprite(distantSheet, hullIconIndex);
|
|
design->fleetIcon = render::Sprite(fleetSheet, hullIconIndex);
|
|
|
|
hullIconIndex += 1;
|
|
}
|
|
|
|
Empire::SubsystemData* Empire::getSubsystemData(const SubsystemDef* def) {
|
|
if(def->index >= (int)subsysData.size())
|
|
return 0;
|
|
return &subsysData[def->index];
|
|
}
|
|
|
|
static inline void sendDetails(net::Message& msg, Empire& emp) {
|
|
msg << emp.name;
|
|
msg << emp.visionMask;
|
|
msg << emp.hostileMask;
|
|
msg << emp.color;
|
|
}
|
|
|
|
static inline void recvDetails(net::Message& msg, Empire& emp) {
|
|
msg >> emp.name;
|
|
msg >> emp.visionMask;
|
|
msg >> emp.hostileMask;
|
|
msg >> emp.color;
|
|
}
|
|
|
|
void Empire::sendDesign(net::Message& msg, const Design* dsg, bool fromServer) {
|
|
msg.writeSmall(dsg->id);
|
|
if(fromServer)
|
|
dsg->writeData(msg);
|
|
else
|
|
dsg->write(msg);
|
|
}
|
|
|
|
Design* Empire::recvDesign(net::Message& msg, bool fromServer) {
|
|
unsigned id = msg.readSmall();
|
|
Design* dsg = getDesignMake(id);
|
|
if(dsg->initialized)
|
|
return dsg;
|
|
|
|
if(fromServer) {
|
|
dsg->initData(msg);
|
|
}
|
|
else {
|
|
dsg->init(msg);
|
|
dsg->used = true;
|
|
}
|
|
|
|
return dsg;
|
|
}
|
|
|
|
void Empire::SubsystemData::write(net::Message& msg) {
|
|
msg.writeBit(unlocked);
|
|
msg.writeSmall(modulesUnlocked.size());
|
|
for(size_t i = 0, cnt = modulesUnlocked.size(); i < cnt; ++i)
|
|
msg.writeBit(modulesUnlocked[i]);
|
|
msg.writeSmall(stages.size());
|
|
foreach(it, stages) {
|
|
msg.writeSmall(it->first);
|
|
msg.writeSmall(it->second.stage->index);
|
|
for(size_t i = 0; i < MODIFY_STAGE_MAXARGS; ++i)
|
|
msg << it->second.arguments[i];
|
|
}
|
|
}
|
|
|
|
void Empire::SubsystemData::read(const SubsystemDef* def, net::Message& msg) {
|
|
unlocked = msg.readBit();
|
|
modulesUnlocked.resize(msg.readSmall());
|
|
for(size_t i = 0, cnt = modulesUnlocked.size(); i < cnt; ++i)
|
|
modulesUnlocked[i] = msg.readBit();
|
|
|
|
unsigned stageCnt = msg.readSmall();
|
|
stages.clear();
|
|
for(unsigned i = 0; i < stageCnt; ++i) {
|
|
unsigned id = msg.readSmall();
|
|
unsigned index = msg.readSmall();
|
|
if(index >= def->modifiers.size()) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
SubsystemDef::AppliedStage as;
|
|
as.stage = def->modifiers[index];
|
|
for(size_t i = 0; i < MODIFY_STAGE_MAXARGS; ++i)
|
|
msg >> as.arguments[i];
|
|
|
|
stages[id] = as;
|
|
}
|
|
}
|
|
|
|
void Empire::writeDelta(net::Message& msg) {
|
|
threads::ReadLock lock(subsystemDataMutex);
|
|
msg.writeAlign();
|
|
auto pos = msg.reserve<unsigned>();
|
|
unsigned amount = 0;
|
|
for(unsigned i = 0, cnt = subsysData.size(); i < cnt; ++i) {
|
|
if(subsysData[i].delta) {
|
|
msg.writeSmall(i);
|
|
subsysData[i].write(msg);
|
|
subsysData[i].delta = false;
|
|
++amount;
|
|
}
|
|
}
|
|
msg.fill<unsigned>(pos, amount);
|
|
for(unsigned i = 0, cnt = statHistories.size(); i < cnt; ++i) {
|
|
auto* stat = lockStatHistory(i);
|
|
if(!stat) {
|
|
msg.write0();
|
|
continue;
|
|
}
|
|
auto* s = stat->getTail();
|
|
if(s) {
|
|
msg.write1();
|
|
msg << s->asInt;
|
|
}
|
|
else {
|
|
msg.write0();
|
|
}
|
|
unlockStatHistory(i);
|
|
}
|
|
}
|
|
|
|
void Empire::readDelta(net::Message& msg) {
|
|
threads::WriteLock lock(subsystemDataMutex);
|
|
msg.readAlign();
|
|
|
|
unsigned amount = 0;
|
|
msg >> amount;
|
|
for(unsigned i = 0; i < amount; ++i) {
|
|
unsigned index = msg.readSmall();
|
|
if(index >= subsysData.size()) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
subsysData[index].read(getSubsystemDef(index), msg);
|
|
}
|
|
|
|
for(unsigned i = 0, cnt = statHistories.size(); i < cnt; ++i) {
|
|
if(!msg.readBit())
|
|
continue;
|
|
if(statIsint(i))
|
|
recordStat(i, msg.readIn<int>());
|
|
else
|
|
recordStat(i, msg.readIn<float>());
|
|
}
|
|
}
|
|
|
|
void Empire::sendInitial(net::Message& msg) {
|
|
assert(devices.network->isServer);
|
|
|
|
//Write static data
|
|
msg << id;
|
|
msg << mask;
|
|
|
|
unsigned shipsetID = shipset ? shipset->id : 0;
|
|
msg << shipsetID;
|
|
|
|
//Write other data
|
|
sendDetails(msg, *this);
|
|
|
|
//Write designs
|
|
unsigned clsCnt = designClassIds.size();
|
|
msg.writeSmall(clsCnt);
|
|
for(unsigned i = 0; i < clsCnt; ++i) {
|
|
DesignClass* cls = designClassIds[i];
|
|
msg << cls->name;
|
|
}
|
|
|
|
unsigned dsgCnt = designIds.size();
|
|
msg.writeSmall(dsgCnt);
|
|
for(unsigned j = 0; j < dsgCnt; ++j) {
|
|
const Design* dsg = designIds[j];
|
|
sendDesign(msg, dsg, true);
|
|
}
|
|
|
|
//Write subsystem data
|
|
for(unsigned i = 0, cnt = subsysData.size(); i < cnt; ++i)
|
|
subsysData[i].write(msg);
|
|
|
|
for(unsigned i = 0, cnt = statHistories.size(); i < cnt; ++i) {
|
|
auto& stat = *lockStatHistory(i);
|
|
bool isInt = statIsint(i);
|
|
|
|
unsigned lastTime = 0;
|
|
|
|
int prevInt = 0;
|
|
float fMin = 0.f, fMax = 128.f;
|
|
|
|
auto* s = stat.getHead();
|
|
while(s) {
|
|
msg.write1();
|
|
unsigned dT = s->time - lastTime;
|
|
if(dT < 15)
|
|
msg.writeLimited(dT, 0, 15);
|
|
else {
|
|
msg.writeLimited(15, 0, 15);
|
|
msg.writeSmall(dT-15);
|
|
}
|
|
lastTime = s->time;
|
|
if(isInt) {
|
|
msg.writeSignedSmall(s->asInt - prevInt);
|
|
prevInt = s->asInt;
|
|
}
|
|
else {
|
|
float v = s->asFloat;
|
|
if(v >= fMin && v <= fMax) {
|
|
msg.write0();
|
|
msg.writeFixed(v, fMin, fMax, 10);
|
|
}
|
|
else {
|
|
msg.write1();
|
|
msg << v;
|
|
if(v < fMin)
|
|
fMin = v - 64.f - v/16.f;
|
|
if(v > fMax)
|
|
fMax = v + 64.f + v/16.f;
|
|
}
|
|
}
|
|
|
|
auto* e = s->evt;
|
|
while(e) {
|
|
msg.write1();
|
|
msg << e->type;
|
|
msg << e->name;
|
|
e = e->next;
|
|
}
|
|
msg.write0();
|
|
|
|
s = s->next;
|
|
}
|
|
msg.write0();
|
|
|
|
unlockStatHistory(i);
|
|
}
|
|
}
|
|
|
|
Empire* recvEmpireInitial(net::Message& msg) {
|
|
assert(devices.network->isClient);
|
|
Empire* emp = new Empire();
|
|
|
|
//Receive static data
|
|
msg >> emp->id;
|
|
msg >> emp->mask;
|
|
|
|
emp->shipset = getShipset(msg.readIn<unsigned>());
|
|
|
|
//Receive other data
|
|
recvDetails(msg, *emp);
|
|
|
|
//Receive designs
|
|
unsigned clsCnt = msg.readSmall();
|
|
for(unsigned i = 0; i < clsCnt; ++i) {
|
|
DesignClass* cls = new DesignClass;
|
|
cls->id = emp->designClassIds.size();
|
|
msg >> cls->name;
|
|
|
|
emp->designClassIds.push_back(cls);
|
|
emp->designClasses[cls->name] = cls;
|
|
}
|
|
|
|
unsigned dsgCnt = msg.readSmall();
|
|
for(unsigned j = 0; j < dsgCnt; ++j) {
|
|
Design* dsg = emp->recvDesign(msg, true);
|
|
|
|
if(!dsg->original || dsg->original == dsg) {
|
|
emp->makeDesignIcon(dsg);
|
|
}
|
|
else if(dsg->original) {
|
|
dsg->icon = dsg->original->icon;
|
|
dsg->distantIcon = dsg->original->distantIcon;
|
|
dsg->fleetIcon = dsg->original->fleetIcon;
|
|
}
|
|
|
|
if(!dsg->newer && !dsg->updated)
|
|
dsg->cls->designs.push_back(dsg);
|
|
if(!dsg->updated)
|
|
emp->designs[dsg->name] = dsg;
|
|
}
|
|
|
|
for(unsigned i = 0, cnt = emp->subsysData.size(); i < cnt; ++i)
|
|
emp->subsysData[i].read(getSubsystemDef(i), msg);
|
|
|
|
for(unsigned i = 0, cnt = emp->statHistories.size(); i < cnt; ++i) {
|
|
auto& stat = *(StatHistory*)emp->lockStatHistory(i);
|
|
if(&stat == 0)
|
|
break;
|
|
bool isInt = statIsint(i);
|
|
|
|
unsigned lastTime = 0;
|
|
|
|
int prevInt = 0;
|
|
float fMin = 0.f, fMax = 128.f;
|
|
|
|
while(msg.readBit()) {
|
|
unsigned dT = msg.readLimited(0, 15);
|
|
if(dT == 15)
|
|
dT = msg.readSmall() + 15;
|
|
|
|
auto* s = stat.addStatEntry(lastTime + dT);
|
|
lastTime = s->time;
|
|
|
|
if(isInt) {
|
|
s->asInt = prevInt + msg.readSignedSmall();
|
|
prevInt = s->asInt;
|
|
}
|
|
else {
|
|
if(!msg.readBit()) {
|
|
s->asFloat = msg.readFixed(fMin, fMax, 10);
|
|
}
|
|
else {
|
|
float v = msg.readIn<float>();
|
|
s->asFloat = v;
|
|
if(v < fMin)
|
|
fMin = v - 64.f - v/16.f;
|
|
if(v > fMax)
|
|
fMax = v + 64.f + v/16.f;
|
|
}
|
|
}
|
|
|
|
while(msg.readBit()) {
|
|
unsigned short id = 0;
|
|
std::string text;
|
|
msg >> id >> text;
|
|
s->addEvent(id, text);
|
|
}
|
|
|
|
s = s->next;
|
|
}
|
|
|
|
emp->unlockStatHistory(i);
|
|
}
|
|
|
|
info("Received empire '%s' (%d).",
|
|
emp->name.c_str(), emp->id);
|
|
|
|
return emp;
|
|
}
|
|
|
|
Empire::Empire(SaveFile& file) : background(0), portrait(0), flag(0), flagID(0), player(0) {
|
|
file >> id >> index >> name >> mask >> visionMask >> hostileMask >> color;
|
|
file >> backgroundDef >> portraitDef >> flagDef;
|
|
if(file >= SFV_0019)
|
|
file >> flagID;
|
|
|
|
if(valid()) {
|
|
validEmpIndex = validEmpireCount;
|
|
currentVision[validEmpIndex] = visionMask;
|
|
++validEmpireCount;
|
|
}
|
|
|
|
//Push this empire immediately so designs can locate us
|
|
empires.push_back(this);
|
|
|
|
if(empStates) {
|
|
void* mixinMem = this + 1;
|
|
empStates->prepare(mixinMem);
|
|
}
|
|
|
|
file.boundary();
|
|
|
|
//Load owned objects
|
|
unsigned objectCount = file;
|
|
objects.resize(objectCount);
|
|
for(unsigned i = 0; i < objectCount; ++i)
|
|
file >> objects[i];
|
|
|
|
if(file < SFV_0004)
|
|
shipset = getShipset("Original_r3000");
|
|
else
|
|
shipset = getShipset(file.readIdentifier(SI_Shipset));
|
|
|
|
if(file >= SFV_0007)
|
|
file >> effectorSkin;
|
|
|
|
//Load designs
|
|
unsigned dsgnCount = file;
|
|
designIds.resize(dsgnCount, 0);
|
|
for(unsigned i = 0; i < designIds.size(); ++i) {
|
|
Design* design = new Design(file);
|
|
designIds[i] = design;
|
|
designs[design->name] = design;
|
|
if(i != design->id) {
|
|
//Try and fix old saves. probably futile in most situations.
|
|
design->id = i;
|
|
}
|
|
}
|
|
|
|
//Load design classes
|
|
unsigned dsgnClassCount = file;
|
|
designClassIds.reserve(dsgnClassCount);
|
|
for(unsigned i = 0; i < dsgnClassCount; ++i) {
|
|
DesignClass* dsgnClass = new DesignClass;
|
|
dsgnClass->id = designClassIds.size();
|
|
designClassIds.push_back(dsgnClass);
|
|
file >> dsgnClass->name;
|
|
designClasses[dsgnClass->name] = dsgnClass;
|
|
|
|
unsigned classDesignCount = file;
|
|
dsgnClass->designs.reserve(classDesignCount);
|
|
|
|
for(unsigned j = 0; j < classDesignCount; ++j) {
|
|
int designID = file;
|
|
const Design* dsg = getDesign(designID);
|
|
if(dsg)
|
|
dsgnClass->designs.push_back(dsg);
|
|
}
|
|
}
|
|
|
|
//Load design links
|
|
for(unsigned i = 0; i < designIds.size(); ++i) {
|
|
Design* dsg = (Design*)designIds[i];
|
|
|
|
unsigned id;
|
|
file >> id;
|
|
if(id < designClassIds.size())
|
|
dsg->cls = designClassIds[id];
|
|
else
|
|
dsg->cls = designClassIds[0];
|
|
|
|
file >> id;
|
|
if(id == (unsigned)-1 || id >= designIds.size())
|
|
dsg->newer = nullptr;
|
|
else
|
|
dsg->newer = designIds[id];
|
|
|
|
file >> id;
|
|
if(id == (unsigned)-1 || id >= designIds.size())
|
|
dsg->updated = nullptr;
|
|
else
|
|
dsg->updated = designIds[id];
|
|
|
|
file >> id;
|
|
if(id == (unsigned)-1 || id >= designIds.size())
|
|
dsg->original = nullptr;
|
|
else
|
|
dsg->original = designIds[id];
|
|
|
|
if(!dsg->original || dsg->original == dsg) {
|
|
makeDesignIcon(dsg);
|
|
}
|
|
else if(dsg->original) {
|
|
dsg->icon = dsg->original->icon;
|
|
dsg->distantIcon = dsg->original->distantIcon;
|
|
dsg->fleetIcon = dsg->original->fleetIcon;
|
|
|
|
dsg->data = dsg->original->data;
|
|
dsg->clientData = dsg->original->clientData;
|
|
dsg->serverData = dsg->original->serverData;
|
|
}
|
|
}
|
|
|
|
file.boundary();
|
|
|
|
//Initialize subsystem data
|
|
unsigned subsysCnt = getSubsystemDefCount();
|
|
subsysData.resize(subsysCnt);
|
|
for(unsigned i = 0; i < subsysCnt; ++i) {
|
|
auto& data = subsysData[i];
|
|
const SubsystemDef* def = getSubsystemDef(i);
|
|
if(def->defaultUnlock)
|
|
data.unlocked = true;
|
|
data.modulesUnlocked.resize(def->modules.size());
|
|
for(unsigned j = 0, jcnt = def->modules.size(); j < jcnt; ++j)
|
|
data.modulesUnlocked[j] = def->modules[j]->defaultUnlock;
|
|
}
|
|
|
|
//Load subsystem data
|
|
unsigned cnt = file;
|
|
for(unsigned n = 0; n < cnt; ++n) {
|
|
int i = file.readIdentifier(SI_Subsystem);
|
|
if(i == -1)
|
|
throw SaveFileError("Subsystem was removed.");
|
|
|
|
SubsystemData& dat = subsysData[i];
|
|
file >> dat.unlocked;
|
|
file >> dat.nextStageId;
|
|
|
|
unsigned modCnt = file;
|
|
dat.modulesUnlocked.resize(modCnt);
|
|
for(unsigned j = 0; j < modCnt; ++j)
|
|
dat.modulesUnlocked[j] = (bool)file;
|
|
|
|
//Assumptions: empire stages are always
|
|
//from modifiers in the subsystem root,
|
|
//and cannot have formula modifiers.
|
|
// (Currently satisfied)
|
|
const SubsystemDef* def = getSubsystemDef(i);
|
|
unsigned stageCnt = file;
|
|
for(unsigned j = 0; j < stageCnt; ++j) {
|
|
SubsystemDef::AppliedStage stg;
|
|
unsigned id = file;
|
|
unsigned index = 0;
|
|
if(file >= SFV_0015) {
|
|
unsigned uid = (unsigned)file.readIdentifier(SI_SubsystemModifier);
|
|
if(uid < ModifiersByID.size())
|
|
index = ModifiersByID[uid]->index;
|
|
else
|
|
index = (unsigned)-1;
|
|
}
|
|
else
|
|
index = file;
|
|
file.read(stg.arguments, MODIFY_STAGE_MAXARGS * sizeof(float));
|
|
|
|
if(index >= def->modifiers.size())
|
|
continue;
|
|
stg.stage = def->modifiers[index];
|
|
dat.stages[id] = stg;
|
|
}
|
|
}
|
|
|
|
statHistories.resize(getEmpireStatCount());
|
|
statLocks.resize(statHistories.size());
|
|
for(unsigned i = 0; i < statHistories.size(); ++i)
|
|
statHistories[i] = new StatHistory();
|
|
|
|
if(file >= SFV_0014) {
|
|
unsigned count = 0;
|
|
file >> count;
|
|
|
|
while(count--) {
|
|
std::string name;
|
|
file >> name;
|
|
|
|
StatHistory* stat = nullptr;
|
|
StatEntry dummy;
|
|
StatEvent evt;
|
|
|
|
auto getEntry = [&](unsigned time) -> StatEntry* {
|
|
if(stat)
|
|
return stat->addStatEntry(time);
|
|
else
|
|
return &dummy;
|
|
};
|
|
|
|
auto addEvent = [&](StatEntry* entry, const std::string& text, unsigned short id) {
|
|
if(stat)
|
|
entry->addEvent(id, text);
|
|
};
|
|
|
|
unsigned index = getStatID(name);
|
|
if(index < statHistories.size())
|
|
stat = statHistories[index];
|
|
|
|
while(true) {
|
|
unsigned time = 0;
|
|
file >> time;
|
|
if(time == 0xffffffff)
|
|
break;
|
|
|
|
auto* h = getEntry(time);
|
|
file >> h->asInt;
|
|
|
|
while(true) {
|
|
unsigned short evtID = 0;
|
|
file >> evtID;
|
|
if(evtID == 0xffff)
|
|
break;
|
|
|
|
std::string text;
|
|
file >> text;
|
|
addEvent(h, text, evtID);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
file.boundary();
|
|
|
|
bool isPlayer = file;
|
|
if(isPlayer) {
|
|
playerEmpire = this;
|
|
devices.network->currentPlayer.emp = playerEmpire;
|
|
devices.network->currentPlayer.controlMask = playerEmpire->mask;
|
|
devices.network->currentPlayer.viewMask = playerEmpire->mask;
|
|
}
|
|
|
|
processing::startEmpireThread(this);
|
|
}
|
|
|
|
void Empire::save(SaveFile& file) {
|
|
file << id << index << name << mask << visionMask << hostileMask << color;
|
|
file << backgroundDef << portraitDef << flagDef << flagID;
|
|
|
|
file.boundary();
|
|
|
|
//Save owned objects
|
|
file << unsigned(objects.size());
|
|
for(unsigned i = 0; i < objects.size(); ++i) {
|
|
Object* obj = objects[i];
|
|
file << obj;
|
|
}
|
|
|
|
if(shipset)
|
|
file.writeIdentifier(SI_Shipset, shipset->id);
|
|
else
|
|
file.writeIdentifier(SI_Shipset, (unsigned)-1);
|
|
|
|
file << effectorSkin;
|
|
|
|
//Save designs
|
|
file << unsigned(designIds.size());
|
|
for(unsigned i = 0; i < designIds.size(); ++i) {
|
|
designIds[i]->save(file);
|
|
}
|
|
|
|
//Save design classes
|
|
file << unsigned(designClassIds.size());
|
|
for(unsigned i = 0; i < designClassIds.size(); ++i) {
|
|
DesignClass* dsgnClass = designClassIds[i];
|
|
file << dsgnClass->name;
|
|
|
|
file << unsigned(dsgnClass->designs.size());
|
|
for(unsigned j = 0; j < dsgnClass->designs.size(); ++j)
|
|
file << dsgnClass->designs[j]->id;
|
|
}
|
|
|
|
//Save design links
|
|
for(unsigned i = 0; i < designIds.size(); ++i) {
|
|
const Design* dsg = designIds[i];
|
|
file << dsg->cls->id;
|
|
|
|
if(dsg->newer)
|
|
file << dsg->newer->id;
|
|
else
|
|
file << (unsigned)-1;
|
|
|
|
if(dsg->updated)
|
|
file << dsg->updated->id;
|
|
else
|
|
file << (unsigned)-1;
|
|
|
|
if(dsg->original)
|
|
file << dsg->original->id;
|
|
else
|
|
file << (unsigned)-1;
|
|
}
|
|
|
|
file.boundary();
|
|
|
|
//Save subsystem data
|
|
unsigned cnt = subsysData.size();
|
|
file << cnt;
|
|
for(unsigned i = 0; i < cnt; ++i) {
|
|
SubsystemData& dat = subsysData[i];
|
|
file.writeIdentifier(SI_Subsystem, i);
|
|
file << dat.unlocked;
|
|
file << dat.nextStageId;
|
|
|
|
unsigned modCnt = dat.modulesUnlocked.size();
|
|
file << modCnt;
|
|
for(unsigned j = 0; j < modCnt; ++j)
|
|
file << (bool)dat.modulesUnlocked[j];
|
|
|
|
//Assumptions: empire stages are always
|
|
//from modifiers in the subsystem root,
|
|
//and cannot have formula modifiers.
|
|
// (Currently satisfied)
|
|
unsigned stageCnt = dat.stages.size();
|
|
file << stageCnt;
|
|
foreach(it, dat.stages) {
|
|
file << it->first;
|
|
auto& stg = it->second;
|
|
file.writeIdentifier(SI_SubsystemModifier, stg.stage->umodifid);
|
|
file.write(stg.arguments, MODIFY_STAGE_MAXARGS * sizeof(float));
|
|
}
|
|
}
|
|
|
|
cnt = statHistories.size();
|
|
file << cnt;
|
|
|
|
unsigned statID = 0;
|
|
for(auto i = statHistories.begin(), end = statHistories.end(); i != end; ++i, ++statID) {
|
|
StatHistory& stat = **i;
|
|
|
|
file << getEmpireStatName(statID);
|
|
|
|
auto* s = stat.getHead();
|
|
while(s) {
|
|
file << s->time;
|
|
file << s->asInt;
|
|
auto* e = s->evt;
|
|
while(e) {
|
|
file << e->type;
|
|
file << e->name;
|
|
e = e->next;
|
|
}
|
|
unsigned short evtEnd = 0xffff;
|
|
file << evtEnd;
|
|
|
|
s = s->next;
|
|
}
|
|
|
|
unsigned statEnd = 0xffffffff;
|
|
file << statEnd;
|
|
}
|
|
|
|
file.boundary();
|
|
|
|
file << (playerEmpire == this);
|
|
}
|
|
|
|
void Empire::saveEmpires(SaveFile& file) {
|
|
file << unsigned(empires.size());
|
|
|
|
for(unsigned i = 0; i < empires.size(); ++i)
|
|
empires[i]->save(file);
|
|
}
|
|
|
|
void Empire::loadEmpires(SaveFile& file) {
|
|
initEmpires();
|
|
unsigned empireCount = file;
|
|
|
|
for(unsigned i = 0; i < empireCount; ++i)
|
|
new Empire(file);
|
|
if(playerEmpire == nullptr)
|
|
playerEmpire = spectatorEmpire;
|
|
}
|