Codechange: Update minimum CMake version to 3.16 for all parts. (#13141)
[openttd-github.git] / src / waypoint_cmd.cpp
blobb6796b25a32cae3cc02862ef9c3cad2369bde54e
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 */
8 /** @file waypoint_cmd.cpp %Command Handling for waypoints. */
10 #include "stdafx.h"
12 #include "command_func.h"
13 #include "landscape.h"
14 #include "bridge_map.h"
15 #include "town.h"
16 #include "waypoint_base.h"
17 #include "pathfinder/yapf/yapf_cache.h"
18 #include "pathfinder/water_regions.h"
19 #include "strings_func.h"
20 #include "viewport_func.h"
21 #include "viewport_kdtree.h"
22 #include "window_func.h"
23 #include "timer/timer_game_calendar.h"
24 #include "vehicle_func.h"
25 #include "string_func.h"
26 #include "company_func.h"
27 #include "newgrf_station.h"
28 #include "newgrf_roadstop.h"
29 #include "company_base.h"
30 #include "water.h"
31 #include "company_gui.h"
32 #include "waypoint_cmd.h"
33 #include "landscape_cmd.h"
35 #include "table/strings.h"
37 #include "safeguards.h"
39 /**
40 * Update the virtual coords needed to draw the waypoint sign.
42 void Waypoint::UpdateVirtCoord()
44 Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE);
45 if (this->sign.kdtree_valid) _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeWaypoint(this->index));
47 SetDParam(0, this->index);
48 this->sign.UpdatePosition(pt.x, pt.y - 32 * ZOOM_BASE, STR_VIEWPORT_WAYPOINT);
50 _viewport_sign_kdtree.Insert(ViewportSignKdtreeItem::MakeWaypoint(this->index));
52 /* Recenter viewport */
53 InvalidateWindowData(WC_WAYPOINT_VIEW, this->index);
56 /**
57 * Move the waypoint main coordinate somewhere else.
58 * @param new_xy new tile location of the sign
60 void Waypoint::MoveSign(TileIndex new_xy)
62 if (this->xy == new_xy) return;
64 this->BaseStation::MoveSign(new_xy);
67 /**
68 * Find a deleted waypoint close to a tile.
69 * @param tile to search from
70 * @param str the string to get the 'type' of
71 * @param cid previous owner of the waypoint
72 * @param is_road whether to find a road waypoint
73 * @return the deleted nearby waypoint
75 static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, CompanyID cid, bool is_road)
77 Waypoint *best = nullptr;
78 uint thres = 8;
80 for (Waypoint *wp : Waypoint::Iterate()) {
81 if (!wp->IsInUse() && wp->string_id == str && wp->owner == cid && HasBit(wp->waypoint_flags, WPF_ROAD) == is_road) {
82 uint cur_dist = DistanceManhattan(tile, wp->xy);
84 if (cur_dist < thres) {
85 thres = cur_dist;
86 best = wp;
91 return best;
94 /**
95 * Get the axis for a new rail waypoint. This means that if it is a valid
96 * tile to build a waypoint on it returns a valid Axis, otherwise an
97 * invalid one.
98 * @param tile the tile to look at.
99 * @return the axis for the to-be-build waypoint.
101 Axis GetAxisForNewRailWaypoint(TileIndex tile)
103 /* The axis for rail waypoints is easy. */
104 if (IsRailWaypointTile(tile)) return GetRailStationAxis(tile);
106 /* Non-plain rail type, no valid axis for waypoints. */
107 if (!IsTileType(tile, MP_RAILWAY) || GetRailTileType(tile) != RAIL_TILE_NORMAL) return INVALID_AXIS;
109 switch (GetTrackBits(tile)) {
110 case TRACK_BIT_X: return AXIS_X;
111 case TRACK_BIT_Y: return AXIS_Y;
112 default: return INVALID_AXIS;
117 * Get the axis for a new road waypoint. This means that if it is a valid
118 * tile to build a waypoint on it returns a valid Axis, otherwise an
119 * invalid one.
120 * @param tile the tile to look at.
121 * @return the axis for the to-be-build waypoint.
123 Axis GetAxisForNewRoadWaypoint(TileIndex tile)
125 /* The axis for existing road waypoints is easy. */
126 if (IsRoadWaypointTile(tile)) return GetDriveThroughStopAxis(tile);
128 /* Non-plain road type, no valid axis for waypoints. */
129 if (!IsNormalRoadTile(tile)) return INVALID_AXIS;
131 RoadBits bits = GetAllRoadBits(tile);
133 if ((bits & ROAD_Y) == 0) return AXIS_X;
134 if ((bits & ROAD_X) == 0) return AXIS_Y;
136 return INVALID_AXIS;
139 extern CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags);
142 * Check whether the given tile is suitable for a waypoint.
143 * @param tile the tile to check for suitability
144 * @param axis the axis of the waypoint
145 * @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.
147 static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID *waypoint)
149 /* if waypoint is set, then we have special handling to allow building on top of already existing waypoints.
150 * so waypoint points to INVALID_STATION if we can build on any waypoint.
151 * Or it points to a waypoint if we're only allowed to build on exactly that waypoint. */
152 if (waypoint != nullptr && IsTileType(tile, MP_STATION)) {
153 if (!IsRailWaypoint(tile)) {
154 return ClearTile_Station(tile, DC_AUTO); // get error message
155 } else {
156 StationID wp = GetStationIndex(tile);
157 if (*waypoint == INVALID_STATION) {
158 *waypoint = wp;
159 } else if (*waypoint != wp) {
160 return_cmd_error(STR_ERROR_WAYPOINT_ADJOINS_MORE_THAN_ONE_EXISTING);
165 if (GetAxisForNewRailWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK);
167 Owner owner = GetTileOwner(tile);
168 CommandCost ret = CheckOwnership(owner);
169 if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile);
170 if (ret.Failed()) return ret;
172 Slope tileh = GetTileSlope(tile);
173 if (tileh != SLOPE_FLAT &&
174 (!_settings_game.construction.build_on_slopes || IsSteepSlope(tileh) || !(tileh & (0x3 << axis)) || !(tileh & ~(0x3 << axis)))) {
175 return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
178 if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
180 return CommandCost();
183 extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec);
184 extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road);
185 extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta);
186 extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlag flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost);
187 extern CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlag flags, int replacement_spec_index);
190 * Convert existing rail to waypoint. Eg build a waypoint station over
191 * piece of rail
192 * @param flags type of operation
193 * @param start_tile northern most tile where waypoint will be built
194 * @param axis orientation (Axis)
195 * @param width width of waypoint
196 * @param height height of waypoint
197 * @param spec_class custom station class
198 * @param spec_index custom station id
199 * @param station_to_join station ID to join (NEW_STATION if build new one)
200 * @param adjacent allow waypoints directly adjacent to other waypoints.
201 * @return the cost of this operation or an error
203 CommandCost CmdBuildRailWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, StationClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent)
205 if (!IsValidAxis(axis)) return CMD_ERROR;
206 /* Check if the given station class is valid */
207 if (static_cast<uint>(spec_class) >= StationClass::GetClassCount()) return CMD_ERROR;
208 const StationClass *cls = StationClass::Get(spec_class);
209 if (!IsWaypointClass(*cls)) return CMD_ERROR;
210 if (spec_index >= cls->GetSpecCount()) return CMD_ERROR;
212 /* The number of parts to build */
213 uint8_t count = axis == AXIS_X ? height : width;
215 if ((axis == AXIS_X ? width : height) != 1) return CMD_ERROR;
216 if (count == 0 || count > _settings_game.station.station_spread) return CMD_ERROR;
218 bool reuse = (station_to_join != NEW_STATION);
219 if (!reuse) station_to_join = INVALID_STATION;
220 bool distant_join = (station_to_join != INVALID_STATION);
222 if (distant_join && (!_settings_game.station.distant_join_stations || !Waypoint::IsValidID(station_to_join))) return CMD_ERROR;
224 TileArea new_location(start_tile, width, height);
226 /* only AddCost for non-existing waypoints */
227 CommandCost cost(EXPENSES_CONSTRUCTION);
228 for (TileIndex cur_tile : new_location) {
229 if (!IsRailWaypointTile(cur_tile)) cost.AddCost(_price[PR_BUILD_WAYPOINT_RAIL]);
232 /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
233 StationID est = INVALID_STATION;
235 /* Check whether the tiles we're building on are valid rail or not. */
236 TileIndexDiff offset = TileOffsByAxis(OtherAxis(axis));
237 for (int i = 0; i < count; i++) {
238 TileIndex tile = start_tile + i * offset;
239 CommandCost ret = IsValidTileForWaypoint(tile, axis, &est);
240 if (ret.Failed()) return ret;
243 Waypoint *wp = nullptr;
244 CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, new_location, &wp, false);
245 if (ret.Failed()) return ret;
247 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
248 TileIndex center_tile = start_tile + (count / 2) * offset;
249 if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company, false);
251 if (wp != nullptr) {
252 /* Reuse an existing waypoint. */
253 if (wp->owner != _current_company) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT);
255 /* Check if we want to expand an already existing waypoint. */
256 if (wp->train_station.tile != INVALID_TILE) {
257 ret = CanExpandRailStation(wp, new_location);
258 if (ret.Failed()) return ret;
261 ret = wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TEST);
262 if (ret.Failed()) return ret;
263 } else {
264 /* Check if we can create a new waypoint. */
265 if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
268 if (flags & DC_EXEC) {
269 if (wp == nullptr) {
270 wp = new Waypoint(start_tile);
271 } else if (!wp->IsInUse()) {
272 /* Move existing (recently deleted) waypoint to the new location */
273 wp->xy = start_tile;
275 wp->owner = GetTileOwner(start_tile);
277 wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TRY);
279 wp->delete_ctr = 0;
280 wp->facilities |= FACIL_TRAIN;
281 wp->build_date = TimerGameCalendar::date;
282 wp->string_id = STR_SV_STNAME_WAYPOINT;
283 wp->train_station = new_location;
285 if (wp->town == nullptr) MakeDefaultName(wp);
287 wp->UpdateVirtCoord();
289 const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index);
290 std::vector<uint8_t> layout(count);
291 if (spec != nullptr) {
292 /* For NewGRF waypoints we like to have their style. */
293 GetStationLayout(layout.data(), count, 1, spec);
295 uint8_t map_spec_index = AllocateSpecToStation(spec, wp, true);
297 Company *c = Company::Get(wp->owner);
298 for (int i = 0; i < count; i++) {
299 TileIndex tile = start_tile + i * offset;
300 uint8_t old_specindex = HasStationTileRail(tile) ? GetCustomStationSpecIndex(tile) : 0;
301 if (!HasStationTileRail(tile)) c->infrastructure.station++;
302 bool reserved = IsTileType(tile, MP_RAILWAY) ?
303 HasBit(GetRailReservationTrackBits(tile), AxisToTrack(axis)) :
304 HasStationReservation(tile);
305 MakeRailWaypoint(tile, wp->owner, wp->index, axis, layout[i], GetRailType(tile));
306 SetCustomStationSpecIndex(tile, map_spec_index);
308 SetRailStationTileFlags(tile, spec);
310 SetRailStationReservation(tile, reserved);
311 MarkTileDirtyByTile(tile);
313 DeallocateSpecFromStation(wp, old_specindex);
314 YapfNotifyTrackLayoutChange(tile, AxisToTrack(axis));
316 DirtyCompanyInfrastructureWindows(wp->owner);
319 return cost;
323 * Build a road waypoint on an existing road.
324 * @param flags type of operation.
325 * @param start_tile northern most tile where waypoint will be built.
326 * @param axis orientation (Axis).
327 * @param width width of waypoint.
328 * @param height height of waypoint.
329 * @param spec_class custom road stop class.
330 * @param spec_index custom road stop id.
331 * @param station_to_join station ID to join (NEW_STATION if build new one).
332 * @param adjacent allow waypoints directly adjacent to other waypoints.
333 * @return the cost of this operation or an error.
335 CommandCost CmdBuildRoadWaypoint(DoCommandFlag flags, TileIndex start_tile, Axis axis, uint8_t width, uint8_t height, RoadStopClassID spec_class, uint16_t spec_index, StationID station_to_join, bool adjacent)
337 if (!IsValidAxis(axis)) return CMD_ERROR;
338 /* Check if the given station class is valid */
339 if (static_cast<uint>(spec_class) >= RoadStopClass::GetClassCount()) return CMD_ERROR;
340 const RoadStopClass *cls = RoadStopClass::Get(spec_class);
341 if (!IsWaypointClass(*cls)) return CMD_ERROR;
342 if (spec_index >= cls->GetSpecCount()) return CMD_ERROR;
344 const RoadStopSpec *roadstopspec = RoadStopClass::Get(spec_class)->GetSpec(spec_index);
346 /* The number of parts to build */
347 uint8_t count = axis == AXIS_X ? height : width;
349 if ((axis == AXIS_X ? width : height) != 1) return CMD_ERROR;
350 if (count == 0 || count > _settings_game.station.station_spread) return CMD_ERROR;
352 bool reuse = (station_to_join != NEW_STATION);
353 if (!reuse) station_to_join = INVALID_STATION;
354 bool distant_join = (station_to_join != INVALID_STATION);
356 if (distant_join && (!_settings_game.station.distant_join_stations || !Waypoint::IsValidID(station_to_join))) return CMD_ERROR;
358 TileArea roadstop_area(start_tile, width, height);
360 /* Total road stop cost. */
361 Money unit_cost;
362 if (roadstopspec != nullptr) {
363 unit_cost = roadstopspec->GetBuildCost(PR_BUILD_STATION_TRUCK);
364 } else {
365 unit_cost = _price[PR_BUILD_STATION_TRUCK];
367 StationID est = INVALID_STATION;
368 CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, true, STATION_ROADWAYPOINT, axis, AxisToDiagDir(axis), &est, INVALID_ROADTYPE, unit_cost);
369 if (cost.Failed()) return cost;
371 Waypoint *wp = nullptr;
372 CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, roadstop_area, &wp, true);
373 if (ret.Failed()) return ret;
375 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
376 TileIndex center_tile = start_tile + (count / 2) * TileOffsByAxis(OtherAxis(axis));
377 if (wp == nullptr && reuse) wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company, true);
379 if (wp != nullptr) {
380 /* Reuse an existing waypoint. */
381 if (!HasBit(wp->waypoint_flags, WPF_ROAD)) return CMD_ERROR;
382 if (wp->owner != _current_company) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT);
384 ret = wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TEST);
385 if (ret.Failed()) return ret;
386 } else {
387 /* Check if we can create a new waypoint. */
388 if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
391 /* Check if we can allocate a custom stationspec to this station */
392 if (AllocateSpecToRoadStop(roadstopspec, wp, false) == -1) return_cmd_error(STR_ERROR_TOO_MANY_STATION_SPECS);
394 if (flags & DC_EXEC) {
395 if (wp == nullptr) {
396 wp = new Waypoint(start_tile);
397 SetBit(wp->waypoint_flags, WPF_ROAD);
398 } else if (!wp->IsInUse()) {
399 /* Move existing (recently deleted) waypoint to the new location */
400 wp->xy = start_tile;
402 wp->owner = _current_company;
404 wp->rect.BeforeAddRect(start_tile, width, height, StationRect::ADD_TRY);
406 if (roadstopspec != nullptr) {
407 /* Include this road stop spec's animation trigger bitmask
408 * in the station's cached copy. */
409 wp->cached_roadstop_anim_triggers |= roadstopspec->animation.triggers;
412 wp->delete_ctr = 0;
413 wp->facilities |= FACIL_BUS_STOP | FACIL_TRUCK_STOP;
414 wp->build_date = TimerGameCalendar::date;
415 wp->string_id = STR_SV_STNAME_WAYPOINT;
417 if (wp->town == nullptr) MakeDefaultName(wp);
419 wp->UpdateVirtCoord();
421 uint8_t map_spec_index = AllocateSpecToRoadStop(roadstopspec, wp, true);
423 /* Check every tile in the area. */
424 for (TileIndex cur_tile : roadstop_area) {
425 /* Get existing road types and owners before any tile clearing */
426 RoadType road_rt = MayHaveRoad(cur_tile) ? GetRoadType(cur_tile, RTT_ROAD) : INVALID_ROADTYPE;
427 RoadType tram_rt = MayHaveRoad(cur_tile) ? GetRoadType(cur_tile, RTT_TRAM) : INVALID_ROADTYPE;
428 Owner road_owner = road_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_ROAD) : _current_company;
429 Owner tram_owner = tram_rt != INVALID_ROADTYPE ? GetRoadOwner(cur_tile, RTT_TRAM) : _current_company;
431 if (IsRoadWaypointTile(cur_tile)) {
432 RemoveRoadWaypointStop(cur_tile, flags, map_spec_index);
435 wp->road_waypoint_area.Add(cur_tile);
437 wp->rect.BeforeAddTile(cur_tile, StationRect::ADD_TRY);
439 /* Update company infrastructure counts. If the current tile is a normal road tile, remove the old
440 * bits first. */
441 if (IsNormalRoadTile(cur_tile)) {
442 UpdateCompanyRoadInfrastructure(road_rt, road_owner, -(int)CountBits(GetRoadBits(cur_tile, RTT_ROAD)));
443 UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, -(int)CountBits(GetRoadBits(cur_tile, RTT_TRAM)));
446 UpdateCompanyRoadInfrastructure(road_rt, road_owner, ROAD_STOP_TRACKBIT_FACTOR);
447 UpdateCompanyRoadInfrastructure(tram_rt, tram_owner, ROAD_STOP_TRACKBIT_FACTOR);
449 MakeDriveThroughRoadStop(cur_tile, wp->owner, road_owner, tram_owner, wp->index, STATION_ROADWAYPOINT, road_rt, tram_rt, axis);
450 SetCustomRoadStopSpecIndex(cur_tile, map_spec_index);
451 if (roadstopspec != nullptr) wp->SetRoadStopRandomBits(cur_tile, 0);
453 Company::Get(wp->owner)->infrastructure.station++;
455 MarkTileDirtyByTile(cur_tile);
457 DirtyCompanyInfrastructureWindows(wp->owner);
459 return cost;
463 * Build a buoy.
464 * @param flags operation to perform
465 * @param tile tile where to place the buoy
466 * @return the cost of this operation or an error
468 CommandCost CmdBuildBuoy(DoCommandFlag flags, TileIndex tile)
470 if (tile == 0 || !HasTileWaterGround(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
471 if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
473 if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
475 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
476 Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE, false);
477 if (wp == nullptr && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
479 CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_WAYPOINT_BUOY]);
480 if (!IsWaterTile(tile)) {
481 CommandCost ret = Command<CMD_LANDSCAPE_CLEAR>::Do(flags | DC_AUTO, tile);
482 if (ret.Failed()) return ret;
483 cost.AddCost(ret);
486 if (flags & DC_EXEC) {
487 if (wp == nullptr) {
488 wp = new Waypoint(tile);
489 } else {
490 /* Move existing (recently deleted) buoy to the new location */
491 wp->xy = tile;
492 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
494 wp->rect.BeforeAddTile(tile, StationRect::ADD_TRY);
496 wp->string_id = STR_SV_STNAME_BUOY;
498 wp->facilities |= FACIL_DOCK;
499 wp->owner = OWNER_NONE;
501 wp->build_date = TimerGameCalendar::date;
503 if (wp->town == nullptr) MakeDefaultName(wp);
505 MakeBuoy(tile, wp->index, GetWaterClass(tile));
506 CheckForDockingTile(tile);
507 MarkTileDirtyByTile(tile);
508 ClearNeighbourNonFloodingStates(tile);
510 wp->UpdateVirtCoord();
511 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
514 return cost;
518 * Remove a buoy
519 * @param tile TileIndex been queried
520 * @param flags operation to perform
521 * @pre IsBuoyTile(tile)
522 * @return cost or failure of operation
524 CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags)
526 /* XXX: strange stuff, allow clearing as invalid company when clearing landscape */
527 if (!Company::IsValidID(_current_company) && !(flags & DC_BANKRUPT)) return_cmd_error(INVALID_STRING_ID);
529 Waypoint *wp = Waypoint::GetByTile(tile);
531 if (HasStationInUse(wp->index, false, _current_company)) return_cmd_error(STR_ERROR_BUOY_IS_IN_USE);
532 /* remove the buoy if there is a ship on tile when company goes bankrupt... */
533 if (!(flags & DC_BANKRUPT)) {
534 CommandCost ret = EnsureNoVehicleOnGround(tile);
535 if (ret.Failed()) return ret;
538 if (flags & DC_EXEC) {
539 wp->facilities &= ~FACIL_DOCK;
541 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
543 /* We have to set the water tile's state to the same state as before the
544 * buoy was placed. Otherwise one could plant a buoy on a canal edge,
545 * remove it and flood the land (if the canal edge is at level 0) */
546 MakeWaterKeepingClass(tile, GetTileOwner(tile));
548 wp->rect.AfterRemoveTile(wp, tile);
550 wp->UpdateVirtCoord();
551 wp->delete_ctr = 0;
554 return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WAYPOINT_BUOY]);
558 * Check whether the name is unique amongst the waypoints.
559 * @param name The name to check.
560 * @return True iff the name is unique.
562 static bool IsUniqueWaypointName(const std::string &name)
564 for (const Waypoint *wp : Waypoint::Iterate()) {
565 if (!wp->name.empty() && wp->name == name) return false;
568 return true;
572 * Rename a waypoint.
573 * @param flags type of operation
574 * @param waypoint_id id of waypoint
575 * @param text the new name or an empty string when resetting to the default
576 * @return the cost of this operation or an error
578 CommandCost CmdRenameWaypoint(DoCommandFlag flags, StationID waypoint_id, const std::string &text)
580 Waypoint *wp = Waypoint::GetIfValid(waypoint_id);
581 if (wp == nullptr) return CMD_ERROR;
583 if (wp->owner != OWNER_NONE) {
584 CommandCost ret = CheckOwnership(wp->owner);
585 if (ret.Failed()) return ret;
588 bool reset = text.empty();
590 if (!reset) {
591 if (Utf8StringLength(text) >= MAX_LENGTH_STATION_NAME_CHARS) return CMD_ERROR;
592 if (!IsUniqueWaypointName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE);
595 if (flags & DC_EXEC) {
596 if (reset) {
597 wp->name.clear();
598 } else {
599 wp->name = text;
602 wp->UpdateVirtCoord();
604 return CommandCost();