From 27f5f91eae22689d25080d343b84b4404d2ecf1c Mon Sep 17 00:00:00 2001 From: dP Date: Thu, 9 Jul 2020 10:49:39 +0300 Subject: [PATCH] Add more data to savegame --- src/citymania/cm_bitstream.cpp | 23 +++ src/citymania/cm_bitstream.hpp | 5 + src/citymania/cm_saveload.cpp | 171 ++++++++++++--------- src/citymania/extensions/cmext_company.hpp | 7 + src/company_base.h | 2 + 5 files changed, 139 insertions(+), 69 deletions(-) diff --git a/src/citymania/cm_bitstream.cpp b/src/citymania/cm_bitstream.cpp index f23f3bf4a3..7c9782dcda 100644 --- a/src/citymania/cm_bitstream.cpp +++ b/src/citymania/cm_bitstream.cpp @@ -2,6 +2,8 @@ #include "cm_bitstream.hpp" +#include "../safeguards.h" + namespace citymania { // Returns 8 higher bits of s-bit integer x @@ -23,6 +25,11 @@ void BitOStream::WriteBytes64(uint64 value, int amount) } while (--amount); } +void BitOStream::WriteMoney(Money value) { + this->WriteBytes64(value, 8); +} + + /** * Reserves memory for encoded data. Use to avoid reallocation of vector. */ @@ -61,4 +68,20 @@ uint32 BitIStream::ReadBytes(uint amount) 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 diff --git a/src/citymania/cm_bitstream.hpp b/src/citymania/cm_bitstream.hpp index ad55105d8c..9b0748d483 100644 --- a/src/citymania/cm_bitstream.hpp +++ b/src/citymania/cm_bitstream.hpp @@ -1,6 +1,8 @@ #ifndef BITSTREAM_H #define BITSTREAM_H +#include "../economy_type.h" + #include namespace citymania { @@ -19,6 +21,7 @@ public: 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; }; @@ -37,6 +40,8 @@ public: BitIStream(u8vector &data): f(data), i(0) {} virtual ~BitIStream(){} uint32 ReadBytes(uint amount); + uint64 ReadBytes64(uint amount); + Money ReadMoney(); }; } // namespace citymania diff --git a/src/citymania/cm_saveload.cpp b/src/citymania/cm_saveload.cpp index 832b2cdcd6..9d1d2026e8 100644 --- a/src/citymania/cm_saveload.cpp +++ b/src/citymania/cm_saveload.cpp @@ -1,5 +1,6 @@ #include "../stdafx.h" +#include "../company_base.h" #include "../date_func.h" #include "../debug.h" #include "../saveload/saveload.h" @@ -25,7 +26,7 @@ static const SaveLoad _storage_desc[] = { }; -static void EncodeTownsExtraInfo(BitOStream &bs) +static void EncodeTowns(BitOStream &bs) { for (const Town *t : Town::Iterate()) { bs.WriteBytes(t->cm.growing_by_chance, 1); @@ -33,9 +34,38 @@ static void EncodeTownsExtraInfo(BitOStream &bs) 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); @@ -46,43 +76,6 @@ static void EncodeTownsGrowthTiles(BitOStream &bs, Game::TownsGrowthTilesIndex & } } -static void EncodeTownsLayoutErrors(BitOStream &bs) -{ - uint n_affected_towns = 0; - for (const Town *t : Town::Iterate()) { - if (t->cm.hr_total || t->cm.hs_total || t->cm.cs_total) - n_affected_towns++; - } - bs.WriteBytes(n_affected_towns, 2); - for (const Town *t : Town::Iterate()) { - if (t->cm.hr_total || t->cm.hs_total || t->cm.cs_total) { - bs.WriteBytes(t->index, 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); - } - } - EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles); - EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month); -} - -static void DecodeTownsExtraInfo(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); - } -} - static void DecodeTownsGrowthTiles(BitIStream &bs, Game::TownsGrowthTilesIndex &tiles) { uint n = bs.ReadBytes(4); @@ -93,29 +86,30 @@ static void DecodeTownsGrowthTiles(BitIStream &bs, Game::TownsGrowthTilesIndex & } } -static void DecodeTownsLayoutErrors(BitIStream &bs) +static void EncodeCompanies(BitOStream &bs) { - 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); + 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) { @@ -247,14 +241,24 @@ static u8vector EncodeData() { bs.WriteBytes(0, 3); // Reserved bs.WriteBytes(0, 4); // Reserved - EncodeTownsExtraInfo(bs); - EncodeTownsLayoutErrors(bs); + 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) { CB_SetStorage(bs.ReadBytes(1)); @@ -296,13 +300,43 @@ static void DecodeTownsCargoV1(BitIStream &bs) } } +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; - } else { - DEBUG(sl, 2, "Ignoring CityMania data in savegame"); - return; } DEBUG(sl, 2, "CityMania save data takes %lu bytes", data.size()); BitIStream bs(data); @@ -312,6 +346,7 @@ static void DecodeData(u8vector &data) { 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); @@ -324,10 +359,8 @@ static void DecodeData(u8vector &data) { _game_type = bs.ReadBytes(1); bs.ReadBytes(3); // reserved bs.ReadBytes(4); // reserved - if (version <= 1 && _controller_type != 0) DecodeTownsCargoV1(bs); - DecodeTownsExtraInfo(bs); - DecodeTownsLayoutErrors(bs); - if (version > 1 && _controller_type == 4) CBController_saveload_decode(bs); + if (version == 1) DecodeDataV1(bs); + else DecodeDataV2(bs); } catch (BitIStreamUnexpectedEnd &e) { DEBUG(sl, 0, "Invalid CityMania save data"); diff --git a/src/citymania/extensions/cmext_company.hpp b/src/citymania/extensions/cmext_company.hpp index 131c3e8b9b..5bca9ec691 100644 --- a/src/citymania/extensions/cmext_company.hpp +++ b/src/citymania/extensions/cmext_company.hpp @@ -6,11 +6,18 @@ namespace citymania { namespace ext { + class CompanyEconomyEntry { public: Money cargo_income[NUM_CARGO]; ///< Cargo income from each cargo type }; +class Company { +public: + bool is_server; ///< whether company is controlled by the server + bool is_scored; ///< whether company is eligible for scoring +}; + } // namespace ext } // namespace citymania diff --git a/src/company_base.h b/src/company_base.h index df5137f74a..646889e1de 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -100,6 +100,8 @@ struct CompanyProperties { CompanyEconomyEntry old_economy[MAX_HISTORY_QUARTERS]; ///< Economic data of the company of the last #MAX_HISTORY_QUARTERS quarters. byte num_valid_stat_ent; ///< Number of valid statistical entries in #old_economy. + citymania::ext::Company cm; ///< CityMania extra company data. + // TODO: Change some of these member variables to use relevant INVALID_xxx constants CompanyProperties() : name_2(0), name_1(0), name(nullptr), president_name_1(0), president_name_2(0), president_name(nullptr),