From 9a8216dc0c0aeafcb0fee8249e2bbe94f0f99099 Mon Sep 17 00:00:00 2001 From: dP Date: Thu, 25 Mar 2021 01:40:30 +0300 Subject: [PATCH 1/6] Add building preview for road depot --- src/citymania/cm_highlight.cpp | 127 ++++++++++++++++++++++++---- src/citymania/cm_highlight_type.hpp | 14 ++- src/citymania/cm_station_gui.hpp | 1 + src/rail_gui.cpp | 2 +- src/road_gui.cpp | 8 +- 5 files changed, 129 insertions(+), 23 deletions(-) diff --git a/src/citymania/cm_highlight.cpp b/src/citymania/cm_highlight.cpp index c20d8e918f..9e74faf89f 100644 --- a/src/citymania/cm_highlight.cpp +++ b/src/citymania/cm_highlight.cpp @@ -47,6 +47,7 @@ extern int _selected_airport_index; extern byte _selected_airport_layout; extern DiagDirection _build_depot_direction; ///< Currently selected depot direction extern DiagDirection _road_station_picker_orientation; +extern DiagDirection _road_depot_orientation; extern uint32 _realtime_tick; extern void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec); @@ -141,6 +142,13 @@ ObjectTileHighlight ObjectTileHighlight::make_road_stop(SpriteID palette, RoadTy return oh; } +ObjectTileHighlight ObjectTileHighlight::make_road_depot(SpriteID palette, RoadType roadtype, DiagDirection ddir) { + auto oh = ObjectTileHighlight(Type::ROAD_DEPOT, palette); + oh.u.road.depot.roadtype = roadtype; + oh.u.road.depot.ddir = ddir; + return oh; +} + ObjectTileHighlight ObjectTileHighlight::make_airport_tile(SpriteID palette, StationGfx gfx) { auto oh = ObjectTileHighlight(Type::AIRPORT_TILE, palette); oh.u.airport_tile.gfx = gfx; @@ -196,6 +204,14 @@ ObjectHighlight ObjectHighlight::make_road_stop(TileIndex start_tile, TileIndex return oh; } +ObjectHighlight ObjectHighlight::make_road_depot(TileIndex tile, RoadType roadtype, DiagDirection orientation) { + auto oh = ObjectHighlight{ObjectHighlight::Type::ROAD_DEPOT}; + oh.tile = tile; + oh.ddir = orientation; + oh.roadtype = roadtype; + return oh; +} + ObjectHighlight ObjectHighlight::make_airport(TileIndex start_tile, int airport_type, byte airport_layout) { auto oh = ObjectHighlight{ObjectHighlight::Type::AIRPORT}; oh.tile = start_tile; @@ -329,6 +345,17 @@ void ObjectHighlight::UpdateTiles() { break; } + case Type::ROAD_DEPOT: { + auto palette = (CanBuild( + this->tile, + this->roadtype << 2 | this->ddir, + 0, + CMD_BUILD_ROAD_DEPOT + ) ? PALETTE_TINT_WHITE : PALETTE_TINT_RED_DEEP); + this->tiles.insert(std::make_pair(this->tile, ObjectTileHighlight::make_road_depot(palette, this->roadtype, this->ddir))); + break; + } + case Type::AIRPORT: { auto palette = (CanBuild( this->tile, @@ -514,6 +541,73 @@ void DrawRoadStop(SpriteID palette, const TileInfo *ti, RoadType roadtype, DiagD // DrawRoadCatenary(ti); } + +struct DrawRoadTileStruct { + uint16 image; + byte subcoord_x; + byte subcoord_y; +}; + +#include "../table/road_land.h" + +// copied from road_gui.cpp +static uint GetRoadSpriteOffset(Slope slope, RoadBits bits) +{ + if (slope != SLOPE_FLAT) { + switch (slope) { + case SLOPE_NE: return 11; + case SLOPE_SE: return 12; + case SLOPE_SW: return 13; + case SLOPE_NW: return 14; + default: NOT_REACHED(); + } + } else { + static const uint offsets[] = { + 0, 18, 17, 7, + 16, 0, 10, 5, + 15, 8, 1, 4, + 9, 3, 6, 2 + }; + return offsets[bits]; + } +} + + +void DrawRoadDepot(SpriteID palette, const TileInfo *ti, RoadType roadtype, DiagDirection orientation) { + const RoadTypeInfo* rti = GetRoadTypeInfo(roadtype); + int relocation = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_DEPOT); + bool default_gfx = relocation == 0; + if (default_gfx) { + if (HasBit(rti->flags, ROTF_CATENARY)) { + if (_loaded_newgrf_features.tram == TRAMWAY_REPLACE_DEPOT_WITH_TRACK && RoadTypeIsTram(roadtype) && !rti->UsesOverlay()) { + /* Sprites with track only work for default tram */ + relocation = SPR_TRAMWAY_DEPOT_WITH_TRACK - SPR_ROAD_DEPOT; + default_gfx = false; + } else { + /* Sprites without track are always better, if provided */ + relocation = SPR_TRAMWAY_DEPOT_NO_TRACK - SPR_ROAD_DEPOT; + } + } + } else { + relocation -= SPR_ROAD_DEPOT; + } + + const DrawTileSprites *dts = &_road_depot[orientation]; + AddSortableSpriteToDraw(dts->ground.sprite, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + + if (default_gfx) { + uint offset = GetRoadSpriteOffset(SLOPE_FLAT, DiagDirToRoadBits(orientation)); + if (rti->UsesOverlay()) { + SpriteID ground = GetCustomRoadSprite(rti, INVALID_TILE, ROTSG_OVERLAY); + if (ground != 0) AddSortableSpriteToDraw(ground + offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + } else if (RoadTypeIsTram(roadtype)) { + AddSortableSpriteToDraw(SPR_TRAMWAY_OVERLAY + offset, palette, ti->x, ti->y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, ti->z); + } + } + + DrawRailTileSeq(ti, dts, TO_INVALID, relocation, 0, palette); +} + #include "../table/station_land.h" void DrawAirportTile(SpriteID palette, const TileInfo *ti, StationGfx gfx) { @@ -708,6 +802,9 @@ void ObjectHighlight::Draw(const TileInfo *ti) { case ObjectTileHighlight::Type::ROAD_STOP: DrawRoadStop(oth.palette, ti, oth.u.road.stop.roadtype, oth.u.road.stop.ddir, oth.u.road.stop.is_truck); break; + case ObjectTileHighlight::Type::ROAD_DEPOT: + DrawRoadDepot(oth.palette, ti, oth.u.road.depot.roadtype, oth.u.road.depot.ddir); + break; case ObjectTileHighlight::Type::AIRPORT_TILE: DrawAirportTile(oth.palette, ti, oth.u.airport_tile.gfx); break; @@ -1042,25 +1139,10 @@ bool DrawTileSelection(const TileInfo *ti, const TileHighlightType &tht) { // if (_thd.drawstyle == CM_HT_BLUEPRINT_PLACE) return true; - // if ((_thd.drawstyle & HT_DRAG_MASK) == HT_RECT && _thd.outersize.x > 0) { if (_thd.select_proc == DDSP_BUILD_STATION || _thd.select_proc == DDSP_BUILD_BUSSTOP - || _thd.select_proc == DDSP_BUILD_TRUCKSTOP || _thd.select_proc == CM_DDSP_BUILD_AIRPORT) { - // station selector, handled by DrawTileZoning - return true; - } - - if (_thd.select_proc == CM_DDSP_BUILD_RAIL_DEPOT) { - // if ((_thd.drawstyle & HT_DRAG_MASK) == HT_RECT && IsInsideSelectedRectangle(ti->x, ti->y) - // && _cursor.sprite_seq[0].sprite == GetRailTypeInfo(_cur_railtype)->cursor.depot) { - // DrawTileSelectionRect(ti, _thd.make_square_red ? PALETTE_SEL_TILE_RED : PAL_NONE); - - // auto rti = GetRailTypeInfo(_cur_railtype); - // int depot_sprite = GetCustomRailSprite(rti, ti->tile, RTSG_DEPOT); - // auto relocation = depot_sprite != 0 ? depot_sprite - SPR_RAIL_DEPOT_SE_1 : rti->GetRailtypeSpriteOffset(); - // AddSortableSpriteToDraw(relocation, PALETTE_TINT_WHITE, ti->x, ti->y, 0x10, 0x10, 1, ti->z); - // AddSortableSpriteToDraw(SPR_RAIL_DEPOT_SE_1, PALETTE_TINT_WHITE, ti->x, ti->y, 0x10, 0x10, 1, ti->z); - // DrawTrainDepotSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), widget - WID_BRAD_DEPOT_NE + DIAGDIR_NE, _cur_railtype); - // DrawTrainDepotSprite(ti, _cur_railtype, (DiagDirection)(_thd.drawstyle & HT_DIR_MASK)); + || _thd.select_proc == DDSP_BUILD_TRUCKSTOP || _thd.select_proc == CM_DDSP_BUILD_AIRPORT + || _thd.select_proc == CM_DDSP_BUILD_ROAD_DEPOT || _thd.select_proc == CM_DDSP_BUILD_RAIL_DEPOT) { + // handled by DrawTileZoning return true; } @@ -1110,6 +1192,15 @@ HighLightStyle UpdateTileSelection(HighLightStyle new_drawstyle) { // new_drawstyle = CM_HT_BLUEPRINT_PLACE; // } else if (_thd.make_square_red) { + } else if (_thd.select_proc == CM_DDSP_BUILD_ROAD_DEPOT) { + auto dir = _road_depot_orientation; + if (pt.x != -1) { + if (dir == DEPOTDIR_AUTO) { + dir = AddAutodetectionRotation(AutodetectRoadObjectDirection(tile, pt, _cur_roadtype)); + } + _thd.cm_new = ObjectHighlight::make_road_depot(tile, _cur_roadtype, dir); + } + new_drawstyle = HT_RECT; } else if (_thd.select_proc == CM_DDSP_BUILD_RAIL_DEPOT) { auto dir = _build_depot_direction; if (pt.x != -1) { diff --git a/src/citymania/cm_highlight_type.hpp b/src/citymania/cm_highlight_type.hpp index 9a86e2e247..79ca931c53 100644 --- a/src/citymania/cm_highlight_type.hpp +++ b/src/citymania/cm_highlight_type.hpp @@ -30,6 +30,7 @@ public: RAIL_BRIDGE_HEAD, RAIL_TUNNEL_HEAD, ROAD_STOP, + ROAD_DEPOT, AIRPORT_TILE, END, }; @@ -67,6 +68,10 @@ public: DiagDirection ddir; bool is_truck; } stop; + struct { + RoadType roadtype; + DiagDirection ddir; + } depot; } road; struct { StationGfx gfx; @@ -83,6 +88,7 @@ public: static ObjectTileHighlight make_rail_tunnel_head(SpriteID palette, DiagDirection ddir); static ObjectTileHighlight make_road_stop(SpriteID palette, RoadType roadtype, DiagDirection ddir, bool is_truck); + static ObjectTileHighlight make_road_depot(SpriteID palette, RoadType roadtype, DiagDirection ddir); static ObjectTileHighlight make_airport_tile(SpriteID palette, StationGfx gfx); }; @@ -109,6 +115,7 @@ public: RAIL_BRIDGE, RAIL_TUNNEL, ROAD_STOP, + ROAD_DEPOT, END, }; Type type; @@ -150,6 +157,9 @@ public: DiagDirection ddir; TileIndexDiffC other_end; } stop; + struct { + DiagDirection ddir; + } depot; } road; } u; Item(Type type, TileIndexDiffC tdiff) @@ -184,7 +194,8 @@ public: RAIL_DEPOT = 1, RAIL_STATION = 2, ROAD_STOP = 3, - AIRPORT = 4, + ROAD_DEPOT = 4, + AIRPORT = 5, // BLUEPRINT = 2, }; @@ -214,6 +225,7 @@ public: static ObjectHighlight make_rail_station(TileIndex start_tile, TileIndex end_tile, Axis axis); // static ObjectHighlight make_blueprint(TileIndex tile, sp blueprint); static ObjectHighlight make_road_stop(TileIndex start_tile, TileIndex end_tile, RoadType roadtype, DiagDirection orientation, bool is_truck); + static ObjectHighlight make_road_depot(TileIndex tile, RoadType roadtype, DiagDirection orientation); static ObjectHighlight make_airport(TileIndex start_tile, int airport_type, byte airport_layout); void Draw(const TileInfo *ti); diff --git a/src/citymania/cm_station_gui.hpp b/src/citymania/cm_station_gui.hpp index b199a42085..2befe59735 100644 --- a/src/citymania/cm_station_gui.hpp +++ b/src/citymania/cm_station_gui.hpp @@ -9,6 +9,7 @@ namespace citymania { +const DiagDirection DEPOTDIR_AUTO = DIAGDIR_END; const DiagDirection STATIONDIR_X = DIAGDIR_END; const DiagDirection STATIONDIR_Y = (DiagDirection)((uint)DIAGDIR_END + 1); const DiagDirection STATIONDIR_AUTO = (DiagDirection)((uint)DIAGDIR_END + 2); diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 2b81497e76..7e786b7e66 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -805,7 +805,7 @@ struct BuildRailToolbarWindow : Window { case WID_RAT_BUILD_DEPOT: ddir = _build_depot_direction; - if (ddir == DIAGDIR_NW + 1) { + if (ddir == citymania::DEPOTDIR_AUTO) { assert(_thd.cm.type == citymania::ObjectHighlight::Type::RAIL_DEPOT); ddir = _thd.cm.ddir; } diff --git a/src/road_gui.cpp b/src/road_gui.cpp index e399cf0304..3fe9de72cd 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -70,7 +70,7 @@ static RoadFlags _place_road_flag; /* CM static */ RoadType _cur_roadtype; -static DiagDirection _road_depot_orientation; +/* CM static */ DiagDirection _road_depot_orientation; DiagDirection _road_station_picker_orientation; void CcPlaySound_SPLAT_OTHER(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd) @@ -449,6 +449,7 @@ struct BuildRoadToolbarWindow : Window { case WID_ROT_DEPOT: if (_game_mode == GM_EDITOR || !CanBuildVehicleInfrastructure(VEH_ROAD, GetRoadTramType(this->roadtype))) return; if (HandlePlacePushButton(this, WID_ROT_DEPOT, this->rti->cursor.depot, HT_RECT, CM_DDSP_BUILD_ROAD_DEPOT)) { + citymania::ResetRotateAutodetection(); ShowRoadDepotPicker(this); this->last_started_action = widget; } @@ -545,8 +546,9 @@ struct BuildRoadToolbarWindow : Window { case WID_ROT_DEPOT: ddir = _road_depot_orientation; - if (ddir == DIAGDIR_NW + 1) { - ddir = citymania::AutodetectRoadObjectDirection(tile, GetTileBelowCursor(), _cur_roadtype); + if (ddir == citymania::DEPOTDIR_AUTO) { + assert(_thd.cm.type == citymania::ObjectHighlight::Type::ROAD_DEPOT); + ddir = _thd.cm.ddir; } DoCommandP(tile, _cur_roadtype << 2 | ddir, 0, CMD_BUILD_ROAD_DEPOT | CMD_MSG(this->rti->strings.err_depot), CcRoadDepot); From a9d7d3d7c2e5ca2ce60d0259fd4fca67f1d0ba7a Mon Sep 17 00:00:00 2001 From: dP Date: Thu, 25 Mar 2021 01:44:39 +0300 Subject: [PATCH 2/6] Add rotation hotkey for depot building window --- src/rail_gui.cpp | 4 ++-- src/road_gui.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 7e786b7e66..cdd56b1287 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -2058,8 +2058,8 @@ static WindowDesc _build_depot_desc( WDP_AUTO, nullptr, 0, 0, WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, WDF_CONSTRUCTION, - _nested_build_depot_widgets, lengthof(_nested_build_depot_widgets), - &BuildRailDepotWindow::hotkeys // CityMania addition + _nested_build_depot_widgets, lengthof(_nested_build_depot_widgets) + ,&BuildRailDepotWindow::hotkeys // CityMania addition ); static void ShowBuildTrainDepotPicker(Window *parent) diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 3fe9de72cd..09b1e72c24 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -1005,6 +1005,13 @@ Window *ShowBuildRoadScenToolbar(RoadType roadtype) } struct BuildRoadDepotWindow : public PickerWindowBase { +/* CityMania code start */ +public: + enum class Hotkey : int { + ROTATE, + }; +/* CityMania code end */ + BuildRoadDepotWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent) { this->CreateNestedTree(); @@ -1062,6 +1069,33 @@ struct BuildRoadDepotWindow : public PickerWindowBase { break; } } + + /* CityMania code start */ + EventState OnHotkey(int hotkey) override + { + switch ((BuildRoadDepotWindow::Hotkey)hotkey) { + /* Indicate to the OnClick that the action comes from a hotkey rather + * then from a click and that the CTRL state should be ignored. */ + case BuildRoadDepotWindow::Hotkey::ROTATE: + if (_road_depot_orientation < DIAGDIR_END) { + this->RaiseWidget(_road_depot_orientation + WID_BROD_DEPOT_NE); + _road_depot_orientation = ChangeDiagDir(_road_depot_orientation, DIAGDIRDIFF_90RIGHT); + this->LowerWidget(_road_depot_orientation + WID_BROD_DEPOT_NE); + } else { + citymania::RotateAutodetection(); + } + this->SetDirty(); + return ES_HANDLED; + + default: + NOT_REACHED(); + } + + return ES_NOT_HANDLED; + } + + static HotkeyList hotkeys; + /* CityMania code end */ }; static const NWidgetPart _nested_build_road_depot_widgets[] = { @@ -1100,11 +1134,20 @@ static const NWidgetPart _nested_build_road_depot_widgets[] = { EndContainer(), }; +/* CityMania code start */ +static Hotkey build_depot_hotkeys[] = { + Hotkey(CM_WKC_MOUSE_MIDDLE, "rotate", (int)BuildRoadDepotWindow::Hotkey::ROTATE), + HOTKEY_LIST_END +}; +HotkeyList BuildRoadDepotWindow::hotkeys("cm_build_road_depot", build_depot_hotkeys); +/* CityMania code end */ + static WindowDesc _build_road_depot_desc( WDP_AUTO, nullptr, 0, 0, WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, WDF_CONSTRUCTION, _nested_build_road_depot_widgets, lengthof(_nested_build_road_depot_widgets) + ,&BuildRoadDepotWindow::hotkeys // CityMania addition ); static void ShowRoadDepotPicker(Window *parent) From 68bb40976f4907f0475e501248c7ede670600732 Mon Sep 17 00:00:00 2001 From: dP Date: Wed, 31 Mar 2021 01:11:40 +0300 Subject: [PATCH 3/6] Fix crash when extending polyrail snap points over the bridge or tunnel --- src/viewport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/viewport.cpp b/src/viewport.cpp index 9e2a7dcc4a..6a12551c6d 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -3975,7 +3975,7 @@ static LineSnapPoint LineSnapPointAtRailTrackEndpoint(TileIndex tile, DiagDirect TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, INVALID_DIAGDIR)) == AxisToTrackBits(DiagDirToAxis(exit_dir)) && IsTileOwner(tile, _local_company)) { /* Check if this is a tunnel/bridge and move the tile to the other end if so. */ - if (IsTileType(tile, MP_TUNNELBRIDGE)) tile = GetOtherTunnelBridgeEnd(tile); + if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeDirection(tile) == exit_dir) tile = GetOtherTunnelBridgeEnd(tile); LineSnapPoint ex = LineSnapPointAtRailTrackEndpoint(tile, exit_dir, false, extended); if (!bidirectional) return ex; // if we are interested in forward direction only then return just the extended point *extended = ex; // otherwise return two points, extended with forward direction and base with reverse direction From de00ea9fe012fa8aca1f98123e8cd0e8b7b84a8c Mon Sep 17 00:00:00 2001 From: dP Date: Thu, 1 Apr 2021 13:24:11 +0300 Subject: [PATCH 4/6] Add ZStandard(zstd) savegame compression --- Doxyfile | 1 + config.lib | 34 +++++++++++ src/crashlog.cpp | 7 +++ src/saveload/saveload.cpp | 123 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) diff --git a/Doxyfile b/Doxyfile index e288ffbd87..3fec520950 100644 --- a/Doxyfile +++ b/Doxyfile @@ -290,6 +290,7 @@ INCLUDE_FILE_PATTERNS = PREDEFINED = WITH_ZLIB \ WITH_LZO \ WITH_LIBLZMA \ + WITH_ZSTD \ WITH_SDL \ WITH_PNG \ WITH_FONTCONFIG \ diff --git a/config.lib b/config.lib index 94b240d3c9..565eb4f6b0 100644 --- a/config.lib +++ b/config.lib @@ -68,6 +68,7 @@ set_default() { with_cocoa="1" with_zlib="1" with_lzma="1" + with_zstd="1" with_lzo2="1" with_xdg_basedir="1" with_png="1" @@ -340,6 +341,10 @@ detect_params() { --without-liblzma) with_lzma="0";; --with-liblzma=*) with_lzma="$optarg";; + --with-zstd) with_zstd="2";; + --without-zstd) with_zstd="0";; + --with-zstd=*) with_zstd="$optarg";; + --with-lzo2) with_lzo2="2";; --without-lzo2) with_lzo2="0";; --with-lzo2=*) with_lzo2="$optarg";; @@ -824,6 +829,20 @@ check_params() { fi fi + pre_detect_with_zstd=$with_zstd + detect_zstd + + if [ "$with_zstd" = "0" ] || [ -z "$zstd_config" ]; then + log 1 "WARNING: zstd was not detected or disabled" + if [ "$pre_detect_with_zstd" = "0" ]; then + log 1 "WARNING: We strongly suggest you to install zstd." + else + log 1 "configure: error: no zstd detected" + log 1 " If you want to compile without zstd use --without-zstd as parameter" + exit + fi + fi + pre_detect_with_lzo2=$with_lzo2 detect_lzo2 @@ -1690,6 +1709,17 @@ make_cflags_and_ldflags() { fi fi + if [ -n "$zstd_config" ]; then + CFLAGS="$CFLAGS -DWITH_ZSTD" + CFLAGS="$CFLAGS `$zstd_config --cflags | tr '\n\r' ' '`" + + if [ "$enable_static" != "0" ]; then + LIBS="$LIBS `$zstd_config --libs --static | tr '\n\r' ' '`" + else + LIBS="$LIBS `$zstd_config --libs | tr '\n\r' ' '`" + fi + fi + if [ "$with_lzo2" != "0" ]; then if [ "$enable_static" != "0" ] && [ "$os" != "OSX" ]; then LIBS="$LIBS $lzo2" @@ -2796,6 +2826,10 @@ detect_lzma() { detect_pkg_config "$with_lzma" "liblzma" "lzma_config" "5.0" } +detect_zstd() { + detect_pkg_config "$with_zstd" "libzstd" "zstd_config" "1.4" +} + detect_xdg_basedir() { detect_pkg_config "$with_xdg_basedir" "libxdg-basedir" "xdg_basedir_config" "1.2" } diff --git a/src/crashlog.cpp b/src/crashlog.cpp index c7cf48154c..63b70193e7 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -56,6 +56,9 @@ #ifdef WITH_LIBLZMA # include #endif +#ifdef WITH_ZSTD +#include +#endif #ifdef WITH_LZO #include #endif @@ -255,6 +258,10 @@ char *CrashLog::LogLibraries(char *buffer, const char *last) const buffer += seprintf(buffer, last, " LZMA: %s\n", lzma_version_string()); #endif +#ifdef WITH_ZSTD + buffer += seprintf(buffer, last, " ZSTD: %s\n", ZSTD_versionString()); +#endif + #ifdef WITH_LZO buffer += seprintf(buffer, last, " LZO: %s\n", lzo_version_string()); #endif diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index f9eebed46a..4a4dce529f 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -2275,6 +2275,118 @@ struct LZMASaveFilter : SaveFilter { #endif /* WITH_LIBLZMA */ +/******************************************** + ********** START OF ZSTD CODE ************** + ********************************************/ + +#if defined(WITH_ZSTD) +#include + + +/** Filter using ZSTD compression. */ +struct ZSTDLoadFilter : LoadFilter { + ZSTD_DCtx *zstd; ///< ZSTD decompression context + byte fread_buf[MEMORY_CHUNK_SIZE]; ///< Buffer for reading from the file + ZSTD_inBuffer input; ///< ZSTD input buffer for fread_buf + + /** + * Initialise this filter. + * @param chain The next filter in this chain. + */ + ZSTDLoadFilter(LoadFilter *chain) : LoadFilter(chain) + { + this->zstd = ZSTD_createDCtx(); + if (!this->zstd) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor"); + this->input = {this->fread_buf, 0, 0}; + } + + /** Clean everything up. */ + ~ZSTDLoadFilter() + { + ZSTD_freeDCtx(this->zstd); + } + + size_t Read(byte *buf, size_t size) override + { + ZSTD_outBuffer output{buf, size, 0}; + + do { + /* read more bytes from the file? */ + if (this->input.pos == this->input.size) { + this->input.size = this->chain->Read(this->fread_buf, sizeof(this->fread_buf)); + this->input.pos = 0; + } + + size_t ret = ZSTD_decompressStream(this->zstd, &output, &this->input); + if (ZSTD_isError(ret)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "libzstd returned error code"); + if (ret == 0) break; + } while (output.pos < output.size); + + return output.pos; + } +}; + +/** Filter using ZSTD compression. */ +struct ZSTDSaveFilter : SaveFilter { + ZSTD_CCtx *zstd; ///< ZSTD compression context + + /** + * Initialise this filter. + * @param chain The next filter in this chain. + * @param compression_level The requested level of compression. + */ + ZSTDSaveFilter(SaveFilter *chain, byte compression_level) : SaveFilter(chain) + { + this->zstd = ZSTD_createCCtx(); + if (!this->zstd) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor"); + if (ZSTD_isError(ZSTD_CCtx_setParameter(this->zstd, ZSTD_c_compressionLevel, (int)compression_level - 100))) { + ZSTD_freeCCtx(this->zstd); + SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "invalid compresison level"); + } + } + + /** Clean up what we allocated. */ + ~ZSTDSaveFilter() + { + ZSTD_freeCCtx(this->zstd); + } + + /** + * Helper loop for writing the data. + * @param p The bytes to write. + * @param len Amount of bytes to write. + * @param mode Mode for ZSTD_compressStream2. + */ + void WriteLoop(byte *p, size_t len, ZSTD_EndDirective mode) + { + byte buf[MEMORY_CHUNK_SIZE]; // output buffer + ZSTD_inBuffer input{p, len, 0}; + + bool finished; + do { + ZSTD_outBuffer output{buf, sizeof(buf), 0}; + size_t remaining = ZSTD_compressStream2(this->zstd, &output, &input, mode); + if (ZSTD_isError(remaining)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "libzstd returned error code"); + + if (output.pos != 0) this->chain->Write(buf, output.pos); + + finished = (mode == ZSTD_e_end ? (remaining == 0) : (input.pos == input.size)); + } while (!finished); + } + + void Write(byte *buf, size_t size) override + { + this->WriteLoop(buf, size, ZSTD_e_continue); + } + + void Finish() override + { + this->WriteLoop(nullptr, 0, ZSTD_e_end); + this->chain->Finish(); + } +}; +#endif /* WITH_LIBZSTD */ + /******************************************* ************* END OF CODE ***************** *******************************************/ @@ -2310,6 +2422,17 @@ static const SaveLoadFormat _saveload_formats[] = { #else {"zlib", TO_BE32X('OTTZ'), nullptr, nullptr, 0, 0, 0}, #endif +#if defined(WITH_ZSTD) + /* Zstd provides a decent compression rate at a very high compression/decompression speed. Compared to lzma level 2 + * zstd saves are about 40% larger (on level 1) but it has about 30x faster compression and 5x decompression making it + * a good choice for multiplayer servers. And zstd level 1 seems to be the optimal one for client connection speed + * (compress + 10 MB/s download + decompress time), about 3x faster than lzma:2 and 1.5x than zlib:2 and lzo. + * As zstd has negative compression levels the values were increased by 100 moving zstd level range -100..22 into + * openttd 0..122. Also note that value 100 mathes zstd level 0 which is a special value for default level 3 (openttd 103) */ + {"zstd", TO_BE32X('OTTS'), CreateLoadFilter, CreateSaveFilter, 0, 101, 122}, +#else + {"zstd", TO_BE32X('OTTS'), nullptr, nullptr, 0, 0, 0}, +#endif #if defined(WITH_LIBLZMA) /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves. * Higher compression levels are possible, and might improve savegame size by up to 25%, but are also up to 10 times slower. From da3cfd28211620bf047a7e59e4185610094bc02c Mon Sep 17 00:00:00 2001 From: dP Date: Thu, 1 Apr 2021 14:07:12 +0300 Subject: [PATCH 5/6] Allow client and server negotiate on savegame format if both are patched --- config.lib | 4 +- src/network/network_client.cpp | 1 + src/network/network_server.cpp | 8 +- src/network/network_server.h | 2 + src/rev.cpp.in | 16 ++-- src/saveload/saveload.cpp | 168 ++++++++++++++++++++++++++------- src/saveload/saveload.h | 41 +++++++- 7 files changed, 197 insertions(+), 43 deletions(-) diff --git a/config.lib b/config.lib index 565eb4f6b0..0751f00185 100644 --- a/config.lib +++ b/config.lib @@ -1499,8 +1499,8 @@ make_cflags_and_ldflags() { CFLAGS="$CFLAGS -D$os" CFLAGS_BUILD="$CFLAGS_BUILD -D$os" - CXXFLAGS="$CXXFLAGS -std=c++11" - CXXFLAGS_BUILD="$CXXFLAGS_BUILD -std=c++11" + CXXFLAGS="$CXXFLAGS -std=c++17" + CXXFLAGS_BUILD="$CXXFLAGS_BUILD -std=c++17" if [ "$enable_debug" = "0" ]; then # No debug, add default stuff diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 74b802f919..2a4333ce77 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -355,6 +355,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin() p->Send_string(_settings_client.network.client_name); // Client name p->Send_uint8 (_network_join_as); // PlayAs p->Send_uint8 (NETLANG_ANY); // Language + p->Send_uint8 (citymania::GetAvailableLoadFormats()); // Compressnion formats that we can decompress my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index c82c51cfd4..7c5f0f571a 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -592,7 +592,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMap() sent_packets = 4; // We start with trying 4 packets /* Make a dump of the current game */ - if (SaveWithFilter(this->savegame, true) != SL_OK) usererror("network savedump failed"); + if (SaveWithFilter(this->savegame, true, this->cm_preset) != SL_OK) usererror("network savedump failed"); } if (this->status == STATUS_MAP) { @@ -908,9 +908,15 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet *p) p->Recv_string(name, sizeof(name)); playas = (Owner)p->Recv_uint8(); client_lang = (NetworkLanguage)p->Recv_uint8(); + uint8 savegame_formats = p->CanReadFromPacket(1) ? p->Recv_uint8() : 23u /* assume non-modded has everything but zstd */; if (this->HasClientQuit()) return NETWORK_RECV_STATUS_CONN_LOST; + /* Find common savegame compression format to use */ + auto preset = citymania::FindCompatibleSavePreset("", savegame_formats); + if (!preset) return this->SendError(NETWORK_ERROR_NOT_EXPECTED); + this->cm_preset = *preset; + /* join another company does not affect these values */ switch (playas) { case COMPANY_NEW_COMPANY: // New company diff --git a/src/network/network_server.h b/src/network/network_server.h index 3dfcf5594f..31866d9a5a 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -12,6 +12,7 @@ #include "network_internal.h" #include "core/tcp_listen.h" +#include "../saveload/saveload.h" class ServerNetworkGameSocketHandler; /** Make the code look slightly nicer/simpler. */ @@ -72,6 +73,7 @@ public: struct PacketWriter *savegame; ///< Writer used to write the savegame. NetworkAddress client_address; ///< IP-address of the client (so he can be banned) + citymania::SavePreset cm_preset; ///< Preset to use for the savegame ServerNetworkGameSocketHandler(SOCKET s); ~ServerNetworkGameSocketHandler(); diff --git a/src/rev.cpp.in b/src/rev.cpp.in index 824f2e9de9..cedafdc92b 100644 --- a/src/rev.cpp.in +++ b/src/rev.cpp.in @@ -19,7 +19,7 @@ */ bool IsReleasedVersion() { - return HasBit(_openttd_newgrf_version, 19); + return HasBit(_openttd_newgrf_version, 19); } /** @@ -35,7 +35,7 @@ bool IsReleasedVersion() * * shows a "M", if the binary is made from modified source code. */ -const char _openttd_revision[] = "!!VERSION!!"; +const char _openttd_revision[] = "1.10.3"; /** * The text version of OpenTTD's build date. @@ -48,12 +48,12 @@ const char _openttd_build_date[] = __DATE__ " " __TIME__; /** * The git revision hash of this version. */ -const char _openttd_revision_hash[] = "!!GITHASH!!"; +const char _openttd_revision_hash[] = "baf5bf29fa68c908e7033e58465562d22ec97a07"; /** * The year of this version. */ -const char _openttd_revision_year[] = "!!YEAR!!"; +const char _openttd_revision_year[] = "2020"; /** * Let us know if current build was modified. This detection @@ -63,14 +63,14 @@ const char _openttd_revision_year[] = "!!YEAR!!"; * (compiling from sources without any version control software) * and 2 is for modified revision. */ -const byte _openttd_revision_modified = !!MODIFIED!!; +const byte _openttd_revision_modified = 0; /** * Indicate whether this is a tagged version. * If this is non-0, then _openttd_revision is the name of the tag, * and the version is likely a beta, release candidate, or real release. */ -const byte _openttd_revision_tagged = !!ISTAG!!; +const byte _openttd_revision_tagged = 1; /** * The NewGRF revision of OTTD: @@ -85,4 +85,6 @@ const byte _openttd_revision_tagged = !!ISTAG!!; * final release will always have a lower version number than the released * version, thus making comparisons on specific revisions easy. */ -const uint32 _openttd_newgrf_version = 1 << 28 | 10 << 24 | 0 << 20 | !!ISSTABLETAG!! << 19 | 28004; +const uint32 _openttd_newgrf_version = 1 << 28 | 10 << 24 | 0 << 20 | 1 << 19 | 28004; + +const char _citymania_version[] = "!!VERSION!! !!DATE!!"; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 4a4dce529f..1e95d544cd 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -44,6 +44,7 @@ #include "../fios.h" #include "../error.h" #include +#include #include "table/strings.h" @@ -2392,35 +2393,35 @@ struct ZSTDSaveFilter : SaveFilter { *******************************************/ /** The format for a reader/writer type of a savegame */ -struct SaveLoadFormat { - const char *name; ///< name of the compressor/decompressor (debug-only) - uint32 tag; ///< the 4-letter tag by which it is identified in the savegame +// struct SaveLoadFormat { +// const char *name; ///< name of the compressor/decompressor (debug-only) +// uint32 tag; ///< the 4-letter tag by which it is identified in the savegame - LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter. - SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter. +// LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter. +// SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter. - byte min_compression; ///< the minimum compression level of this format - byte default_compression; ///< the default compression level of this format - byte max_compression; ///< the maximum compression level of this format -}; +// byte min_compression; ///< the minimum compression level of this format +// byte default_compression; ///< the default compression level of this format +// byte max_compression; ///< the maximum compression level of this format +// }; /** The different saveload formats known/understood by OpenTTD. */ -static const SaveLoadFormat _saveload_formats[] = { +static const citymania::SaveLoadFormat _saveload_formats[] = { + /* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */ + {0, "none", TO_BE32X('OTTN'), CreateLoadFilter, CreateSaveFilter, citymania::CompressionMethod::None, 0, 0, 0}, #if defined(WITH_LZO) /* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */ - {"lzo", TO_BE32X('OTTD'), CreateLoadFilter, CreateSaveFilter, 0, 0, 0}, + {1, "lzo", TO_BE32X('OTTD'), CreateLoadFilter, CreateSaveFilter, citymania::CompressionMethod::LZO, 0, 0, 0}, #else - {"lzo", TO_BE32X('OTTD'), nullptr, nullptr, 0, 0, 0}, + {1, "lzo", TO_BE32X('OTTD'), nullptr, nullptr, citymania::CompressionMethod::LZO, 0, 0, 0}, #endif - /* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */ - {"none", TO_BE32X('OTTN'), CreateLoadFilter, CreateSaveFilter, 0, 0, 0}, #if defined(WITH_ZLIB) /* After level 6 the speed reduction is significant (1.5x to 2.5x slower per level), but the reduction in filesize is * fairly insignificant (~1% for each step). Lower levels become ~5-10% bigger by each level than level 6 while level * 1 is "only" 3 times as fast. Level 0 results in uncompressed savegames at about 8 times the cost of "none". */ - {"zlib", TO_BE32X('OTTZ'), CreateLoadFilter, CreateSaveFilter, 0, 6, 9}, + {2, "zlib", TO_BE32X('OTTZ'), CreateLoadFilter, CreateSaveFilter, citymania::CompressionMethod::Zlib, 0, 6, 9}, #else - {"zlib", TO_BE32X('OTTZ'), nullptr, nullptr, 0, 0, 0}, + {2, "zlib", TO_BE32X('OTTZ'), nullptr, nullptr, citymania::CompressionMethod::Zlib, 0, 0, 0}, #endif #if defined(WITH_ZSTD) /* Zstd provides a decent compression rate at a very high compression/decompression speed. Compared to lzma level 2 @@ -2429,9 +2430,9 @@ static const SaveLoadFormat _saveload_formats[] = { * (compress + 10 MB/s download + decompress time), about 3x faster than lzma:2 and 1.5x than zlib:2 and lzo. * As zstd has negative compression levels the values were increased by 100 moving zstd level range -100..22 into * openttd 0..122. Also note that value 100 mathes zstd level 0 which is a special value for default level 3 (openttd 103) */ - {"zstd", TO_BE32X('OTTS'), CreateLoadFilter, CreateSaveFilter, 0, 101, 122}, + {3, "zstd", TO_BE32X('OTTS'), CreateLoadFilter, CreateSaveFilter, citymania::CompressionMethod::ZSTD, 0, 101, 122}, #else - {"zstd", TO_BE32X('OTTS'), nullptr, nullptr, 0, 0, 0}, + {3, "zstd", TO_BE32X('OTTS'), nullptr, nullptr, citymania::CompressionMethod::ZSTD, 0, 0, 0}, #endif #if defined(WITH_LIBLZMA) /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves. @@ -2439,12 +2440,111 @@ static const SaveLoadFormat _saveload_formats[] = { * The next significant reduction in file size is at level 4, but that is already 4 times slower. Level 3 is primarily 50% * slower while not improving the filesize, while level 0 and 1 are faster, but don't reduce savegame size much. * It's OTTX and not e.g. OTTL because liblzma is part of xz-utils and .tar.xz is preferred over .tar.lzma. */ - {"lzma", TO_BE32X('OTTX'), CreateLoadFilter, CreateSaveFilter, 0, 2, 9}, + {4, "lzma", TO_BE32X('OTTX'), CreateLoadFilter, CreateSaveFilter, citymania::CompressionMethod::LZMA, 0, 2, 9}, #else - {"lzma", TO_BE32X('OTTX'), nullptr, nullptr, 0, 0, 0}, + {4, "lzma", TO_BE32X('OTTX'), nullptr, nullptr, citymania::CompressionMethod::LZMA, 0, 0, 0}, #endif }; +namespace citymania { // citymania savegame format handling + +static const std::string DEFAULT_NETWORK_SAVEGAME_COMPRESSION = "zstd:1 zlib:2 lzma:0 lzo:0"; + +/** + * Parses the savegame format and compression level string ("format:[compression_level]"). + * @param str String to parse + * @return Parsest SavePreset or std::nullopt + */ +static std::optional ParseSavePreset(const std::string &str) +{ + auto delimiter_pos = str.find(':'); + auto format = (delimiter_pos != std::string::npos ? str.substr(0, delimiter_pos) : str); + for (auto &slf : _saveload_formats) { + if (slf.init_write != nullptr && format == slf.name) { + /* If compression level wasn't specified use the default one */ + if (delimiter_pos == std::string::npos) return SavePreset{&slf, slf.default_compression}; + + auto level_str = str.substr(delimiter_pos + 1); + int level; + try{ + level = stoi(level_str); + } catch(const std::exception &e) { + /* Can't parse compression level, set it out ouf bounds to fail later */ + level = (int)slf.max_compression + 1; + } + + if (level != Clamp(level, slf.min_compression, slf.max_compression)) { + /* Invalid compression level, show the error and use default level */ + SetDParamStr(0, level_str.c_str()); + ShowErrorMessage(STR_CONFIG_ERROR, STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_LEVEL, WL_CRITICAL); + return SavePreset{&slf, slf.default_compression}; + } + + return SavePreset{&slf, (byte)level}; + } + } + SetDParamStr(0, str.c_str()); + ShowErrorMessage(STR_CONFIG_ERROR, STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_ALGORITHM, WL_CRITICAL); + return {}; +} + +static_assert(lengthof(_saveload_formats) <= 8); // uint8 is used for the bitset of format ids + +/** + * Finds the best savegame preset to use in network game based on server settings and client capabilies. + * @param server_formats String of space-separated format descriptions in form format[:compression_level] acceptable for the server (listed first take priority). + * @param client_formats Bitset of savegame formats available to the client (as returned by GetAvailableLoadFormats) + * @return SavePreset that satisfies both server and client or std::nullopt + */ +std::optional FindCompatibleSavePreset(const std::string &server_formats, uint8 client_formats) +{ + std::istringstream iss(server_formats.empty() ? DEFAULT_NETWORK_SAVEGAME_COMPRESSION : server_formats); + std::string preset_str; + while (std::getline(iss, preset_str, ' ')) { + auto preset = ParseSavePreset(preset_str); + if (!preset) continue; + if ((client_formats & (1 << preset->format->id)) != 0) return preset; + } + return {}; +} + +/** + * Return the bitset of savegame formats that this game instance can load + * @return bitset of available savegame formats + */ +uint8 GetAvailableLoadFormats() +{ + return 3; + uint8 res = 0; + for(auto &slf : _saveload_formats) { + if (slf.init_load != nullptr) { + res &= (1 << slf.id); + } + } + return res; +} + +/** + * Return the save preset to use for local game saves. + * @return SavePreset to use + */ +static SavePreset GetLocalSavePreset() +{ + if (!StrEmpty(_savegame_format)) { + auto config = ParseSavePreset(_savegame_format); + if (config) return *config; + } + + const citymania::SaveLoadFormat *def = lastof(_saveload_formats); + + /* find default savegame format, the highest one with which files can be written */ + while (!def->init_write) def--; + + return {def, def->default_compression}; +} + +} // namespace citymania + /** * Return the savegameformat of the game. Whether it was created with ZLIB compression * uncompressed, or another type @@ -2452,6 +2552,8 @@ static const SaveLoadFormat _saveload_formats[] = { * @param compression_level Output for telling what compression level we want. * @return Pointer to SaveLoadFormat struct giving all characteristics of this type of savegame */ +#if 0 +Citymania uses other way static const SaveLoadFormat *GetSavegameFormat(char *s, byte *compression_level) { const SaveLoadFormat *def = lastof(_saveload_formats); @@ -2499,6 +2601,8 @@ static const SaveLoadFormat *GetSavegameFormat(char *s, byte *compression_level) return def; } +#endif + /* actual loader/saver function */ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings); extern bool AfterLoadGame(); @@ -2586,17 +2690,17 @@ static void SaveFileError() * We have written the whole game into memory, _memory_savegame, now find * and appropriate compressor and start writing to file. */ -static SaveOrLoadResult SaveFileToDisk(bool threaded) +static SaveOrLoadResult SaveFileToDisk(bool threaded, citymania::SavePreset preset) { try { - byte compression; - const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression); + // byte compression; + // const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression); /* We have written our stuff to memory, now write it to file! */ - uint32 hdr[2] = { fmt->tag, TO_BE32(SAVEGAME_VERSION << 16) }; + uint32 hdr[2] = { preset.format->tag, TO_BE32(SAVEGAME_VERSION << 16) }; _sl.sf->Write((byte*)hdr, sizeof(hdr)); - _sl.sf = fmt->init_write(_sl.sf, compression); + _sl.sf = preset.format->init_write(_sl.sf, preset.compression_level); _sl.dumper->Flush(_sl.sf); ClearSaveLoadState(); @@ -2644,7 +2748,7 @@ void WaitTillSaved() * @param threaded Whether to try to perform the saving asynchronously. * @return Return the result of the action. #SL_OK or #SL_ERROR */ -static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded) +static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded, citymania::SavePreset preset) { assert(!_sl.saveinprogress); @@ -2658,10 +2762,10 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded) SaveFileStart(); - if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true)) { + if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true, std::move(preset))) { if (threaded) DEBUG(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode..."); - SaveOrLoadResult result = SaveFileToDisk(false); + SaveOrLoadResult result = SaveFileToDisk(false, preset); SaveFileDone(); return result; @@ -2676,11 +2780,11 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded) * @param threaded Whether to try to perform the saving asynchronously. * @return Return the result of the action. #SL_OK or #SL_ERROR */ -SaveOrLoadResult SaveWithFilter(SaveFilter *writer, bool threaded) +SaveOrLoadResult SaveWithFilter(SaveFilter *writer, bool threaded, citymania::SavePreset preset) { try { _sl.action = SLA_SAVE; - return DoSave(writer, threaded); + return DoSave(writer, threaded, preset); } catch (...) { ClearSaveLoadState(); return SL_ERROR; @@ -2708,7 +2812,7 @@ static SaveOrLoadResult DoLoad(LoadFilter *reader, bool load_check) if (_sl.lf->Read((byte*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE); /* see if we have any loader for this type. */ - const SaveLoadFormat *fmt = _saveload_formats; + const citymania::SaveLoadFormat *fmt = _saveload_formats; for (;;) { /* No loader found, treat as version 0 and use LZO format */ if (fmt == endof(_saveload_formats)) { @@ -2920,7 +3024,7 @@ SaveOrLoadResult SaveOrLoad(const char *filename, SaveLoadOperation fop, Detaile DEBUG(desync, 1, "save: %08x; %02x; %s", _date, _date_fract, filename); if (_network_server || !_settings_client.gui.threaded_saves) threaded = false; - return DoSave(new FileWriter(fh), threaded); + return DoSave(new FileWriter(fh), threaded, citymania::GetLocalSavePreset()); } /* LOAD game */ diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 9ae0956549..904a057fe0 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -12,6 +12,9 @@ #include "../fileio_type.h" #include "../strings_type.h" +#include "saveload_filter.h" +#include +#include /** SaveLoad versions * Previous savegame versions, the trunk revision where they were @@ -337,6 +340,42 @@ enum SavegameType { SGT_INVALID = 0xFF, ///< broken savegame (used internally) }; +namespace citymania { + +enum class CompressionMethod : uint8 { + None = 0u, + LZO = 1u, + Zlib = 2u, + ZSTD = 3u, + LZMA = 4u, +}; + +/** The format for a reader/writer type of a savegame */ +struct SaveLoadFormat { + uint8 id; ///< unique integer id of this savegame format (olny used for networkking so is not guaranteed to be preserved between versions) + const char *name; ///< name of the compressor/decompressor (debug-only) + uint32 tag; ///< the 4-letter tag by which it is identified in the savegame + + LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter. + SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter. + + CompressionMethod method; ///< compression method used in this format + byte min_compression; ///< the minimum compression level of this format + byte default_compression; ///< the default compression level of this format + byte max_compression; ///< the maximum compression level of this format +}; + +/** The preset to use for generating savegames */ +struct SavePreset { + const SaveLoadFormat *format; ///< savegame format to use + byte compression_level; ///< compression level to use +}; + +std::optional FindCompatibleSavePreset(const std::string &server_formats, uint8 client_format_flags); +uint8 GetAvailableLoadFormats(); + +} // namespace citymania + extern FileToSaveLoad _file_to_saveload; void GenerateDefaultSaveName(char *buf, const char *last); @@ -347,7 +386,7 @@ void WaitTillSaved(); void ProcessAsyncSaveFinish(); void DoExitSave(); -SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded); +SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded, citymania::SavePreset preset); SaveOrLoadResult LoadWithFilter(struct LoadFilter *reader); typedef void ChunkSaveLoadProc(); From 6659107625a566d8b34718e9e307f42999dedd3e Mon Sep 17 00:00:00 2001 From: dP Date: Thu, 1 Apr 2021 14:14:20 +0300 Subject: [PATCH 6/6] Fix c++17 compilation --- src/citymania/cm_highlight_type.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/citymania/cm_highlight_type.hpp b/src/citymania/cm_highlight_type.hpp index 79ca931c53..3a9a74c244 100644 --- a/src/citymania/cm_highlight_type.hpp +++ b/src/citymania/cm_highlight_type.hpp @@ -94,7 +94,7 @@ public: class TileIndexDiffCCompare{ public: - bool operator()(const TileIndexDiffC &a, const TileIndexDiffC &b) { + bool operator()(const TileIndexDiffC &a, const TileIndexDiffC &b) const { if (a.x < b.x) return true; if (a.x == b.x && a.y < b.y) return true; return false;