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/>.
8 /** @file waypoint_cmd.cpp %Command Handling for waypoints. */
12 #include "cmd_helper.h"
13 #include "command_func.h"
14 #include "landscape.h"
15 #include "bridge_map.h"
17 #include "waypoint_base.h"
18 #include "pathfinder/yapf/yapf_cache.h"
19 #include "strings_func.h"
20 #include "viewport_func.h"
21 #include "viewport_kdtree.h"
22 #include "window_func.h"
23 #include "date_func.h"
24 #include "vehicle_func.h"
25 #include "string_func.h"
26 #include "company_func.h"
27 #include "newgrf_station.h"
28 #include "company_base.h"
30 #include "company_gui.h"
32 #include "table/strings.h"
34 #include "safeguards.h"
37 * Update the virtual coords needed to draw the waypoint sign.
39 void Waypoint::UpdateVirtCoord()
41 Point pt
= RemapCoords2(TileX(this->xy
) * TILE_SIZE
, TileY(this->xy
) * TILE_SIZE
);
42 if (this->sign
.kdtree_valid
) _viewport_sign_kdtree
.Remove(ViewportSignKdtreeItem::MakeWaypoint(this->index
));
44 SetDParam(0, this->index
);
45 this->sign
.UpdatePosition(pt
.x
, pt
.y
- 32 * ZOOM_LVL_BASE
, STR_VIEWPORT_WAYPOINT
);
47 _viewport_sign_kdtree
.Insert(ViewportSignKdtreeItem::MakeWaypoint(this->index
));
49 /* Recenter viewport */
50 InvalidateWindowData(WC_WAYPOINT_VIEW
, this->index
);
54 * Move the waypoint main coordinate somewhere else.
55 * @param new_xy new tile location of the sign
57 void Waypoint::MoveSign(TileIndex new_xy
)
59 if (this->xy
== new_xy
) return;
61 this->BaseStation::MoveSign(new_xy
);
65 * Find a deleted waypoint close to a tile.
66 * @param tile to search from
67 * @param str the string to get the 'type' of
68 * @param cid previous owner of the waypoint
69 * @return the deleted nearby waypoint
71 static Waypoint
*FindDeletedWaypointCloseTo(TileIndex tile
, StringID str
, CompanyID cid
)
73 Waypoint
*best
= nullptr;
76 for (Waypoint
*wp
: Waypoint::Iterate()) {
77 if (!wp
->IsInUse() && wp
->string_id
== str
&& wp
->owner
== cid
) {
78 uint cur_dist
= DistanceManhattan(tile
, wp
->xy
);
80 if (cur_dist
< thres
) {
91 * Get the axis for a new waypoint. This means that if it is a valid
92 * tile to build a waypoint on it returns a valid Axis, otherwise an
94 * @param tile the tile to look at.
95 * @return the axis for the to-be-build waypoint.
97 Axis
GetAxisForNewWaypoint(TileIndex tile
)
99 /* The axis for rail waypoints is easy. */
100 if (IsRailWaypointTile(tile
)) return GetRailStationAxis(tile
);
102 /* Non-plain rail type, no valid axis for waypoints. */
103 if (!IsTileType(tile
, MP_RAILWAY
) || GetRailTileType(tile
) != RAIL_TILE_NORMAL
) return INVALID_AXIS
;
105 switch (GetTrackBits(tile
)) {
106 case TRACK_BIT_X
: return AXIS_X
;
107 case TRACK_BIT_Y
: return AXIS_Y
;
108 default: return INVALID_AXIS
;
112 extern CommandCost
ClearTile_Station(TileIndex tile
, DoCommandFlag flags
);
115 * Check whether the given tile is suitable for a waypoint.
116 * @param tile the tile to check for suitability
117 * @param axis the axis of the waypoint
118 * @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.
120 static CommandCost
IsValidTileForWaypoint(TileIndex tile
, Axis axis
, StationID
*waypoint
)
122 /* if waypoint is set, then we have special handling to allow building on top of already existing waypoints.
123 * so waypoint points to INVALID_STATION if we can build on any waypoint.
124 * Or it points to a waypoint if we're only allowed to build on exactly that waypoint. */
125 if (waypoint
!= nullptr && IsTileType(tile
, MP_STATION
)) {
126 if (!IsRailWaypoint(tile
)) {
127 return ClearTile_Station(tile
, DC_AUTO
); // get error message
129 StationID wp
= GetStationIndex(tile
);
130 if (*waypoint
== INVALID_STATION
) {
132 } else if (*waypoint
!= wp
) {
133 return_cmd_error(STR_ERROR_WAYPOINT_ADJOINS_MORE_THAN_ONE_EXISTING
);
138 if (GetAxisForNewWaypoint(tile
) != axis
) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK
);
140 Owner owner
= GetTileOwner(tile
);
141 CommandCost ret
= CheckOwnership(owner
);
142 if (ret
.Succeeded()) ret
= EnsureNoVehicleOnGround(tile
);
143 if (ret
.Failed()) return ret
;
145 Slope tileh
= GetTileSlope(tile
);
146 if (tileh
!= SLOPE_FLAT
&&
147 (!_settings_game
.construction
.build_on_slopes
|| IsSteepSlope(tileh
) || !(tileh
& (0x3 << axis
)) || !(tileh
& ~(0x3 << axis
)))) {
148 return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED
);
151 if (IsBridgeAbove(tile
)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST
);
153 return CommandCost();
156 extern void GetStationLayout(byte
*layout
, int numtracks
, int plat_len
, const StationSpec
*statspec
);
157 extern CommandCost
FindJoiningWaypoint(StationID existing_station
, StationID station_to_join
, bool adjacent
, TileArea ta
, Waypoint
**wp
);
158 extern CommandCost
CanExpandRailStation(const BaseStation
*st
, TileArea
&new_ta
, Axis axis
);
161 * Convert existing rail to waypoint. Eg build a waypoint station over
163 * @param start_tile northern most tile where waypoint will be built
164 * @param flags type of operation
165 * @param p1 various bitstuffed elements
166 * - p1 = (bit 0- 5) - railtype (not used)
167 * - p1 = (bit 6) - orientation (Axis)
168 * - p1 = (bit 8-15) - width of waypoint
169 * - p1 = (bit 16-23) - height of waypoint
170 * - p1 = (bit 24) - allow waypoints directly adjacent to other waypoints.
171 * @param p2 various bitstuffed elements
172 * - p2 = (bit 0- 7) - custom station class
173 * - p2 = (bit 8-15) - custom station id
175 * @return the cost of this operation or an error
177 CommandCost
CmdBuildRailWaypoint(TileIndex start_tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
179 /* Unpack parameters */
180 Axis axis
= Extract
<Axis
, 6, 1>(p1
);
181 byte width
= GB(p1
, 8, 8);
182 byte height
= GB(p1
, 16, 8);
183 bool adjacent
= HasBit(p1
, 24);
185 StationClassID spec_class
= Extract
<StationClassID
, 0, 8>(p2
);
186 byte spec_index
= GB(p2
, 8, 8);
187 StationID station_to_join
= GB(p2
, 16, 16);
189 /* Check if the given station class is valid */
190 if (spec_class
!= STAT_CLASS_WAYP
) return CMD_ERROR
;
191 if (spec_index
>= StationClass::Get(spec_class
)->GetSpecCount()) return CMD_ERROR
;
193 /* The number of parts to build */
194 byte count
= axis
== AXIS_X
? height
: width
;
196 if ((axis
== AXIS_X
? width
: height
) != 1) return CMD_ERROR
;
197 if (count
== 0 || count
> _settings_game
.station
.station_spread
) return CMD_ERROR
;
199 bool reuse
= (station_to_join
!= NEW_STATION
);
200 if (!reuse
) station_to_join
= INVALID_STATION
;
201 bool distant_join
= (station_to_join
!= INVALID_STATION
);
203 if (distant_join
&& (!_settings_game
.station
.distant_join_stations
|| !Waypoint::IsValidID(station_to_join
))) return CMD_ERROR
;
205 /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
206 StationID est
= INVALID_STATION
;
208 /* Check whether the tiles we're building on are valid rail or not. */
209 TileIndexDiff offset
= TileOffsByDiagDir(AxisToDiagDir(OtherAxis(axis
)));
210 for (int i
= 0; i
< count
; i
++) {
211 TileIndex tile
= start_tile
+ i
* offset
;
212 CommandCost ret
= IsValidTileForWaypoint(tile
, axis
, &est
);
213 if (ret
.Failed()) return ret
;
216 Waypoint
*wp
= nullptr;
217 TileArea
new_location(start_tile
, width
, height
);
218 CommandCost ret
= FindJoiningWaypoint(est
, station_to_join
, adjacent
, new_location
, &wp
);
219 if (ret
.Failed()) return ret
;
221 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
222 TileIndex center_tile
= start_tile
+ (count
/ 2) * offset
;
223 if (wp
== nullptr && reuse
) wp
= FindDeletedWaypointCloseTo(center_tile
, STR_SV_STNAME_WAYPOINT
, _current_company
);
226 /* Reuse an existing waypoint. */
227 if (wp
->owner
!= _current_company
) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT
);
229 /* check if we want to expand an already existing waypoint? */
230 if (wp
->train_station
.tile
!= INVALID_TILE
) {
231 CommandCost ret
= CanExpandRailStation(wp
, new_location
, axis
);
232 if (ret
.Failed()) return ret
;
235 CommandCost ret
= wp
->rect
.BeforeAddRect(start_tile
, width
, height
, StationRect::ADD_TEST
);
236 if (ret
.Failed()) return ret
;
238 /* allocate and initialize new waypoint */
239 if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING
);
242 if (flags
& DC_EXEC
) {
244 wp
= new Waypoint(start_tile
);
245 } else if (!wp
->IsInUse()) {
246 /* Move existing (recently deleted) waypoint to the new location */
249 wp
->owner
= GetTileOwner(start_tile
);
251 wp
->rect
.BeforeAddRect(start_tile
, width
, height
, StationRect::ADD_TRY
);
254 wp
->facilities
|= FACIL_TRAIN
;
255 wp
->build_date
= _date
;
256 wp
->string_id
= STR_SV_STNAME_WAYPOINT
;
257 wp
->train_station
= new_location
;
259 if (wp
->town
== nullptr) MakeDefaultName(wp
);
261 wp
->UpdateVirtCoord();
263 const StationSpec
*spec
= StationClass::Get(spec_class
)->GetSpec(spec_index
);
264 byte
*layout_ptr
= AllocaM(byte
, count
);
265 if (spec
== nullptr) {
266 /* The layout must be 0 for the 'normal' waypoints by design. */
267 memset(layout_ptr
, 0, count
);
269 /* But for NewGRF waypoints we like to have their style. */
270 GetStationLayout(layout_ptr
, count
, 1, spec
);
272 byte map_spec_index
= AllocateSpecToStation(spec
, wp
, true);
274 Company
*c
= Company::Get(wp
->owner
);
275 for (int i
= 0; i
< count
; i
++) {
276 TileIndex tile
= start_tile
+ i
* offset
;
277 byte old_specindex
= HasStationTileRail(tile
) ? GetCustomStationSpecIndex(tile
) : 0;
278 if (!HasStationTileRail(tile
)) c
->infrastructure
.station
++;
279 bool reserved
= IsTileType(tile
, MP_RAILWAY
) ?
280 HasBit(GetRailReservationTrackBits(tile
), AxisToTrack(axis
)) :
281 HasStationReservation(tile
);
282 MakeRailWaypoint(tile
, wp
->owner
, wp
->index
, axis
, layout_ptr
[i
], GetRailType(tile
));
283 SetCustomStationSpecIndex(tile
, map_spec_index
);
284 SetRailStationReservation(tile
, reserved
);
285 MarkTileDirtyByTile(tile
);
287 DeallocateSpecFromStation(wp
, old_specindex
);
288 YapfNotifyTrackLayoutChange(tile
, AxisToTrack(axis
));
290 DirtyCompanyInfrastructureWindows(wp
->owner
);
293 return CommandCost(EXPENSES_CONSTRUCTION
, count
* _price
[PR_BUILD_WAYPOINT_RAIL
]);
298 * @param tile tile where to place the buoy
299 * @param flags operation to perform
303 * @return the cost of this operation or an error
305 CommandCost
CmdBuildBuoy(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
307 if (tile
== 0 || !HasTileWaterGround(tile
)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE
);
308 if (IsBridgeAbove(tile
)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST
);
310 if (!IsTileFlat(tile
)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE
);
312 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
313 Waypoint
*wp
= FindDeletedWaypointCloseTo(tile
, STR_SV_STNAME_BUOY
, OWNER_NONE
);
314 if (wp
== nullptr && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING
);
316 CommandCost
cost(EXPENSES_CONSTRUCTION
, _price
[PR_BUILD_WAYPOINT_BUOY
]);
317 if (!IsWaterTile(tile
)) {
318 CommandCost ret
= DoCommand(tile
, 0, 0, flags
| DC_AUTO
, CMD_LANDSCAPE_CLEAR
);
319 if (ret
.Failed()) return ret
;
323 if (flags
& DC_EXEC
) {
325 wp
= new Waypoint(tile
);
327 /* Move existing (recently deleted) buoy to the new location */
329 InvalidateWindowData(WC_WAYPOINT_VIEW
, wp
->index
);
331 wp
->rect
.BeforeAddTile(tile
, StationRect::ADD_TRY
);
333 wp
->string_id
= STR_SV_STNAME_BUOY
;
335 wp
->facilities
|= FACIL_DOCK
;
336 wp
->owner
= OWNER_NONE
;
338 wp
->build_date
= _date
;
340 if (wp
->town
== nullptr) MakeDefaultName(wp
);
342 MakeBuoy(tile
, wp
->index
, GetWaterClass(tile
));
343 CheckForDockingTile(tile
);
344 MarkTileDirtyByTile(tile
);
346 wp
->UpdateVirtCoord();
347 InvalidateWindowData(WC_WAYPOINT_VIEW
, wp
->index
);
355 * @param tile TileIndex been queried
356 * @param flags operation to perform
357 * @pre IsBuoyTile(tile)
358 * @return cost or failure of operation
360 CommandCost
RemoveBuoy(TileIndex tile
, DoCommandFlag flags
)
362 /* XXX: strange stuff, allow clearing as invalid company when clearing landscape */
363 if (!Company::IsValidID(_current_company
) && !(flags
& DC_BANKRUPT
)) return_cmd_error(INVALID_STRING_ID
);
365 Waypoint
*wp
= Waypoint::GetByTile(tile
);
367 if (HasStationInUse(wp
->index
, false, _current_company
)) return_cmd_error(STR_ERROR_BUOY_IS_IN_USE
);
368 /* remove the buoy if there is a ship on tile when company goes bankrupt... */
369 if (!(flags
& DC_BANKRUPT
)) {
370 CommandCost ret
= EnsureNoVehicleOnGround(tile
);
371 if (ret
.Failed()) return ret
;
374 if (flags
& DC_EXEC
) {
375 wp
->facilities
&= ~FACIL_DOCK
;
377 InvalidateWindowData(WC_WAYPOINT_VIEW
, wp
->index
);
379 /* We have to set the water tile's state to the same state as before the
380 * buoy was placed. Otherwise one could plant a buoy on a canal edge,
381 * remove it and flood the land (if the canal edge is at level 0) */
382 MakeWaterKeepingClass(tile
, GetTileOwner(tile
));
384 wp
->rect
.AfterRemoveTile(wp
, tile
);
386 wp
->UpdateVirtCoord();
390 return CommandCost(EXPENSES_CONSTRUCTION
, _price
[PR_CLEAR_WAYPOINT_BUOY
]);
394 * Check whether the name is unique amongst the waypoints.
395 * @param name The name to check.
396 * @return True iff the name is unique.
398 static bool IsUniqueWaypointName(const char *name
)
400 for (const Waypoint
*wp
: Waypoint::Iterate()) {
401 if (wp
->name
!= nullptr && strcmp(wp
->name
, name
) == 0) return false;
410 * @param flags type of operation
411 * @param p1 id of waypoint
413 * @param text the new name or an empty string when resetting to the default
414 * @return the cost of this operation or an error
416 CommandCost
CmdRenameWaypoint(TileIndex tile
, DoCommandFlag flags
, uint32 p1
, uint32 p2
, const char *text
)
418 Waypoint
*wp
= Waypoint::GetIfValid(p1
);
419 if (wp
== nullptr) return CMD_ERROR
;
421 if (wp
->owner
!= OWNER_NONE
) {
422 CommandCost ret
= CheckOwnership(wp
->owner
);
423 if (ret
.Failed()) return ret
;
426 bool reset
= StrEmpty(text
);
429 if (Utf8StringLength(text
) >= MAX_LENGTH_STATION_NAME_CHARS
) return CMD_ERROR
;
430 if (!IsUniqueWaypointName(text
)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE
);
433 if (flags
& DC_EXEC
) {
435 wp
->name
= reset
? nullptr : stredup(text
);
437 wp
->UpdateVirtCoord();
439 return CommandCost();