1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 /** @file waypoint_sl.cpp Code handling saving and loading of waypoints */
9
10 #include "../stdafx.h"
11 #include "../waypoint_base.h"
12 #include "../debug.h"
13 #include "../newgrf_station.h"
14 #include "../vehicle_base.h"
15 #include "../town.h"
16 #include "../newgrf.h"
17
18 #include "table/strings.h"
19
20 #include "saveload_internal.h"
21
22 #include "../safeguards.h"
23
24 /** Helper structure to convert from the old waypoint system. */
25 struct OldWaypoint {
26 size_t index;
27 TileIndex xy;
28 TownID town_index;
29 Town *town;
30 uint16 town_cn;
31 StringID string_id;
32 std::string name;
33 uint8 delete_ctr;
34 Date build_date;
35 uint8 localidx;
36 uint32 grfid;
37 const StationSpec *spec;
38 Owner owner;
39
40 size_t new_index;
41 };
42
43 /** Temporary array with old waypoints. */
44 static std::vector<OldWaypoint> _old_waypoints;
45
46 /**
47 * Update the waypoint orders to get the new waypoint ID.
48 * @param o the order 'list' to check.
49 */
UpdateWaypointOrder(Order * o)50 static void UpdateWaypointOrder(Order *o)
51 {
52 if (!o->IsType(OT_GOTO_WAYPOINT)) return;
53
54 for (OldWaypoint &wp : _old_waypoints) {
55 if (wp.index != o->GetDestination()) continue;
56
57 o->SetDestination((DestinationID)wp.new_index);
58 return;
59 }
60 }
61
62 /**
63 * Perform all steps to upgrade from the old waypoints to the new version
64 * that uses station. This includes some old saveload mechanics.
65 */
MoveWaypointsToBaseStations()66 void MoveWaypointsToBaseStations()
67 {
68 /* In version 17, ground type is moved from m2 to m4 for depots and
69 * waypoints to make way for storing the index in m2. The custom graphics
70 * id which was stored in m4 is now saved as a grf/id reference in the
71 * waypoint struct. */
72 if (IsSavegameVersionBefore(SLV_17)) {
73 for (OldWaypoint &wp : _old_waypoints) {
74 if (wp.delete_ctr != 0) continue; // The waypoint was deleted
75
76 /* Waypoint indices were not added to the map prior to this. */
77 _m[wp.xy].m2 = (StationID)wp.index;
78
79 if (HasBit(_m[wp.xy].m3, 4)) {
80 wp.spec = StationClass::Get(STAT_CLASS_WAYP)->GetSpec(_m[wp.xy].m4 + 1);
81 }
82 }
83 } else {
84 /* As of version 17, we recalculate the custom graphic ID of waypoints
85 * from the GRF ID / station index. */
86 for (OldWaypoint &wp : _old_waypoints) {
87 StationClass* stclass = StationClass::Get(STAT_CLASS_WAYP);
88 for (uint i = 0; i < stclass->GetSpecCount(); i++) {
89 const StationSpec *statspec = stclass->GetSpec(i);
90 if (statspec != nullptr && statspec->grf_prop.grffile->grfid == wp.grfid && statspec->grf_prop.local_id == wp.localidx) {
91 wp.spec = statspec;
92 break;
93 }
94 }
95 }
96 }
97
98 if (!Waypoint::CanAllocateItem(_old_waypoints.size())) SlError(STR_ERROR_TOO_MANY_STATIONS_LOADING);
99
100 /* All saveload conversions have been done. Create the new waypoints! */
101 for (OldWaypoint &wp : _old_waypoints) {
102 TileIndex t = wp.xy;
103 /* Sometimes waypoint (sign) locations became disconnected from their actual location in
104 * the map array. If this is the case, try to locate the actual location in the map array */
105 if (!IsTileType(t, MP_RAILWAY) || GetRailTileType(t) != 2 /* RAIL_TILE_WAYPOINT */ || _m[t].m2 != wp.index) {
106 Debug(sl, 0, "Found waypoint tile {} with invalid position", t);
107 for (t = 0; t < MapSize(); t++) {
108 if (IsTileType(t, MP_RAILWAY) && GetRailTileType(t) == 2 /* RAIL_TILE_WAYPOINT */ && _m[t].m2 == wp.index) {
109 Debug(sl, 0, "Found actual waypoint position at {}", t);
110 break;
111 }
112 }
113 }
114 if (t == MapSize()) {
115 SlErrorCorrupt("Waypoint with invalid tile");
116 }
117
118 Waypoint *new_wp = new Waypoint(t);
119 new_wp->town = wp.town;
120 new_wp->town_cn = wp.town_cn;
121 new_wp->name = wp.name;
122 new_wp->delete_ctr = 0; // Just reset delete counter for once.
123 new_wp->build_date = wp.build_date;
124 new_wp->owner = wp.owner;
125 new_wp->string_id = STR_SV_STNAME_WAYPOINT;
126
127 /* The tile might've been reserved! */
128 bool reserved = !IsSavegameVersionBefore(SLV_100) && HasBit(_m[t].m5, 4);
129
130 /* The tile really has our waypoint, so reassign the map array */
131 MakeRailWaypoint(t, GetTileOwner(t), new_wp->index, (Axis)GB(_m[t].m5, 0, 1), 0, GetRailType(t));
132 new_wp->facilities |= FACIL_TRAIN;
133 new_wp->owner = GetTileOwner(t);
134
135 SetRailStationReservation(t, reserved);
136
137 if (wp.spec != nullptr) {
138 SetCustomStationSpecIndex(t, AllocateSpecToStation(wp.spec, new_wp, true));
139 }
140 new_wp->rect.BeforeAddTile(t, StationRect::ADD_FORCE);
141
142 wp.new_index = new_wp->index;
143 }
144
145 /* Update the orders of vehicles */
146 for (OrderList *ol : OrderList::Iterate()) {
147 if (ol->GetFirstSharedVehicle()->type != VEH_TRAIN) continue;
148
149 for (Order *o = ol->GetFirstOrder(); o != nullptr; o = o->next) UpdateWaypointOrder(o);
150 }
151
152 for (Vehicle *v : Vehicle::Iterate()) {
153 if (v->type != VEH_TRAIN) continue;
154
155 UpdateWaypointOrder(&v->current_order);
156 }
157
158 ResetOldWaypoints();
159 }
160
ResetOldWaypoints()161 void ResetOldWaypoints()
162 {
163 _old_waypoints.clear();
164 _old_waypoints.shrink_to_fit();
165 }
166
167 static const SaveLoad _old_waypoint_desc[] = {
168 SLE_CONDVAR(OldWaypoint, xy, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6),
169 SLE_CONDVAR(OldWaypoint, xy, SLE_UINT32, SLV_6, SL_MAX_VERSION),
170 SLE_CONDVAR(OldWaypoint, town_index, SLE_UINT16, SLV_12, SLV_122),
171 SLE_CONDREF(OldWaypoint, town, REF_TOWN, SLV_122, SL_MAX_VERSION),
172 SLE_CONDVAR(OldWaypoint, town_cn, SLE_FILE_U8 | SLE_VAR_U16, SLV_12, SLV_89),
173 SLE_CONDVAR(OldWaypoint, town_cn, SLE_UINT16, SLV_89, SL_MAX_VERSION),
174 SLE_CONDVAR(OldWaypoint, string_id, SLE_STRINGID, SL_MIN_VERSION, SLV_84),
175 SLE_CONDSSTR(OldWaypoint, name, SLE_STR, SLV_84, SL_MAX_VERSION),
176 SLE_VAR(OldWaypoint, delete_ctr, SLE_UINT8),
177
178 SLE_CONDVAR(OldWaypoint, build_date, SLE_FILE_U16 | SLE_VAR_I32, SLV_3, SLV_31),
179 SLE_CONDVAR(OldWaypoint, build_date, SLE_INT32, SLV_31, SL_MAX_VERSION),
180 SLE_CONDVAR(OldWaypoint, localidx, SLE_UINT8, SLV_3, SL_MAX_VERSION),
181 SLE_CONDVAR(OldWaypoint, grfid, SLE_UINT32, SLV_17, SL_MAX_VERSION),
182 SLE_CONDVAR(OldWaypoint, owner, SLE_UINT8, SLV_101, SL_MAX_VERSION),
183 };
184
185 struct CHKPChunkHandler : ChunkHandler {
CHKPChunkHandlerCHKPChunkHandler186 CHKPChunkHandler() : ChunkHandler('CHKP', CH_READONLY) {}
187
LoadCHKPChunkHandler188 void Load() const override
189 {
190 /* Precaution for when loading failed and it didn't get cleared */
191 ResetOldWaypoints();
192
193 int index;
194
195 while ((index = SlIterateArray()) != -1) {
196 OldWaypoint *wp = &_old_waypoints.emplace_back();
197
198 wp->index = index;
199 SlObject(wp, _old_waypoint_desc);
200 }
201 }
202
FixPointersCHKPChunkHandler203 void FixPointers() const override
204 {
205 for (OldWaypoint &wp : _old_waypoints) {
206 SlObject(&wp, _old_waypoint_desc);
207
208 if (IsSavegameVersionBefore(SLV_12)) {
209 wp.town_cn = (wp.string_id & 0xC000) == 0xC000 ? (wp.string_id >> 8) & 0x3F : 0;
210 wp.town = ClosestTownFromTile(wp.xy, UINT_MAX);
211 } else if (IsSavegameVersionBefore(SLV_122)) {
212 /* Only for versions 12 .. 122 */
213 if (!Town::IsValidID(wp.town_index)) {
214 /* Upon a corrupted waypoint we'll likely get here. The next step will be to
215 * loop over all Ptrs procs to nullptr the pointers. However, we don't know
216 * whether we're in the nullptr or "normal" Ptrs proc. So just clear the list
217 * of old waypoints we constructed and then this waypoint (and the other
218 * possibly corrupt ones) will not be queried in the nullptr Ptrs proc run. */
219 _old_waypoints.clear();
220 SlErrorCorrupt("Referencing invalid Town");
221 }
222 wp.town = Town::Get(wp.town_index);
223 }
224 if (IsSavegameVersionBefore(SLV_84)) {
225 wp.name = CopyFromOldName(wp.string_id);
226 }
227 }
228 }
229 };
230
231 static const CHKPChunkHandler CHKP;
232 static const ChunkHandlerRef waypoint_chunk_handlers[] = {
233 CHKP,
234 };
235
236 extern const ChunkHandlerTable _waypoint_chunk_handlers(waypoint_chunk_handlers);
237