Merge branch 'master' into 1.11

This commit is contained in:
dP
2021-04-01 17:11:53 +03:00
14 changed files with 4098 additions and 58 deletions

View File

@@ -290,6 +290,7 @@ INCLUDE_FILE_PATTERNS =
PREDEFINED = WITH_ZLIB \ PREDEFINED = WITH_ZLIB \
WITH_LZO \ WITH_LZO \
WITH_LIBLZMA \ WITH_LIBLZMA \
WITH_ZSTD \
WITH_SDL \ WITH_SDL \
WITH_PNG \ WITH_PNG \
WITH_FONTCONFIG \ WITH_FONTCONFIG \

3609
config.lib Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,7 @@ extern int _selected_airport_index;
extern byte _selected_airport_layout; extern byte _selected_airport_layout;
extern DiagDirection _build_depot_direction; ///< Currently selected depot direction extern DiagDirection _build_depot_direction; ///< Currently selected depot direction
extern DiagDirection _road_station_picker_orientation; extern DiagDirection _road_station_picker_orientation;
extern DiagDirection _road_depot_orientation;
extern uint32 _realtime_tick; extern uint32 _realtime_tick;
extern void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec); extern void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec);
@@ -142,6 +143,13 @@ ObjectTileHighlight ObjectTileHighlight::make_road_stop(SpriteID palette, RoadTy
return oh; 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) { ObjectTileHighlight ObjectTileHighlight::make_airport_tile(SpriteID palette, StationGfx gfx) {
auto oh = ObjectTileHighlight(Type::AIRPORT_TILE, palette); auto oh = ObjectTileHighlight(Type::AIRPORT_TILE, palette);
oh.u.airport_tile.gfx = gfx; oh.u.airport_tile.gfx = gfx;
@@ -197,6 +205,14 @@ ObjectHighlight ObjectHighlight::make_road_stop(TileIndex start_tile, TileIndex
return oh; 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) { ObjectHighlight ObjectHighlight::make_airport(TileIndex start_tile, int airport_type, byte airport_layout) {
auto oh = ObjectHighlight{ObjectHighlight::Type::AIRPORT}; auto oh = ObjectHighlight{ObjectHighlight::Type::AIRPORT};
oh.tile = start_tile; oh.tile = start_tile;
@@ -330,6 +346,17 @@ void ObjectHighlight::UpdateTiles() {
break; 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: { case Type::AIRPORT: {
auto palette = (CanBuild( auto palette = (CanBuild(
this->tile, this->tile,
@@ -515,6 +542,73 @@ void DrawRoadStop(SpriteID palette, const TileInfo *ti, RoadType roadtype, DiagD
// DrawRoadCatenary(ti); // 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" #include "../table/station_land.h"
void DrawAirportTile(SpriteID palette, const TileInfo *ti, StationGfx gfx) { void DrawAirportTile(SpriteID palette, const TileInfo *ti, StationGfx gfx) {
@@ -709,6 +803,9 @@ void ObjectHighlight::Draw(const TileInfo *ti) {
case ObjectTileHighlight::Type::ROAD_STOP: 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); DrawRoadStop(oth.palette, ti, oth.u.road.stop.roadtype, oth.u.road.stop.ddir, oth.u.road.stop.is_truck);
break; 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: case ObjectTileHighlight::Type::AIRPORT_TILE:
DrawAirportTile(oth.palette, ti, oth.u.airport_tile.gfx); DrawAirportTile(oth.palette, ti, oth.u.airport_tile.gfx);
break; break;
@@ -1047,25 +1144,10 @@ bool DrawTileSelection(const TileInfo *ti, const TileHighlightType &tht) {
// if (_thd.drawstyle == CM_HT_BLUEPRINT_PLACE) return true; // 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 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) { || _thd.select_proc == DDSP_BUILD_TRUCKSTOP || _thd.select_proc == CM_DDSP_BUILD_AIRPORT
// station selector, handled by DrawTileZoning || _thd.select_proc == CM_DDSP_BUILD_ROAD_DEPOT || _thd.select_proc == CM_DDSP_BUILD_RAIL_DEPOT) {
return true; // handled by DrawTileZoning
}
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));
return true; return true;
} }
@@ -1115,6 +1197,15 @@ HighLightStyle UpdateTileSelection(HighLightStyle new_drawstyle) {
// new_drawstyle = CM_HT_BLUEPRINT_PLACE; // new_drawstyle = CM_HT_BLUEPRINT_PLACE;
// } else // } else
if (_thd.make_square_red) { 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) { } else if (_thd.select_proc == CM_DDSP_BUILD_RAIL_DEPOT) {
auto dir = _build_depot_direction; auto dir = _build_depot_direction;
if (pt.x != -1) { if (pt.x != -1) {

View File

@@ -30,6 +30,7 @@ public:
RAIL_BRIDGE_HEAD, RAIL_BRIDGE_HEAD,
RAIL_TUNNEL_HEAD, RAIL_TUNNEL_HEAD,
ROAD_STOP, ROAD_STOP,
ROAD_DEPOT,
AIRPORT_TILE, AIRPORT_TILE,
END, END,
}; };
@@ -67,6 +68,10 @@ public:
DiagDirection ddir; DiagDirection ddir;
bool is_truck; bool is_truck;
} stop; } stop;
struct {
RoadType roadtype;
DiagDirection ddir;
} depot;
} road; } road;
struct { struct {
StationGfx gfx; StationGfx gfx;
@@ -83,6 +88,7 @@ public:
static ObjectTileHighlight make_rail_tunnel_head(SpriteID palette, DiagDirection ddir); 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_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); static ObjectTileHighlight make_airport_tile(SpriteID palette, StationGfx gfx);
}; };
@@ -109,6 +115,7 @@ public:
RAIL_BRIDGE, RAIL_BRIDGE,
RAIL_TUNNEL, RAIL_TUNNEL,
ROAD_STOP, ROAD_STOP,
ROAD_DEPOT,
END, END,
}; };
Type type; Type type;
@@ -150,6 +157,9 @@ public:
DiagDirection ddir; DiagDirection ddir;
TileIndexDiffC other_end; TileIndexDiffC other_end;
} stop; } stop;
struct {
DiagDirection ddir;
} depot;
} road; } road;
} u; } u;
Item(Type type, TileIndexDiffC tdiff) Item(Type type, TileIndexDiffC tdiff)
@@ -184,7 +194,8 @@ public:
RAIL_DEPOT = 1, RAIL_DEPOT = 1,
RAIL_STATION = 2, RAIL_STATION = 2,
ROAD_STOP = 3, ROAD_STOP = 3,
AIRPORT = 4, ROAD_DEPOT = 4,
AIRPORT = 5,
// BLUEPRINT = 2, // BLUEPRINT = 2,
}; };
@@ -214,6 +225,7 @@ public:
static ObjectHighlight make_rail_station(TileIndex start_tile, TileIndex end_tile, Axis axis); static ObjectHighlight make_rail_station(TileIndex start_tile, TileIndex end_tile, Axis axis);
// static ObjectHighlight make_blueprint(TileIndex tile, sp<Blueprint> blueprint); // static ObjectHighlight make_blueprint(TileIndex tile, sp<Blueprint> blueprint);
static ObjectHighlight make_road_stop(TileIndex start_tile, TileIndex end_tile, RoadType roadtype, DiagDirection orientation, bool is_truck); 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); static ObjectHighlight make_airport(TileIndex start_tile, int airport_type, byte airport_layout);
void Draw(const TileInfo *ti); void Draw(const TileInfo *ti);

View File

@@ -9,6 +9,7 @@
namespace citymania { namespace citymania {
const DiagDirection DEPOTDIR_AUTO = DIAGDIR_END;
const DiagDirection STATIONDIR_X = DIAGDIR_END; const DiagDirection STATIONDIR_X = DIAGDIR_END;
const DiagDirection STATIONDIR_Y = (DiagDirection)((uint)DIAGDIR_END + 1); const DiagDirection STATIONDIR_Y = (DiagDirection)((uint)DIAGDIR_END + 1);
const DiagDirection STATIONDIR_AUTO = (DiagDirection)((uint)DIAGDIR_END + 2); const DiagDirection STATIONDIR_AUTO = (DiagDirection)((uint)DIAGDIR_END + 2);

View File

@@ -56,6 +56,9 @@
#ifdef WITH_LIBLZMA #ifdef WITH_LIBLZMA
# include <lzma.h> # include <lzma.h>
#endif #endif
#ifdef WITH_ZSTD
#include <zstd.h>
#endif
#ifdef WITH_LZO #ifdef WITH_LZO
#include <lzo/lzo1x.h> #include <lzo/lzo1x.h>
#endif #endif
@@ -255,6 +258,10 @@ char *CrashLog::LogLibraries(char *buffer, const char *last) const
buffer += seprintf(buffer, last, " LZMA: %s\n", lzma_version_string()); buffer += seprintf(buffer, last, " LZMA: %s\n", lzma_version_string());
#endif #endif
#ifdef WITH_ZSTD
buffer += seprintf(buffer, last, " ZSTD: %s\n", ZSTD_versionString());
#endif
#ifdef WITH_LZO #ifdef WITH_LZO
buffer += seprintf(buffer, last, " LZO: %s\n", lzo_version_string()); buffer += seprintf(buffer, last, " LZO: %s\n", lzo_version_string());
#endif #endif

View File

@@ -395,6 +395,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
p->Send_string(_settings_client.network.client_name); // Client name p->Send_string(_settings_client.network.client_name); // Client name
p->Send_uint8 (_network_join_as); // PlayAs p->Send_uint8 (_network_join_as); // PlayAs
p->Send_uint8 (NETLANG_ANY); // Language p->Send_uint8 (NETLANG_ANY); // Language
p->Send_uint8 (citymania::GetAvailableLoadFormats()); // Compressnion formats that we can decompress
my_client->SendPacket(p); my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY; return NETWORK_RECV_STATUS_OKAY;
} }

View File

@@ -629,7 +629,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMap()
sent_packets = 4; // We start with trying 4 packets sent_packets = 4; // We start with trying 4 packets
/* Make a dump of the current game */ /* 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) { if (this->status == STATUS_MAP) {
@@ -925,9 +925,15 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet *p)
p->Recv_string(name, sizeof(name)); p->Recv_string(name, sizeof(name));
playas = (Owner)p->Recv_uint8(); playas = (Owner)p->Recv_uint8();
client_lang = (NetworkLanguage)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; 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 */ /* join another company does not affect these values */
switch (playas) { switch (playas) {
case COMPANY_NEW_COMPANY: // New company case COMPANY_NEW_COMPANY: // New company

View File

@@ -12,6 +12,7 @@
#include "network_internal.h" #include "network_internal.h"
#include "core/tcp_listen.h" #include "core/tcp_listen.h"
#include "../saveload/saveload.h"
class ServerNetworkGameSocketHandler; class ServerNetworkGameSocketHandler;
/** Make the code look slightly nicer/simpler. */ /** Make the code look slightly nicer/simpler. */
@@ -71,6 +72,7 @@ public:
struct PacketWriter *savegame; ///< Writer used to write the savegame. struct PacketWriter *savegame; ///< Writer used to write the savegame.
NetworkAddress client_address; ///< IP-address of the client (so he can be banned) 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(SOCKET s);
~ServerNetworkGameSocketHandler(); ~ServerNetworkGameSocketHandler();

View File

@@ -809,7 +809,7 @@ struct BuildRailToolbarWindow : Window {
case WID_RAT_BUILD_DEPOT: case WID_RAT_BUILD_DEPOT:
ddir = _build_depot_direction; ddir = _build_depot_direction;
if (ddir == DIAGDIR_NW + 1) { if (ddir == citymania::DEPOTDIR_AUTO) {
assert(_thd.cm.type == citymania::ObjectHighlight::Type::RAIL_DEPOT); assert(_thd.cm.type == citymania::ObjectHighlight::Type::RAIL_DEPOT);
ddir = _thd.cm.ddir; ddir = _thd.cm.ddir;
} }
@@ -2203,8 +2203,8 @@ static WindowDesc _build_depot_desc(
WDP_AUTO, nullptr, 0, 0, WDP_AUTO, nullptr, 0, 0,
WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
WDF_CONSTRUCTION, WDF_CONSTRUCTION,
_nested_build_depot_widgets, lengthof(_nested_build_depot_widgets), _nested_build_depot_widgets, lengthof(_nested_build_depot_widgets)
&BuildRailDepotWindow::hotkeys // CityMania addition ,&BuildRailDepotWindow::hotkeys // CityMania addition
); );
static void ShowBuildTrainDepotPicker(Window *parent) static void ShowBuildTrainDepotPicker(Window *parent)

View File

@@ -70,7 +70,7 @@ static RoadFlags _place_road_flag;
/* CM static */ RoadType _cur_roadtype; /* CM static */ RoadType _cur_roadtype;
static DiagDirection _road_depot_orientation; /* CM static */ DiagDirection _road_depot_orientation;
DiagDirection _road_station_picker_orientation; DiagDirection _road_station_picker_orientation;
void CcPlaySound_CONSTRUCTION_OTHER(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd) void CcPlaySound_CONSTRUCTION_OTHER(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
@@ -472,6 +472,7 @@ struct BuildRoadToolbarWindow : Window {
case WID_ROT_DEPOT: case WID_ROT_DEPOT:
if (HandlePlacePushButton(this, WID_ROT_DEPOT, this->rti->cursor.depot, HT_RECT, CM_DDSP_BUILD_ROAD_DEPOT)) { if (HandlePlacePushButton(this, WID_ROT_DEPOT, this->rti->cursor.depot, HT_RECT, CM_DDSP_BUILD_ROAD_DEPOT)) {
citymania::ResetRotateAutodetection();
ShowRoadDepotPicker(this); ShowRoadDepotPicker(this);
this->last_started_action = widget; this->last_started_action = widget;
} }
@@ -566,8 +567,9 @@ struct BuildRoadToolbarWindow : Window {
case WID_ROT_DEPOT: case WID_ROT_DEPOT:
ddir = _road_depot_orientation; ddir = _road_depot_orientation;
if (ddir == DIAGDIR_NW + 1) { if (ddir == citymania::DEPOTDIR_AUTO) {
ddir = citymania::AutodetectRoadObjectDirection(tile, GetTileBelowCursor(), _cur_roadtype); assert(_thd.cm.type == citymania::ObjectHighlight::Type::ROAD_DEPOT);
ddir = _thd.cm.ddir;
} }
DoCommandP(tile, _cur_roadtype << 2 | ddir, 0, DoCommandP(tile, _cur_roadtype << 2 | ddir, 0,
CMD_BUILD_ROAD_DEPOT | CMD_MSG(this->rti->strings.err_depot), CcRoadDepot); CMD_BUILD_ROAD_DEPOT | CMD_MSG(this->rti->strings.err_depot), CcRoadDepot);
@@ -1023,6 +1025,13 @@ Window *ShowBuildRoadScenToolbar(RoadType roadtype)
} }
struct BuildRoadDepotWindow : public PickerWindowBase { struct BuildRoadDepotWindow : public PickerWindowBase {
/* CityMania code start */
public:
enum class Hotkey : int {
ROTATE,
};
/* CityMania code end */
BuildRoadDepotWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent) BuildRoadDepotWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent)
{ {
this->CreateNestedTree(); this->CreateNestedTree();
@@ -1080,6 +1089,33 @@ struct BuildRoadDepotWindow : public PickerWindowBase {
break; 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[] = { static const NWidgetPart _nested_build_road_depot_widgets[] = {
@@ -1118,11 +1154,20 @@ static const NWidgetPart _nested_build_road_depot_widgets[] = {
EndContainer(), 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( static WindowDesc _build_road_depot_desc(
WDP_AUTO, nullptr, 0, 0, WDP_AUTO, nullptr, 0, 0,
WC_BUILD_DEPOT, WC_BUILD_TOOLBAR, WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
WDF_CONSTRUCTION, WDF_CONSTRUCTION,
_nested_build_road_depot_widgets, lengthof(_nested_build_road_depot_widgets) _nested_build_road_depot_widgets, lengthof(_nested_build_road_depot_widgets)
,&BuildRoadDepotWindow::hotkeys // CityMania addition
); );
static void ShowRoadDepotPicker(Window *parent) static void ShowRoadDepotPicker(Window *parent)

View File

@@ -44,6 +44,7 @@
#include "../fios.h" #include "../fios.h"
#include "../error.h" #include "../error.h"
#include <atomic> #include <atomic>
#include <sstream>
#include <string> #include <string>
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
# include <emscripten.h> # include <emscripten.h>
@@ -2301,40 +2302,163 @@ struct LZMASaveFilter : SaveFilter {
#endif /* WITH_LIBLZMA */ #endif /* WITH_LIBLZMA */
/********************************************
********** START OF ZSTD CODE **************
********************************************/
#if defined(WITH_ZSTD)
#include <zstd.h>
/** 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 ***************** ************* END OF CODE *****************
*******************************************/ *******************************************/
/** The format for a reader/writer type of a savegame */ /** The format for a reader/writer type of a savegame */
struct SaveLoadFormat { // struct SaveLoadFormat {
const char *name; ///< name of the compressor/decompressor (debug-only) // const char *name; ///< name of the compressor/decompressor (debug-only)
uint32 tag; ///< the 4-letter tag by which it is identified in the savegame // uint32 tag; ///< the 4-letter tag by which it is identified in the savegame
LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter. // LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter.
SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter. // SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter.
byte min_compression; ///< the minimum 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 default_compression; ///< the default compression level of this format
byte max_compression; ///< the maximum compression level of this format // byte max_compression; ///< the maximum compression level of this format
}; // };
/** The different saveload formats known/understood by OpenTTD. */ /** 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<NoCompLoadFilter>, CreateSaveFilter<NoCompSaveFilter>, citymania::CompressionMethod::None, 0, 0, 0},
#if defined(WITH_LZO) #if defined(WITH_LZO)
/* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */ /* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */
{"lzo", TO_BE32X('OTTD'), CreateLoadFilter<LZOLoadFilter>, CreateSaveFilter<LZOSaveFilter>, 0, 0, 0}, {1, "lzo", TO_BE32X('OTTD'), CreateLoadFilter<LZOLoadFilter>, CreateSaveFilter<LZOSaveFilter>, citymania::CompressionMethod::LZO, 0, 0, 0},
#else #else
{"lzo", TO_BE32X('OTTD'), nullptr, nullptr, 0, 0, 0}, {1, "lzo", TO_BE32X('OTTD'), nullptr, nullptr, citymania::CompressionMethod::LZO, 0, 0, 0},
#endif #endif
/* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */
{"none", TO_BE32X('OTTN'), CreateLoadFilter<NoCompLoadFilter>, CreateSaveFilter<NoCompSaveFilter>, 0, 0, 0},
#if defined(WITH_ZLIB) #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 /* 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 * 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". */ * 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<ZlibLoadFilter>, CreateSaveFilter<ZlibSaveFilter>, 0, 6, 9}, {2, "zlib", TO_BE32X('OTTZ'), CreateLoadFilter<ZlibLoadFilter>, CreateSaveFilter<ZlibSaveFilter>, citymania::CompressionMethod::Zlib, 0, 6, 9},
#else #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
* 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) */
{3, "zstd", TO_BE32X('OTTS'), CreateLoadFilter<ZSTDLoadFilter>, CreateSaveFilter<ZSTDSaveFilter>, citymania::CompressionMethod::ZSTD, 0, 101, 122},
#else
{3, "zstd", TO_BE32X('OTTS'), nullptr, nullptr, citymania::CompressionMethod::ZSTD, 0, 0, 0},
#endif #endif
#if defined(WITH_LIBLZMA) #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. /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
@@ -2342,12 +2466,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% * 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. * 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. */ * 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<LZMALoadFilter>, CreateSaveFilter<LZMASaveFilter>, 0, 2, 9}, {4, "lzma", TO_BE32X('OTTX'), CreateLoadFilter<LZMALoadFilter>, CreateSaveFilter<LZMASaveFilter>, citymania::CompressionMethod::LZMA, 0, 2, 9},
#else #else
{"lzma", TO_BE32X('OTTX'), nullptr, nullptr, 0, 0, 0}, {4, "lzma", TO_BE32X('OTTX'), nullptr, nullptr, citymania::CompressionMethod::LZMA, 0, 0, 0},
#endif #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<SavePreset> 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<int>(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<SavePreset> 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 * Return the savegameformat of the game. Whether it was created with ZLIB compression
* uncompressed, or another type * uncompressed, or another type
@@ -2355,6 +2578,8 @@ static const SaveLoadFormat _saveload_formats[] = {
* @param compression_level Output for telling what compression level we want. * @param compression_level Output for telling what compression level we want.
* @return Pointer to SaveLoadFormat struct giving all characteristics of this type of savegame * @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) static const SaveLoadFormat *GetSavegameFormat(char *s, byte *compression_level)
{ {
const SaveLoadFormat *def = lastof(_saveload_formats); const SaveLoadFormat *def = lastof(_saveload_formats);
@@ -2402,6 +2627,8 @@ static const SaveLoadFormat *GetSavegameFormat(char *s, byte *compression_level)
return def; return def;
} }
#endif
/* actual loader/saver function */ /* actual loader/saver function */
void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings); void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings);
extern bool AfterLoadGame(); extern bool AfterLoadGame();
@@ -2493,17 +2720,17 @@ static void SaveFileError()
* We have written the whole game into memory, _memory_savegame, now find * We have written the whole game into memory, _memory_savegame, now find
* and appropriate compressor and start writing to file. * and appropriate compressor and start writing to file.
*/ */
static SaveOrLoadResult SaveFileToDisk(bool threaded) static SaveOrLoadResult SaveFileToDisk(bool threaded, citymania::SavePreset preset)
{ {
try { try {
byte compression; // byte compression;
const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression); // const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression);
/* We have written our stuff to memory, now write it to file! */ /* 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->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); _sl.dumper->Flush(_sl.sf);
ClearSaveLoadState(); ClearSaveLoadState();
@@ -2551,7 +2778,7 @@ void WaitTillSaved()
* @param threaded Whether to try to perform the saving asynchronously. * @param threaded Whether to try to perform the saving asynchronously.
* @return Return the result of the action. #SL_OK or #SL_ERROR * @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); assert(!_sl.saveinprogress);
@@ -2565,10 +2792,10 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded)
SaveFileStart(); 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..."); if (threaded) DEBUG(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode...");
SaveOrLoadResult result = SaveFileToDisk(false); SaveOrLoadResult result = SaveFileToDisk(false, preset);
SaveFileDone(); SaveFileDone();
return result; return result;
@@ -2583,11 +2810,11 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded)
* @param threaded Whether to try to perform the saving asynchronously. * @param threaded Whether to try to perform the saving asynchronously.
* @return Return the result of the action. #SL_OK or #SL_ERROR * @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 { try {
_sl.action = SLA_SAVE; _sl.action = SLA_SAVE;
return DoSave(writer, threaded); return DoSave(writer, threaded, preset);
} catch (...) { } catch (...) {
ClearSaveLoadState(); ClearSaveLoadState();
return SL_ERROR; return SL_ERROR;
@@ -2615,7 +2842,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); 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. */ /* see if we have any loader for this type. */
const SaveLoadFormat *fmt = _saveload_formats; const citymania::SaveLoadFormat *fmt = _saveload_formats;
for (;;) { for (;;) {
/* No loader found, treat as version 0 and use LZO format */ /* No loader found, treat as version 0 and use LZO format */
if (fmt == endof(_saveload_formats)) { if (fmt == endof(_saveload_formats)) {
@@ -2828,7 +3055,7 @@ SaveOrLoadResult SaveOrLoad(const std::string &filename, SaveLoadOperation fop,
DEBUG(desync, 1, "save: %08x; %02x; %s", _date, _date_fract, filename.c_str()); DEBUG(desync, 1, "save: %08x; %02x; %s", _date, _date_fract, filename.c_str());
if (_network_server || !_settings_client.gui.threaded_saves) threaded = false; 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 */ /* LOAD game */

View File

@@ -12,6 +12,8 @@
#include "../fileio_type.h" #include "../fileio_type.h"
#include "../strings_type.h" #include "../strings_type.h"
#include "saveload_filter.h"
#include <optional>
#include <string> #include <string>
/** SaveLoad versions /** SaveLoad versions
@@ -359,6 +361,42 @@ enum SavegameType {
SGT_INVALID = 0xFF, ///< broken savegame (used internally) 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<SavePreset> FindCompatibleSavePreset(const std::string &server_formats, uint8 client_format_flags);
uint8 GetAvailableLoadFormats();
} // namespace citymania
extern FileToSaveLoad _file_to_saveload; extern FileToSaveLoad _file_to_saveload;
void GenerateDefaultSaveName(char *buf, const char *last); void GenerateDefaultSaveName(char *buf, const char *last);
@@ -369,7 +407,7 @@ void WaitTillSaved();
void ProcessAsyncSaveFinish(); void ProcessAsyncSaveFinish();
void DoExitSave(); void DoExitSave();
SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded); SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded, citymania::SavePreset preset);
SaveOrLoadResult LoadWithFilter(struct LoadFilter *reader); SaveOrLoadResult LoadWithFilter(struct LoadFilter *reader);
typedef void ChunkSaveLoadProc(); typedef void ChunkSaveLoadProc();

View File

@@ -4006,7 +4006,7 @@ static LineSnapPoint LineSnapPointAtRailTrackEndpoint(TileIndex tile, DiagDirect
TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, INVALID_DIAGDIR)) == AxisToTrackBits(DiagDirToAxis(exit_dir)) && TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, INVALID_DIAGDIR)) == AxisToTrackBits(DiagDirToAxis(exit_dir)) &&
IsTileOwner(tile, _local_company)) { IsTileOwner(tile, _local_company)) {
/* Check if this is a tunnel/bridge and move the tile to the other end if so. */ /* 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); 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 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 *extended = ex; // otherwise return two points, extended with forward direction and base with reverse direction