Rearrange storage of reserved tracks for railway tiles
[openttd/fttd.git] / src / waypoint_cmd.cpp
blob1bfc9ee3fbf7106da4876e258875d1c2ce51c9da
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
10 /** @file waypoint_cmd.cpp %Command Handling for waypoints. */
12 #include "stdafx.h"
14 #include "cmd_helper.h"
15 #include "command_func.h"
16 #include "landscape.h"
17 #include "map/rail.h"
18 #include "map/slope.h"
19 #include "town.h"
20 #include "waypoint_base.h"
21 #include "pathfinder/yapf/yapf.h"
22 #include "strings_func.h"
23 #include "viewport_func.h"
24 #include "window_func.h"
25 #include "date_func.h"
26 #include "vehicle_func.h"
27 #include "string_func.h"
28 #include "company_func.h"
29 #include "newgrf_station.h"
30 #include "company_base.h"
31 #include "water.h"
32 #include "company_gui.h"
33 #include "station_func.h"
35 #include "table/strings.h"
37 /**
38 * Update the virtual coords needed to draw the waypoint sign.
40 void Waypoint::UpdateVirtCoord()
42 Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE);
43 SetDParam(0, this->index);
44 this->sign.UpdatePosition(pt.x, pt.y - 32 * ZOOM_LVL_BASE, STR_VIEWPORT_WAYPOINT);
45 /* Recenter viewport */
46 InvalidateWindowData(WC_WAYPOINT_VIEW, this->index);
49 /**
50 * Find a deleted waypoint close to a tile.
51 * @param tile to search from
52 * @param str the string to get the 'type' of
53 * @param cid previous owner of the waypoint
54 * @return the deleted nearby waypoint
56 static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, CompanyID cid)
58 Waypoint *wp, *best = NULL;
59 uint thres = 8;
61 FOR_ALL_WAYPOINTS(wp) {
62 if (!wp->IsInUse() && wp->string_id == str && wp->owner == cid) {
63 uint cur_dist = DistanceManhattan(tile, wp->xy);
65 if (cur_dist < thres) {
66 thres = cur_dist;
67 best = wp;
72 return best;
75 /**
76 * Get the axis for a new waypoint. This means that if it is a valid
77 * tile to build a waypoint on it returns a valid Axis, otherwise an
78 * invalid one.
79 * @param tile the tile to look at.
80 * @return the axis for the to-be-build waypoint.
82 Axis GetAxisForNewWaypoint(TileIndex tile)
84 /* The axis for rail waypoints is easy. */
85 if (IsRailWaypointTile(tile)) return GetRailStationAxis(tile);
87 /* Non-plain rail type, no valid axis for waypoints. */
88 if (!IsNormalRailTile(tile) || HasSignalOnTrack(tile, TRACK_UPPER)) return INVALID_AXIS;
90 switch (GetTrackBits(tile)) {
91 case TRACK_BIT_X: return AXIS_X;
92 case TRACK_BIT_Y: return AXIS_Y;
93 default: return INVALID_AXIS;
97 extern CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags);
99 /**
100 * Check whether the given tile is suitable for a waypoint.
101 * @param tile the tile to check for suitability
102 * @param axis the axis of the waypoint
103 * @param waypoint Waypoint the waypoint to check for is already joined to. If we find another waypoint it can join to it will throw an error.
105 static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID *waypoint)
107 /* if waypoint is set, then we have special handling to allow building on top of already existing waypoints.
108 * so waypoint points to INVALID_STATION if we can build on any waypoint.
109 * Or it points to a waypoint if we're only allowed to build on exactly that waypoint. */
110 if (waypoint != NULL && IsStationTile(tile)) {
111 if (!IsRailWaypoint(tile)) {
112 return ClearTile_Station(tile, DC_AUTO); // get error message
113 } else {
114 StationID wp = GetStationIndex(tile);
115 if (*waypoint == INVALID_STATION) {
116 *waypoint = wp;
117 } else if (*waypoint != wp) {
118 return_cmd_error(STR_ERROR_WAYPOINT_ADJOINS_MORE_THAN_ONE_EXISTING);
123 if (GetAxisForNewWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK);
125 Owner owner = GetTileOwner(tile);
126 CommandCost ret = CheckOwnership(owner);
127 if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile);
128 if (ret.Failed()) return ret;
130 Slope tileh = GetTileSlope(tile);
131 if (tileh != SLOPE_FLAT &&
132 (!_settings_game.construction.build_on_slopes || IsSteepSlope(tileh) || !(tileh & (0x3 << axis)) || !(tileh & ~(0x3 << axis)))) {
133 return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
136 if (HasBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
138 return CommandCost();
141 extern void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec);
142 extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp);
143 extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta, Axis axis);
146 * Convert existing rail to waypoint. Eg build a waypoint station over
147 * piece of rail
148 * @param start_tile northern most tile where waypoint will be built
149 * @param flags type of operation
150 * @param p1 various bitstuffed elements
151 * - p1 = (bit 4) - orientation (Axis)
152 * - p1 = (bit 8-15) - width of waypoint
153 * - p1 = (bit 16-23) - height of waypoint
154 * - p1 = (bit 24) - allow waypoints directly adjacent to other waypoints.
155 * @param p2 various bitstuffed elements
156 * - p2 = (bit 0- 7) - custom station class
157 * - p2 = (bit 8-15) - custom station id
158 * @param text unused
159 * @return the cost of this operation or an error
161 CommandCost CmdBuildRailWaypoint(TileIndex start_tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
163 /* Unpack parameters */
164 Axis axis = Extract<Axis, 4, 1>(p1);
165 byte width = GB(p1, 8, 8);
166 byte height = GB(p1, 16, 8);
167 bool adjacent = HasBit(p1, 24);
169 StationClassID spec_class = Extract<StationClassID, 0, 8>(p2);
170 byte spec_index = GB(p2, 8, 8);
171 StationID station_to_join = GB(p2, 16, 16);
173 /* Check if the given station class is valid */
174 if (spec_class != STAT_CLASS_WAYP) return CMD_ERROR;
175 if (spec_index >= StationClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR;
177 /* The number of parts to build */
178 byte count = axis == AXIS_X ? height : width;
180 if ((axis == AXIS_X ? width : height) != 1) return CMD_ERROR;
181 if (count == 0 || count > _settings_game.station.station_spread) return CMD_ERROR;
183 bool reuse = (station_to_join != NEW_STATION);
184 if (!reuse) station_to_join = INVALID_STATION;
185 bool distant_join = (station_to_join != INVALID_STATION);
187 if (distant_join && (!_settings_game.station.distant_join_stations || !Waypoint::IsValidID(station_to_join))) return CMD_ERROR;
189 /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
190 StationID est = INVALID_STATION;
192 /* Check whether the tiles we're building on are valid rail or not. */
193 TileIndexDiff offset = TileOffsByDiagDir(AxisToDiagDir(OtherAxis(axis)));
194 for (int i = 0; i < count; i++) {
195 TileIndex tile = start_tile + i * offset;
196 CommandCost ret = IsValidTileForWaypoint(tile, axis, &est);
197 if (ret.Failed()) return ret;
200 Waypoint *wp = NULL;
201 TileArea new_location(TileArea(start_tile, width, height));
202 CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, new_location, &wp);
203 if (ret.Failed()) return ret;
205 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
206 TileIndex center_tile = start_tile + (count / 2) * offset;
207 if (wp == NULL && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company);
209 if (wp != NULL) {
210 /* Reuse an existing waypoint. */
211 if (wp->owner != _current_company) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT);
213 /* check if we want to expand an already existing waypoint? */
214 if (wp->train_station.tile != INVALID_TILE) {
215 CommandCost ret = CanExpandRailStation(wp, new_location, axis);
216 if (ret.Failed()) return ret;
219 CommandCost ret = wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TEST);
220 if (ret.Failed()) return ret;
221 } else {
222 /* allocate and initialize new waypoint */
223 if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
226 if (flags & DC_EXEC) {
227 if (wp == NULL) {
228 wp = new Waypoint(start_tile);
229 } else if (!wp->IsInUse()) {
230 /* Move existing (recently deleted) waypoint to the new location */
231 wp->xy = start_tile;
233 wp->owner = GetTileOwner(start_tile);
235 wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TRY);
237 wp->delete_ctr = 0;
238 wp->facilities |= FACIL_TRAIN;
239 wp->build_date = _date;
240 wp->string_id = STR_SV_STNAME_WAYPOINT;
241 wp->train_station = new_location;
243 if (wp->town == NULL) MakeDefaultName(wp);
245 wp->UpdateVirtCoord();
247 const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index);
248 byte *layout_ptr = AllocaM(byte, count);
249 if (spec == NULL) {
250 /* The layout must be 0 for the 'normal' waypoints by design. */
251 memset(layout_ptr, 0, count);
252 } else {
253 /* But for NewGRF waypoints we like to have their style. */
254 GetStationLayout(layout_ptr, count, 1, spec);
256 byte map_spec_index = AllocateSpecToStation(spec, wp, true);
258 Company *c = Company::Get(wp->owner);
259 for (int i = 0; i < count; i++) {
260 TileIndex tile = start_tile + i * offset;
261 byte old_specindex = HasStationTileRail(tile) ? GetCustomStationSpecIndex(tile) : 0;
262 if (!HasStationTileRail(tile)) c->infrastructure.station++;
263 bool reserved = IsRailwayTile(tile) ?
264 HasBit(GetRailReservationTrackBits(tile), AxisToTrack(axis)) :
265 HasStationReservation(tile);
266 MakeRailWaypoint(tile, wp->owner, wp->index, axis, layout_ptr[i], GetRailType(tile));
267 SetCustomStationSpecIndex(tile, map_spec_index);
268 SetRailStationReservation(tile, reserved);
269 MarkTileDirtyByTile(tile);
271 DeallocateSpecFromStation(wp, old_specindex);
272 YapfNotifyTrackLayoutChange(tile, AxisToTrack(axis));
274 DirtyCompanyInfrastructureWindows(wp->owner);
277 return CommandCost(EXPENSES_CONSTRUCTION, count * _price[PR_BUILD_WAYPOINT_RAIL]);
281 * Build a buoy.
282 * @param tile tile where to place the buoy
283 * @param flags operation to perform
284 * @param p1 unused
285 * @param p2 unused
286 * @param text unused
287 * @return the cost of this operation or an error
289 CommandCost CmdBuildBuoy(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
291 if (tile == 0 || !HasTileWaterGround(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
292 if (HasBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
294 if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
296 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
297 Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE);
298 if (wp == NULL && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
300 CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_WAYPOINT_BUOY]);
301 if (!IsPlainWaterTile(tile)) {
302 CommandCost ret = DoCommand(tile, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR);
303 if (ret.Failed()) return ret;
304 cost.AddCost(ret);
307 if (flags & DC_EXEC) {
308 if (wp == NULL) {
309 wp = new Waypoint(tile);
310 } else {
311 /* Move existing (recently deleted) buoy to the new location */
312 wp->xy = tile;
313 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
315 wp->rect.BeforeAddTile(tile, StationRect::ADD_TRY);
317 wp->string_id = STR_SV_STNAME_BUOY;
319 wp->facilities |= FACIL_DOCK;
320 wp->owner = OWNER_NONE;
322 wp->build_date = _date;
324 if (wp->town == NULL) MakeDefaultName(wp);
326 MakeBuoy(tile, wp->index, GetWaterClass(tile));
328 wp->UpdateVirtCoord();
329 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
332 return cost;
336 * Remove a buoy
337 * @param tile TileIndex been queried
338 * @param flags operation to perform
339 * @pre IsBuoyTile(tile)
340 * @return cost or failure of operation
342 CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags)
344 /* XXX: strange stuff, allow clearing as invalid company when clearing landscape */
345 if (!Company::IsValidID(_current_company) && !(flags & DC_BANKRUPT)) return_cmd_error(INVALID_STRING_ID);
347 Waypoint *wp = Waypoint::GetByTile(tile);
349 if (HasStationInUse(wp->index, false, _current_company)) return_cmd_error(STR_ERROR_BUOY_IS_IN_USE);
350 /* remove the buoy if there is a ship on tile when company goes bankrupt... */
351 if (!(flags & DC_BANKRUPT)) {
352 CommandCost ret = EnsureNoVehicleOnGround(tile);
353 if (ret.Failed()) return ret;
356 if (flags & DC_EXEC) {
357 wp->facilities &= ~FACIL_DOCK;
359 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
361 /* We have to set the water tile's state to the same state as before the
362 * buoy was placed. Otherwise one could plant a buoy on a canal edge,
363 * remove it and flood the land (if the canal edge is at level 0) */
364 MakeWaterKeepingClass(tile, GetTileOwner(tile));
366 wp->rect.AfterRemoveTile(wp, tile);
368 wp->UpdateVirtCoord();
369 wp->delete_ctr = 0;
372 return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WAYPOINT_BUOY]);
376 * Check whether the name is unique amongst the waypoints.
377 * @param name The name to check.
378 * @return True iff the name is unique.
380 static bool IsUniqueWaypointName(const char *name)
382 const Waypoint *wp;
384 FOR_ALL_WAYPOINTS(wp) {
385 if (wp->name != NULL && strcmp(wp->name, name) == 0) return false;
388 return true;
392 * Rename a waypoint.
393 * @param tile unused
394 * @param flags type of operation
395 * @param p1 id of waypoint
396 * @param p2 unused
397 * @param text the new name or an empty string when resetting to the default
398 * @return the cost of this operation or an error
400 CommandCost CmdRenameWaypoint(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
402 Waypoint *wp = Waypoint::GetIfValid(p1);
403 if (wp == NULL) return CMD_ERROR;
405 if (wp->owner != OWNER_NONE) {
406 CommandCost ret = CheckOwnership(wp->owner);
407 if (ret.Failed()) return ret;
410 bool reset = StrEmpty(text);
412 if (!reset) {
413 if (Utf8StringLength(text) >= MAX_LENGTH_STATION_NAME_CHARS) return CMD_ERROR;
414 if (!IsUniqueWaypointName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE);
417 if (flags & DC_EXEC) {
418 free(wp->name);
419 wp->name = reset ? NULL : strdup(text);
421 wp->UpdateVirtCoord();
423 return CommandCost();