diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bbe66b7645..7fb18d527a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -442,6 +442,7 @@ add_files( tree_cmd.cpp tree_gui.cpp tree_map.h + tunnel_base.h tunnel_map.cpp tunnel_map.h tunnelbridge.h diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp index 0f1915c964..0a734a5b55 100644 --- a/src/ground_vehicle.cpp +++ b/src/ground_vehicle.cpp @@ -11,6 +11,8 @@ #include "train.h" #include "roadveh.h" #include "depot_map.h" +#include "tunnel_base.h" +#include "slope_type.h" #include "safeguards.h" @@ -203,6 +205,69 @@ bool GroundVehicle::IsChainInDepot() const return true; } +/** + * Updates vehicle's Z inclination inside a wormhole, where applicable. + */ +template +void GroundVehicle::UpdateZPositionInWormhole() +{ + if (!IsTunnel(this->tile)) return; + + const Tunnel *t = Tunnel::GetByTile(this->tile); + if (!t->is_chunnel) return; + + TileIndex pos_tile = TileVirtXY(this->x_pos, this->y_pos); + + ClrBit(this->gv_flags, GVF_GOINGUP_BIT); + ClrBit(this->gv_flags, GVF_GOINGDOWN_BIT); + + if (pos_tile == t->tile_n || pos_tile == t->tile_s) { + this->z_pos = 0; + return; + } + + int north_coord, south_coord, pos_coord; + Slope slope = SLOPE_FLAT; + int delta; + if (t->tile_s - t->tile_n > MapMaxX()) { + // tunnel extends along Y axis (DIAGDIR_SE from north end), has same X values + north_coord = TileY(t->tile_n); + south_coord = TileY(t->tile_s); + pos_coord = TileY(pos_tile); + + if ((delta = pos_coord - north_coord) <= 3) { + this->z_pos = TILE_HEIGHT * (delta == 3 ? -2 : -1); + if (delta != 2) { + slope = SLOPE_NW; + SetBit(this->gv_flags, this->direction == DIR_NW ? GVF_GOINGUP_BIT : GVF_GOINGDOWN_BIT); + } + } else if ((delta = south_coord - pos_coord) <= 3) { + this->z_pos = TILE_HEIGHT * (delta == 3 ? -2 : -1); + if (delta != 2) { + slope = SLOPE_ELEVATED ^ SLOPE_NW; + SetBit(this->gv_flags, this->direction == DIR_NW ? GVF_GOINGDOWN_BIT : GVF_GOINGUP_BIT); + } + } + } else { + // tunnel extends along X axis (DIAGDIR_SW from north end), has same Y values + north_coord = TileX(t->tile_n); + south_coord = TileX(t->tile_s); + pos_coord = TileX(pos_tile); + + if ((delta = pos_coord - north_coord) <= 3) { + this->z_pos = TILE_HEIGHT * (delta == 3 ? -3 : (delta == 2 ? -2 : -1)); + slope = SLOPE_NE; + SetBit(this->gv_flags, this->direction == DIR_NE ? GVF_GOINGUP_BIT : GVF_GOINGDOWN_BIT); + } else if ((delta = south_coord - pos_coord) <= 3) { + this->z_pos = TILE_HEIGHT * (delta == 3 ? -3 : (delta == 2 ? -2 : -1)); + slope = SLOPE_ELEVATED ^ SLOPE_NE; + SetBit(this->gv_flags, this->direction == DIR_NE ? GVF_GOINGDOWN_BIT : GVF_GOINGUP_BIT); + } + } + + if (slope != SLOPE_FLAT) this->z_pos += GetPartialPixelZ(this->x_pos & 0xF, this->y_pos & 0xF, slope); +} + /* Instantiation for Train */ template struct GroundVehicle; /* Instantiation for RoadVehicle */ diff --git a/src/ground_vehicle.hpp b/src/ground_vehicle.hpp index af6e25c806..ae4e9ead11 100644 --- a/src/ground_vehicle.hpp +++ b/src/ground_vehicle.hpp @@ -14,6 +14,7 @@ #include "vehicle_gui.h" #include "landscape.h" #include "window_func.h" +#include "tunnel_map.h" #include "widgets/vehicle_widget.h" /** What is the status of our acceleration? */ @@ -52,6 +53,7 @@ enum GroundVehicleFlags { GVF_GOINGUP_BIT = 0, ///< Vehicle is currently going uphill. (Cached track information for acceleration) GVF_GOINGDOWN_BIT = 1, ///< Vehicle is currently going downhill. (Cached track information for acceleration) GVF_SUPPRESS_IMPLICIT_ORDERS = 2, ///< Disable insertion and removal of automatic orders until the vehicle completes the real order. + GVF_CHUNNEL_BIT = 3, ///< Vehicle may currently be in a chunnel. (Cached track information for inclination changes) }; /** @@ -220,20 +222,28 @@ struct GroundVehicle : public SpecializedVehicle { this->z_pos += HasBit(this->gv_flags, GVF_GOINGUP_BIT) ? d : -d; } + if (HasBit(this->gv_flags, GVF_CHUNNEL_BIT) && !IsTunnelTile(this->tile)) { + ClrBit(this->gv_flags, GVF_CHUNNEL_BIT); + } + assert(this->z_pos == GetSlopePixelZ(this->x_pos, this->y_pos)); } + void UpdateZPositionInWormhole(); + /** * Checks if the vehicle is in a slope and sets the required flags in that case. * @param new_tile True if the vehicle reached a new tile. * @param update_delta Indicates to also update the delta. * @return Old height of the vehicle. */ - inline int UpdateInclination(bool new_tile, bool update_delta) + inline int UpdateInclination(bool new_tile, bool update_delta, bool in_wormhole = false) { int old_z = this->z_pos; - if (new_tile) { + if (in_wormhole) { + if (HasBit(this->gv_flags, GVF_CHUNNEL_BIT)) this->UpdateZPositionInWormhole(); + } else if (new_tile) { this->UpdateZPositionAndInclination(); } else { this->UpdateZPosition(); diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index 816146212f..5491936557 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -20,6 +20,7 @@ #include "company_base.h" #include "genworld.h" #include "tree_map.h" +#include "tunnel_map.h" #include "newgrf_cargo.h" #include "newgrf_debug.h" #include "newgrf_industrytiles.h" @@ -1521,6 +1522,11 @@ static CommandCost CheckIfIndustryIsAllowed(TileIndex tile, int type, const Town return_cmd_error(STR_ERROR_CAN_ONLY_BE_BUILT_NEAR_TOWN_CENTER); } + if (type == IT_OIL_RIG && + (IsTunnelInWay(tile, 0) || + IsTunnelInWay(tile + TileDiffXY(0, 1), 0) || + IsTunnelInWay(tile + TileDiffXY(1, 2), 0))) return_cmd_error(STR_ERROR_NO_DRILLING_ABOVE_CHUNNEL); + return CommandCost(); } diff --git a/src/lang/english.txt b/src/lang/english.txt index 8d8a128717..765fc146a1 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2321,6 +2321,7 @@ STR_TRANSPARENT_BRIDGES_TOOLTIP :{BLACK}Toggle t STR_TRANSPARENT_STRUCTURES_TOOLTIP :{BLACK}Toggle transparency for structures like lighthouses and antennas. Ctrl+Click to lock STR_TRANSPARENT_CATENARY_TOOLTIP :{BLACK}Toggle transparency for catenary. Ctrl+Click to lock STR_TRANSPARENT_LOADING_TOOLTIP :{BLACK}Toggle transparency for loading indicators. Ctrl+Click to lock +STR_TRANSPARENT_TUNNELS_TOOLTIP :{BLACK}Toggle transparency for vehicles in tunnels. Ctrl+Click to lock. STR_TRANSPARENT_INVISIBLE_TOOLTIP :{BLACK}Set objects invisible instead of transparent # Linkgraph legend window @@ -2720,7 +2721,9 @@ STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot # Industries come directly from their industry names STR_LAI_TUNNEL_DESCRIPTION_RAILROAD :Railway tunnel +STR_LAI_TUNNEL_DESCRIPTION_RAILROAD_CHUNNEL :Railway tunnel (underwater) STR_LAI_TUNNEL_DESCRIPTION_ROAD :Road tunnel +STR_LAI_TUNNEL_DESCRIPTION_ROAD_CHUNNEL :Road tunnel (underwater) STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL :Steel suspension rail bridge STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL :Steel girder rail bridge @@ -4542,6 +4545,10 @@ STR_ERROR_ANOTHER_TUNNEL_IN_THE_WAY :{WHITE}Another STR_ERROR_TUNNEL_THROUGH_MAP_BORDER :{WHITE}Tunnel would end out of the map STR_ERROR_UNABLE_TO_EXCAVATE_LAND :{WHITE}Unable to excavate land for other end of tunnel STR_ERROR_TUNNEL_TOO_LONG :{WHITE}... tunnel too long +STR_ERROR_TUNNEL_RAMP_TOO_SHORT :{WHITE}... ramp too short, tunnels under water must have a ramp at least three tiles long at both ends. +STR_ERROR_TUNNEL_TOO_MANY :{WHITE}... too many tunnels +STR_ERROR_NO_DRILLING_ABOVE_CHUNNEL :{WHITE}No oil rigs allowed above underwater tunnels. +STR_ERROR_ANOTHER_TUNNEL_IN_THE_WAY_FOR_CHUNNEL :{WHITE}Three tiles are needed to pass under the other tunnel. # Object related errors STR_ERROR_TOO_MANY_OBJECTS :{WHITE}... too many objects diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 14fa8d79d8..604b51ea46 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1178,6 +1178,7 @@ bool IndividualRoadVehicleController(RoadVehicle *v, const RoadVehicle *prev) v->x_pos = gp.x; v->y_pos = gp.y; v->UpdatePosition(); + RoadZPosAffectSpeed(v, v->UpdateInclination(false, false, true)); if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true); return true; } @@ -1545,7 +1546,7 @@ again: v->x_pos = x; v->y_pos = y; v->UpdatePosition(); - RoadZPosAffectSpeed(v, v->UpdateInclination(false, true)); + RoadZPosAffectSpeed(v, v->UpdateInclination(false, true, v->state == RVSB_WORMHOLE)); return true; } diff --git a/src/saveload/CMakeLists.txt b/src/saveload/CMakeLists.txt index 52f103fa7e..c888a899cd 100644 --- a/src/saveload/CMakeLists.txt +++ b/src/saveload/CMakeLists.txt @@ -38,6 +38,7 @@ add_files( story_sl.cpp subsidy_sl.cpp town_sl.cpp + tunnel_sl.cpp vehicle_sl.cpp waypoint_sl.cpp ) diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 329152ab27..7ebefb7ad7 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -11,6 +11,7 @@ #include "../void_map.h" #include "../signs_base.h" #include "../depot_base.h" +#include "../tunnel_base.h" #include "../fios.h" #include "../gamelog_internal.h" #include "../network/network.h" @@ -534,6 +535,25 @@ static inline bool MayHaveBridgeAbove(TileIndex t) IsTileType(t, MP_WATER) || IsTileType(t, MP_TUNNELBRIDGE) || IsTileType(t, MP_OBJECT); } +TileIndex GetOtherTunnelBridgeEndOld(TileIndex tile) +{ + DiagDirection dir = GetTunnelBridgeDirection(tile); + TileIndexDiff delta = TileOffsByDiagDir(dir); + int z = GetTileZ(tile); + + dir = ReverseDiagDir(dir); + do { + tile += delta; + } while ( + !IsTunnelTile(tile) || + GetTunnelBridgeDirection(tile) != dir || + GetTileZ(tile) != z + ); + + return tile; +} + + /** * Perform a (large) amount of savegame conversion *magic* in order to * load older savegames and to fill the caches for various purposes. @@ -1944,6 +1964,32 @@ bool AfterLoadGame() } } + /* Tunnel pool has to be initiated before reservations. */ + if (IsSavegameVersionBefore(SLV_196)) { + for (TileIndex t = 0; t < map_size; t++) { + if (IsTunnelTile(t)) { + DiagDirection dir = GetTunnelBridgeDirection(t); + if (dir == DIAGDIR_SE || dir == DIAGDIR_SW) { + TileIndex start_tile = t; + TileIndex end_tile = GetOtherTunnelBridgeEndOld(start_tile); + + if (!Tunnel::CanAllocateItem()) { + SetSaveLoadError(STR_ERROR_TUNNEL_TOO_MANY); + /* Restore the signals */ + ResetSignalHandlers(); + return false; + } + + const Tunnel *t = new Tunnel(start_tile, end_tile, false); + + _m[start_tile].m2 = t->index; + _m[end_tile].m2 = t->index; + } + } + } + } + + /* Move the signal variant back up one bit for PBS. We don't convert the old PBS * format here, as an old layout wouldn't work properly anyway. To be safe, we * clear any possible PBS reservations as well. */ @@ -2547,7 +2593,7 @@ bool AfterLoadGame() } else if (dir == ReverseDiagDir(vdir)) { // Leaving tunnel hidden = frame < TILE_SIZE - _tunnel_visibility_frame[dir]; /* v->tile changes at the moment when the vehicle leaves the tunnel. */ - v->tile = hidden ? GetOtherTunnelBridgeEnd(vtile) : vtile; + v->tile = hidden ? GetOtherTunnelBridgeEndOld(vtile) : vtile; } else { /* We could get here in two cases: * - for road vehicles, it is reversing at the end of the tunnel diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 1b79ad926b..69e8b60905 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -248,6 +248,7 @@ extern const ChunkHandler _linkgraph_chunk_handlers[]; extern const ChunkHandler _airport_chunk_handlers[]; extern const ChunkHandler _object_chunk_handlers[]; extern const ChunkHandler _persistent_storage_chunk_handlers[]; +extern const ChunkHandler _tunnel_chunk_handlers[]; /** Array of all chunks in a savegame, \c nullptr terminated. */ static const ChunkHandler * const _chunk_handlers[] = { @@ -284,6 +285,8 @@ static const ChunkHandler * const _chunk_handlers[] = { _airport_chunk_handlers, _object_chunk_handlers, _persistent_storage_chunk_handlers, + _tunnel_chunk_handlers, + nullptr, }; diff --git a/src/saveload/tunnel_sl.cpp b/src/saveload/tunnel_sl.cpp new file mode 100644 index 0000000000..7ad37b12b9 --- /dev/null +++ b/src/saveload/tunnel_sl.cpp @@ -0,0 +1,48 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file tunnel_sl.cpp Code handling saving and loading of tunnels */ + +#include "../stdafx.h" +#include "../tunnel_base.h" + +#include "saveload.h" + +#include "../safeguards.h" + + +static const SaveLoad _tunnel_desc[] = { + SLE_CONDVAR(Tunnel, tile_n, SLE_UINT32, SLV_196, SL_MAX_VERSION), + SLE_CONDVAR(Tunnel, tile_s, SLE_UINT32, SLV_196, SL_MAX_VERSION), + SLE_CONDVAR(Tunnel, is_chunnel, SLE_BOOL, SLV_196, SL_MAX_VERSION), + SLE_END() +}; + +static void Save_TUNN() +{ + for(Tunnel *tunnel : Tunnel::Iterate()) { + SlSetArrayIndex(tunnel->index); + SlObject(tunnel, _tunnel_desc); + } +} + +static void Load_TUNN() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + Tunnel *tunnel = new (index) Tunnel(); + SlObject(tunnel, _tunnel_desc); + } +} + + +extern const ChunkHandler _tunnel_chunk_handlers[] = { + { 'TUNN', Save_TUNN, Load_TUNN, nullptr, nullptr, CH_ARRAY | CH_LAST}, +}; diff --git a/src/table/misc_settings.ini b/src/table/misc_settings.ini index edad848165..f0322ee359 100644 --- a/src/table/misc_settings.ini +++ b/src/table/misc_settings.ini @@ -254,7 +254,7 @@ type = SLE_UINT var = _transparency_opt def = 0 min = 0 -max = 0x1FF +max = 0x3FF cat = SC_BASIC [SDTG_VAR] @@ -263,7 +263,7 @@ type = SLE_UINT var = _transparency_lock def = 0 min = 0 -max = 0x1FF +max = 0x3FF cat = SC_BASIC [SDTG_VAR] diff --git a/src/terraform_cmd.cpp b/src/terraform_cmd.cpp index 85edb73128..1a902dd59c 100644 --- a/src/terraform_cmd.cpp +++ b/src/terraform_cmd.cpp @@ -268,7 +268,7 @@ CommandCost CmdTerraformLand(TileIndex tile, DoCommandFlag flags, uint32 p1, uin } } /* Check if tunnel would take damage */ - if (direction == -1 && IsTunnelInWay(tile, z_min)) { + if (direction == -1 && IsTunnelInWay(tile, z_min, true)) { _terraform_err_tile = tile; // highlight the tile above the tunnel return_cmd_error(STR_ERROR_EXCAVATION_WOULD_DAMAGE); } diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index ae9cb45de3..a9ca119ec3 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1563,6 +1563,7 @@ static void UpdateStatusAfterSwap(Train *v) } v->UpdatePosition(); + if (v->track == TRACK_BIT_WORMHOLE) v->UpdateInclination(false, false, true); v->UpdateViewport(true, true); } @@ -3326,6 +3327,15 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) v->x_pos = gp.x; v->y_pos = gp.y; v->UpdatePosition(); + if (v->track == TRACK_BIT_WORMHOLE) { + /* update the Z position of the vehicle */ + int old_z = v->UpdateInclination(false, false, true); + + if (prev == nullptr) { + /* This is the first vehicle in the train */ + AffectSpeedByZChange(v, old_z); + } + } if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true); continue; } @@ -3339,7 +3349,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) v->UpdatePosition(); /* update the Z position of the vehicle */ - int old_z = v->UpdateInclination(gp.new_tile != gp.old_tile, false); + int old_z = v->UpdateInclination(gp.new_tile != gp.old_tile, false, v->track == TRACK_BIT_WORMHOLE); if (prev == nullptr) { /* This is the first vehicle in the train */ @@ -3853,7 +3863,7 @@ static bool TrainLocoHandler(Train *v, bool mode) } for (Train *u = v; u != nullptr; u = u->Next()) { - if ((u->vehstatus & VS_HIDDEN) != 0) continue; + if (!u->IsDrawn()) continue; u->UpdateViewport(false, false); } diff --git a/src/transparency.h b/src/transparency.h index 54ba24e933..08b13ca9b9 100644 --- a/src/transparency.h +++ b/src/transparency.h @@ -29,6 +29,7 @@ enum TransparencyOption { TO_STRUCTURES, ///< other objects such as transmitters and lighthouses TO_CATENARY, ///< catenary TO_LOADING, ///< loading indicators + TO_TUNNELS, ///< vehicles in tunnels TO_END, TO_INVALID, ///< Invalid transparency option }; diff --git a/src/transparency_gui.cpp b/src/transparency_gui.cpp index 153dcb5d03..611094cdeb 100644 --- a/src/transparency_gui.cpp +++ b/src/transparency_gui.cpp @@ -50,7 +50,8 @@ public: case WID_TT_BRIDGES: case WID_TT_STRUCTURES: case WID_TT_CATENARY: - case WID_TT_LOADING: { + case WID_TT_LOADING: + case WID_TT_TUNNELS: { uint i = widget - WID_TT_BEGIN; if (HasBit(_transparency_lock, i)) DrawSprite(SPR_LOCK, PAL_NONE, r.left + 1, r.top + 1); break; @@ -58,6 +59,7 @@ public: case WID_TT_BUTTONS: for (uint i = WID_TT_BEGIN; i < WID_TT_END; i++) { if (i == WID_TT_LOADING) continue; // Do not draw button for invisible loading indicators. + if (i == WID_TT_TUNNELS) continue; // Do not draw button for invisible vehicles in tunnels. const NWidgetBase *wi = this->GetWidget(i); DrawFrameRect(wi->pos_x + 1, r.top + 2, wi->pos_x + wi->current_x - 2, r.bottom - 2, COLOUR_PALE_GREEN, @@ -69,7 +71,7 @@ public: void OnClick(Point pt, int widget, int click_count) override { - if (widget >= WID_TT_BEGIN && widget < WID_TT_END) { + if (IsInsideMM(widget, WID_TT_BEGIN, WID_TT_END)) { if (_ctrl_pressed) { /* toggle the bit of the transparencies lock variable */ ToggleTransparencyLock((TransparencyOption)(widget - WID_TT_BEGIN)); @@ -139,6 +141,7 @@ static const NWidgetPart _nested_transparency_widgets[] = { NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_TT_STRUCTURES), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRANSMITTER, STR_TRANSPARENT_STRUCTURES_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_TT_CATENARY), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_BUILD_X_ELRAIL, STR_TRANSPARENT_CATENARY_TOOLTIP), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_TT_LOADING), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRAINLIST, STR_TRANSPARENT_LOADING_TOOLTIP), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_TT_TUNNELS), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_ROAD_TUNNEL, STR_TRANSPARENT_TUNNELS_TOOLTIP), NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(1, 1), EndContainer(), EndContainer(), /* Panel with 'invisibility' buttons. */ diff --git a/src/tunnel_base.h b/src/tunnel_base.h new file mode 100644 index 0000000000..4a2493fa7f --- /dev/null +++ b/src/tunnel_base.h @@ -0,0 +1,39 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file tunnel_base.h Base for all tunnels */ + +#ifndef TUNNEL_BASE_H +#define TUNNEL_BASE_H + +#include "tunnel_map.h" +#include "core/pool_type.hpp" + +struct Tunnel; + +typedef Pool TunnelPool; +extern TunnelPool _tunnel_pool; + +struct Tunnel : TunnelPool::PoolItem<&_tunnel_pool> { + + TileIndex tile_n; // North tile of tunnel. + TileIndex tile_s; // South tile of tunnel. + bool is_chunnel; + + Tunnel() {} + Tunnel(TileIndex tile_n, TileIndex tile_s, bool is_chunnel) : tile_n(tile_n), tile_s(tile_s), is_chunnel(is_chunnel) {} + ~Tunnel(); + + static inline Tunnel *GetByTile(TileIndex tile) + { + return Tunnel::Get(GetTunnelIndex(tile)); + } +}; + +#endif /* TUNNEL_BASE_H */ diff --git a/src/tunnel_map.cpp b/src/tunnel_map.cpp index 9de5235801..6234193e1e 100644 --- a/src/tunnel_map.cpp +++ b/src/tunnel_map.cpp @@ -10,63 +10,62 @@ #include "stdafx.h" #include "tunnelbridge_map.h" +#include "core/pool_func.hpp" + #include "safeguards.h" +/** All tunnel portals tucked away in a pool. */ +TunnelPool _tunnel_pool("Tunnel"); +INSTANTIATE_POOL_METHODS(Tunnel) /** - * Gets the other end of the tunnel. Where a vehicle would reappear when it - * enters at the given tile. - * @param tile the tile to search from. - * @return the tile of the other end of the tunnel. + * Clean up a tunnel tile */ -TileIndex GetOtherTunnelEnd(TileIndex tile) +Tunnel::~Tunnel() { - DiagDirection dir = GetTunnelBridgeDirection(tile); - TileIndexDiff delta = TileOffsByDiagDir(dir); - int z = GetTileZ(tile); - - dir = ReverseDiagDir(dir); - do { - tile += delta; - } while ( - !IsTunnelTile(tile) || - GetTunnelBridgeDirection(tile) != dir || - GetTileZ(tile) != z - ); - - return tile; + if (CleaningPool()) return; } - /** - * Is there a tunnel in the way in the given direction? + * Gets the other end of the tunnel. Where a vehicle would reappear when it + * enters at the given tile. * @param tile the tile to search from. - * @param z the 'z' to search on. - * @param dir the direction to start searching to. - * @return true if and only if there is a tunnel. + * @return the tile of the other end of the tunnel. */ -bool IsTunnelInWayDir(TileIndex tile, int z, DiagDirection dir) +TileIndex GetOtherTunnelEnd(TileIndex tile) { - TileIndexDiff delta = TileOffsByDiagDir(dir); - int height; - - do { - tile -= delta; - if (!IsValidTile(tile)) return false; - height = GetTileZ(tile); - } while (z < height); - - return z == height && IsTunnelTile(tile) && GetTunnelBridgeDirection(tile) == dir; + Tunnel *t = Tunnel::GetByTile(tile); + return t->tile_n == tile ? t->tile_s : t->tile_n; } /** * Is there a tunnel in the way in any direction? * @param tile the tile to search from. * @param z the 'z' to search on. + * @param chunnel_allowed True if chunnel mid-parts are allowed, used when terraforming. * @return true if and only if there is a tunnel. */ -bool IsTunnelInWay(TileIndex tile, int z) +bool IsTunnelInWay(TileIndex tile, int z, bool chunnel_allowed) { - return IsTunnelInWayDir(tile, z, (TileX(tile) > (MapMaxX() / 2)) ? DIAGDIR_NE : DIAGDIR_SW) || - IsTunnelInWayDir(tile, z, (TileY(tile) > (MapMaxY() / 2)) ? DIAGDIR_NW : DIAGDIR_SE); + uint x = TileX(tile); + uint y = TileY(tile); + + for(Tunnel *t : Tunnel::Iterate()) { + if (t->tile_n > tile || tile > t->tile_s) continue; + + if (t->tile_s - t->tile_n > MapMaxX()){ + if (TileX(t->tile_n) != x || (int)TileHeight(t->tile_n) != z) continue; // dir DIAGDIR_SE + } else { + if (TileY(t->tile_n) != y || (int)TileHeight(t->tile_n) != z) continue; // dir DIAGDIR_SW + } + + if (t->is_chunnel && chunnel_allowed) { + /* Only if tunnel was build over water terraforming is allowed between portals. */ + TileIndexDiff delta = GetTunnelBridgeDirection(t->tile_n) == DIAGDIR_SE ? TileOffsByDiagDir(DIAGDIR_SE) * 4 : 4; // 4 tiles ramp. + if (tile < t->tile_n + delta || t->tile_s - delta < tile) return true; + continue; + } + return true; + } + return false; } diff --git a/src/tunnel_map.h b/src/tunnel_map.h index c347626d55..45589dacb4 100644 --- a/src/tunnel_map.h +++ b/src/tunnel_map.h @@ -13,6 +13,7 @@ #include "rail_map.h" #include "road_map.h" +typedef uint16 TunnelID; ///< Type for the unique identifier of tunnels. /** * Is this a tunnel (entrance)? @@ -36,22 +37,34 @@ static inline bool IsTunnelTile(TileIndex t) return IsTileType(t, MP_TUNNELBRIDGE) && IsTunnel(t); } +/** + * Get the index of tunnel tile. + * @param t the tile + * @pre IsTunnelTile(t) + * @return TunnelID + */ +static inline TunnelID GetTunnelIndex(TileIndex t) +{ + assert(IsTunnelTile(t)); + return _m[t].m2; +} + TileIndex GetOtherTunnelEnd(TileIndex); -bool IsTunnelInWay(TileIndex, int z); -bool IsTunnelInWayDir(TileIndex tile, int z, DiagDirection dir); +bool IsTunnelInWay(TileIndex, int z, bool chunnel_allowed = false); /** * Makes a road tunnel entrance * @param t the entrance of the tunnel * @param o the owner of the entrance + * @param id the tunnel ID * @param d the direction facing out of the tunnel * @param r the road type used in the tunnel */ -static inline void MakeRoadTunnel(TileIndex t, Owner o, DiagDirection d, RoadType road_rt, RoadType tram_rt) +static inline void MakeRoadTunnel(TileIndex t, Owner o, TunnelID id, DiagDirection d, RoadType road_rt, RoadType tram_rt) { SetTileType(t, MP_TUNNELBRIDGE); SetTileOwner(t, o); - _m[t].m2 = 0; + _m[t].m2 = id; _m[t].m3 = 0; _m[t].m4 = 0; _m[t].m5 = TRANSPORT_ROAD << 2 | d; @@ -67,14 +80,15 @@ static inline void MakeRoadTunnel(TileIndex t, Owner o, DiagDirection d, RoadTyp * Makes a rail tunnel entrance * @param t the entrance of the tunnel * @param o the owner of the entrance + * @param id the tunnel ID * @param d the direction facing out of the tunnel * @param r the rail type used in the tunnel */ -static inline void MakeRailTunnel(TileIndex t, Owner o, DiagDirection d, RailType r) +static inline void MakeRailTunnel(TileIndex t, Owner o, TunnelID id, DiagDirection d, RailType r) { SetTileType(t, MP_TUNNELBRIDGE); SetTileOwner(t, o); - _m[t].m2 = 0; + _m[t].m2 = id; _m[t].m3 = 0; _m[t].m4 = 0; _m[t].m5 = TRANSPORT_RAIL << 2 | d; diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index 0423cde1f9..159f06f3fd 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -40,12 +40,15 @@ #include "water.h" #include "company_gui.h" #include "station_func.h" +#include "station_map.h" +#include "industry_map.h" #include "table/strings.h" #include "table/bridge_land.h" #include "safeguards.h" + BridgeSpec _bridge[MAX_BRIDGES]; ///< The specification of all bridges. TileIndex _build_tunnel_endtile; ///< The end of a tunnel; as hidden return from the tunnel build command for GUI purposes. @@ -649,12 +652,6 @@ CommandCost CmdBuildTunnel(TileIndex start_tile, DoCommandFlag flags, uint32 p1, * position, because of increased-cost-by-length: 'cost += cost >> 3' */ TileIndexDiff delta = TileOffsByDiagDir(direction); - DiagDirection tunnel_in_way_dir; - if (DiagDirToAxis(direction) == AXIS_Y) { - tunnel_in_way_dir = (TileX(start_tile) < (MapMaxX() / 2)) ? DIAGDIR_SW : DIAGDIR_NE; - } else { - tunnel_in_way_dir = (TileY(start_tile) < (MapMaxX() / 2)) ? DIAGDIR_SE : DIAGDIR_NW; - } TileIndex end_tile = start_tile; @@ -662,9 +659,15 @@ CommandCost CmdBuildTunnel(TileIndex start_tile, DoCommandFlag flags, uint32 p1, int tiles_coef = 3; /* Number of tiles from start of tunnel */ int tiles = 0; + /* flag for chunnels. */ + bool is_chunnel = false; + /* Number of chunnel head tiles. */ + int head_tiles = 0; /* Number of tiles at which the cost increase coefficient per tile is halved */ int tiles_bump = 25; + TileIndex found_tunnel_tile = INVALID_TILE; + CommandCost cost(EXPENSES_CONSTRUCTION); Slope end_tileh; for (;;) { @@ -672,13 +675,69 @@ CommandCost CmdBuildTunnel(TileIndex start_tile, DoCommandFlag flags, uint32 p1, if (!IsValidTile(end_tile)) return_cmd_error(STR_ERROR_TUNNEL_THROUGH_MAP_BORDER); end_tileh = GetTileSlope(end_tile, &end_z); - if (start_z == end_z) break; + if (start_z == end_z) { + _build_tunnel_endtile = found_tunnel_tile != INVALID_TILE ? found_tunnel_tile : end_tile; + + /* Test if we are on a shore. */ + if (end_z == 0 && + (IsCoastTile(end_tile) || + (IsValidTile(end_tile + delta) && HasTileWaterGround(end_tile + delta)) || + (IsValidTile(end_tile + delta * 2) && HasTileWaterGround(end_tile + delta * 2)))) { + if (!is_chunnel) { + /*We are about to pass water for the first time so check if not to close to other tunnel */ + if (tiles + 1 < head_tiles + 4 && found_tunnel_tile != INVALID_TILE) return_cmd_error(STR_ERROR_ANOTHER_TUNNEL_IN_THE_WAY_FOR_CHUNNEL); + if (tiles + 1 < 4) return_cmd_error(STR_ERROR_TUNNEL_RAMP_TOO_SHORT); + } + } else {/* We are leaving.*/ + if (is_chunnel) { + /* Check if there is enough ramp space to come up. */ + if (head_tiles < 4 && found_tunnel_tile != INVALID_TILE) return_cmd_error(STR_ERROR_ANOTHER_TUNNEL_IN_THE_WAY_FOR_CHUNNEL); + if (head_tiles < 4) return_cmd_error(STR_ERROR_TUNNEL_RAMP_TOO_SHORT); + } else { + if (found_tunnel_tile != INVALID_TILE) return_cmd_error(STR_ERROR_ANOTHER_TUNNEL_IN_THE_WAY); + } + break; + } - if (!_cheats.crossing_tunnels.value && IsTunnelInWayDir(end_tile, start_z, tunnel_in_way_dir)) { - return_cmd_error(STR_ERROR_ANOTHER_TUNNEL_IN_THE_WAY); - } + /* A shore was found so pass the water and find a proper shore tile that potentially + * could have a tunnel portal behind. */ + for (;;) { + if (!IsValidTile(end_tile)) return_cmd_error(STR_ERROR_TUNNEL_THROUGH_MAP_BORDER); + + end_tileh = GetTileSlope(end_tile); + if(direction == DIAGDIR_NE && (end_tileh & SLOPE_NE) == SLOPE_NE) break; + if(direction == DIAGDIR_SE && (end_tileh & SLOPE_SE) == SLOPE_SE) break; + if(direction == DIAGDIR_SW && (end_tileh & SLOPE_SW) == SLOPE_SW) break; + if(direction == DIAGDIR_NW && (end_tileh & SLOPE_NW) == SLOPE_NW) break; + + /* No drilling under oil rigs.*/ + if ((IsTileType(end_tile, MP_STATION) && IsOilRig(end_tile)) || + (IsTileType(end_tile, MP_INDUSTRY) && + GetIndustryGfx(end_tile) >= GFX_OILRIG_1 && + GetIndustryGfx(end_tile) <= GFX_OILRIG_5)) { + _build_tunnel_endtile = end_tile; + return_cmd_error(STR_ERROR_NO_DRILLING_ABOVE_CHUNNEL); + } + end_tile += delta; + tiles++; + } + /* The water was passed */ + is_chunnel = true; + head_tiles = 0; + found_tunnel_tile = INVALID_TILE; + } + if (!_cheats.crossing_tunnels.value && IsTunnelInWay(end_tile, start_z, (tiles + 1 < 4 || (is_chunnel && head_tiles < 4) ? false : true))) { + if (found_tunnel_tile == INVALID_TILE || is_chunnel) { // Remember the first or the last when we pass a tunnel. + found_tunnel_tile = end_tile; + head_tiles = 0; + } + } + head_tiles++; tiles++; + } + /* The cost of the digging. */ + for (int i = tiles; i > 0; i--) { if (tiles == tiles_bump) { tiles_coef++; tiles_bump *= 2; @@ -746,21 +805,32 @@ CommandCost CmdBuildTunnel(TileIndex start_tile, DoCommandFlag flags, uint32 p1, default: NOT_REACHED(); } + if (is_chunnel) cost.MultiplyCost(2); + if (flags & DC_EXEC) { Company *c = Company::GetIfValid(company); uint num_pieces = (tiles + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR; + + /* The most northern tile first. */ + TileIndex tn = start_tile; + TileIndex ts = end_tile; + if(start_tile > end_tile) Swap(tn, ts); + + if (!Tunnel::CanAllocateItem()) return_cmd_error(STR_ERROR_TUNNEL_TOO_MANY); + const Tunnel *t = new Tunnel(tn, ts, is_chunnel); + if (transport_type == TRANSPORT_RAIL) { if (c != nullptr) c->infrastructure.rail[railtype] += num_pieces; - MakeRailTunnel(start_tile, company, direction, railtype); - MakeRailTunnel(end_tile, company, ReverseDiagDir(direction), railtype); + MakeRailTunnel(start_tile, company, t->index, direction, railtype); + MakeRailTunnel(end_tile, company, t->index, ReverseDiagDir(direction), railtype); AddSideToSignalBuffer(start_tile, INVALID_DIAGDIR, company); YapfNotifyTrackLayoutChange(start_tile, DiagDirToDiagTrack(direction)); } else { if (c != nullptr) c->infrastructure.road[roadtype] += num_pieces * 2; // A full diagonal road has two road bits. RoadType road_rt = RoadTypeIsRoad(roadtype) ? roadtype : INVALID_ROADTYPE; RoadType tram_rt = RoadTypeIsTram(roadtype) ? roadtype : INVALID_ROADTYPE; - MakeRoadTunnel(start_tile, company, direction, road_rt, tram_rt); - MakeRoadTunnel(end_tile, company, ReverseDiagDir(direction), road_rt, tram_rt); + MakeRoadTunnel(start_tile, company, t->index, direction, road_rt, tram_rt); + MakeRoadTunnel(end_tile, company, t->index, ReverseDiagDir(direction), road_rt, tram_rt); } DirtyCompanyInfrastructureWindows(company); } @@ -850,6 +920,8 @@ static CommandCost DoClearTunnel(TileIndex tile, DoCommandFlag flags) ChangeTownRating(t, RATING_TUNNEL_BRIDGE_DOWN_STEP, RATING_TUNNEL_BRIDGE_MINIMUM, flags); } + const bool is_chunnel = Tunnel::GetByTile(tile)->is_chunnel; + uint len = GetTunnelBridgeLength(tile, endtile) + 2; // Don't forget the end tiles. if (flags & DC_EXEC) { @@ -870,6 +942,8 @@ static CommandCost DoClearTunnel(TileIndex tile, DoCommandFlag flags) DirtyCompanyInfrastructureWindows(owner); } + delete Tunnel::GetByTile(tile); + DoClearSquare(tile); DoClearSquare(endtile); @@ -886,11 +960,13 @@ static CommandCost DoClearTunnel(TileIndex tile, DoCommandFlag flags) UpdateCompanyRoadInfrastructure(GetRoadTypeRoad(tile), GetRoadOwner(tile, RTT_ROAD), -(int)(len * 2 * TUNNELBRIDGE_TRACKBIT_FACTOR)); UpdateCompanyRoadInfrastructure(GetRoadTypeTram(tile), GetRoadOwner(tile, RTT_TRAM), -(int)(len * 2 * TUNNELBRIDGE_TRACKBIT_FACTOR)); + delete Tunnel::GetByTile(tile); + DoClearSquare(tile); DoClearSquare(endtile); } } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_TUNNEL] * len); + return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_TUNNEL] * len * (is_chunnel ? 2 : 1)); } @@ -1866,6 +1942,7 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti if (frame == _tunnel_visibility_frame[dir]) { t->tile = tile; t->track = TRACK_BIT_WORMHOLE; + if (Tunnel::GetByTile(tile)->is_chunnel) SetBit(t->gv_flags, GVF_CHUNNEL_BIT); t->vehstatus |= VS_HIDDEN; return VETSB_ENTERED_WORMHOLE; } @@ -1873,6 +1950,7 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti if (dir == ReverseDiagDir(vdir) && frame == TILE_SIZE - _tunnel_visibility_frame[dir] && z == 0) { /* We're at the tunnel exit ?? */ + if (t->tile != tile && GetOtherTunnelEnd(t->tile) != tile) return VETSB_CONTINUE; // In chunnel t->tile = tile; t->track = DiagDirToDiagTrackBits(vdir); assert(t->track); @@ -1889,6 +1967,7 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti assert(frame == rv->frame + 1); rv->tile = tile; rv->state = RVSB_WORMHOLE; + if (Tunnel::GetByTile(tile)->is_chunnel) SetBit(rv->gv_flags, GVF_CHUNNEL_BIT); rv->vehstatus |= VS_HIDDEN; return VETSB_ENTERED_WORMHOLE; } else { @@ -1898,6 +1977,7 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti /* We're at the tunnel exit ?? */ if (dir == ReverseDiagDir(vdir) && frame == TILE_SIZE - _tunnel_visibility_frame[dir] && z == 0) { + if (rv->tile != tile && GetOtherTunnelEnd(rv->tile) != tile) return VETSB_CONTINUE; // In chunnel rv->tile = tile; rv->state = DiagDirToDiagTrackdir(vdir); rv->frame = frame; @@ -1906,6 +1986,7 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti } } } else { // IsBridge(tile) + if (v->vehstatus & VS_HIDDEN) return VETSB_CONTINUE; // Building bridges between chunnel portals allowed. if (v->type != VEH_SHIP) { /* modify speed of vehicle */ uint16 spd = GetBridgeSpec(GetBridgeType(tile))->speed; diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h index 62d3c14b2d..37e00457e4 100644 --- a/src/tunnelbridge_map.h +++ b/src/tunnelbridge_map.h @@ -11,7 +11,7 @@ #define TUNNELBRIDGE_MAP_H #include "bridge_map.h" -#include "tunnel_map.h" +#include "tunnel_base.h" /** diff --git a/src/vehicle.cpp b/src/vehicle.cpp index e358421186..da107682f0 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -285,6 +285,14 @@ uint Vehicle::Crash(bool flooded) return RandomRange(pass + 1); // Randomise deceased passengers. } +bool Vehicle::IsDrawn() const +{ + return !(this->vehstatus & VS_HIDDEN) || + (!IsTransparencySet(TO_TUNNELS) && + ((this->type == VEH_TRAIN && Train::From(this)->track == TRACK_BIT_WORMHOLE) || + (this->type == VEH_ROAD && RoadVehicle::From(this)->state == RVSB_WORMHOLE))); +} + /** * Displays a "NewGrf Bug" error message for a engine, and pauses the game if not networking. @@ -1082,7 +1090,7 @@ static void DoDrawVehicle(const Vehicle *v) if (v->vehstatus & VS_DEFPAL) pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v); /* Check whether the vehicle shall be transparent due to the game state */ - bool shadowed = (v->vehstatus & VS_SHADOW) != 0; + bool shadowed = (v->vehstatus & (VS_SHADOW | VS_HIDDEN)) != 0; if (v->type == VEH_EFFECT) { /* Check whether the vehicle shall be transparent/invisible due to GUI settings. @@ -1155,7 +1163,7 @@ void ViewportAddVehicles(DrawPixelInfo *dpi) while (v != nullptr) { - if (!(v->vehstatus & VS_HIDDEN) && + if (v->IsDrawn() && l <= v->coord.right && t <= v->coord.bottom && r >= v->coord.left && @@ -1209,7 +1217,7 @@ Vehicle *CheckClickOnVehicle(const Viewport *vp, int x, int y) y = ScaleByZoom(y, vp->zoom) + vp->virtual_top; for (Vehicle *v : Vehicle::Iterate()) { - if ((v->vehstatus & (VS_HIDDEN | VS_UNCLICKABLE)) == 0 && + if (v->IsDrawn() && !(v->vehstatus & VS_UNCLICKABLE) && x >= v->coord.left && x <= v->coord.right && y >= v->coord.top && y <= v->coord.bottom) { diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 24facfb3e4..f3a964261c 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -363,6 +363,12 @@ public: uint GetConsistTotalCapacity() const; + /** + * Is this vehicle drawn? + * @return true if it is drawn + */ + bool IsDrawn() const; + /** * Marks the vehicles to be redrawn and updates cached variables * diff --git a/src/widgets/transparency_widget.h b/src/widgets/transparency_widget.h index 2b096e733b..099d48a339 100644 --- a/src/widgets/transparency_widget.h +++ b/src/widgets/transparency_widget.h @@ -23,6 +23,7 @@ enum TransparencyToolbarWidgets { WID_TT_STRUCTURES, ///< Object structure transparency toggle button. WID_TT_CATENARY, ///< Catenary transparency toggle button. WID_TT_LOADING, ///< Loading indicators transparency toggle button. + WID_TT_TUNNELS, ///< Vehicles in tunnels toggle button. WID_TT_END, ///< End of toggle buttons. /* Panel with buttons for invisibility */