Add CityMania savegame data (a bit broken for now)

This commit is contained in:
dP
2020-07-06 16:47:03 +03:00
parent eed1731bd8
commit 889b833af1
7 changed files with 595 additions and 3 deletions

View File

@@ -1193,6 +1193,8 @@ sound/null_s.cpp
thread.h
# CityMania
citymania/cm_bitstream.hpp
citymania/cm_bitstream.cpp
citymania/cm_console_cmds.hpp
citymania/cm_console_cmds.cpp
citymania/cm_event.hpp
@@ -1203,5 +1205,8 @@ citymania/cm_game.cpp
citymania/cm_main.hpp
citymania/cm_main.cpp
citymania/cm_type.hpp
citymania/cm_saveload.hpp
citymania/cm_saveload.cpp
citymania/cm_settings.hpp
citymania/extensions/cmext_town.hpp
citymania/extensions/cmext_company.hpp

View File

@@ -0,0 +1,87 @@
#include "../stdafx.h"
#include "cm_bitstream.hpp"
#include "../safeguards.h"
namespace citymania {
// Returns 8 higher bits of s-bit integer x
#define FIRST8(x, s) (((x) >> ((s) - 8)) & 0xFF)
void BitOStream::WriteBytes(uint32 value, int amount)
{
do {
v = (v << 8) | FIRST8(value, amount << 3);
f.push_back(FIRST8(v, c + 8));
} while (--amount);
}
void BitOStream::WriteBytes64(uint64 value, int amount)
{
do {
v = (v << 8) | FIRST8(value, amount << 3);
f.push_back(FIRST8(v, c + 8));
} while (--amount);
}
void BitOStream::WriteMoney(Money value) {
this->WriteBytes64(value, 8);
}
/**
* Reserves memory for encoded data. Use to avoid reallocation of vector.
*/
void BitOStream::Reserve(int bytes)
{
f.reserve(bytes);
}
/**
* Returns encoded data
*/
const u8vector &BitOStream::GetVector()
{
return f;
}
/**
* Size of encoded data in bytes
*/
uint BitOStream::GetByteSize() const
{
return f.size() + (c ? 1 : 0);
}
BitIStreamUnexpectedEnd _bit_i_stream_unexpected_end;
uint32 BitIStream::ReadBytes(uint amount)
{
uint32 res = 0;
while (amount--) {
if (i >= f.size()) {
throw _bit_i_stream_unexpected_end;
}
res = (res << 8) | f[i++];
}
return res;
}
uint64 BitIStream::ReadBytes64(uint amount)
{
uint64 res = 0;
while (amount--) {
if (i >= f.size()) {
throw _bit_i_stream_unexpected_end;
}
res = (res << 8) | f[i++];
}
return res;
}
Money BitIStream::ReadMoney() {
return (Money)this->ReadBytes64(8);
}
} // namespace citymania

View File

@@ -0,0 +1,49 @@
#ifndef BITSTREAM_H
#define BITSTREAM_H
#include "../economy_type.h"
#include <vector>
namespace citymania {
typedef std::vector<uint8> u8vector;
class BitOStream {
protected:
u8vector f;
uint32 c;
uint32 v;
public:
BitOStream(): c(0), v(0) {}
virtual ~BitOStream(){}
void Reserve(int bytes);
void WriteBytes(uint32 value, int amount);
void WriteBytes64(uint64 value, int amount);
void WriteMoney(Money value);
const u8vector &GetVector();
uint GetByteSize() const;
};
class BitIStreamUnexpectedEnd: public std::exception {
virtual const char* what() const throw() {
return "Unexpected end of bit input stream";
}
};
class BitIStream {
protected:
u8vector &f;
uint32 i;
public:
BitIStream(u8vector &data): f(data), i(0) {}
virtual ~BitIStream(){}
uint32 ReadBytes(uint amount);
uint64 ReadBytes64(uint amount);
Money ReadMoney();
};
} // namespace citymania
#endif

View File

@@ -19,12 +19,11 @@ enum class TownGrowthTileState : uint8 {
};
class Game {
protected:
public:
typedef std::map<TileIndex, TownGrowthTileState> TownsGrowthTilesIndex;
TownsGrowthTilesIndex towns_growth_tiles_last_month;
TownsGrowthTilesIndex towns_growth_tiles;
public:
event::Dispatcher events;
Game();

View File

@@ -0,0 +1,440 @@
#include "../stdafx.h"
#include "../company_base.h"
#include "../date_func.h"
#include "../debug.h"
#include "../saveload/saveload.h"
#include "../town.h"
#include "cm_bitstream.hpp"
#include "cm_saveload.hpp"
#include "cm_game.hpp"
#include "cm_main.hpp"
namespace citymania {
static const uint SAVEGAME_DATA_FORMAT_VERSION = 2;
static const uint CITYMANIA_GRFID = 0x534B0501U; // Luukland_Citybuilder grf id actually
// copied form storage_sl.cpp
static const SaveLoad _storage_desc[] = {
SLE_CONDVAR(PersistentStorage, grfid, SLE_UINT32, SLV_6, SL_MAX_VERSION),
SLE_CONDARR(PersistentStorage, storage, SLE_UINT32, 16, SLV_161, SLV_EXTEND_PERSISTENT_STORAGE),
SLE_CONDARR(PersistentStorage, storage, SLE_UINT32, 256, SLV_EXTEND_PERSISTENT_STORAGE, SL_MAX_VERSION),
SLE_END()
};
static void EncodeTowns(BitOStream &bs)
{
for (const Town *t : Town::Iterate()) {
bs.WriteBytes(t->cm.growing_by_chance, 1);
bs.WriteBytes(t->cm.houses_reconstructed_this_month, 2);
bs.WriteBytes(t->cm.houses_reconstructed_last_month, 2);
bs.WriteBytes(t->cm.houses_demolished_this_month, 2);
bs.WriteBytes(t->cm.houses_demolished_last_month, 2);
bs.WriteBytes(t->cm.hs_total, 4);
bs.WriteBytes(t->cm.hs_total_prev, 2);
bs.WriteBytes(t->cm.hs_last_month, 2);
bs.WriteBytes(t->cm.cs_total, 4);
bs.WriteBytes(t->cm.cs_total_prev, 2);
bs.WriteBytes(t->cm.cs_last_month, 2);
bs.WriteBytes(t->cm.hr_total, 4);
bs.WriteBytes(t->cm.hr_total_prev, 2);
bs.WriteBytes(t->cm.hr_last_month, 2);
}
}
static void DecodeTowns(BitIStream &bs)
{
for (Town *t : Town::Iterate()) {
t->cm.growing_by_chance = bs.ReadBytes(1);
t->cm.houses_reconstructed_this_month = bs.ReadBytes(2);
t->cm.houses_reconstructed_last_month = bs.ReadBytes(2);
t->cm.houses_demolished_this_month = bs.ReadBytes(2);
t->cm.houses_demolished_last_month = bs.ReadBytes(2);
t->cm.hs_total = bs.ReadBytes(2);
t->cm.hs_total_prev = bs.ReadBytes(2);
t->cm.hs_last_month = bs.ReadBytes(2);
t->cm.cs_total = bs.ReadBytes(2);
t->cm.cs_total_prev = bs.ReadBytes(2);
t->cm.cs_last_month = bs.ReadBytes(2);
t->cm.hr_total = bs.ReadBytes(2);
t->cm.hr_total_prev = bs.ReadBytes(2);
t->cm.hr_last_month = bs.ReadBytes(2);
}
}
static void EncodeTownsGrowthTiles(BitOStream &bs, Game::TownsGrowthTilesIndex &tiles)
{
bs.WriteBytes(tiles.size(), 4);
for (Game::TownsGrowthTilesIndex::iterator p = tiles.begin();
p != tiles.end(); p++) {
bs.WriteBytes(p->first, 4);
bs.WriteBytes((uint8)p->second, 1);
}
}
static void DecodeTownsGrowthTiles(BitIStream &bs, Game::TownsGrowthTilesIndex &tiles)
{
uint n = bs.ReadBytes(4);
for (uint i = 0; i < n; i++) {
TileIndex tile = bs.ReadBytes(4);
TownGrowthTileState state = (TownGrowthTileState)bs.ReadBytes(1);
tiles[tile] = state;
}
}
static void EncodeCompanies(BitOStream &bs)
{
for (const Company *c : Company::Iterate()) {
bs.WriteBytes(c->cm.is_server, 1);
bs.WriteBytes(c->cm.is_scored, 1);
for (uint i = 0; i < NUM_CARGO; i++)
bs.WriteMoney(c->cur_economy.cm.cargo_income[i]);
for (uint j = 0; j < MAX_HISTORY_QUARTERS; j++)
for (uint i = 0; i < NUM_CARGO; i++)
bs.WriteMoney(c->old_economy[j].cm.cargo_income[i]);
}
}
static void DecodeCompanies(BitIStream &bs)
{
for (Company *c : Company::Iterate()) {
c->cm.is_server = bs.ReadBytes(1);
c->cm.is_scored = bs.ReadBytes(1);
for (uint i = 0; i < NUM_CARGO; i++)
c->cur_economy.cm.cargo_income[i] = bs.ReadMoney();
for (uint j = 0; j < MAX_HISTORY_QUARTERS; j++)
for (uint i = 0; i < NUM_CARGO; i++)
c->old_economy[j].cm.cargo_income[i] = bs.ReadMoney();
}
}
void CBController_saveload_encode(BitOStream &bs) {
bs.WriteBytes(0 /* version */, 2);
// Controller::saveload_encode(bs);
bs.WriteBytes(0, 1);
bs.WriteBytes(0, 1);
// bs.WriteBytes(_settings_client.gui.cb_distance_check, 1);
bs.WriteBytes(0, 1);
bs.WriteBytes(0, 1);
bs.WriteBytes(0, 1);
bs.WriteBytes(0, 1);
bs.WriteBytes(0, 1);
bs.WriteBytes(0, 1);
std::vector<CargoID> cb_cargos;
// for(CargoID cargo = 0; cargo < NUM_CARGO; cargo++) {
// if (CB_GetReq(cargo) > 0)
// cb_cargos.push_back(cargo);
// }
// for (auto cargo_id : cb_cargos) {
// // bs.WriteBytes(req.cargo_id, 1);
// // bs.WriteBytes(req.amount, 4);
// // bs.WriteBytes(req.from, 4);
// // bs.WriteBytes(req.decay, 1);
// bs.WriteBytes(cargo_id, 1);
// bs.WriteBytes(CB_GetReq(cargo_id), 4);
// bs.WriteBytes(CB_GetFrom(cargo_id), 4);
// bs.WriteBytes(CB_GetDecay(cargo_id), 1);
// }
// // uint16 cb_towns = 0;
// // ForEachCBTown([this, &cb_towns](Town *, Company *) { cb_towns++; });
// bs.WriteBytes(Town::GetNumItems(), 2);
// for (Town *t : Town::Iterate()) {
// auto &tcb = t->cb;
// bs.WriteBytes(t->index, 2);
// bs.WriteBytes(tcb.pax_delivered, 4);
// bs.WriteBytes(tcb.mail_delivered, 4);
// bs.WriteBytes(tcb.pax_delivered_last_month, 4);
// bs.WriteBytes(tcb.mail_delivered_last_month, 4);
// bs.WriteBytes((uint8)tcb.growth_state, 1);
// bs.WriteBytes(tcb.shrink_effeciency, 1);
// bs.WriteBytes(tcb.shrink_rate, 2);
// bs.WriteBytes(tcb.shrink_counter, 2);
// for (auto cargo_id : cb_cargos) {
// bs.WriteBytes(tcb.stored[cargo_id], 4);
// bs.WriteBytes(tcb.delivered[cargo_id], 4);
// bs.WriteBytes(tcb.required[cargo_id], 4);
// bs.WriteBytes(tcb.delivered_last_month[cargo_id], 4);
// bs.WriteBytes(tcb.required_last_month[cargo_id], 4);
// }
// }
}
bool CBController_saveload_decode(BitIStream &bs) {
auto version = bs.ReadBytes(2);
// if (version != 0)
// ConsoleError("Save uses incompatible CB data version {}", version);
// ConsoleError("Server is not supposed to load CB games");
// if (!Controller::saveload_decode(bs))
// return false;
bs.ReadBytes(1); /* _settings_game.citymania.cb.requirements_type */
bs.ReadBytes(1); /* _settings_game.citymania.cb.acceptance_range */
// _settings_client.gui.cb_distance_check = bs.ReadBytes(1); /* _settings_game.citymania.cb.acceptance_range */
bs.ReadBytes(1); /* _settings_game.citymania.cb.storage_size */
bs.ReadBytes(1); /* _settings_game.citymania.cb.town_protection_range */
bs.ReadBytes(2); /* _settings_game.citymania.cb.claim_max_houses */
bs.ReadBytes(1); /* _settings_game.citymania.cb.smooth_growth_rate */
bs.ReadBytes(1); /* _settings_game.citymania.cb.allow_negative_growth */
// _settings_game.citymania.cb.requirements.clear();
auto n_cargos = bs.ReadBytes(1);
std::vector<CargoID> cb_cargos;
for (uint i = 0; i < n_cargos; i++) {
auto cargo_id = (CargoID)bs.ReadBytes(1);
if (cargo_id >= NUM_CARGO) {
DEBUG(sl, 0, "Invalid CargoID in CB towns cargo data (%u)", cargo_id);
// ConsoleError("Invalid CargoID in CB towns cargo data ({})", cargo_id);
return false;
}
cb_cargos.push_back(cargo_id);
auto required = bs.ReadBytes(4);
auto from = bs.ReadBytes(4);
auto decay = bs.ReadBytes(1);
// CB_SetRequirements(cargo_id, required, from, decay);
// _settings_game.citymania.cb.requirements.push_back(CBRequirement(cargo_id, from, required, decay, i, ""));
}
auto n_towns = bs.ReadBytes(2);
for (uint i = 0; i < n_towns; i++) {
auto town_id = bs.ReadBytes(2);
auto t = Town::GetIfValid(town_id);
if (!t) {
DEBUG(sl, 0, "Invalid TownID in CB towns cargo data (%u)", town_id);
// ConsoleError("Invalid TownID in CB towns cargo data ({})", town_id);
return false;
}
// auto &tcb = t->cb;
// tcb.pax_delivered = bs.ReadBytes(4);
// tcb.mail_delivered = bs.ReadBytes(4);
// tcb.pax_delivered_last_month = bs.ReadBytes(4);
// tcb.mail_delivered_last_month = bs.ReadBytes(4);
// tcb.growth_state = (TownGrowthState)bs.ReadBytes(1);
// tcb.shrink_effeciency = bs.ReadBytes(1);
// tcb.shrink_rate = bs.ReadBytes(2);
// tcb.shrink_counter = bs.ReadBytes(2);
// for (auto cargo_id : cb_cargos) {
// tcb.stored[cargo_id] = bs.ReadBytes(4);
// tcb.delivered[cargo_id] = bs.ReadBytes(4);
// tcb.required[cargo_id] = bs.ReadBytes(4);
// tcb.delivered_last_month[cargo_id] = bs.ReadBytes(4);
// tcb.required_last_month[cargo_id] = bs.ReadBytes(4);
// }
};
return true;
}
uint8 _controller_type = 0;
uint8 _game_type = 0;
uint16 _last_client_version = 1512;
static u8vector EncodeData() {
BitOStream bs;
bs.Reserve(1000);
bs.WriteBytes(SAVEGAME_DATA_FORMAT_VERSION, 2);
bs.WriteBytes(_last_client_version, 2);
bs.WriteBytes(_controller_type, 1);
bs.WriteBytes(_date, 4); // Just in case we'll need to detect that game
bs.WriteBytes(_date_fract, 1); // was saved by unmodified client
bs.WriteBytes(_game_type, 1);
bs.WriteBytes(0, 3); // Reserved
bs.WriteBytes(0, 4); // Reserved
EncodeCompanies(bs);
EncodeTowns(bs);
EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles);
EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month);
if (_controller_type == 4)
CBController_saveload_encode(bs);
return bs.GetVector();
}
static void DecodeDataV2(BitIStream &bs) {
DecodeCompanies(bs);
DecodeTowns(bs);
DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles);
DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month);
if (_controller_type == 4) CBController_saveload_decode(bs);
}
static void DecodeTownsCargoV1(BitIStream &bs)
{
bs.ReadBytes(1);bs.ReadBytes(1);
// CB_SetStorage(bs.ReadBytes(1));
// _settings_client.gui.cb_distance_check = bs.ReadBytes(1);
uint n_cb_cargos = bs.ReadBytes(1);
// CB_ResetRequirements();
std::vector<CargoID> cb_cargos;
for (uint i = 0; i < n_cb_cargos; i++) {
uint cargo = bs.ReadBytes(1);
if (cargo >= NUM_CARGO) {
DEBUG(sl, 0, "Invalid CargoID in CB towns cargo data (%u)", cargo);
return;
}
cb_cargos.push_back(cargo);
uint req = bs.ReadBytes(4);
uint from = bs.ReadBytes(4);
uint decay = bs.ReadBytes(1);
// CB_SetRequirements(cargo, req, from, decay);
}
Town *t;
uint n_affected_towns = bs.ReadBytes(2);
for (uint i = 0; i < n_affected_towns; i++) {
uint town_id = bs.ReadBytes(2);
t = Town::GetIfValid(town_id);
if (!t) {
DEBUG(sl, 0, "Invalid TownID in CB towns cargo data (%u)", town_id);
return;
}
// auto &tcb = t->cb;
// for (auto cargo_id : cb_cargos) {
// tcb.stored[cargo_id] = bs.ReadBytes(4);
// tcb.delivered_last_month[cargo_id] = bs.ReadBytes(4);
// tcb.delivered[cargo_id] = bs.ReadBytes(4);
// bs.ReadBytes(1); /* delivered enough */
// tcb.required[cargo_id] = 0;
// tcb.required_last_month[cargo_id] = 0;
// }
}
}
static void DecodeDataV1(BitIStream &bs) {
if (_controller_type != 0) DecodeTownsCargoV1(bs);
for (Town *t : Town::Iterate()) {
t->cm.growing_by_chance = bs.ReadBytes(1);
t->cm.houses_reconstructed_this_month = bs.ReadBytes(2);
t->cm.houses_reconstructed_last_month = bs.ReadBytes(2);
t->cm.houses_demolished_this_month = bs.ReadBytes(2);
t->cm.houses_demolished_last_month = bs.ReadBytes(2);
}
Town *t;
uint n_affected_towns = bs.ReadBytes(2);
for (uint i = 0; i < n_affected_towns; i++) {
uint town_id = bs.ReadBytes(2);
t = Town::Get(town_id);
if (!t) {
DEBUG(sl, 0, "Invalid TownID in CB towns layout errors (%u)", town_id);
continue;
}
t->cm.hs_total = bs.ReadBytes(2);
t->cm.hs_total_prev = bs.ReadBytes(2);
t->cm.hs_last_month = bs.ReadBytes(2);
t->cm.cs_total = bs.ReadBytes(2);
t->cm.cs_total_prev = bs.ReadBytes(2);
t->cm.cs_last_month = bs.ReadBytes(2);
t->cm.hr_total = bs.ReadBytes(2);
t->cm.hr_total_prev = bs.ReadBytes(2);
t->cm.hr_last_month = bs.ReadBytes(2);
}
DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles);
DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month);
}
static void DecodeData(u8vector &data) {
if (data.size() == 0) {
DEBUG(sl, 2, "No CityMania save data");
return;
}
DEBUG(sl, 2, "CityMania save data takes %lu bytes", data.size());
BitIStream bs(data);
try {
uint version = bs.ReadBytes(2);
if (version > SAVEGAME_DATA_FORMAT_VERSION) {
DEBUG(sl, 0, "Savegame was made with newer version of client, extra save data was not loaded");
return;
}
DEBUG(sl, 2, "CityMania savegame data version %u", version);
_last_client_version = bs.ReadBytes(2);
_controller_type = bs.ReadBytes(1);
if (version <= 1) _controller_type = (_controller_type ? 4 : 0);
int32 date = bs.ReadBytes(4);
uint32 date_fract = bs.ReadBytes(1);
if (date != _date || date_fract != _date_fract) {
DEBUG(sl, 0, "Savegame was run in unmodified client, extra save data "
"preserved, but may not be accurate");
}
_game_type = bs.ReadBytes(1);
bs.ReadBytes(3); // reserved
bs.ReadBytes(4); // reserved
if (version == 1) DecodeDataV1(bs);
else DecodeDataV2(bs);
}
catch (BitIStreamUnexpectedEnd &e) {
DEBUG(sl, 0, "Invalid CityMania save data");
}
}
void Save_PSAC() {
/* Write the industries */
for (PersistentStorage *ps : PersistentStorage::Iterate()) {
if (ps->grfid == CITYMANIA_GRFID) {
continue;
}
ps->ClearChanges();
SlSetArrayIndex(ps->index);
SlObject(ps, _storage_desc);
}
uint32 grfid = CITYMANIA_GRFID;
u8vector data = EncodeData();
int n_chunks = (data.size() + 1023) / 1024;
data.resize(n_chunks * 1024);
DEBUG(sl, 2, "Citybuilder data takes %u bytes", (uint)data.size());
uint8 *ptr = &data[0];
SaveLoadGlobVarList _desc[] = {
SLEG_CONDVAR(grfid, SLE_UINT32, SLV_6, SL_MAX_VERSION),
SLEG_CONDARR(*ptr, SLE_UINT32, 256, SLV_EXTEND_PERSISTENT_STORAGE, SL_MAX_VERSION),
SLEG_END()
};
uint index = 0;
for (PersistentStorage *ps : PersistentStorage::Iterate()) {
if (ps->grfid != CITYMANIA_GRFID)
index = max(index, ps->index + 1);
}
for (int i = 0; i < n_chunks; i++, ptr += 1024) {
_desc[1].address = (void *)ptr;
SlSetArrayIndex(index + i);
SlGlobList(_desc);
}
}
void Load_PSAC()
{
int index;
/*
CITYMANIA_GRFID is used to hide extra data in persitant storages.
To save a bit of memory we only keep at most one PS with this
grfid and it is later discarded on save.
*/
PersistentStorage *ps = NULL;
u8vector cmdata;
uint chunk_size = IsSavegameVersionBefore(SLV_EXTEND_PERSISTENT_STORAGE) ? 64 : 1024;
while ((index = SlIterateArray()) != -1) {
if (ps == NULL) {
assert(PersistentStorage::CanAllocateItem());
ps = new (index) PersistentStorage(0, 0, 0);
}
SlObject(ps, _storage_desc);
if (ps->grfid == CITYMANIA_GRFID) {
uint8 *data = (uint8 *)(ps->storage);
cmdata.insert(cmdata.end(), data, data + chunk_size);
} else {
ps = NULL;
}
}
DecodeData(cmdata);
}
} // namespace citymania

View File

@@ -0,0 +1,11 @@
#ifndef CITYMANIA_SAVELOAD_HPP
#define CITYMANIA_SAVELOAD_HPP
namespace citymania {
void Save_PSAC();
void Load_PSAC();
} // namespace citymania
#endif

View File

@@ -9,6 +9,7 @@
#include "../stdafx.h"
#include "../newgrf_storage.h"
#include "../citymania/cm_saveload.hpp"
#include "saveload.h"
#include "../safeguards.h"
@@ -46,5 +47,5 @@ static void Save_PSAC()
/** Chunk handler for persistent storages. */
extern const ChunkHandler _persistent_storage_chunk_handlers[] = {
{ 'PSAC', Save_PSAC, Load_PSAC, nullptr, nullptr, CH_ARRAY | CH_LAST},
{ 'PSAC', citymania::Save_PSAC, citymania::Load_PSAC, nullptr, nullptr, CH_ARRAY | CH_LAST},
};