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/>.
10 /** @file waypoint_cmd.cpp %Command Handling for waypoints. */
14 #include "cmd_helper.h"
15 #include "command_func.h"
16 #include "landscape.h"
17 #include "bridge_map.h"
19 #include "waypoint_base.h"
20 #include "pathfinder/yapf/yapf_cache.h"
21 #include "strings_func.h"
22 #include "viewport_func.h"
23 #include "window_func.h"
24 #include "date_func.h"
25 #include "vehicle_func.h"
26 #include "string_func.h"
27 #include "company_func.h"
28 #include "newgrf_station.h"
29 #include "company_base.h"
31 #include "company_gui.h"
33 #include "table/strings.h"
35 #include "safeguards.h"
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
);
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
;
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
) {
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
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 (!IsTileType(tile
, MP_RAILWAY
) || GetRailTileType(tile
) != RAIL_TILE_NORMAL
) 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
);
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
&& IsTileType(tile
, MP_STATION
)) {
111 if (!IsRailWaypoint(tile
)) {
112 return ClearTile_Station(tile
, DC_AUTO
); // get error message
114 StationID wp
= GetStationIndex(tile
);
115 if (*waypoint
== INVALID_STATION
) {
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 (IsBridgeAbove(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
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
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
;
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
);
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
;
222 /* allocate and initialize new waypoint */
223 if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING
);
226 if (flags
& DC_EXEC
) {
228 wp
= new Waypoint(start_tile
);
229 } else if (!wp
->IsInUse()) {
230 /* Move existing (recently deleted) waypoint to the new location */
233 wp
->owner
= GetTileOwner(start_tile
);
235 wp
->rect
.BeforeAddRect(start_tile
, width
, height
, StationRect::ADD_TRY
);
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
);
250 /* The layout must be 0 for the 'normal' waypoints by design. */
251 memset(layout_ptr
, 0, count
);
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
= IsTileType(tile
, MP_RAILWAY
) ?
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
, ZOOM_LVL_DRAW_MAP
);
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
]);
282 * @param tile tile where to place the buoy
283 * @param flags operation to perform
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 (IsBridgeAbove(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 (!IsWaterTile(tile
)) {
302 CommandCost ret
= DoCommand(tile
, 0, 0, flags
| DC_AUTO
, CMD_LANDSCAPE_CLEAR
);
303 if (ret
.Failed()) return ret
;
307 if (flags
& DC_EXEC
) {
309 wp
= new Waypoint(tile
);
311 /* Move existing (recently deleted) buoy to the new location */
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
));
327 MarkTileDirtyByTile(tile
);
329 wp
->UpdateVirtCoord();
330 InvalidateWindowData(WC_WAYPOINT_VIEW
, wp
->index
);
338 * @param tile TileIndex been queried
339 * @param flags operation to perform
340 * @pre IsBuoyTile(tile)
341 * @return cost or failure of operation
343 CommandCost
RemoveBuoy(TileIndex tile
, DoCommandFlag flags
)
345 /* XXX: strange stuff, allow clearing as invalid company when clearing landscape */
346 if (!Company::IsValidID(_current_company
) && !(flags
& DC_BANKRUPT
)) return_cmd_error(INVALID_STRING_ID
);
348 Waypoint
*wp
= Waypoint::GetByTile(tile
);
350 if (HasStationInUse(wp
->index
, false, _current_company
)) return_cmd_error(STR_ERROR_BUOY_IS_IN_USE
);
351 /* remove the buoy if there is a ship on tile when company goes bankrupt... */
352 if (!(flags
& DC_BANKRUPT
)) {
353 CommandCost ret
= EnsureNoVehicleOnGround(tile
);
354 if (ret
.Failed()) return ret
;
357 if (flags
& DC_EXEC
) {
358 wp
->facilities
&= ~FACIL_DOCK
;
360 InvalidateWindowData(WC_WAYPOINT_VIEW
, wp
->index
);
362 /* We have to set the water tile's state to the same state as before the
363 * buoy was placed. Otherwise one could plant a buoy on a canal edge,
364 * remove it and flood the land (if the canal edge is at level 0) */
365 MakeWaterKeepingClass(tile
, GetTileOwner(tile
));
367 wp
->rect
.AfterRemoveTile(wp
, tile
);
369 wp
->UpdateVirtCoord();
373 return CommandCost(EXPENSES_CONSTRUCTION
, _price
[PR_CLEAR_WAYPOINT_BUOY
]);
377 * Check whether the name is unique amongst the waypoints.
378 * @param name The name to check.
379 * @return True iff the name is unique.
381 static bool IsUniqueWaypointName(const char *name
)
385 FOR_ALL_WAYPOINTS(wp
) {
386 if (wp
->name
!= NULL
&& strcmp(wp
->name
, name
) == 0) return false;
395 * @param flags type of operation
396 * @param p1 id of waypoint
398 * @param text the new name or an empty string when resetting to the default
399 * @return the cost of this operation or an error
401 CommandCost
CmdRenameWaypoint(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
403 Waypoint
*wp
= Waypoint::GetIfValid(p1
);
404 if (wp
== NULL
) return CMD_ERROR
;
406 if (wp
->owner
!= OWNER_NONE
) {
407 CommandCost ret
= CheckOwnership(wp
->owner
);
408 if (ret
.Failed()) return ret
;
411 bool reset
= StrEmpty(text
);
414 if (Utf8StringLength(text
) >= MAX_LENGTH_STATION_NAME_CHARS
) return CMD_ERROR
;
415 if (!IsUniqueWaypointName(text
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
418 if (flags
& DC_EXEC
) {
420 wp
->name
= reset
? NULL
: stredup(text
);
422 wp
->UpdateVirtCoord();
424 return CommandCost();