diff --git a/bin/baseset/no_music.obm b/bin/baseset/no_music.obm index 0991661601..96634fc068 100644 --- a/bin/baseset/no_music.obm +++ b/bin/baseset/no_music.obm @@ -1,4 +1,4 @@ -; $Id: no_music.obm 27041 2014-10-25 12:35:48Z rubidium $ +; $Id: no_music.obm 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents more or less nothingness ; diff --git a/bin/baseset/no_sound.obs b/bin/baseset/no_sound.obs index c7f2ed7f06..3dd3d89fb0 100644 --- a/bin/baseset/no_sound.obs +++ b/bin/baseset/no_sound.obs @@ -1,4 +1,4 @@ -; $Id: no_sound.obs 27103 2015-01-01 20:51:18Z rubidium $ +; $Id: no_sound.obs 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents more or less nothingness ; diff --git a/bin/baseset/orig_dos.obg b/bin/baseset/orig_dos.obg index 4d2c1f2b13..5e7ede3c0b 100644 --- a/bin/baseset/orig_dos.obg +++ b/bin/baseset/orig_dos.obg @@ -1,4 +1,4 @@ -; $Id: orig_dos.obg 27041 2014-10-25 12:35:48Z rubidium $ +; $Id: orig_dos.obg 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents the original graphics as on the non-German Transport ; Tycoon Deluxe DOS CD. diff --git a/bin/baseset/orig_dos.obs b/bin/baseset/orig_dos.obs index afcbcd513d..6acb49dead 100644 --- a/bin/baseset/orig_dos.obs +++ b/bin/baseset/orig_dos.obs @@ -1,4 +1,4 @@ -; $Id: orig_dos.obs 27041 2014-10-25 12:35:48Z rubidium $ +; $Id: orig_dos.obs 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents the original sounds as on the Transport ; Tycoon Deluxe DOS CD. diff --git a/bin/baseset/orig_dos_de.obg b/bin/baseset/orig_dos_de.obg index 1345ed5dad..1ed7df8ba1 100644 --- a/bin/baseset/orig_dos_de.obg +++ b/bin/baseset/orig_dos_de.obg @@ -1,4 +1,4 @@ -; $Id: orig_dos_de.obg 27041 2014-10-25 12:35:48Z rubidium $ +; $Id: orig_dos_de.obg 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents the original graphics as on the German Transport ; Tycoon Deluxe DOS CD. It contains one broken sprite. diff --git a/bin/baseset/orig_win.obg b/bin/baseset/orig_win.obg index 62dccca98a..33dec2ce6f 100644 --- a/bin/baseset/orig_win.obg +++ b/bin/baseset/orig_win.obg @@ -1,4 +1,4 @@ -; $Id: orig_win.obg 27041 2014-10-25 12:35:48Z rubidium $ +; $Id: orig_win.obg 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents the original graphics as on the Transport ; Tycoon Deluxe for Windows CD. diff --git a/bin/baseset/orig_win.obm b/bin/baseset/orig_win.obm index 122c50af03..f5f3db5db7 100644 --- a/bin/baseset/orig_win.obm +++ b/bin/baseset/orig_win.obm @@ -1,4 +1,4 @@ -; $Id: orig_win.obm 27274 2015-05-08 17:45:13Z rubidium $ +; $Id: orig_win.obm 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents the original music as on the Transport ; Tycoon Deluxe for Windows CD. diff --git a/bin/baseset/orig_win.obs b/bin/baseset/orig_win.obs index 985e6c49d8..f3ab6b0adf 100644 --- a/bin/baseset/orig_win.obs +++ b/bin/baseset/orig_win.obs @@ -1,4 +1,4 @@ -; $Id: orig_win.obs 27041 2014-10-25 12:35:48Z rubidium $ +; $Id: orig_win.obs 25205 2013-04-24 20:30:02Z frosch $ ; ; This represents the original sounds as on the Transport ; Tycoon Deluxe for Windows CD. diff --git a/source.list b/source.list index e062c1b22c..3d49fa1380 100644 --- a/source.list +++ b/source.list @@ -3,6 +3,7 @@ airport.cpp animated_tile.cpp articulated_vehicles.cpp autoreplace.cpp +bitstream.cpp bmp.cpp cargoaction.cpp cargomonitor.cpp @@ -141,6 +142,7 @@ autoslope.h base_media_base.h base_media_func.h base_station_base.h +bitstream.h bmp.h bridge.h cargo_table_gui.h @@ -608,6 +610,8 @@ saveload/animated_tile_sl.cpp saveload/autoreplace_sl.cpp saveload/cargomonitor_sl.cpp saveload/cargopacket_sl.cpp +saveload/citymania_sl.cpp +saveload/citymania_sl.h saveload/cheat_sl.cpp saveload/company_sl.cpp saveload/depot_sl.cpp diff --git a/src/bitstream.cpp b/src/bitstream.cpp new file mode 100644 index 0000000000..9b39484390 --- /dev/null +++ b/src/bitstream.cpp @@ -0,0 +1,58 @@ +#include "bitstream.h" + +// 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); +} + +/** + * 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; +} diff --git a/src/bitstream.h b/src/bitstream.h new file mode 100644 index 0000000000..03c4b48354 --- /dev/null +++ b/src/bitstream.h @@ -0,0 +1,41 @@ +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#include "stdafx.h" +#include + +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); + 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); +}; + +#endif diff --git a/src/saveload/citymania_sl.cpp b/src/saveload/citymania_sl.cpp new file mode 100644 index 0000000000..4f17929080 --- /dev/null +++ b/src/saveload/citymania_sl.cpp @@ -0,0 +1,242 @@ +#include "citymania_sl.h" +#include "../bitstream.h" +#include "../date_func.h" +#include "../debug.h" +#include "../town.h" + +#define CM_DATA_FORMAT_VERSION 1 + +assert_compile(NUM_CARGO == 32); + + +static void CM_EncodeTownsExtraInfo(BitOStream &bs) +{ + Town *t; + uint n_affected_towns = 0; + FOR_ALL_TOWNS(t) { + if (t->growing_by_chance || t->houses_reconstruction || + t->houses_construction || t->houses_demolished) + n_affected_towns++; + } + bs.WriteBytes(n_affected_towns, 2); + FOR_ALL_TOWNS(t) { + if (t->growing_by_chance || t->houses_reconstruction || + t->houses_construction || t->houses_demolished) { + bs.WriteBytes(t->index, 2); + bs.WriteBytes(t->growing_by_chance, 1); + bs.WriteBytes(t->houses_reconstruction, 2); + bs.WriteBytes(t->houses_demolished, 2); + } + } +} + +static void CM_EncodeTownsGrowthTiles(BitOStream &bs, TownsGrowthTilesIndex &tiles) +{ + bs.WriteBytes(tiles.size(), 4); + for (TownsGrowthTilesIndex::iterator p = tiles.begin(); + p != tiles.end(); p++) { + bs.WriteBytes(p->first, 4); + bs.WriteBytes(p->second, 1); + } +} + +static void CM_EncodeTownsLayoutErrors(BitOStream &bs) +{ + Town *t; + uint n_affected_towns = 0; + FOR_ALL_TOWNS(t) { + if (t->cb_houses_removed || t->houses_skipped || t->cycles_skipped) + n_affected_towns++; + } + bs.WriteBytes(n_affected_towns, 2); + FOR_ALL_TOWNS(t) { + if (t->cb_houses_removed || t->houses_skipped || t->cycles_skipped) { + bs.WriteBytes(t->index, 2); + bs.WriteBytes(t->houses_skipped, 2); + bs.WriteBytes(t->houses_skipped_prev, 2); + bs.WriteBytes(t->houses_skipped_last_month, 2); + bs.WriteBytes(t->cycles_skipped, 2); + bs.WriteBytes(t->cycles_skipped_prev, 2); + bs.WriteBytes(t->cycles_skipped_last_month, 2); + bs.WriteBytes(t->cb_houses_removed, 2); + bs.WriteBytes(t->cb_houses_removed_prev, 2); + bs.WriteBytes(t->cb_houses_removed_last_month, 2); + } + } + CM_EncodeTownsGrowthTiles(bs, _towns_growth_tiles); + CM_EncodeTownsGrowthTiles(bs, _towns_growth_tiles_last_month); +} + +static void CM_EncodeTownsCargo(BitOStream &bs) +{ + uint n_cb_cargos = 0; + CargoID cb_cargos[NUM_CARGO]; + for(CargoID cargo = 0; cargo < NUM_CARGO; cargo++) { + if (CB_GetReq(cargo) > 0) + cb_cargos[n_cb_cargos++] = cargo; + } + + bs.WriteBytes(n_cb_cargos, 1); + for (uint i = 0; i < n_cb_cargos; i++) { + CargoID cargo = cb_cargos[i]; + bs.WriteBytes(cargo, 1); + bs.WriteBytes(CB_GetReq(cargo), 4); + bs.WriteBytes(CB_GetFrom(cargo), 4); + bs.WriteBytes(CB_GetDecay(cargo), 1); + } + + Town *t; + bs.WriteBytes(Town::GetNumItems(), 2); + FOR_ALL_TOWNS(t) { + bs.WriteBytes(t->index, 2); + for (uint i = 0; i < n_cb_cargos; i++) { + CargoID cargo = cb_cargos[i]; + bs.WriteBytes(t->storage[cargo], 4); + bs.WriteBytes(t->act_cargo[cargo], 4); + bs.WriteBytes(t->new_act_cargo[cargo], 4); + bs.WriteBytes(t->delivered_enough[cargo], 1); + } + } +} + +static void CM_DecodeTownsExtraInfo(BitIStream &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 CM extra towns info (%u)", town_id); + continue; + } + t->growing_by_chance = bs.ReadBytes(1); + t->houses_reconstruction = bs.ReadBytes(2); + t->houses_demolished = bs.ReadBytes(2); + } +} + +static void CM_DecodeTownsGrowthTiles(BitIStream &bs, 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 CM_DecodeTownsLayoutErrors(BitIStream &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->houses_skipped = bs.ReadBytes(2); + t->houses_skipped_prev = bs.ReadBytes(2); + t->houses_skipped_last_month = bs.ReadBytes(2); + t->cycles_skipped = bs.ReadBytes(2); + t->cycles_skipped_prev = bs.ReadBytes(2); + t->cycles_skipped_last_month = bs.ReadBytes(2); + t->cb_houses_removed = bs.ReadBytes(2); + t->cb_houses_removed_prev = bs.ReadBytes(2); + t->cb_houses_removed_last_month = bs.ReadBytes(2); + } + CM_DecodeTownsGrowthTiles(bs, _towns_growth_tiles); + CM_DecodeTownsGrowthTiles(bs, _towns_growth_tiles_last_month); +} + +static void CM_DecodeTownsCargo(BitIStream &bs) +{ + uint n_cb_cargos = bs.ReadBytes(1); + CB_ResetRequirements(); + CargoID cb_cargos[NUM_CARGO]; + 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[i] = 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::Get(town_id); + if (!t) { + DEBUG(sl, 0, "Invalid TownID in CB towns cargo data (%u)", town_id); + continue; + } + for (uint i = 0; i < n_cb_cargos; i++) { + CargoID cargo = cb_cargos[i]; + t->storage[cargo] = bs.ReadBytes(4); + t->act_cargo[cargo] = bs.ReadBytes(4); + t->new_act_cargo[cargo] = bs.ReadBytes(4); + t->delivered_enough[cargo] = bs.ReadBytes(1); + } + } +} + +u8vector CM_EncodeData() +{ + BitOStream bs; + bs.Reserve(1000); + bs.WriteBytes(CM_DATA_FORMAT_VERSION, 2); + 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(0, 4); // Reserved + bs.WriteBytes(0, 4); // Reserved + + CM_EncodeTownsCargo(bs); + CM_EncodeTownsExtraInfo(bs); + CM_EncodeTownsLayoutErrors(bs); + + // fill with 0 to fit in persistent storage chunks (16 * 4) + int mod = bs.GetByteSize() % 64; + if (mod) { + for (int i = 0; i < 64 - mod; i++) + bs.WriteBytes(0, 1); + } + DEBUG(sl, 2, "Citybuilder data takes %u bytes", bs.GetByteSize()); + return bs.GetVector(); +} + +void CM_DecodeData(u8vector &data) +{ + ResetTownsGrowthTiles(); + if (data.size() == 0) { + CB_SetCB(false); + DEBUG(sl, 2, "No citybuilder data"); + return; + } + DEBUG(sl, 2, "Citybuilder data takes %lu bytes", data.size()); + CB_SetCB(true); + BitIStream bs(data); + try { + uint version = bs.ReadBytes(2); + if (version != CM_DATA_FORMAT_VERSION) { + DEBUG(sl, 0, "Savegame was made with different version of client, extra citybuilder data was not loaded"); + return; + } + bs.ReadBytes(4); // _date + bs.ReadBytes(1); // _date_fract + bs.ReadBytes(4); // reserved + bs.ReadBytes(4); // reserved + CM_DecodeTownsCargo(bs); + CM_DecodeTownsExtraInfo(bs); + CM_DecodeTownsLayoutErrors(bs); + } + catch (BitIStreamUnexpectedEnd &e) { + DEBUG(sl, 0, "Invalid citybuilder data"); + } +} \ No newline at end of file diff --git a/src/saveload/citymania_sl.h b/src/saveload/citymania_sl.h new file mode 100644 index 0000000000..a565d62ef6 --- /dev/null +++ b/src/saveload/citymania_sl.h @@ -0,0 +1,9 @@ +#ifndef SAVELOAD_CITYMANIA_SL_H +#define SAVELOAD_CITYMANIA_SL_H + +#include "../bitstream.h" + +u8vector CM_EncodeData(); +void CM_DecodeData(u8vector &data); + +#endif diff --git a/src/saveload/storage_sl.cpp b/src/saveload/storage_sl.cpp index bf4a3097eb..d6a19406a8 100644 --- a/src/saveload/storage_sl.cpp +++ b/src/saveload/storage_sl.cpp @@ -11,10 +11,14 @@ #include "../stdafx.h" #include "../newgrf_storage.h" +#include "citymania_sl.h" #include "saveload.h" #include "../safeguards.h" +// Luukland_Citybuilder grf id actually +#define CITYMANIA_GRFID 0x534B0501U + /** Description of the data to save and load in #PersistentStorage. */ static const SaveLoad _storage_desc[] = { SLE_CONDVAR(PersistentStorage, grfid, SLE_UINT32, 6, SL_MAX_VERSION), @@ -22,15 +26,62 @@ static const SaveLoad _storage_desc[] = { SLE_END() }; +// static void hexdump(uint8 *data) { +// uint i; +// for (i = 0; i < 20; i++) { +// if (i) fprintf(stderr, " : "); +// fprintf(stderr, "%02x", data[i]); +// } +// fprintf(stderr, i >= 20 ? " ...\n" : "\n"); +// } + /** Load persistent storage data. */ static 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; + while ((index = SlIterateArray()) != -1) { - assert(PersistentStorage::CanAllocateItem()); - PersistentStorage *ps = new (index) PersistentStorage(0, 0, 0); + 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 + 64); + } else { + ps = NULL; + } + } + + CM_DecodeData(cmdata); +} + +static void Save_CMDataAsPSAC() { + uint32 grfid = CITYMANIA_GRFID; + u8vector data = CM_EncodeData(); + uint8 *ptr = &data[0]; + SaveLoadGlobVarList _desc[] = { + SLEG_CONDVAR(grfid, SLE_UINT32, 6, SL_MAX_VERSION), + SLEG_CONDARR(*ptr, SLE_UINT32, 16, 161, SL_MAX_VERSION), + SLEG_END() + }; + uint index = PersistentStorage::GetNumItems(); + + int n_chunks = data.size() / 64; + for (int i = 0; i < n_chunks; i++, ptr += 64) { + _desc[1].address = (void *)ptr; + SlSetArrayIndex(index + i); + SlGlobList(_desc); } } @@ -38,13 +89,17 @@ static void Load_PSAC() static void Save_PSAC() { PersistentStorage *ps; - /* Write the industries */ FOR_ALL_STORAGES(ps) { + if (ps->grfid == CITYMANIA_GRFID) { + continue; + } ps->ClearChanges(); SlSetArrayIndex(ps->index); SlObject(ps, _storage_desc); } + + Save_CMDataAsPSAC(); } /** Chunk handler for persistent storages. */ diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp index 4404622049..6e19be2f9b 100644 --- a/src/saveload/town_sl.cpp +++ b/src/saveload/town_sl.cpp @@ -33,9 +33,6 @@ void RebuildTownCaches() town->cache.population = 0; town->cache.num_houses = 0; town->cache.potential_pop = 0; - town->houses_construction = 0; - town->houses_reconstruction = 0; - town->houses_demolished = 0; } for (TileIndex t = 0; t < MapSize(); t++) { @@ -117,7 +114,6 @@ void UpdateHousesAndTowns() } RebuildTownCaches(); - ResetTownsGrowthTiles(); } /** Save and load of towns. */ diff --git a/src/town.h b/src/town.h index cc24ffd611..60a5c59ccd 100644 --- a/src/town.h +++ b/src/town.h @@ -256,6 +256,7 @@ bool CB_Enabled(); void CB_SetCB(bool cb); void CB_SetStorage(uint storage); void CB_SetRequirements(CargoID cargo, uint req, uint from, uint decay); +void CB_ResetRequirements(); uint CB_GetReq(CargoID cargo); uint CB_GetFrom(CargoID cargo); uint CB_GetDecay(CargoID cargo); @@ -274,8 +275,9 @@ enum TownGrowthTileState { TGTS_CB_HOUSE_REMOVED }; -extern std::map _towns_growth_tiles_last_month; -extern std::map _towns_growth_tiles; +typedef std::map TownsGrowthTilesIndex; +extern TownsGrowthTilesIndex _towns_growth_tiles_last_month; +extern TownsGrowthTilesIndex _towns_growth_tiles; void UpdateTownGrowthTile(TileIndex tile, TownGrowthTileState state); void ResetTownsGrowthTiles(); diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 8810a5f31a..24053715d0 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -61,8 +61,8 @@ uint days_in_month[] = {31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};//CB void CB_UpdateTownStorage(Town *t); //CB const Money NOVAPOLIS_COMPANY_MONEY_THRESHOLD = INT64_MAX >> 4; -std::map _towns_growth_tiles_last_month; -std::map _towns_growth_tiles; +TownsGrowthTilesIndex _towns_growth_tiles_last_month; +TownsGrowthTilesIndex _towns_growth_tiles; TownID _new_town_id; uint32 _town_cargoes_accepted; ///< Bitmap of all cargoes accepted by houses. @@ -1683,15 +1683,14 @@ void UpdateTownMaxPass(Town *t) } //CB + bool CB_Enabled(){ return _cb_enabled; } void CB_SetCB(bool cb){ _cb_enabled = cb; if(!_cb_enabled){ - for(CargoID cargo = 0; cargo < NUM_CARGO; cargo++){ - CB_SetRequirements(cargo, 0, 0, 0); - } + CB_ResetRequirements(); } } void CB_SetStorage(uint storage){ @@ -1702,6 +1701,11 @@ void CB_SetRequirements(CargoID cargo, uint req, uint from, uint decay){ CBFROM[cargo] = from; CBDECAY[cargo] = decay; } +void CB_ResetRequirements() { + for(CargoID cargo = 0; cargo < NUM_CARGO; cargo++){ + CB_SetRequirements(cargo, 0, 0, 0); + } +} uint CB_GetReq(CargoID cargo){ return CBREQ[cargo]; }