OpenTTD Source 20250312-master-gcdcc6b491d
terraform_cmd.cpp
Go to the documentation of this file.
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 */
7
10#include "stdafx.h"
11#include "command_func.h"
12#include "tunnel_map.h"
13#include "bridge_map.h"
14#include "viewport_func.h"
15#include "genworld.h"
16#include "object_base.h"
17#include "company_base.h"
18#include "company_func.h"
19#include "core/backup_type.hpp"
20#include "terraform_cmd.h"
21#include "landscape_cmd.h"
22
23#include "table/strings.h"
24
25#include "safeguards.h"
26
28typedef std::set<TileIndex> TileIndexSet;
30typedef std::map<TileIndex, int> TileIndexToHeightMap;
31
37
46{
47 TileIndexToHeightMap::const_iterator it = ts->tile_to_new_height.find(tile);
48 return it != ts->tile_to_new_height.end() ? it->second : TileHeight(tile);
49}
50
58static void TerraformSetHeightOfTile(TerraformerState *ts, TileIndex tile, int height)
59{
60 ts->tile_to_new_height[tile] = height;
61}
62
71{
72 ts->dirty_tiles.insert(tile);
73}
74
83{
84 /* Make sure all tiles passed to TerraformAddDirtyTile are within [0, Map::Size()] */
85 if (TileY(tile) >= 1) TerraformAddDirtyTile(ts, tile + TileDiffXY( 0, -1));
86 if (TileY(tile) >= 1 && TileX(tile) >= 1) TerraformAddDirtyTile(ts, tile + TileDiffXY(-1, -1));
87 if (TileX(tile) >= 1) TerraformAddDirtyTile(ts, tile + TileDiffXY(-1, 0));
88 TerraformAddDirtyTile(ts, tile);
89}
90
99static std::tuple<CommandCost, TileIndex> TerraformTileHeight(TerraformerState *ts, TileIndex tile, int height)
100{
101 assert(tile < Map::Size());
102
103 /* Check range of destination height */
104 if (height < 0) return { CommandCost(STR_ERROR_ALREADY_AT_SEA_LEVEL), INVALID_TILE };
105 if (height > _settings_game.construction.map_height_limit) return { CommandCost(STR_ERROR_TOO_HIGH), INVALID_TILE };
106
107 /*
108 * Check if the terraforming has any effect.
109 * This can only be true, if multiple corners of the start-tile are terraformed (i.e. the terraforming is done by towns/industries etc.).
110 * In this case the terraforming should fail. (Don't know why.)
111 */
112 if (height == TerraformGetHeightOfTile(ts, tile)) return { CMD_ERROR, INVALID_TILE };
113
114 /* Check "too close to edge of map". Only possible when freeform-edges is off. */
115 uint x = TileX(tile);
116 uint y = TileY(tile);
117 if (!_settings_game.construction.freeform_edges && ((x <= 1) || (y <= 1) || (x >= Map::MaxX() - 1) || (y >= Map::MaxY() - 1))) {
118 /*
119 * Determine a sensible error tile
120 */
121 if (x == 1) x = 0;
122 if (y == 1) y = 0;
123 return { CommandCost(STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP), TileXY(x, y) };
124 }
125
126 /* Mark incident tiles that are involved in the terraforming. */
128
129 /* Store the height modification */
130 TerraformSetHeightOfTile(ts, tile, height);
131
133
134 /* Increment cost */
135 total_cost.AddCost(_price[PR_TERRAFORM]);
136
137 /* Recurse to neighboured corners if height difference is larger than 1 */
138 for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) {
139 TileIndex neighbour_tile = AddTileIndexDiffCWrap(tile, TileIndexDiffCByDiagDir(dir));
140
141 /* Not using IsValidTile as we want to also change MP_VOID tiles, which IsValidTile excludes. */
142 if (neighbour_tile == INVALID_TILE) continue;
143
144 /* Get TileHeight of neighboured tile as of current terraform progress */
145 int r = TerraformGetHeightOfTile(ts, neighbour_tile);
146 int height_diff = height - r;
147
148 /* Is the height difference to the neighboured corner greater than 1? */
149 if (abs(height_diff) > 1) {
150 /* Terraform the neighboured corner. The resulting height difference should be 1. */
151 height_diff += (height_diff < 0 ? 1 : -1);
152 auto [cost, err_tile] = TerraformTileHeight(ts, neighbour_tile, r + height_diff);
153 if (cost.Failed()) return { cost, err_tile };
154 total_cost.AddCost(cost);
155 }
156 }
157
158 return { total_cost, INVALID_TILE };
159}
160
169std::tuple<CommandCost, Money, TileIndex> CmdTerraformLand(DoCommandFlags flags, TileIndex tile, Slope slope, bool dir_up)
170{
172 int direction = (dir_up ? 1 : -1);
174
175 /* Compute the costs and the terraforming result in a model of the landscape */
176 if ((slope & SLOPE_W) != 0 && tile + TileDiffXY(1, 0) < Map::Size()) {
177 TileIndex t = tile + TileDiffXY(1, 0);
178 auto [cost, err_tile] = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
179 if (cost.Failed()) return { cost, 0, err_tile };
180 total_cost.AddCost(cost);
181 }
182
183 if ((slope & SLOPE_S) != 0 && tile + TileDiffXY(1, 1) < Map::Size()) {
184 TileIndex t = tile + TileDiffXY(1, 1);
185 auto [cost, err_tile] = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
186 if (cost.Failed()) return { cost, 0, err_tile };
187 total_cost.AddCost(cost);
188 }
189
190 if ((slope & SLOPE_E) != 0 && tile + TileDiffXY(0, 1) < Map::Size()) {
191 TileIndex t = tile + TileDiffXY(0, 1);
192 auto [cost, err_tile] = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
193 if (cost.Failed()) return { cost, 0, err_tile };
194 total_cost.AddCost(cost);
195 }
196
197 if ((slope & SLOPE_N) != 0) {
198 TileIndex t = tile + TileDiffXY(0, 0);
199 auto [cost, err_tile] = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
200 if (cost.Failed()) return { cost, 0, err_tile };
201 total_cost.AddCost(cost);
202 }
203
204 /* Check if the terraforming is valid wrt. tunnels, bridges and objects on the surface
205 * Pass == 0: Collect tileareas which are caused to be auto-cleared.
206 * Pass == 1: Collect the actual cost. */
207 for (int pass = 0; pass < 2; pass++) {
208 for (const auto &t : ts.dirty_tiles) {
209 assert(t < Map::Size());
210 /* MP_VOID tiles can be terraformed but as tunnels and bridges
211 * cannot go under / over these tiles they don't need checking. */
212 if (IsTileType(t, MP_VOID)) continue;
213
214 /* Find new heights of tile corners */
215 int z_N = TerraformGetHeightOfTile(&ts, t + TileDiffXY(0, 0));
216 int z_W = TerraformGetHeightOfTile(&ts, t + TileDiffXY(1, 0));
217 int z_S = TerraformGetHeightOfTile(&ts, t + TileDiffXY(1, 1));
218 int z_E = TerraformGetHeightOfTile(&ts, t + TileDiffXY(0, 1));
219
220 /* Find min and max height of tile */
221 int z_min = std::min({z_N, z_W, z_S, z_E});
222 int z_max = std::max({z_N, z_W, z_S, z_E});
223
224 /* Compute tile slope */
225 Slope tileh = (z_max > z_min + 1 ? SLOPE_STEEP : SLOPE_FLAT);
226 if (z_W > z_min) tileh |= SLOPE_W;
227 if (z_S > z_min) tileh |= SLOPE_S;
228 if (z_E > z_min) tileh |= SLOPE_E;
229 if (z_N > z_min) tileh |= SLOPE_N;
230
231 if (pass == 0) {
232 /* Check if bridge would take damage */
233 if (IsBridgeAbove(t)) {
234 int bridge_height = GetBridgeHeight(GetSouthernBridgeEnd(t));
235
236 /* Check if bridge would take damage. */
237 if (direction == 1 && bridge_height <= z_max) {
238 return { CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST), 0, t }; // highlight the tile under the bridge
239 }
240
241 /* Is the bridge above not too high afterwards? */
242 if (direction == -1 && bridge_height > (z_min + _settings_game.construction.max_bridge_height)) {
243 return { CommandCost(STR_ERROR_BRIDGE_TOO_HIGH_AFTER_LOWER_LAND), 0, t };
244 }
245 }
246 /* Check if tunnel would take damage */
247 if (direction == -1 && IsTunnelInWay(t, z_min)) {
248 return { CommandCost(STR_ERROR_EXCAVATION_WOULD_DAMAGE), 0, t }; // highlight the tile above the tunnel
249 }
250 }
251
252 /* Is the tile already cleared? */
253 const ClearedObjectArea *coa = FindClearedObject(t);
254 bool indirectly_cleared = coa != nullptr && coa->first_tile != t;
255
256 /* Check tiletype-specific things, and add extra-cost */
257 Backup<bool> old_generating_world(_generating_world);
258 if (_game_mode == GM_EDITOR) old_generating_world.Change(true); // used to create green terraformed land
260 if (pass == 0) {
261 tile_flags.Reset(DoCommandFlag::Execute);
263 }
264 CommandCost cost;
265 if (indirectly_cleared) {
266 cost = Command<CMD_LANDSCAPE_CLEAR>::Do(tile_flags, t);
267 } else {
268 cost = _tile_type_procs[GetTileType(t)]->terraform_tile_proc(t, tile_flags, z_min, tileh);
269 }
270 old_generating_world.Restore();
271 if (cost.Failed()) {
272 return { cost, 0, t };
273 }
274 if (pass == 1) total_cost.AddCost(cost);
275 }
276 }
277
279 if (c != nullptr && GB(c->terraform_limit, 16, 16) < ts.tile_to_new_height.size()) {
280 return { CommandCost(STR_ERROR_TERRAFORM_LIMIT_REACHED), 0, INVALID_TILE };
281 }
282
283 if (flags.Test(DoCommandFlag::Execute)) {
284 /* Mark affected areas dirty. */
285 for (const auto &t : ts.dirty_tiles) {
287 TileIndexToHeightMap::const_iterator new_height = ts.tile_to_new_height.find(t);
288 if (new_height == ts.tile_to_new_height.end()) continue;
289 MarkTileDirtyByTile(t, 0, new_height->second);
290 }
291
292 /* change the height */
293 for (const auto &it : ts.tile_to_new_height) {
294 TileIndex t = it.first;
295 int height = it.second;
296
297 SetTileHeight(t, (uint)height);
298 }
299
300 if (c != nullptr) c->terraform_limit -= (uint32_t)ts.tile_to_new_height.size() << 16;
301 }
302 return { total_cost, 0, total_cost.Succeeded() ? tile : INVALID_TILE };
303}
304
305
315std::tuple<CommandCost, Money, TileIndex> CmdLevelLand(DoCommandFlags flags, TileIndex tile, TileIndex start_tile, bool diagonal, LevelMode lm)
316{
317 if (start_tile >= Map::Size()) return { CMD_ERROR, 0, INVALID_TILE };
318
319 /* remember level height */
320 uint oldh = TileHeight(start_tile);
321
322 /* compute new height */
323 uint h = oldh;
324 switch (lm) {
325 case LM_LEVEL: break;
326 case LM_RAISE: h++; break;
327 case LM_LOWER: h--; break;
328 default: return { CMD_ERROR, 0, INVALID_TILE };
329 }
330
331 /* Check range of destination height */
332 if (h > _settings_game.construction.map_height_limit) return { CommandCost(oldh == 0 ? STR_ERROR_ALREADY_AT_SEA_LEVEL : STR_ERROR_TOO_HIGH), 0, INVALID_TILE };
333
336 CommandCost last_error(lm == LM_LEVEL ? STR_ERROR_ALREADY_LEVELLED : INVALID_STRING_ID);
337 bool had_success = false;
338
340 int limit = (c == nullptr ? INT32_MAX : GB(c->terraform_limit, 16, 16));
341 if (limit == 0) return { CommandCost(STR_ERROR_TERRAFORM_LIMIT_REACHED), 0, INVALID_TILE };
342
343 TileIndex error_tile = INVALID_TILE;
344 std::unique_ptr<TileIterator> iter = TileIterator::Create(tile, start_tile, diagonal);
345 for (; *iter != INVALID_TILE; ++(*iter)) {
346 TileIndex t = *iter;
347 uint curh = TileHeight(t);
348 while (curh != h) {
349 CommandCost ret;
350 std::tie(ret, std::ignore, error_tile) = Command<CMD_TERRAFORM_LAND>::Do(DoCommandFlags{flags}.Reset(DoCommandFlag::Execute), t, SLOPE_N, curh <= h);
351 if (ret.Failed()) {
352 last_error = ret;
353
354 /* Did we reach the limit? */
355 if (ret.GetErrorMessage() == STR_ERROR_TERRAFORM_LIMIT_REACHED) limit = 0;
356 break;
357 }
358
359 if (flags.Test(DoCommandFlag::Execute)) {
360 money -= ret.GetCost();
361 if (money < 0) {
362 return { cost, ret.GetCost(), error_tile };
363 }
364 Command<CMD_TERRAFORM_LAND>::Do(flags, t, SLOPE_N, curh <= h);
365 } else {
366 /* When we're at the terraform limit we better bail (unneeded) testing as well.
367 * This will probably cause the terraforming cost to be underestimated, but only
368 * when it's near the terraforming limit. Even then, the estimation is
369 * completely off due to it basically counting terraforming double, so it being
370 * cut off earlier might even give a better estimate in some cases. */
371 if (--limit <= 0) {
372 had_success = true;
373 break;
374 }
375 }
376
377 cost.AddCost(ret);
378 curh += (curh > h) ? -1 : 1;
379 had_success = true;
380 }
381
382 if (limit <= 0) break;
383 }
384
385 CommandCost cc_ret = had_success ? cost : last_error;
386 return { cc_ret, 0, cc_ret.Succeeded() ? tile : error_tile };
387}
Class for backupping variables and making sure they are restored later.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
TileIndex GetSouthernBridgeEnd(TileIndex t)
Finds the southern end of a bridge starting at a middle tile.
int GetBridgeHeight(TileIndex t)
Get the height ('z') of a bridge.
Map accessor functions for bridges.
bool IsBridgeAbove(Tile t)
checks if a bridge is set above the ground of this tile
Definition bridge_map.h:45
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Timpl & Set()
Set all bits.
constexpr Timpl & Reset(Tvalue_type value)
Reset the value-th bit.
Common return value for all commands.
bool Succeeded() const
Did this command succeed?
void AddCost(const Money &cost)
Adds the given cost to the cost of the command.
Money GetCost() const
The costs as made up to this moment.
bool Failed() const
Did this command fail?
StringID GetErrorMessage() const
Returns the error message of a command.
Enum-as-bit-set wrapper.
static std::unique_ptr< TileIterator > Create(TileIndex corner1, TileIndex corner2, bool diagonal)
Create either an OrthogonalTileIterator or DiagonalTileIterator given the diagonal parameter.
Definition tilearea.cpp:291
Functions related to commands.
static const CommandCost CMD_ERROR
Define a default return value for a failed command.
@ Execute
execute the given command
@ NoModifyTownRating
do not change town rating
@ Auto
don't allow building on structures
@ ForceClearTile
do not only remove the object on the tile, but also clear any water left on it
Definition of stuff that is very close to a company, like the company struct itself.
Money GetAvailableMoneyForCommand()
This functions returns the money which can be used to execute a command.
CompanyID _current_company
Company currently doing an action.
Functions related to companies.
DiagDirection
Enumeration for diagonal directions.
@ DIAGDIR_END
Used for iterations.
@ DIAGDIR_BEGIN
Used for iterations.
@ EXPENSES_CONSTRUCTION
Construction costs.
bool _generating_world
Whether we are generating the map or not.
Definition genworld.cpp:72
Functions related to world/map generation.
static void TerraformAddDirtyTile(TerraformerState *ts, TileIndex tile)
Adds a tile to the "tile_table" in a TerraformerState.
void MarkTileDirtyByTile(TileIndex tile, int bridge_level_offset, int tile_height_override)
Mark a tile given by its index dirty for repaint.
static void TerraformAddDirtyTileAround(TerraformerState *ts, TileIndex tile)
Adds all tiles that incident with the north corner of a specific tile to the "tile_table" in a Terraf...
const TileTypeProcs *const _tile_type_procs[16]
Tile callback functions for each type of tile.
Definition landscape.cpp:65
Command definitions related to landscape (slopes etc.).
TileIndex AddTileIndexDiffCWrap(TileIndex tile, TileIndexDiffC diff)
Add a TileIndexDiffC to a TileIndex and returns the new one.
Definition map_func.h:514
static debug_inline TileIndex TileXY(uint x, uint y)
Returns the TileIndex of a coordinate.
Definition map_func.h:372
TileIndexDiff TileDiffXY(int x, int y)
Calculates an offset for the given coordinate(-offset).
Definition map_func.h:388
TileIndexDiffC TileIndexDiffCByDiagDir(DiagDirection dir)
Returns the TileIndexDiffC offset from a DiagDirection.
Definition map_func.h:482
static debug_inline uint TileY(TileIndex tile)
Get the Y component of a tile.
Definition map_func.h:424
static debug_inline uint TileX(TileIndex tile)
Get the X component of a tile.
Definition map_func.h:414
LevelMode
Argument for CmdLevelLand describing what to do.
Definition map_type.h:43
@ LM_LEVEL
Level the land.
Definition map_type.h:44
@ LM_LOWER
Lower the land.
Definition map_type.h:45
@ LM_RAISE
Raise the land.
Definition map_type.h:46
constexpr T abs(const T a)
Returns the absolute value of (scalar) variable.
Definition math_func.hpp:23
Base for all objects.
ClearedObjectArea * FindClearedObject(TileIndex tile)
Find the entry in _cleared_object_areas which occupies a certain tile.
A number of safeguards to prevent using unsafe methods.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:58
Slope
Enumeration for the slope-type.
Definition slope_type.h:48
@ SLOPE_W
the west corner of the tile is raised
Definition slope_type.h:50
@ SLOPE_E
the east corner of the tile is raised
Definition slope_type.h:52
@ SLOPE_S
the south corner of the tile is raised
Definition slope_type.h:51
@ SLOPE_N
the north corner of the tile is raised
Definition slope_type.h:53
@ SLOPE_FLAT
a flat tile
Definition slope_type.h:49
@ SLOPE_STEEP
indicates the slope is steep
Definition slope_type.h:54
Definition of base types and functions in a cross-platform compatible way.
static const StringID INVALID_STRING_ID
Constant representing an invalid string (16bit in case it is used in savegames)
Class to backup a specific variable and restore it later.
void Change(const U &new_value)
Change the value of the variable.
void Restore()
Restore the variable.
Keeps track of removed objects during execution/testruns of commands.
Definition object_base.h:86
TileIndex first_tile
The first tile being cleared, which then causes the whole object to be cleared.
Definition object_base.h:87
uint32_t terraform_limit
Amount of tileheights we can (still) terraform (times 65536).
uint8_t max_bridge_height
maximum height of bridges
bool freeform_edges
allow terraforming the tiles at the map edges
uint8_t map_height_limit
the maximum allowed heightlevel
ConstructionSettings construction
construction of things in-game
static uint MaxY()
Gets the maximum Y coordinate within the map, including MP_VOID.
Definition map_func.h:305
static debug_inline uint Size()
Get the size of the map.
Definition map_func.h:287
static debug_inline uint MaxX()
Gets the maximum X coordinate within the map, including MP_VOID.
Definition map_func.h:296
static Titem * GetIfValid(auto index)
Returns Titem with given index.
State of the terraforming.
TileIndexSet dirty_tiles
The tiles that need to be redrawn.
TileIndexToHeightMap tile_to_new_height
The tiles for which the height has changed.
TerraformTileProc * terraform_tile_proc
Called when a terraforming operation is about to take place.
Definition tile_cmd.h:173
static std::tuple< CommandCost, TileIndex > TerraformTileHeight(TerraformerState *ts, TileIndex tile, int height)
Terraform the north corner of a tile to a specific height.
static int TerraformGetHeightOfTile(const TerraformerState *ts, TileIndex tile)
Gets the TileHeight (height of north corner) of a tile as of current terraforming progress.
std::tuple< CommandCost, Money, TileIndex > CmdLevelLand(DoCommandFlags flags, TileIndex tile, TileIndex start_tile, bool diagonal, LevelMode lm)
Levels a selected (rectangle) area of land.
std::set< TileIndex > TileIndexSet
Set of tiles.
std::tuple< CommandCost, Money, TileIndex > CmdTerraformLand(DoCommandFlags flags, TileIndex tile, Slope slope, bool dir_up)
Terraform land.
static void TerraformSetHeightOfTile(TerraformerState *ts, TileIndex tile, int height)
Stores the TileHeight (height of north corner) of a tile in a TerraformerState.
std::map< TileIndex, int > TileIndexToHeightMap
Mapping of tiles to their height.
Command definitions related to terraforming.
static debug_inline TileType GetTileType(Tile tile)
Get the tiletype of a given tile.
Definition tile_map.h:96
void SetTileHeight(Tile tile, uint height)
Sets the height of a tile.
Definition tile_map.h:57
static debug_inline bool IsTileType(Tile tile, TileType type)
Checks if a tile is a given tiletype.
Definition tile_map.h:150
static debug_inline uint TileHeight(Tile tile)
Returns the height of a tile.
Definition tile_map.h:29
constexpr TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition tile_type.h:95
@ MP_VOID
Invisible tiles at the SW and SE border.
Definition tile_type.h:55
bool IsTunnelInWay(TileIndex tile, int z)
Is there a tunnel in the way in any direction?
Map accessors for tunnels.
Functions related to (drawing on) viewports.