# terraform_cmd.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "command_func.h"
    00014 #include "tunnel_map.h"
    00015 #include "bridge_map.h"
    00016 #include "viewport_func.h"
    00017 #include "economy_func.h"
    00018 #include "genworld.h"
    00019 #include "object_base.h"
    00020 #include "company_base.h"
    00021 #include "company_func.h"
    00022 #include "tunnelbridge.h"
    00023
    00024 #include "table/strings.h"
    00025
    00026 #include <map>
    00027 #include <set>
    00028
    00029 struct TerraformerState {
    00030   int modheight_count;
    00031   std::set<TileIndex> dirty_tiles;
    00032
    00033   std::map<TileIndex, uint16> tile_to_new_height;
    00034 };
    00035
    00036 TileIndex _terraform_err_tile;
    00037
    00045 static int TerraformGetHeightOfTile(TerraformerState *ts, TileIndex tile)
    00046 {
    00047   if (ts->tile_to_new_height.find(tile) != ts->tile_to_new_height.end()) {
    00048     return ts->tile_to_new_height[tile];
    00049   } else {
    00050     return TileHeight(tile);
    00051   }
    00052 }
    00053
    00061 static void TerraformSetHeightOfTile(TerraformerState *ts, TileIndex tile, int height)
    00062 {
    00063   ts->tile_to_new_height[tile] = (uint16)height;
    00064 }
    00065
    00073 static void TerraformAddDirtyTile(TerraformerState *ts, TileIndex tile)
    00074 {
    00075   ts->dirty_tiles.insert(tile);
    00076 }
    00077
    00085 static void TerraformAddDirtyTileAround(TerraformerState *ts, TileIndex tile)
    00086 {
    00087   /* Make sure all tiles passed to TerraformAddDirtyTile are within [0, MapSize()] */
    00088   if (TileY(tile) >= 1) TerraformAddDirtyTile(ts, tile + TileDiffXY( 0, -1));
    00089   if (TileY(tile) >= 1 && TileX(tile) >= 1) TerraformAddDirtyTile(ts, tile + TileDiffXY(-1, -1));
    00090   if (TileX(tile) >= 1) TerraformAddDirtyTile(ts, tile + TileDiffXY(-1,  0));
    00091   TerraformAddDirtyTile(ts, tile);
    00092 }
    00093
    00102 static CommandCost TerraformTileHeight(TerraformerState *ts, TileIndex tile, int height)
    00103 {
    00104   assert(tile < MapSize());
    00105
    00106   /* Check range of destination height */
    00107   if (height < 0) return_cmd_error(STR_ERROR_ALREADY_AT_SEA_LEVEL);
    00108   if (height > (int)GetMaxTileHeight()) return_cmd_error(STR_ERROR_TOO_HIGH);
    00109
    00110   /*
    00111    * Check if the terraforming has any effect.
    00112    * This can only be true, if multiple corners of the start-tile are terraformed (i.e. the terraforming is done by towns/industries etc.).
    00113    * In this case the terraforming should fail. (Don't know why.)
    00114    */
    00115   if (height == TerraformGetHeightOfTile(ts, tile)) return CMD_ERROR;
    00116
    00117   /* Check "too close to edge of map". Only possible when freeform-edges is off. */
    00118   uint x = TileX(tile);
    00119   uint y = TileY(tile);
    00120   if (!_settings_game.construction.freeform_edges && ((x <= 1) || (y <= 1) || (x >= MapMaxX() - 1) || (y >= MapMaxY() - 1))) {
    00121     /*
    00122      * Determine a sensible error tile
    00123      */
    00124     if (x == 1) x = 0;
    00125     if (y == 1) y = 0;
    00126     _terraform_err_tile = TileXY(x, y);
    00127     return_cmd_error(STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP);
    00128   }
    00129
    00130   /* Mark incident tiles that are involved in the terraforming. */
    00131   TerraformAddDirtyTileAround(ts, tile);
    00132
    00133   /* Store the height modification */
    00134   TerraformSetHeightOfTile(ts, tile, height);
    00135
    00136   CommandCost total_cost(EXPENSES_CONSTRUCTION);
    00137
    00138   /* Increment cost */
    00139   total_cost.AddCost(_price[PR_TERRAFORM]);
    00140
    00141   /* Recurse to neighboured corners if height difference is larger than 1 */
    00142   {
    00143     const TileIndexDiffC *ttm;
    00144
    00145     TileIndex orig_tile = tile;
    00146     static const TileIndexDiffC _terraform_tilepos[] = {
    00147       { 1,  0}, // move to tile in SE
    00148       {-2,  0}, // undo last move, and move to tile in NW
    00149       { 1,  1}, // undo last move, and move to tile in SW
    00150       { 0, -2}  // undo last move, and move to tile in NE
    00151     };
    00152
    00153     for (ttm = _terraform_tilepos; ttm != endof(_terraform_tilepos); ttm++) {
    00154       tile += ToTileIndexDiff(*ttm);
    00155
    00156       if (tile >= MapSize()) continue;
    00157       /* Make sure we don't wrap around the map */
    00158       if (Delta(TileX(orig_tile), TileX(tile)) == MapSizeX() - 1) continue;
    00159       if (Delta(TileY(orig_tile), TileY(tile)) == MapSizeY() - 1) continue;
    00160
    00161       /* Get TileHeight of neighboured tile as of current terraform progress */
    00162       int r = TerraformGetHeightOfTile(ts, tile);
    00163       int height_diff = height - r;
    00164
    00165       /* Is the height difference to the neighboured corner greater than 1? */
    00166       if (abs(height_diff) > 1) {
    00167         /* Terraform the neighboured corner. The resulting height difference should be 1. */
    00168         height_diff += (height_diff < 0 ? 1 : -1);
    00169         CommandCost cost = TerraformTileHeight(ts, tile, r + height_diff);
    00170         if (cost.Failed()) return cost;
    00171         total_cost.AddCost(cost);
    00172       }
    00173     }
    00174   }
    00175
    00176   return total_cost;
    00177 }
    00178
    00179 static void MarkTilesDirty(TerraformerState &ts)
    00180 {
    00181   for (std::set<TileIndex>::const_iterator it = ts.dirty_tiles.begin(); it != ts.dirty_tiles.end(); it++) {
    00182     TileIndex ti = *it;
    00183     MarkTileDirtyByTile(ti);
    00184     int height = TerraformGetHeightOfTile(&ts, ti);
    00185
    00186     /* Now, if we alter the height of the map edge, we need to take care
    00187      * about repainting the affected areas outside map as well.
    00188      * Remember:
    00189      * Outside map, we assume that our landscape descends to
    00190      * height zero as fast as possible.
    00191      * Those simulated tiles (they don't exist as datastructure,
    00192      * only as concept in code) need to be repainted properly,
    00193      * otherwise we will get ugly glitches.
    00194      *
    00195      * Furthermore, note that we have to take care about the possibility,
    00196      * that landscape was higher before the change,
    00197      * so also tiles a bit outside need to be repainted. */
    00198     int x = TileX(ti);
    00199     int y = TileY(ti);
    00200
    00201     if (x == 0) {
    00202
    00203       if (y == 0) {
    00204         /* Height of the northern corner is altered. */
    00205         for (int cx = 0; cx >= -height - 1; cx--) {
    00206           for (int cy = 0; cy >= -height - 1; cy--) {
    00207             /* This means, tiles in the sector north of that
    00208              * corner need to be repainted. */
    00209
    00210             if (cx + cy >= -height - 2) {
    00211               /* But only tiles that actually might have changed. */
    00212               MarkTileDirtyByTileOutsideMap(cx, cy);
    00213             }
    00214           }
    00215         }
    00216       } else if (y < (int)MapMaxY()) {
    00217         for (int cx = 0; cx >= -height - 1; cx--) {
    00218           MarkTileDirtyByTileOutsideMap(cx, y);
    00219         }
    00220       } else {
    00221         for (int cx = 0; cx >= -height - 1; cx--) {
    00222           for (int cy = (int)MapMaxY(); cy <= (int)MapMaxY() + height + 1; cy++) {
    00223
    00224             if (cx + ((int)MapMaxY() - cy) >= -height - 2) {
    00225               MarkTileDirtyByTileOutsideMap(cx, cy);
    00226             }
    00227           }
    00228         }
    00229       }
    00230
    00231     } else if (x < (int)MapMaxX()) {
    00232
    00233       if (y == 0) {
    00234         for (int cy = 0; cy >= -height - 1; cy--) {
    00235           MarkTileDirtyByTileOutsideMap(x, cy);
    00236         }
    00237       } else if (y < (int)MapMaxY()) {
    00238         /* Nothing to be done here, we are inside the map. */
    00239       } else {
    00240         for (int cy = (int)MapMaxY(); cy <= (int)MapMaxY() + height + 1; cy++) {
    00241           MarkTileDirtyByTileOutsideMap(x, cy);
    00242         }
    00243       }
    00244
    00245     } else {
    00246
    00247       if (y == 0) {
    00248         for (int cx = (int)MapMaxX(); cx <= (int)MapMaxX() + height + 1; cx++) {
    00249           for (int cy = 0; cy >= -height - 1; cy--) {
    00250             if (((int)MapMaxX() - cx) + cy >= -height - 2) {
    00251               MarkTileDirtyByTileOutsideMap(cx, cy);
    00252             }
    00253           }
    00254         }
    00255       } else if (y < (int)MapMaxY()) {
    00256         for (int cx = (int)MapMaxX(); cx <= (int)MapMaxX() + height + 1; cx++) {
    00257           MarkTileDirtyByTileOutsideMap(cx, y);
    00258         }
    00259       } else {
    00260         for (int cx = (int)MapMaxX(); cx <= (int)MapMaxX() + height + 1; cx++) {
    00261           for (int cy = (int)MapMaxY(); cy <= (int)MapMaxY() + height + 1; cy++) {
    00262
    00263             if (((int)MapMaxX() - cx) + ((int)MapMaxY() - cy) >= -height - 2) {
    00264               MarkTileDirtyByTileOutsideMap(cx, cy);
    00265             }
    00266           }
    00267         }
    00268       }
    00269     }
    00270   }
    00271 }
    00272
    00282 CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
    00283 {
    00284   _terraform_err_tile = INVALID_TILE;
    00285
    00286   CommandCost total_cost(EXPENSES_CONSTRUCTION);
    00287   int direction = (p2 != 0 ? 1 : -1);
    00288   TerraformerState ts;
    00289
    00290   ts.modheight_count = 0;
    00291
    00292   /* Compute the costs and the terraforming result in a model of the landscape */
    00293   if ((p1 & SLOPE_W) != 0 && tile + TileDiffXY(1, 0) < MapSize()) {
    00294     TileIndex t = tile + TileDiffXY(1, 0);
    00295     CommandCost cost = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
    00296     if (cost.Failed()) return cost;
    00297     total_cost.AddCost(cost);
    00298   }
    00299
    00300   if ((p1 & SLOPE_S) != 0 && tile + TileDiffXY(1, 1) < MapSize()) {
    00301     TileIndex t = tile + TileDiffXY(1, 1);
    00302     CommandCost cost = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
    00303     if (cost.Failed()) return cost;
    00304     total_cost.AddCost(cost);
    00305   }
    00306
    00307   if ((p1 & SLOPE_E) != 0 && tile + TileDiffXY(0, 1) < MapSize()) {
    00308     TileIndex t = tile + TileDiffXY(0, 1);
    00309     CommandCost cost = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
    00310     if (cost.Failed()) return cost;
    00311     total_cost.AddCost(cost);
    00312   }
    00313
    00314   if ((p1 & SLOPE_N) != 0) {
    00315     TileIndex t = tile + TileDiffXY(0, 0);
    00316     CommandCost cost = TerraformTileHeight(&ts, t, TileHeight(t) + direction);
    00317     if (cost.Failed()) return cost;
    00318     total_cost.AddCost(cost);
    00319   }
    00320
    00321   /* Check if the terraforming is valid wrt. tunnels, bridges and objects on the surface
    00322    * Pass == 0: Collect tileareas which are caused to be auto-cleared.
    00323    * Pass == 1: Collect the actual cost. */
    00324   for (int pass = 0; pass < 2; pass++) {
    00325     for (std::set<TileIndex>::const_iterator it = ts.dirty_tiles.begin(); it != ts.dirty_tiles.end(); it++) {
    00326       TileIndex tile = *it;
    00327
    00328       assert(tile < MapSize());
    00329       /* MP_VOID tiles can be terraformed but as tunnels and bridges
    00330        * cannot go under / over these tiles they don't need checking. */
    00331       if (IsTileType(tile, MP_VOID)) continue;
    00332
    00333       /* Find new heights of tile corners */
    00334       uint z_N = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(0, 0));
    00335       uint z_W = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(1, 0));
    00336       uint z_S = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(1, 1));
    00337       uint z_E = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(0, 1));
    00338
    00339       /* Find min and max height of tile */
    00340       uint z_min = min(min(z_N, z_W), min(z_S, z_E));
    00341       uint z_max = max(max(z_N, z_W), max(z_S, z_E));
    00342
    00343       /* Compute tile slope */
    00344       Slope tileh = (z_max > z_min + 1 ? SLOPE_STEEP : SLOPE_FLAT);
    00345       if (z_W > z_min) tileh |= SLOPE_W;
    00346       if (z_S > z_min) tileh |= SLOPE_S;
    00347       if (z_E > z_min) tileh |= SLOPE_E;
    00348       if (z_N > z_min) tileh |= SLOPE_N;
    00349
    00350       if (pass == 0) {
    00351         /* Check if bridge would take damage. */
    00352         if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) {
    00353           uint bridge_height = GetBridgeHeight(GetSouthernBridgeEnd(tile));
    00354
    00355           if (direction == 1 && bridge_height <= z_max * TILE_HEIGHT) {
    00356             _terraform_err_tile = tile;
    00357             return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
    00358           }
    00359
    00360           /* Is the bridge above not too high afterwards?
    00361            * @see tunnelbridge_cmd.cpp for a detailed discussion. */
    00362           if (direction == -1 && bridge_height > (z_min + MAX_BRIDGE_HEIGHT) * TILE_HEIGHT) {
    00363             _terraform_err_tile = tile;
    00364             return_cmd_error(STR_ERROR_BRIDGE_TOO_HIGH_AFTER_LOWER_LAND);
    00365           }
    00366         }
    00367
    00368         /* Check if bridge would take damage */
    00369         if (direction == 1 && MayHaveBridgeAbove(tile) && IsBridgeAbove(tile) &&
    00370             GetBridgeHeight(GetSouthernBridgeEnd(tile)) <= z_max * TILE_HEIGHT) {
    00371           _terraform_err_tile = tile; // highlight the tile under the bridge
    00372           return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
    00373         }
    00374
    00375         /* Check if tunnel would take damage */
    00376         if (direction == -1 && IsTunnelInWay(tile, z_min * TILE_HEIGHT)) {
    00377           _terraform_err_tile = tile; // Highlight the tile above the tunnel.
    00378           return_cmd_error(STR_ERROR_EXCAVATION_WOULD_DAMAGE);
    00379         }
    00380       }
    00381
    00382       /* Is the tile already cleared? */
    00383       const ClearedObjectArea *coa = FindClearedObject(tile);
    00384       bool indirectly_cleared = coa != NULL && coa->first_tile != tile;
    00385
    00386       /* Check tiletype-specific things, and add extra-cost */
    00387       const bool curr_gen = _generating_world;
    00388       if (_game_mode == GM_EDITOR) _generating_world = true; // used to create green terraformed land
    00389       DoCommandFlag tile_flags = flags | DC_AUTO | DC_FORCE_CLEAR_TILE;
    00390       if (pass == 0) {
    00391         tile_flags &= ~DC_EXEC;
    00392         tile_flags |= DC_NO_MODIFY_TOWN_RATING;
    00393       }
    00394       CommandCost cost;
    00395       if (indirectly_cleared) {
    00396         cost = DoCommand(tile, 0, 0, tile_flags, CMD_LANDSCAPE_CLEAR);
    00397       } else {
    00398         cost = _tile_type_procs[GetTileType(tile)]->terraform_tile_proc(tile, tile_flags, z_min * TILE_HEIGHT, tileh);
    00399       }
    00400       _generating_world = curr_gen;
    00401       if (cost.Failed()) {
    00402         _terraform_err_tile = tile;
    00403         return cost;
    00404       }
    00405       if (pass == 1) total_cost.AddCost(cost);
    00406     }
    00407   }
    00408
    00409   Company *c = Company::GetIfValid(_current_company);
    00410   if (c != NULL && (int)GB(c->terraform_limit, 16, 16) < ts.modheight_count) {
    00411     return_cmd_error(STR_ERROR_TERRAFORM_LIMIT_REACHED);
    00412   }
    00413
    00414   if (flags & DC_EXEC) {
    00415     /* change the height */
    00416     {
    00417       for (std::map<TileIndex, uint16>::const_iterator it = ts.tile_to_new_height.begin();
    00418           it != ts.tile_to_new_height.end(); it++) {
    00419         TileIndex tile = it->first;
    00420         uint16 height = it->second;
    00421
    00422         ts.modheight_count += 1; // TODO: replace 1 by number of tiles affected.
    00423
    00424         SetTileHeight(tile, height);
    00425       }
    00426     }
    00427
    00428     /* finally mark the dirty tiles dirty */
    00429     {
    00430       MarkTilesDirty(ts);
    00431     }
    00432
    00433     if (c != NULL) c->terraform_limit -= ts.modheight_count << 16;
    00434   }
    00435   return total_cost;
    00436 }
    00437
    00449 CommandCost CmdLevelLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
    00450 {
    00451   if (p1 >= MapSize()) return CMD_ERROR;
    00452
    00453   _terraform_err_tile = INVALID_TILE;
    00454
    00455   /* remember level height */
    00456   uint oldh = TileHeight(p1);
    00457
    00458   /* compute new height */
    00459   uint h = oldh;
    00460   LevelMode lm = (LevelMode)GB(p2, 1, 2);
    00461   switch (lm) {
    00462     case LM_LEVEL: break;
    00463     case LM_RAISE: h++; break;
    00464     case LM_LOWER: h--; break;
    00465     default: return CMD_ERROR;
    00466   }
    00467
    00468   /* Check range of destination height */
    00469   if (h > GetMaxTileHeight()) return_cmd_error((oldh == 0) ? STR_ERROR_ALREADY_AT_SEA_LEVEL : STR_ERROR_TOO_HIGH);
    00470
    00471   Money money = GetAvailableMoneyForCommand();
    00472   CommandCost cost(EXPENSES_CONSTRUCTION);
    00473   CommandCost last_error(lm == LM_LEVEL ? STR_ERROR_ALREADY_LEVELLED : INVALID_STRING_ID);
    00474   bool had_success = false;
    00475
    00476   const Company *c = Company::GetIfValid(_current_company);
    00477   int limit = (c == NULL ? INT32_MAX : GB(c->terraform_limit, 16, 16));
    00478   if (limit == 0) return_cmd_error(STR_ERROR_TERRAFORM_LIMIT_REACHED);
    00479
    00480   TileArea ta(tile, p1);
    00481   TileIterator *iter = HasBit(p2, 0) ? (TileIterator *)new DiagonalTileIterator(tile, p1) : new OrthogonalTileIterator(ta);
    00482   for (; *iter != INVALID_TILE; ++(*iter)) {
    00483     TileIndex t = *iter;
    00484     uint curh = TileHeight(t);
    00485     while (curh != h) {
    00486       CommandCost ret = DoCommand(t, SLOPE_N, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND);
    00487       if (ret.Failed()) {
    00488         last_error = ret;
    00489
    00490         /* Did we reach the limit? */
    00491         if (ret.GetErrorMessage() == STR_ERROR_TERRAFORM_LIMIT_REACHED) limit = 0;
    00492         break;
    00493       }
    00494
    00495       if (flags & DC_EXEC) {
    00496         money -= ret.GetCost();
    00497         if (money < 0) {
    00498           _additional_cash_required = ret.GetCost();
    00499           delete iter;
    00500           return cost;
    00501         }
    00502         DoCommand(t, SLOPE_N, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND);
    00503       } else {
    00504         /* When we're at the terraform limit we better bail (unneeded) testing as well.
    00505          * This will probably cause the terraforming cost to be underestimated, but only
    00506          * when it's near the terraforming limit. Even then, the estimation is
    00507          * completely off due to it basically counting terraforming double, so it being
    00508          * cut off earlier might even give a better estimate in some cases. */
    00509         if (--limit <= 0) {
    00510           had_success = true;
    00511           break;
    00512         }
    00513       }
    00514
    00515       cost.AddCost(ret);
    00516       curh += (curh > h) ? -1 : 1;
    00517       had_success = true;
    00518
    00519       limit -= 1; // TODO: replace 1 by number of tiles affected.
    00520     }
    00521     if (limit <= 0) break;
    00522   }
    00523
    00524   delete iter;
    00525   return had_success ? cost : last_error;
    00526 }