Translations update
[openttd/fttd.git] / src / waypoint_cmd.cpp
blob95c2afc87c87e5ff0ea1d58755054c3a1245ea77
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 "map/bridge.h"
20 #include "bridge.h"
21 #include "town.h"
22 #include "waypoint_base.h"
23 #include "pathfinder/yapf/yapf.h"
24 #include "strings_func.h"
25 #include "viewport_func.h"
26 #include "window_func.h"
27 #include "date_func.h"
28 #include "vehicle_func.h"
29 #include "string.h"
30 #include "company_func.h"
31 #include "newgrf_station.h"
32 #include "company_base.h"
33 #include "water.h"
34 #include "company_gui.h"
35 #include "station_func.h"
37 #include "table/strings.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 SetDParam(0, this->index);
46 this->sign.UpdatePosition(pt.x, pt.y - 32 * ZOOM_LVL_BASE, STR_VIEWPORT_WAYPOINT);
47 /* Recenter viewport */
48 InvalidateWindowData(WC_WAYPOINT_VIEW, this->index);
51 /**
52 * Find a deleted waypoint close to a tile.
53 * @param tile to search from
54 * @param str the string to get the 'type' of
55 * @param cid previous owner of the waypoint
56 * @return the deleted nearby waypoint
58 static Waypoint *FindDeletedWaypointCloseTo(TileIndex tile, StringID str, CompanyID cid)
60 Waypoint *wp, *best = NULL;
61 uint thres = 8;
63 FOR_ALL_WAYPOINTS(wp) {
64 if (!wp->IsInUse() && wp->string_id == str && wp->owner == cid) {
65 uint cur_dist = DistanceManhattan(tile, wp->xy);
67 if (cur_dist < thres) {
68 thres = cur_dist;
69 best = wp;
74 return best;
77 /**
78 * Get the axis for a new waypoint. This means that if it is a valid
79 * tile to build a waypoint on it returns a valid Axis, otherwise an
80 * invalid one.
81 * @param tile the tile to look at.
82 * @return the axis for the to-be-build waypoint.
84 Axis GetAxisForNewWaypoint(TileIndex tile)
86 /* The axis for rail waypoints is easy. */
87 if (IsRailWaypointTile(tile)) return GetRailStationAxis(tile);
89 /* Non-plain rail type, no valid axis for waypoints. */
90 if (!IsNormalRailTile(tile) || HasSignalOnTrack(tile, TRACK_UPPER)) return INVALID_AXIS;
92 switch (GetTrackBits(tile)) {
93 case TRACK_BIT_X: return AXIS_X;
94 case TRACK_BIT_Y: return AXIS_Y;
95 default: return INVALID_AXIS;
99 extern CommandCost ClearTile_Station(TileIndex tile, DoCommandFlag flags);
102 * Check whether the given tile is suitable for a waypoint.
103 * @param tile the tile to check for suitability
104 * @param axis the axis of the waypoint
105 * @param check_bridge Minimum allowed height for a bridge, 0 for none.
106 * @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.
108 static CommandCost IsValidTileForWaypoint (TileIndex tile, Axis axis, int check_bridge, StationID *waypoint)
110 /* if waypoint is set, then we have special handling to allow building on top of already existing waypoints.
111 * so waypoint points to INVALID_STATION if we can build on any waypoint.
112 * Or it points to a waypoint if we're only allowed to build on exactly that waypoint. */
113 if (waypoint != NULL && IsStationTile(tile)) {
114 if (!IsRailWaypoint(tile)) {
115 return ClearTile_Station(tile, DC_AUTO); // get error message
116 } else {
117 StationID wp = GetStationIndex(tile);
118 if (*waypoint == INVALID_STATION) {
119 *waypoint = wp;
120 } else if (*waypoint != wp) {
121 return_cmd_error(STR_ERROR_WAYPOINT_ADJOINS_MORE_THAN_ONE_EXISTING);
126 if (GetAxisForNewWaypoint(tile) != axis) return_cmd_error(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK);
128 Owner owner = GetTileOwner(tile);
129 CommandCost ret = CheckOwnership(owner);
130 if (ret.Succeeded()) ret = EnsureNoVehicleOnGround(tile);
131 if (ret.Failed()) return ret;
133 Slope tileh = GetTileSlope(tile);
134 if (tileh != SLOPE_FLAT &&
135 (!_settings_game.construction.build_on_slopes || IsSteepSlope(tileh) || !(tileh & (0x3 << axis)) || !(tileh & ~(0x3 << axis)))) {
136 return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
139 if (HasBridgeAbove (tile) && ((check_bridge == 0)
140 || (GetBridgeHeight (GetSouthernBridgeEnd (tile)) < GetTileMaxZ (tile) + check_bridge))) {
141 return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
145 return CommandCost();
148 extern void GetStationLayout(byte *layout, int numtracks, int plat_len, const StationSpec *statspec);
149 extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp);
150 extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta, Axis axis);
153 * Convert existing rail to waypoint. Eg build a waypoint station over
154 * piece of rail
155 * @param start_tile northern most tile where waypoint will be built
156 * @param flags type of operation
157 * @param p1 various bitstuffed elements
158 * - p1 = (bit 4) - orientation (Axis)
159 * - p1 = (bit 8-15) - width of waypoint
160 * - p1 = (bit 16-23) - height of waypoint
161 * - p1 = (bit 24) - allow waypoints directly adjacent to other waypoints.
162 * @param p2 various bitstuffed elements
163 * - p2 = (bit 0- 7) - custom station class
164 * - p2 = (bit 8-15) - custom station id
165 * @param text unused
166 * @return the cost of this operation or an error
168 CommandCost CmdBuildRailWaypoint(TileIndex start_tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
170 /* Unpack parameters */
171 Axis axis = Extract<Axis, 4, 1>(p1);
172 byte width = GB(p1, 8, 8);
173 byte height = GB(p1, 16, 8);
174 bool adjacent = HasBit(p1, 24);
176 StationClassID spec_class = Extract<StationClassID, 0, 8>(p2);
177 byte spec_index = GB(p2, 8, 8);
178 StationID station_to_join = GB(p2, 16, 16);
180 /* Check if the given station class is valid */
181 if (spec_class != STAT_CLASS_WAYP) return CMD_ERROR;
182 if (spec_index >= StationClass::Get(spec_class)->GetSpecCount()) return CMD_ERROR;
184 /* The number of parts to build */
185 byte count = axis == AXIS_X ? height : width;
187 if ((axis == AXIS_X ? width : height) != 1) return CMD_ERROR;
188 if (count == 0 || count > _settings_game.station.station_spread) return CMD_ERROR;
190 const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index);
192 /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
193 StationID est = INVALID_STATION;
195 /* Check whether the tiles we're building on are valid rail or not. */
196 TileIndexDiff offset = TileOffsByDiagDir(AxisToDiagDir(OtherAxis(axis)));
197 for (int i = 0; i < count; i++) {
198 TileIndex tile = start_tile + i * offset;
199 CommandCost ret = IsValidTileForWaypoint (tile, axis, (spec == NULL) ? 3 : 0, &est);
200 if (ret.Failed()) return ret;
203 Waypoint *wp = NULL;
204 TileArea new_location(TileArea(start_tile, width, height));
205 CommandCost ret = FindJoiningWaypoint(est, station_to_join, adjacent, new_location, &wp);
206 if (ret.Failed()) return ret;
208 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
209 TileIndex center_tile = start_tile + (count / 2) * offset;
210 if (wp == NULL && !adjacent) {
211 wp = FindDeletedWaypointCloseTo(center_tile, STR_SV_STNAME_WAYPOINT, _current_company);
214 if (wp != NULL) {
215 /* Reuse an existing waypoint. */
216 if (wp->owner != _current_company) return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_WAYPOINT);
218 /* check if we want to expand an already existing waypoint? */
219 if (wp->train_station.tile != INVALID_TILE) {
220 CommandCost ret = CanExpandRailStation(wp, new_location, axis);
221 if (ret.Failed()) return ret;
224 if (!wp->TestAddRect (TileArea (start_tile, width, height))) {
225 return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
227 } else {
228 /* allocate and initialize new waypoint */
229 if (!Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
232 if (flags & DC_EXEC) {
233 if (wp == NULL) {
234 wp = new Waypoint(start_tile);
235 } else if (!wp->IsInUse()) {
236 /* Move existing (recently deleted) waypoint to the new location */
237 wp->xy = start_tile;
239 wp->owner = GetTileOwner(start_tile);
241 wp->rect.Add (TileArea (start_tile, width, height));
243 wp->delete_ctr = 0;
244 wp->facilities |= FACIL_TRAIN;
245 wp->build_date = _date;
246 wp->string_id = STR_SV_STNAME_WAYPOINT;
247 wp->train_station = new_location;
249 if (wp->town == NULL) MakeDefaultName(wp);
251 wp->UpdateVirtCoord();
253 byte *layout_ptr = AllocaM(byte, count);
254 if (spec == NULL) {
255 /* The layout must be 0 for the 'normal' waypoints by design. */
256 memset(layout_ptr, 0, count);
257 } else {
258 /* But for NewGRF waypoints we like to have their style. */
259 GetStationLayout(layout_ptr, count, 1, spec);
261 byte map_spec_index = AllocateSpecToStation(spec, wp, true);
263 Company *c = Company::Get(wp->owner);
264 for (int i = 0; i < count; i++) {
265 TileIndex tile = start_tile + i * offset;
266 byte old_specindex = HasStationTileRail(tile) ? GetCustomStationSpecIndex(tile) : 0;
267 if (!HasStationTileRail(tile)) c->infrastructure.station++;
268 bool reserved = IsRailwayTile(tile) ?
269 (GetRailReservationTrackBits (tile) != TRACK_BIT_NONE) :
270 HasStationReservation(tile);
271 MakeRailWaypoint(tile, wp->owner, wp->index, axis, layout_ptr[i], GetRailType(tile));
272 SetCustomStationSpecIndex(tile, map_spec_index);
273 SetRailStationReservation(tile, reserved);
274 MarkTileDirtyByTile(tile);
276 DeallocateSpecFromStation(wp, old_specindex);
277 YapfNotifyTrackLayoutChange();
279 DirtyCompanyInfrastructureWindows(wp->owner);
282 return CommandCost(EXPENSES_CONSTRUCTION, count * _price[PR_BUILD_WAYPOINT_RAIL]);
286 * Build a buoy.
287 * @param tile tile where to place the buoy
288 * @param flags operation to perform
289 * @param p1 unused
290 * @param p2 unused
291 * @param text unused
292 * @return the cost of this operation or an error
294 CommandCost CmdBuildBuoy(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
296 if (tile == 0 || !HasTileWaterGround(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
298 if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
300 /* Check if there is an already existing, deleted, waypoint close to us that we can reuse. */
301 Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE);
302 if (wp == NULL && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING);
304 CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_WAYPOINT_BUOY]);
305 if (!IsPlainWaterTile(tile)) {
306 CommandCost ret = DoCommand(tile, 0, 0, flags | DC_AUTO, CMD_LANDSCAPE_CLEAR);
307 if (ret.Failed()) return ret;
308 cost.AddCost(ret);
311 if (flags & DC_EXEC) {
312 if (wp == NULL) {
313 wp = new Waypoint(tile);
314 } else {
315 /* Move existing (recently deleted) buoy to the new location */
316 wp->xy = tile;
317 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
319 wp->rect.Add (tile);
321 wp->string_id = STR_SV_STNAME_BUOY;
323 wp->facilities |= FACIL_DOCK;
324 wp->owner = OWNER_NONE;
326 wp->build_date = _date;
328 if (wp->town == NULL) MakeDefaultName(wp);
330 MakeBuoy(tile, wp->index, GetWaterClass(tile));
331 MarkTileDirtyByTile(tile);
333 wp->UpdateVirtCoord();
334 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
337 return cost;
341 * Remove a buoy
342 * @param tile TileIndex been queried
343 * @param flags operation to perform
344 * @pre IsBuoyTile(tile)
345 * @return cost or failure of operation
347 CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags)
349 /* XXX: strange stuff, allow clearing as invalid company when clearing landscape */
350 if (!Company::IsValidID(_current_company) && !(flags & DC_BANKRUPT)) return_cmd_error(INVALID_STRING_ID);
352 Waypoint *wp = Waypoint::GetByTile(tile);
354 if (HasStationInUse(wp->index, false, _current_company)) return_cmd_error(STR_ERROR_BUOY_IS_IN_USE);
355 /* remove the buoy if there is a ship on tile when company goes bankrupt... */
356 if (!(flags & DC_BANKRUPT)) {
357 CommandCost ret = EnsureNoVehicleOnGround(tile);
358 if (ret.Failed()) return ret;
361 if (flags & DC_EXEC) {
362 wp->facilities &= ~FACIL_DOCK;
364 InvalidateWindowData(WC_WAYPOINT_VIEW, wp->index);
366 /* We have to set the water tile's state to the same state as before the
367 * buoy was placed. Otherwise one could plant a buoy on a canal edge,
368 * remove it and flood the land (if the canal edge is at level 0) */
369 MakeWaterKeepingClass(tile, GetTileOwner(tile));
371 wp->AfterRemoveTile(tile);
373 wp->UpdateVirtCoord();
374 wp->delete_ctr = 0;
377 return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WAYPOINT_BUOY]);
381 * Check whether the name is unique amongst the waypoints.
382 * @param name The name to check.
383 * @return True iff the name is unique.
385 static bool IsUniqueWaypointName(const char *name)
387 const Waypoint *wp;
389 FOR_ALL_WAYPOINTS(wp) {
390 if (wp->name != NULL && strcmp(wp->name, name) == 0) return false;
393 return true;
397 * Rename a waypoint.
398 * @param tile unused
399 * @param flags type of operation
400 * @param p1 id of waypoint
401 * @param p2 unused
402 * @param text the new name or an empty string when resetting to the default
403 * @return the cost of this operation or an error
405 CommandCost CmdRenameWaypoint(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
407 Waypoint *wp = Waypoint::GetIfValid(p1);
408 if (wp == NULL) return CMD_ERROR;
410 if (wp->owner != OWNER_NONE) {
411 CommandCost ret = CheckOwnership(wp->owner);
412 if (ret.Failed()) return ret;
415 bool reset = StrEmpty(text);
417 if (!reset) {
418 if (Utf8StringLength(text) >= MAX_LENGTH_STATION_NAME_CHARS) return CMD_ERROR;
419 if (!IsUniqueWaypointName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE);
422 if (flags & DC_EXEC) {
423 free(wp->name);
424 wp->name = reset ? NULL : xstrdup(text);
426 wp->UpdateVirtCoord();
428 return CommandCost();