From eed1731bd878bc712b0629906014e565b18eab38 Mon Sep 17 00:00:00 2001 From: dP Date: Mon, 6 Jul 2020 14:46:48 +0300 Subject: [PATCH 1/6] Add is_server and is_scored flags to a company --- src/citymania/extensions/cmext_company.hpp | 6 ++++++ src/company_base.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/citymania/extensions/cmext_company.hpp b/src/citymania/extensions/cmext_company.hpp index 57b73a826c..bd6c606f2f 100644 --- a/src/citymania/extensions/cmext_company.hpp +++ b/src/citymania/extensions/cmext_company.hpp @@ -11,6 +11,12 @@ 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 citymania } // 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), From 889b833af1e3d1798ec36cd0a438c0f15cee6e94 Mon Sep 17 00:00:00 2001 From: dP Date: Mon, 6 Jul 2020 16:47:03 +0300 Subject: [PATCH 2/6] Add CityMania savegame data (a bit broken for now) --- source.list | 5 + src/citymania/cm_bitstream.cpp | 87 +++++++ src/citymania/cm_bitstream.hpp | 49 ++++ src/citymania/cm_game.hpp | 3 +- src/citymania/cm_saveload.cpp | 440 +++++++++++++++++++++++++++++++++ src/citymania/cm_saveload.hpp | 11 + src/saveload/storage_sl.cpp | 3 +- 7 files changed, 595 insertions(+), 3 deletions(-) create mode 100644 src/citymania/cm_bitstream.cpp create mode 100644 src/citymania/cm_bitstream.hpp create mode 100644 src/citymania/cm_saveload.cpp create mode 100644 src/citymania/cm_saveload.hpp diff --git a/source.list b/source.list index 7aa7e52f5f..5944c757b9 100644 --- a/source.list +++ b/source.list @@ -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 diff --git a/src/citymania/cm_bitstream.cpp b/src/citymania/cm_bitstream.cpp new file mode 100644 index 0000000000..7c9782dcda --- /dev/null +++ b/src/citymania/cm_bitstream.cpp @@ -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 diff --git a/src/citymania/cm_bitstream.hpp b/src/citymania/cm_bitstream.hpp new file mode 100644 index 0000000000..9b0748d483 --- /dev/null +++ b/src/citymania/cm_bitstream.hpp @@ -0,0 +1,49 @@ +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#include "../economy_type.h" + +#include + +namespace citymania { + +typedef std::vector 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 diff --git a/src/citymania/cm_game.hpp b/src/citymania/cm_game.hpp index b7942a627e..a2ddbef41d 100644 --- a/src/citymania/cm_game.hpp +++ b/src/citymania/cm_game.hpp @@ -19,12 +19,11 @@ enum class TownGrowthTileState : uint8 { }; class Game { -protected: +public: typedef std::map TownsGrowthTilesIndex; TownsGrowthTilesIndex towns_growth_tiles_last_month; TownsGrowthTilesIndex towns_growth_tiles; -public: event::Dispatcher events; Game(); diff --git a/src/citymania/cm_saveload.cpp b/src/citymania/cm_saveload.cpp new file mode 100644 index 0000000000..c9b272a3a9 --- /dev/null +++ b/src/citymania/cm_saveload.cpp @@ -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 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 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 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 diff --git a/src/citymania/cm_saveload.hpp b/src/citymania/cm_saveload.hpp new file mode 100644 index 0000000000..569feefb6a --- /dev/null +++ b/src/citymania/cm_saveload.hpp @@ -0,0 +1,11 @@ +#ifndef CITYMANIA_SAVELOAD_HPP +#define CITYMANIA_SAVELOAD_HPP + +namespace citymania { + +void Save_PSAC(); +void Load_PSAC(); + +} // namespace citymania + +#endif diff --git a/src/saveload/storage_sl.cpp b/src/saveload/storage_sl.cpp index 7abe396f38..1be1047ee3 100644 --- a/src/saveload/storage_sl.cpp +++ b/src/saveload/storage_sl.cpp @@ -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}, }; From 130c301c2c7642e514947f2376247c0498a38a3c Mon Sep 17 00:00:00 2001 From: dP Date: Mon, 6 Jul 2020 23:24:52 +0300 Subject: [PATCH 3/6] Add some citymania settings to extra save data --- src/citymania/cm_saveload.cpp | 50 +++++++++++++++++++------ src/citymania/cm_settings.hpp | 69 +++++++++++++++++++++++++++++++++++ src/settings_type.h | 4 ++ 3 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 src/citymania/cm_settings.hpp diff --git a/src/citymania/cm_saveload.cpp b/src/citymania/cm_saveload.cpp index c9b272a3a9..e5861c2ba3 100644 --- a/src/citymania/cm_saveload.cpp +++ b/src/citymania/cm_saveload.cpp @@ -8,6 +8,7 @@ #include "cm_bitstream.hpp" #include "cm_saveload.hpp" +#include "cm_settings.hpp" #include "cm_game.hpp" #include "cm_main.hpp" @@ -227,8 +228,34 @@ bool CBController_saveload_decode(BitIStream &bs) { return true; } -uint8 _controller_type = 0; -uint8 _game_type = 0; +void EncodeSettings(BitOStream &bs, Settings &settings) { + bs.WriteBytes(settings.max_players_in_company, 1); + bs.WriteBytes(settings.destroyed_houses_per_month, 2); + bs.WriteBytes(settings.game_length_years, 2); + bs.WriteBytes(settings.protect_funded_industries, 1); + bs.WriteBytes(settings.same_depot_sell_years, 2); + bs.WriteBytes(settings.economy.cashback_for_extra_land_clear, 1); + bs.WriteBytes(settings.economy.cashback_for_bridges_and_tunnels, 1); + bs.WriteBytes(settings.economy.cashback_for_foundations, 1); + bs.WriteBytes(settings.limits.max_airports, 2); + bs.WriteBytes(settings.limits.disable_canals, 1); + bs.WriteBytes(settings.limits.min_distance_between_docks, 2); +} + +void DecodeSettings(BitIStream &bs, Settings &settings) { + settings.max_players_in_company = bs.ReadBytes(1); + settings.destroyed_houses_per_month = bs.ReadBytes(2); + settings.game_length_years = bs.ReadBytes(2); + settings.protect_funded_industries = bs.ReadBytes(1); + settings.same_depot_sell_years = bs.ReadBytes(2); + settings.economy.cashback_for_extra_land_clear = bs.ReadBytes(1); + settings.economy.cashback_for_bridges_and_tunnels = bs.ReadBytes(1); + settings.economy.cashback_for_foundations = bs.ReadBytes(1); + settings.limits.max_airports = bs.ReadBytes(2); + settings.limits.disable_canals = bs.ReadBytes(1); + settings.limits.min_distance_between_docks = bs.ReadBytes(2); +} + uint16 _last_client_version = 1512; static u8vector EncodeData() { @@ -236,29 +263,30 @@ static u8vector EncodeData() { bs.Reserve(1000); bs.WriteBytes(SAVEGAME_DATA_FORMAT_VERSION, 2); bs.WriteBytes(_last_client_version, 2); - bs.WriteBytes(_controller_type, 1); + bs.WriteBytes(_settings_game.cm.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(_settings_game.cm.game_type, 1); bs.WriteBytes(0, 3); // Reserved bs.WriteBytes(0, 4); // Reserved - + EncodeSettings(bs, _settings_game.cm); EncodeCompanies(bs); EncodeTowns(bs); EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles); EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month); - if (_controller_type == 4) + if (_settings_game.cm.controller_type == 4) CBController_saveload_encode(bs); return bs.GetVector(); } static void DecodeDataV2(BitIStream &bs) { + DecodeSettings(bs, _settings_game.cm); 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); + if (_settings_game.cm.controller_type == 4) CBController_saveload_decode(bs); } static void DecodeTownsCargoV1(BitIStream &bs) @@ -304,7 +332,7 @@ static void DecodeTownsCargoV1(BitIStream &bs) } static void DecodeDataV1(BitIStream &bs) { - if (_controller_type != 0) DecodeTownsCargoV1(bs); + if (_settings_game.cm.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); @@ -351,15 +379,15 @@ static void DecodeData(u8vector &data) { } 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); + _settings_game.cm.controller_type = bs.ReadBytes(1); + if (version <= 1) _settings_game.cm.controller_type = (_settings_game.cm.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); + _settings_game.cm.game_type = bs.ReadBytes(1); bs.ReadBytes(3); // reserved bs.ReadBytes(4); // reserved if (version == 1) DecodeDataV1(bs); diff --git a/src/citymania/cm_settings.hpp b/src/citymania/cm_settings.hpp new file mode 100644 index 0000000000..fafb17bcf7 --- /dev/null +++ b/src/citymania/cm_settings.hpp @@ -0,0 +1,69 @@ +#ifndef CM_SETTINGS_HPP +#define CM_SETTINGS_HPP + +#include +#include + +// #include "types.hpp" + +namespace citymania { + +class CBRequirement { +public: + CargoID cargo_id; + uint32 from; + uint32 amount; + uint8 decay; + uint8 index; + std::string name; + bool has_storage; + + static CBRequirement Parse(const char *name, const char *value, uint8 index); + + CBRequirement(CargoID cargo_id, uint32 from, uint32 amount, uint8 decay, + uint8 index, std::string name) + :cargo_id{cargo_id}, from{from}, amount{amount}, decay{decay}, + index{index}, name{name}, has_storage{decay < 100} {} +}; + +struct EconomySettings { + bool cashback_for_extra_land_clear; + bool cashback_for_bridges_and_tunnels; + bool cashback_for_foundations; +}; + +struct LimitsSettings { + uint16 max_airports; ///< maximum number of airports per company, 0=unlimited + bool disable_canals; + uint16 min_distance_between_docks; ///< docks can be build only x tiles apart another, 0=disable check +}; + + +struct CBSettings { + uint8 requirements_type; // 0 - regular 1 - income-based requirements (new cb only) + std::vector requirements; + uint8 acceptance_range; // How far can station be to count towards requiremnts + uint8 storage_size; // cargo storage multiplier (x * monthly requirements) + uint8 town_protection_range; // Claimed town protection range (square from centre), overlaped with tz0 + uint16 claim_max_houses; // Max amount of houses claimable town can have + bool smooth_growth_rate; // Calculate growth rate precisely instead of rounding to 50 houses and allow going below 70 ticks (default max) + bool allow_negative_growth; // Make town shrink (with the same speed as growth) if requirements aren't satisfied +}; + +struct Settings { + CBSettings cb; + EconomySettings economy; + LimitsSettings limits; + + uint8 game_type; // GameType + uint8 controller_type; // ControllerType + uint8 max_players_in_company; + uint16 destroyed_houses_per_month; // max amount of houses a company can destroy per month + uint16 game_length_years; // game length in years(0 = disabled) + bool protect_funded_industries; + uint16 same_depot_sell_years; // can only sell vehicles in the same place (20 tiles radius) for first x yearss of its lifetime (0 = disabled) +}; + +}; // namespace citymania + +#endif diff --git a/src/settings_type.h b/src/settings_type.h index fdb2b0f862..69de20a2a6 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -20,6 +20,8 @@ #include "zoom_type.h" #include "openttd.h" +#include "citymania/cm_settings.hpp" + /** Settings profiles and highscore tables. */ enum SettingsProfile { @@ -558,6 +560,8 @@ struct GameSettings { LinkGraphSettings linkgraph; ///< settings for link graph calculations StationSettings station; ///< settings related to station management LocaleSettings locale; ///< settings related to used currency/unit system in the current game + + citymania::Settings cm; }; /** All settings that are only important for the local client. */ From ee7d3c9df845a024afdda3705a241497ce4bad04 Mon Sep 17 00:00:00 2001 From: dP Date: Tue, 7 Jul 2020 20:54:19 +0300 Subject: [PATCH 4/6] Some adjustments for better cmserver compatibility --- src/citymania/cm_event.hpp | 4 ++++ src/citymania/cm_game.cpp | 14 +++++++++----- src/citymania/cm_game.hpp | 5 +++-- src/citymania/cm_main.cpp | 2 +- src/citymania/cm_saveload.cpp | 20 ++++++++++---------- src/settings_type.h | 2 +- src/town_cmd.cpp | 16 +++++++++------- 7 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/citymania/cm_event.hpp b/src/citymania/cm_event.hpp index f0b73ccc6c..b699ae4e93 100644 --- a/src/citymania/cm_event.hpp +++ b/src/citymania/cm_event.hpp @@ -42,12 +42,15 @@ struct HouseRebuilt { struct HouseBuilt { Town *town; TileIndex tile; + HouseID house_id; const HouseSpec *house_spec; + bool is_rebuilding; }; struct HouseCleared { Town *town; TileIndex tile; + HouseID house_id; const HouseSpec *house_spec; bool was_completed; ///< whether house was completed before destruction }; @@ -55,6 +58,7 @@ struct HouseCleared { struct HouseCompleted { Town *town; TileIndex tile; + HouseID house_id; const HouseSpec *house_spec; }; diff --git a/src/citymania/cm_game.cpp b/src/citymania/cm_game.cpp index fc3891fc0d..1a64748333 100644 --- a/src/citymania/cm_game.cpp +++ b/src/citymania/cm_game.cpp @@ -31,29 +31,29 @@ Game::Game() { this->events.listen([this] (const event::TownGrowthSucceeded &event) { if (event.town->cache.num_houses <= event.prev_houses) { event.town->cm.hs_total++; - this->towns_growth_tiles[event.tile] = TownGrowthTileState::HS; + this->set_town_growth_tile(event.tile, TownGrowthTileState::HS); } }); this->events.listen([this] (const event::TownGrowthFailed &event) { event.town->cm.cs_total++; - this->towns_growth_tiles[event.tile] = TownGrowthTileState::CS; + this->set_town_growth_tile(event.tile, TownGrowthTileState::CS); }); this->events.listen([this] (const event::HouseRebuilt &event) { if (event.was_successful) { event.town->cm.houses_reconstructed_this_month++; - this->towns_growth_tiles[event.tile] = TownGrowthTileState::RH_REBUILT; + this->set_town_growth_tile(event.tile, TownGrowthTileState::RH_REBUILT); } else { event.town->cm.houses_demolished_this_month++; - this->towns_growth_tiles[event.tile] = TownGrowthTileState::RH_REMOVED; + this->set_town_growth_tile(event.tile, TownGrowthTileState::RH_REMOVED); } }); this->events.listen([this] (const event::HouseBuilt &event) { event.town->cm.houses_constructing++; event.town->cm.real_population += event.house_spec->population; - this->towns_growth_tiles[event.tile] = TownGrowthTileState::NEW_HOUSE; + this->set_town_growth_tile(event.tile, TownGrowthTileState::NEW_HOUSE); }); this->events.listen([this] (const event::HouseCleared &event) { @@ -86,4 +86,8 @@ Game::Game() { }); } +void Game::set_town_growth_tile(TileIndex tile, TownGrowthTileState state) { + if (this->towns_growth_tiles[tile] < state) this->towns_growth_tiles[tile] = state; +} + } // namespace citymania \ No newline at end of file diff --git a/src/citymania/cm_game.hpp b/src/citymania/cm_game.hpp index a2ddbef41d..342f2da96f 100644 --- a/src/citymania/cm_game.hpp +++ b/src/citymania/cm_game.hpp @@ -1,5 +1,5 @@ -#ifndef CMEXT_GAME_HPP -#define CMEXT_GAME_HPP +#ifndef CM_GAME_HPP +#define CM_GAME_HPP #include "../town.h" @@ -27,6 +27,7 @@ public: event::Dispatcher events; Game(); + void set_town_growth_tile(TileIndex tile, TownGrowthTileState state); }; } // namespace citymania diff --git a/src/citymania/cm_main.cpp b/src/citymania/cm_main.cpp index 4fb14f046d..39d6604c4a 100644 --- a/src/citymania/cm_main.cpp +++ b/src/citymania/cm_main.cpp @@ -13,7 +13,7 @@ void ResetGame() { } void SwitchToMode(SwitchMode new_mode) { - ResetGame(); + if (new_mode != SM_SAVE_GAME) ResetGame(); } diff --git a/src/citymania/cm_saveload.cpp b/src/citymania/cm_saveload.cpp index e5861c2ba3..9919d44b2f 100644 --- a/src/citymania/cm_saveload.cpp +++ b/src/citymania/cm_saveload.cpp @@ -263,30 +263,30 @@ static u8vector EncodeData() { bs.Reserve(1000); bs.WriteBytes(SAVEGAME_DATA_FORMAT_VERSION, 2); bs.WriteBytes(_last_client_version, 2); - bs.WriteBytes(_settings_game.cm.controller_type, 1); + bs.WriteBytes(_settings_game.citymania.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(_settings_game.cm.game_type, 1); + bs.WriteBytes(_settings_game.citymania.game_type, 1); bs.WriteBytes(0, 3); // Reserved bs.WriteBytes(0, 4); // Reserved - EncodeSettings(bs, _settings_game.cm); + EncodeSettings(bs, _settings_game.citymania); EncodeCompanies(bs); EncodeTowns(bs); EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles); EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month); - if (_settings_game.cm.controller_type == 4) + if (_settings_game.citymania.controller_type == 4) CBController_saveload_encode(bs); return bs.GetVector(); } static void DecodeDataV2(BitIStream &bs) { - DecodeSettings(bs, _settings_game.cm); + DecodeSettings(bs, _settings_game.citymania); DecodeCompanies(bs); DecodeTowns(bs); DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles); DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month); - if (_settings_game.cm.controller_type == 4) CBController_saveload_decode(bs); + if (_settings_game.citymania.controller_type == 4) CBController_saveload_decode(bs); } static void DecodeTownsCargoV1(BitIStream &bs) @@ -332,7 +332,7 @@ static void DecodeTownsCargoV1(BitIStream &bs) } static void DecodeDataV1(BitIStream &bs) { - if (_settings_game.cm.controller_type != 0) DecodeTownsCargoV1(bs); + if (_settings_game.citymania.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); @@ -379,15 +379,15 @@ static void DecodeData(u8vector &data) { } DEBUG(sl, 2, "CityMania savegame data version %u", version); _last_client_version = bs.ReadBytes(2); - _settings_game.cm.controller_type = bs.ReadBytes(1); - if (version <= 1) _settings_game.cm.controller_type = (_settings_game.cm.controller_type ? 4 : 0); + _settings_game.citymania.controller_type = bs.ReadBytes(1); + if (version <= 1) _settings_game.citymania.controller_type = (_settings_game.citymania.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"); } - _settings_game.cm.game_type = bs.ReadBytes(1); + _settings_game.citymania.game_type = bs.ReadBytes(1); bs.ReadBytes(3); // reserved bs.ReadBytes(4); // reserved if (version == 1) DecodeDataV1(bs); diff --git a/src/settings_type.h b/src/settings_type.h index 69de20a2a6..3d2432c5f9 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -561,7 +561,7 @@ struct GameSettings { StationSettings station; ///< settings related to station management LocaleSettings locale; ///< settings related to used currency/unit system in the current game - citymania::Settings cm; + citymania::Settings citymania; }; /** All settings that are only important for the local client. */ diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 69fcbeecc8..396bb28963 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -227,7 +227,7 @@ enum TownGrowthResult { // GROWTH_SEARCH_RUNNING >= 1 }; -static bool BuildTownHouse(Town *t, TileIndex tile); +static bool BuildTownHouse(Town *t, TileIndex tile, bool is_rebuilding = false); static Town *CreateRandomTown(uint attempts, uint32 townnameparts, TownSize size, bool city, TownLayout layout); static void TownDrawHouseLift(const TileInfo *ti) @@ -503,7 +503,7 @@ static void MakeSingleHouseBigger(TileIndex tile) ResetHouseAge(tile); if (hs->building_flags & BUILDING_HAS_1_TILE) - citymania::Emit(citymania::event::HouseCompleted{town, tile, hs}); + citymania::Emit(citymania::event::HouseCompleted{town, tile, house_id, hs}); } MarkTileDirtyByTile(tile); } @@ -639,7 +639,7 @@ static void TileLoop_Town(TileIndex tile) /* Rebuild with another house? */ bool rebuild_res = false; - if (GB(r, 24, 8) >= 12) rebuild_res = BuildTownHouse(t, tile); + if (GB(r, 24, 8) >= 12) rebuild_res = BuildTownHouse(t, tile, true); citymania::Emit(citymania::event::HouseRebuilt{t, tile, rebuild_res}); } @@ -2474,7 +2474,7 @@ static bool CheckTownBuild2x2House(TileIndex *tile, Town *t, int maxz, bool nosl * @param tile where the house will be built * @return false iff no house can be built at this tile */ -static bool BuildTownHouse(Town *t, TileIndex tile) +static bool BuildTownHouse(Town *t, TileIndex tile, bool is_rebuilding) { /* forbidden building here by town layout */ if (!TownLayoutAllowsHouseHere(t, tile)) return false; @@ -2621,8 +2621,10 @@ static bool BuildTownHouse(Town *t, TileIndex tile) UpdateTownGrowthRate(t); UpdateTownCargoes(t, tile); - citymania::Emit(citymania::event::HouseBuilt{t, tile, hs}); - if (completed) citymania::Emit(citymania::event::HouseCompleted{t, tile, hs}); + if (!_generating_world) { + citymania::Emit(citymania::event::HouseBuilt{t, tile, house, hs, is_rebuilding}); + if (completed) citymania::Emit(citymania::event::HouseCompleted{t, tile, house, hs}); + } return true; } @@ -2712,7 +2714,7 @@ void ClearTownHouse(Town *t, TileIndex tile) /* Update cargo acceptance. */ UpdateTownCargoes(t, tile); - citymania::Emit(citymania::event::HouseCleared{t, tile, hs, is_completed}); + citymania::Emit(citymania::event::HouseCleared{t, tile, house, hs, is_completed}); } /** From f99c33b48c5afb05ec1ae90a5f13bd971e92c9bb Mon Sep 17 00:00:00 2001 From: dP Date: Tue, 7 Jul 2020 21:27:51 +0300 Subject: [PATCH 5/6] Add event slots --- src/citymania/cm_event.hpp | 31 +++++++++++++++++++++++-------- src/citymania/cm_game.cpp | 18 +++++++++--------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/citymania/cm_event.hpp b/src/citymania/cm_event.hpp index b699ae4e93..2a5fe1eb7c 100644 --- a/src/citymania/cm_event.hpp +++ b/src/citymania/cm_event.hpp @@ -2,6 +2,8 @@ #define CM_EVENT_HPP #include "cm_type.hpp" + +#include "../console_func.h" #include "../cargo_type.h" #include "../company_type.h" #include "../economy_type.h" @@ -104,6 +106,12 @@ struct CompanyBalanceChanged { Money delta; }; +enum class Slot : uint8 { + CONTROLLER = 10, + GAME = 20, + CONTROLLER_POST = 30, + RECORDER = 40, +}; class TypeDispatcherBase { public: @@ -119,14 +127,20 @@ public: TypeDispatcher() { } virtual ~TypeDispatcher() {} - void listen(Handler &handler) { - this->new_handlers.push_back(handler); + void listen(Slot slot, Handler &handler) { + auto p = this->handler_map.find(slot); + if (p != this->handler_map.end()) + IConsolePrintF(CC_ERROR, "ERROR: Ignored duplicate handler for event %s slot %d", typeid(T).name(), (int)slot); + this->handler_map.insert(p, std::make_pair(slot, handler)); + this->new_handlers = true; } void emit(const T &event) { - if (!this->new_handlers.empty()) { - this->handlers.insert(this->handlers.end(), this->new_handlers.begin(), this->new_handlers.end()); - this->new_handlers.clear(); + if (this->new_handlers) { // only rebuild handlers while not iterating + this->handlers.clear(); + for (auto &p : this->handler_map) { + this->handlers.push_back(p.second); + } } for (auto &h : this->handlers) { h(event); @@ -135,7 +149,8 @@ public: protected: std::vector handlers; - std::vector new_handlers; + std::map handler_map; + bool new_handlers = false; }; @@ -155,8 +170,8 @@ protected: public: template - void listen(std::function handler) { - this->get_dispatcher().listen(handler); + void listen(Slot slot, std::function handler) { + this->get_dispatcher().listen(slot, handler); } template diff --git a/src/citymania/cm_game.cpp b/src/citymania/cm_game.cpp index 1a64748333..c4e035daef 100644 --- a/src/citymania/cm_game.cpp +++ b/src/citymania/cm_game.cpp @@ -9,7 +9,7 @@ namespace citymania { Game::Game() { - this->events.listen([this] (const event::NewMonth &) { + this->events.listen(event::Slot::GAME, [this] (const event::NewMonth &) { for (Town *t : Town::Iterate()) { t->cm.hs_last_month = t->cm.hs_total - t->cm.hs_total_prev; t->cm.hs_total_prev = t->cm.hs_total; @@ -28,19 +28,19 @@ Game::Game() { this->towns_growth_tiles.clear(); }); - this->events.listen([this] (const event::TownGrowthSucceeded &event) { + this->events.listen(event::Slot::GAME, [this] (const event::TownGrowthSucceeded &event) { if (event.town->cache.num_houses <= event.prev_houses) { event.town->cm.hs_total++; this->set_town_growth_tile(event.tile, TownGrowthTileState::HS); } }); - this->events.listen([this] (const event::TownGrowthFailed &event) { + this->events.listen(event::Slot::GAME, [this] (const event::TownGrowthFailed &event) { event.town->cm.cs_total++; this->set_town_growth_tile(event.tile, TownGrowthTileState::CS); }); - this->events.listen([this] (const event::HouseRebuilt &event) { + this->events.listen(event::Slot::GAME, [this] (const event::HouseRebuilt &event) { if (event.was_successful) { event.town->cm.houses_reconstructed_this_month++; this->set_town_growth_tile(event.tile, TownGrowthTileState::RH_REBUILT); @@ -50,23 +50,23 @@ Game::Game() { } }); - this->events.listen([this] (const event::HouseBuilt &event) { + this->events.listen(event::Slot::GAME, [this] (const event::HouseBuilt &event) { event.town->cm.houses_constructing++; event.town->cm.real_population += event.house_spec->population; this->set_town_growth_tile(event.tile, TownGrowthTileState::NEW_HOUSE); }); - this->events.listen([this] (const event::HouseCleared &event) { + this->events.listen(event::Slot::GAME, [this] (const event::HouseCleared &event) { if (!event.was_completed) event.town->cm.houses_constructing--; event.town->cm.real_population -= event.house_spec->population; }); - this->events.listen([this] (const event::HouseCompleted &event) { + this->events.listen(event::Slot::GAME, [this] (const event::HouseCompleted &event) { event.town->cm.houses_constructing--; }); - this->events.listen([this] (const event::TownCachesRebuilt &event) { + this->events.listen(event::Slot::GAME, [this] (const event::TownCachesRebuilt &event) { for (Town *town : Town::Iterate()) { town->cm.real_population = 0; town->cm.houses_constructing = 0; @@ -81,7 +81,7 @@ Game::Game() { } }); - this->events.listen([this] (const event::CargoAccepted &event) { + this->events.listen(event::Slot::GAME, [this] (const event::CargoAccepted &event) { event.company->cur_economy.cm.cargo_income[event.cargo_type] += event.profit; }); } From fb70135c3e71f8d3afa0114339ffb6cd6c2304ff Mon Sep 17 00:00:00 2001 From: dP Date: Tue, 7 Jul 2020 21:50:27 +0300 Subject: [PATCH 6/6] Allow multiple event handlers for the same slot --- src/citymania/cm_event.hpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/citymania/cm_event.hpp b/src/citymania/cm_event.hpp index 2a5fe1eb7c..4c0bde4fdd 100644 --- a/src/citymania/cm_event.hpp +++ b/src/citymania/cm_event.hpp @@ -107,10 +107,11 @@ struct CompanyBalanceChanged { }; enum class Slot : uint8 { - CONTROLLER = 10, - GAME = 20, - CONTROLLER_POST = 30, - RECORDER = 40, + GOAL = 10, + CONTROLLER = 20, + GAME = 30, + CONTROLLER_POST = 40, + RECORDER = 50, }; class TypeDispatcherBase { @@ -128,11 +129,7 @@ public: virtual ~TypeDispatcher() {} void listen(Slot slot, Handler &handler) { - auto p = this->handler_map.find(slot); - if (p != this->handler_map.end()) - IConsolePrintF(CC_ERROR, "ERROR: Ignored duplicate handler for event %s slot %d", typeid(T).name(), (int)slot); - this->handler_map.insert(p, std::make_pair(slot, handler)); - this->new_handlers = true; + this->handler_map.insert(std::make_pair(slot, handler)); } void emit(const T &event) { @@ -149,7 +146,7 @@ public: protected: std::vector handlers; - std::map handler_map; + std::multimap handler_map; bool new_handlers = false; };