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 }