tron@2186: /* $Id$ */ tron@2186: celestar@2262: /** @file tunnelbridge_cmd.c celestar@2262: * This file deals with tunnels and bridges (non-gui stuff) celestar@2262: * @todo seperate this file into two celestar@2262: */ celestar@2262: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@3189: #include "bridge_map.h" tron@3187: #include "rail_map.h" tron@3144: #include "road_map.h" tron@1363: #include "table/sprites.h" tron@507: #include "table/strings.h" tron@2163: #include "functions.h" tron@679: #include "map.h" tron@1209: #include "tile.h" tron@3154: #include "tunnel_map.h" celestar@5573: #include "unmovable_map.h" truelight@0: #include "vehicle.h" truelight@0: #include "viewport.h" truelight@0: #include "command.h" truelight@0: #include "player.h" truelight@0: #include "town.h" tron@337: #include "sound.h" tron@2159: #include "variables.h" celestar@2262: #include "bridge.h" bjarni@2676: #include "train.h" tron@3187: #include "water_map.h" KUDr@3900: #include "yapf/yapf.h" rubidium@4261: #include "date.h" peter1138@4656: #include "newgrf_sound.h" truelight@0: ludde@2261: #include "table/bridge_land.h" ludde@2261: Darkvater@1782: /** Build Tunnel. tron@3491: * @param tile start tile of tunnel Darkvater@1784: * @param p1 railtype, 0x200 for road tunnel tron@2639: * @param p2 unused truelight@0: */ tron@3491: int32 CmdBuildTunnel(TileIndex start_tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { tron@3063: TileIndexDiff delta; tron@3063: TileIndex end_tile; tron@3063: DiagDirection direction; tron@3636: Slope start_tileh; tron@3636: Slope end_tileh; tron@3063: uint start_z; tron@3063: uint end_z; tron@3063: int32 cost; tron@3063: int32 ret; truelight@0: tron@3063: _build_tunnel_endtile = 0; truelight@0: Darkvater@1784: if (p1 != 0x200 && !ValParamRailtype(p1)) return CMD_ERROR; Darkvater@1782: tron@3063: start_tileh = GetTileSlope(start_tile, &start_z); truelight@0: tron@3063: switch (start_tileh) { tron@3636: case SLOPE_SW: direction = DIAGDIR_SW; break; tron@3636: case SLOPE_SE: direction = DIAGDIR_SE; break; tron@3636: case SLOPE_NW: direction = DIAGDIR_NW; break; tron@3636: case SLOPE_NE: direction = DIAGDIR_NE; break; tron@3063: default: return_cmd_error(STR_500B_SITE_UNSUITABLE_FOR_TUNNEL); tron@2639: } truelight@0: tron@3491: ret = DoCommand(start_tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); tron@3063: if (CmdFailed(ret)) return ret; truelight@0: Darkvater@4273: /* XXX - do NOT change 'ret' in the loop, as it is used as the price Darkvater@4273: * for the clearing of the entrance of the tunnel. Assigning it to Darkvater@4273: * cost before the loop will yield different costs depending on start- Darkvater@4273: * position, because of increased-cost-by-length: 'cost += cost >> 3' */ Darkvater@4273: cost = 0; Darkvater@4559: delta = TileOffsByDiagDir(direction); tron@3063: end_tile = start_tile; tron@3063: for (;;) { tron@3063: end_tile += delta; tron@3063: end_tileh = GetTileSlope(end_tile, &end_z); truelight@0: tron@3063: if (start_z == end_z) break; tron@3063: tron@3156: if (!_cheats.crossing_tunnels.value && IsTunnelInWay(end_tile, start_z)) { tron@3156: return_cmd_error(STR_5003_ANOTHER_TUNNEL_IN_THE_WAY); tron@3063: } tron@3063: tron@3063: cost += _price.build_tunnel; Darkvater@4273: cost += cost >> 3; // add a multiplier for longer tunnels tron@3063: if (cost >= 400000000) cost = 400000000; truelight@0: } truelight@0: Darkvater@4273: /* Add the cost of the entrance */ Darkvater@4273: cost += _price.build_tunnel + ret; Darkvater@4273: tron@3063: // if the command fails from here on we want the end tile to be highlighted tron@3063: _build_tunnel_endtile = end_tile; tron@3063: tron@3063: // slope of end tile must be complementary to the slope of the start tile tron@3636: if (end_tileh != ComplementSlope(start_tileh)) { tron@3491: ret = DoCommand(end_tile, end_tileh & start_tileh, 0, flags, CMD_TERRAFORM_LAND); tron@3063: if (CmdFailed(ret)) return_cmd_error(STR_5005_UNABLE_TO_EXCAVATE_LAND); tron@3063: } else { tron@3491: ret = DoCommand(end_tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); tron@3063: if (CmdFailed(ret)) return ret; tron@3063: } tron@3064: cost += _price.build_tunnel + ret; tron@3063: tron@3063: if (flags & DC_EXEC) { tron@3154: if (GB(p1, 9, 1) == TRANSPORT_RAIL) { tron@3154: MakeRailTunnel(start_tile, _current_player, direction, GB(p1, 0, 4)); tron@3154: MakeRailTunnel(end_tile, _current_player, ReverseDiagDir(direction), GB(p1, 0, 4)); KUDr@3900: UpdateSignalsOnSegment(start_tile, direction); tron@4158: YapfNotifyTrackLayoutChange(start_tile, AxisToTrack(DiagDirToAxis(direction))); tron@3154: } else { tron@3154: MakeRoadTunnel(start_tile, _current_player, direction); tron@3154: MakeRoadTunnel(end_tile, _current_player, ReverseDiagDir(direction)); tron@3154: } tron@3063: } tron@3063: tron@3063: return cost; truelight@0: } truelight@0: tron@1430: TileIndex CheckTunnelBusy(TileIndex tile, uint *length) truelight@0: { tron@1335: uint z = GetTileZ(tile); tron@3154: DiagDirection dir = GetTunnelDirection(tile); Darkvater@4559: TileIndexDiff delta = TileOffsByDiagDir(dir); tron@1430: uint len = 0; tron@1977: TileIndex starttile = tile; truelight@0: Vehicle *v; truelight@193: tron@1035: do { tron@1035: tile += delta; tron@1035: len++; tron@1035: } while ( tron@3184: !IsTunnelTile(tile) || tron@3154: ReverseDiagDir(GetTunnelDirection(tile)) != dir || tron@1035: GetTileZ(tile) != z tron@1035: ); truelight@0: tron@1430: v = FindVehicleBetween(starttile, tile, z); tron@1430: if (v != NULL) { tron@1430: _error_message = v->type == VEH_Train ? tron@1430: STR_5000_TRAIN_IN_TUNNEL : STR_5001_ROAD_VEHICLE_IN_TUNNEL; tron@1430: return INVALID_TILE; truelight@0: } truelight@193: tron@1430: if (length != NULL) *length = len; truelight@0: return tile; truelight@0: } truelight@0: celestar@5595: static inline bool CheckAllowRemoveTunnel(TileIndex tile) Darkvater@5009: { Darkvater@5009: /* Floods can remove anything as well as the scenario editor */ Darkvater@5009: if (_current_player == OWNER_WATER || _game_mode == GM_EDITOR) return true; Darkvater@5009: /* Obviously if the bridge/tunnel belongs to us, or no-one, we can remove it */ Darkvater@5009: if (CheckTileOwnership(tile) || IsTileOwner(tile, OWNER_NONE)) return true; Darkvater@5009: /* Otherwise we can only remove town-owned stuff with extra patch-settings, or cheat */ Darkvater@5009: if (IsTileOwner(tile, OWNER_TOWN) && (_patches.extra_dynamite || _cheats.magic_bulldozer.value)) return true; Darkvater@5009: return false; Darkvater@5009: } Darkvater@5009: tron@1977: static int32 DoClearTunnel(TileIndex tile, uint32 flags) truelight@0: { Darkvater@5009: Town *t = NULL; Darkvater@1782: TileIndex endtile; tron@1430: uint length; truelight@0: truelight@0: SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); truelight@0: celestar@5595: if (!CheckAllowRemoveTunnel(tile)) return CMD_ERROR; truelight@0: truelight@0: endtile = CheckTunnelBusy(tile, &length); tron@1430: if (endtile == INVALID_TILE) return CMD_ERROR; truelight@0: truelight@0: _build_tunnel_endtile = endtile; truelight@193: tron@1901: if (IsTileOwner(tile, OWNER_TOWN) && _game_mode != GM_EDITOR) { Darkvater@5009: t = ClosestTownFromTile(tile, (uint)-1); // town penalty rating Darkvater@5009: Darkvater@5009: /* Check if you are allowed to remove the tunnel owned by a town Darkvater@5009: * Removal depends on difficulty settings */ tron@2958: if (!CheckforTownRating(flags, t, TUNNELBRIDGE_REMOVE)) { tron@534: SetDParam(0, t->index); truelight@0: return_cmd_error(STR_2009_LOCAL_AUTHORITY_REFUSES); truelight@0: } truelight@0: } truelight@0: truelight@0: if (flags & DC_EXEC) { darkvater@38: // We first need to request the direction before calling DoClearSquare darkvater@38: // else the direction is always 0.. dah!! ;) tron@3157: DiagDirection dir = GetTunnelDirection(tile); tron@4158: Track track; peter1138@2870: peter1138@2870: // Adjust the town's player rating. Do this before removing the tile owner info. peter1138@2870: if (IsTileOwner(tile, OWNER_TOWN) && _game_mode != GM_EDITOR) peter1138@2870: ChangeTownRating(t, RATING_TUNNEL_BRIDGE_DOWN_STEP, RATING_TUNNEL_BRIDGE_MINIMUM); peter1138@2870: truelight@0: DoClearSquare(tile); truelight@0: DoClearSquare(endtile); tron@3172: UpdateSignalsOnSegment(tile, ReverseDiagDir(dir)); tron@3172: UpdateSignalsOnSegment(endtile, dir); tron@4158: track = AxisToTrack(DiagDirToAxis(dir)); tron@4158: YapfNotifyTrackLayoutChange(tile, track); tron@4158: YapfNotifyTrackLayoutChange(endtile, track); truelight@0: } tron@2639: return _price.clear_tunnel * (length + 1); truelight@0: } truelight@0: tron@3977: truelight@0: celestar@5593: static int32 ClearTile_Tunnel(TileIndex tile, byte flags) tron@1977: { celestar@5593: assert(IsTunnelTile(tile)); celestar@5593: if (flags & DC_AUTO) return_cmd_error(STR_5006_MUST_DEMOLISH_TUNNEL_FIRST); celestar@5593: return DoClearTunnel(tile, flags); celestar@5593: } tron@1109: truelight@0: celestar@5595: int32 DoConvertTunnelRail(TileIndex tile, RailType totype, bool exec) truelight@0: { Darkvater@1782: TileIndex endtile; truelight@0: celestar@5595: if (GetTunnelTransportType(tile) == TRANSPORT_RAIL) { tron@4000: uint length; tron@4000: truelight@0: if (!CheckTileOwnership(tile)) return CMD_ERROR; truelight@0: tron@3242: if (GetRailType(tile) == totype) return CMD_ERROR; truelight@193: KUDr@5116: // 'hidden' elrails can't be downgraded to normal rail when elrails are disabled KUDr@5116: if (_patches.disable_elrails && totype == RAILTYPE_RAIL && GetRailType(tile) == RAILTYPE_ELECTRIC) return CMD_ERROR; KUDr@5116: truelight@0: endtile = CheckTunnelBusy(tile, &length); tron@1430: if (endtile == INVALID_TILE) return CMD_ERROR; truelight@0: truelight@0: if (exec) { tron@4158: Track track; tron@3242: SetRailType(tile, totype); tron@3242: SetRailType(endtile, totype); truelight@0: MarkTileDirtyByTile(tile); truelight@0: MarkTileDirtyByTile(endtile); KUDr@3900: tron@4158: track = AxisToTrack(DiagDirToAxis(GetTunnelDirection(tile))); KUDr@3900: YapfNotifyTrackLayoutChange(tile, track); tron@4158: YapfNotifyTrackLayoutChange(endtile, track); truelight@0: } truelight@0: return (length + 1) * (_price.build_rail >> 1); tron@4000: } else { truelight@0: return CMD_ERROR; tron@4000: } truelight@0: } truelight@0: celestar@2536: /** celestar@5593: * Draws a tunnel tile. celestar@5593: * Please note that in this code, "roads" are treated as railtype 1, whilst the real railtypes are 0, 2 and 3 celestar@5593: */ celestar@5593: static void DrawTile_Tunnel(TileInfo *ti) celestar@5593: { celestar@5593: uint32 image; celestar@5593: celestar@5593: if (GetTunnelTransportType(ti->tile) == TRANSPORT_RAIL) { celestar@5593: image = GetRailTypeInfo(GetRailType(ti->tile))->base_sprites.tunnel; celestar@5593: } else { celestar@5593: image = SPR_TUNNEL_ENTRY_REAR_ROAD; celestar@5593: } celestar@5593: celestar@5593: if (HasTunnelSnowOrDesert(ti->tile)) image += 32; celestar@5593: celestar@5593: image += GetTunnelDirection(ti->tile) * 2; celestar@5593: DrawGroundSprite(image); celestar@5593: if (GetRailType(ti->tile) == RAILTYPE_ELECTRIC) DrawCatenary(ti); celestar@5593: celestar@5593: AddSortableSpriteToDraw(image+1, ti->x + TILE_SIZE - 1, ti->y + TILE_SIZE - 1, 1, 1, 8, (byte)ti->z); celestar@5593: DrawBridgeMiddle(ti); celestar@5593: } celestar@5593: celestar@5573: celestar@5593: /** Gets the absolute z coordinate of a point inside a tunnel tile celestar@5593: * When we're on the track (that means between position 5 and 10) celestar@5593: * on the coordinate perpendicular to the track it returns only the celestar@5593: * base height of the tile (because the track is horizontal). celestar@5593: * Outside this range (from 0 to 4 and from 11 to 15) it returns the celestar@5593: * "true" Z coordinate of the tile by taking the slope into account celestar@5593: * @param tile The index of the tile we are talking about celestar@5593: * @param x Absolute or relative x coordinate celestar@5593: * @param y Absolute or relative y coordinate celestar@5593: * @return Absolute z coordinate celestar@5593: */ celestar@5593: static uint GetSlopeZ_Tunnel(TileIndex tile, uint x, uint y) tron@2537: { celestar@5593: uint z, pos; tron@4231: Slope tileh = GetTileSlope(tile, &z); tron@4231: tron@4231: x &= 0xF; tron@4231: y &= 0xF; truelight@0: celestar@5593: pos = (DiagDirToAxis(GetTunnelDirection(tile)) == AXIS_X ? y : x); tron@3517: celestar@5593: // In the tunnel entrance? celestar@5593: if (5 <= pos && pos <= 10) return z; truelight@0: tron@3517: return z + GetPartialZ(x, y, tileh); truelight@0: } truelight@0: celestar@5593: celestar@5593: static Slope GetSlopeTileh_Tunnel(TileIndex tile, Slope tileh) celestar@5593: { celestar@5593: return tileh; celestar@5593: } celestar@5593: dominik@39: celestar@5593: static void GetAcceptedCargo_Tunnel(TileIndex tile, AcceptedCargo ac) celestar@5593: { celestar@5593: /* not used */ celestar@5593: } dominik@39: celestar@5593: static void GetTileDesc_Tunnel(TileIndex tile, TileDesc *td) truelight@0: { celestar@5593: td->str = (GetTunnelTransportType(tile) == TRANSPORT_RAIL) ? STR_5017_RAILROAD_TUNNEL : STR_5018_ROAD_TUNNEL; celestar@5593: td->owner = GetTileOwner(tile); celestar@5593: } celestar@5593: celestar@5593: static void AnimateTile_Tunnel(TileIndex tile) truelight@0: { truelight@0: /* not used */ truelight@0: } truelight@0: celestar@5593: static void TileLoop_Tunnel(TileIndex tile) celestar@5593: { celestar@5593: bool snow_or_desert = HasTunnelSnowOrDesert(tile); tron@3017: switch (_opt.landscape) { tron@3017: case LT_HILLY: celestar@5592: if (snow_or_desert != (GetTileZ(tile) > _opt.snow_line)) { celestar@5593: SetTunnelSnowOrDesert(tile, !snow_or_desert); tron@4160: MarkTileDirtyByTile(tile); tron@3017: } tron@3017: break; tron@3017: tron@3017: case LT_DESERT: celestar@5592: if (GetTropicZone(tile) == TROPICZONE_DESERT && !snow_or_desert) { celestar@5593: SetTunnelSnowOrDesert(tile, true); truelight@0: MarkTileDirtyByTile(tile); truelight@0: } tron@3017: break; truelight@0: } truelight@0: } truelight@0: celestar@5593: celestar@5593: static void ClickTile_Tunnel(TileIndex tile) celestar@5593: { celestar@5593: /* not used */ celestar@5593: } celestar@5593: celestar@5593: static uint32 GetTileTrackStatus_Tunnel(TileIndex tile, TransportType mode) truelight@0: { celestar@5593: if (GetTunnelTransportType(tile) != mode) return 0; celestar@5593: return AxisToTrackBits(DiagDirToAxis(GetTunnelDirection(tile))) * 0x101; celestar@5593: } celestar@5593: celestar@5593: static void ChangeTileOwner_Tunnel(TileIndex tile, PlayerID old_player, PlayerID new_player) celestar@5593: { celestar@5593: if (!IsTileOwner(tile, old_player)) return; celestar@5593: celestar@5593: if (new_player != PLAYER_SPECTATOR) { celestar@5593: SetTileOwner(tile, new_player); tron@3996: } else { celestar@5593: DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); truelight@0: } truelight@0: } truelight@0: truelight@0: rubidium@4344: static const byte _tunnel_fractcoord_1[4] = {0x8E, 0x18, 0x81, 0xE8}; rubidium@4344: static const byte _tunnel_fractcoord_2[4] = {0x81, 0x98, 0x87, 0x38}; rubidium@4344: static const byte _tunnel_fractcoord_3[4] = {0x82, 0x88, 0x86, 0x48}; rubidium@4344: static const byte _exit_tunnel_track[4] = {1, 2, 1, 2}; truelight@0: truelight@0: static const byte _road_exit_tunnel_state[4] = {8, 9, 0, 1}; truelight@0: static const byte _road_exit_tunnel_frame[4] = {2, 7, 9, 4}; truelight@0: rubidium@4344: static const byte _tunnel_fractcoord_4[4] = {0x52, 0x85, 0x98, 0x29}; rubidium@4344: static const byte _tunnel_fractcoord_5[4] = {0x92, 0x89, 0x58, 0x25}; rubidium@4344: static const byte _tunnel_fractcoord_6[4] = {0x92, 0x89, 0x56, 0x45}; rubidium@4344: static const byte _tunnel_fractcoord_7[4] = {0x52, 0x85, 0x96, 0x49}; truelight@0: celestar@5593: static uint32 VehicleEnter_Tunnel(Vehicle *v, TileIndex tile, int x, int y) truelight@0: { celestar@5573: int z = GetSlopeZ(x, y) - v->z_pos; celestar@5573: celestar@5593: byte fc; celestar@5593: DiagDirection dir; celestar@5593: DiagDirection vdir; celestar@5593: celestar@5573: if (myabs(z) > 2) return 8; celestar@5573: celestar@5593: if (v->type == VEH_Train) { celestar@5593: fc = (x & 0xF) + (y << 4); truelight@0: celestar@5593: dir = GetTunnelDirection(tile); celestar@5593: vdir = DirToDiagDir(v->direction); celestar@5593: celestar@5593: if (v->u.rail.track != 0x40 && dir == vdir) { celestar@5593: if (IsFrontEngine(v) && fc == _tunnel_fractcoord_1[dir]) { celestar@5593: if (!PlayVehicleSound(v, VSE_TUNNEL) && v->spritenum < 4) { celestar@5593: SndPlayVehicleFx(SND_05_TRAIN_THROUGH_TUNNEL, v); truelight@0: } celestar@5593: return 0; truelight@0: } celestar@5593: if (fc == _tunnel_fractcoord_2[dir]) { truelight@0: v->tile = tile; celestar@5593: v->u.rail.track = 0x40; celestar@5593: v->vehstatus |= VS_HIDDEN; truelight@0: return 4; truelight@0: } truelight@0: } celestar@5573: celestar@5593: if (dir == ReverseDiagDir(vdir) && fc == _tunnel_fractcoord_3[dir] && z == 0) { celestar@5593: /* We're at the tunnel exit ?? */ celestar@5593: v->tile = tile; celestar@5593: v->u.rail.track = _exit_tunnel_track[dir]; celestar@5593: assert(v->u.rail.track); celestar@5593: v->vehstatus &= ~VS_HIDDEN; celestar@5593: return 4; celestar@5593: } celestar@5593: } else if (v->type == VEH_Road) { celestar@5593: fc = (x & 0xF) + (y << 4); celestar@5593: dir = GetTunnelDirection(tile); celestar@5593: vdir = DirToDiagDir(v->direction); celestar@5573: celestar@5593: // Enter tunnel? celestar@5593: if (v->u.road.state != 0xFF && dir == vdir) { celestar@5593: if (fc == _tunnel_fractcoord_4[dir] || celestar@5593: fc == _tunnel_fractcoord_5[dir]) { celestar@5593: v->tile = tile; celestar@5593: v->u.road.state = 0xFF; celestar@5593: v->vehstatus |= VS_HIDDEN; celestar@5593: return 4; celestar@5593: } else { celestar@5593: return 0; celestar@5593: } celestar@5573: } celestar@5573: celestar@5593: if (dir == ReverseDiagDir(vdir) && ( celestar@5593: /* We're at the tunnel exit ?? */ celestar@5593: fc == _tunnel_fractcoord_6[dir] || celestar@5593: fc == _tunnel_fractcoord_7[dir] celestar@5593: ) && celestar@5593: z == 0) { celestar@5593: v->tile = tile; celestar@5593: v->u.road.state = _road_exit_tunnel_state[dir]; celestar@5593: v->u.road.frame = _road_exit_tunnel_frame[dir]; celestar@5593: v->vehstatus &= ~VS_HIDDEN; celestar@5573: return 4; truelight@0: } truelight@0: } truelight@0: return 0; truelight@0: } truelight@0: celestar@5593: celestar@5593: const TileTypeProcs _tile_type_tunnel_procs = { celestar@5593: DrawTile_Tunnel, /* draw_tile_proc */ celestar@5593: GetSlopeZ_Tunnel, /* get_slope_z_proc */ celestar@5593: ClearTile_Tunnel, /* clear_tile_proc */ celestar@5593: GetAcceptedCargo_Tunnel, /* get_accepted_cargo_proc */ celestar@5593: GetTileDesc_Tunnel, /* get_tile_desc_proc */ celestar@5593: GetTileTrackStatus_Tunnel, /* get_tile_track_status_proc */ celestar@5593: ClickTile_Tunnel, /* click_tile_proc */ celestar@5593: AnimateTile_Tunnel, /* animate_tile_proc */ celestar@5593: TileLoop_Tunnel, /* tile_loop_clear */ celestar@5593: ChangeTileOwner_Tunnel, /* change_tile_owner_clear */ rubidium@4344: NULL, /* get_produced_cargo_proc */ celestar@5593: VehicleEnter_Tunnel, /* vehicle_enter_tile_proc */ celestar@5593: GetSlopeTileh_Tunnel, /* get_slope_tileh_proc */ truelight@0: }; celestar@5593: