tunnel_cmd.c
author KUDr
Sun, 31 Dec 2006 23:48:04 +0000
branchcustombridgeheads
changeset 5618 a7db50b9f817
parent 5595 049ed4486972
permissions -rw-r--r--
(svn r7710) [cbh] - Fix: [YAPF] one more assert fixed. Call from the TrainController() added by (r7705) has broken YAPF because it was called when vehicle was already on the next tile (with cbh choice). Before it was always called before the train entered tile with choice.
/* $Id$ */

/** @file tunnelbridge_cmd.c
 * This file deals with tunnels and bridges (non-gui stuff)
 * @todo seperate this file into two
 */

#include "stdafx.h"
#include "openttd.h"
#include "bridge_map.h"
#include "rail_map.h"
#include "road_map.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "functions.h"
#include "map.h"
#include "tile.h"
#include "tunnel_map.h"
#include "unmovable_map.h"
#include "vehicle.h"
#include "viewport.h"
#include "command.h"
#include "player.h"
#include "town.h"
#include "sound.h"
#include "variables.h"
#include "bridge.h"
#include "train.h"
#include "water_map.h"
#include "yapf/yapf.h"
#include "date.h"
#include "newgrf_sound.h"

#include "table/bridge_land.h"

/** Build Tunnel.
 * @param tile start tile of tunnel
 * @param p1 railtype, 0x200 for road tunnel
 * @param p2 unused
 */
int32 CmdBuildTunnel(TileIndex start_tile, uint32 flags, uint32 p1, uint32 p2)
{
	TileIndexDiff delta;
	TileIndex end_tile;
	DiagDirection direction;
	Slope start_tileh;
	Slope end_tileh;
	uint start_z;
	uint end_z;
	int32 cost;
	int32 ret;

	_build_tunnel_endtile = 0;

	if (p1 != 0x200 && !ValParamRailtype(p1)) return CMD_ERROR;

	start_tileh = GetTileSlope(start_tile, &start_z);

	switch (start_tileh) {
		case SLOPE_SW: direction = DIAGDIR_SW; break;
		case SLOPE_SE: direction = DIAGDIR_SE; break;
		case SLOPE_NW: direction = DIAGDIR_NW; break;
		case SLOPE_NE: direction = DIAGDIR_NE; break;
		default: return_cmd_error(STR_500B_SITE_UNSUITABLE_FOR_TUNNEL);
	}

	ret = DoCommand(start_tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
	if (CmdFailed(ret)) return ret;

	/* XXX - do NOT change 'ret' in the loop, as it is used as the price
	 * for the clearing of the entrance of the tunnel. Assigning it to
	 * cost before the loop will yield different costs depending on start-
	 * position, because of increased-cost-by-length: 'cost += cost >> 3' */
	cost = 0;
	delta = TileOffsByDiagDir(direction);
	end_tile = start_tile;
	for (;;) {
		end_tile += delta;
		end_tileh = GetTileSlope(end_tile, &end_z);

		if (start_z == end_z) break;

		if (!_cheats.crossing_tunnels.value && IsTunnelInWay(end_tile, start_z)) {
			return_cmd_error(STR_5003_ANOTHER_TUNNEL_IN_THE_WAY);
		}

		cost += _price.build_tunnel;
		cost += cost >> 3; // add a multiplier for longer tunnels
		if (cost >= 400000000) cost = 400000000;
	}

	/* Add the cost of the entrance */
	cost += _price.build_tunnel + ret;

	// if the command fails from here on we want the end tile to be highlighted
	_build_tunnel_endtile = end_tile;

	// slope of end tile must be complementary to the slope of the start tile
	if (end_tileh != ComplementSlope(start_tileh)) {
		ret = DoCommand(end_tile, end_tileh & start_tileh, 0, flags, CMD_TERRAFORM_LAND);
		if (CmdFailed(ret)) return_cmd_error(STR_5005_UNABLE_TO_EXCAVATE_LAND);
	} else {
		ret = DoCommand(end_tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
		if (CmdFailed(ret)) return ret;
	}
	cost += _price.build_tunnel + ret;

	if (flags & DC_EXEC) {
		if (GB(p1, 9, 1) == TRANSPORT_RAIL) {
			MakeRailTunnel(start_tile, _current_player, direction,                 GB(p1, 0, 4));
			MakeRailTunnel(end_tile,   _current_player, ReverseDiagDir(direction), GB(p1, 0, 4));
			UpdateSignalsOnSegment(start_tile, direction);
			YapfNotifyTrackLayoutChange(start_tile, AxisToTrack(DiagDirToAxis(direction)));
		} else {
			MakeRoadTunnel(start_tile, _current_player, direction);
			MakeRoadTunnel(end_tile,   _current_player, ReverseDiagDir(direction));
		}
	}

	return cost;
}

TileIndex CheckTunnelBusy(TileIndex tile, uint *length)
{
	uint z = GetTileZ(tile);
	DiagDirection dir = GetTunnelDirection(tile);
	TileIndexDiff delta = TileOffsByDiagDir(dir);
	uint len = 0;
	TileIndex starttile = tile;
	Vehicle *v;

	do {
		tile += delta;
		len++;
	} while (
		!IsTunnelTile(tile) ||
		ReverseDiagDir(GetTunnelDirection(tile)) != dir ||
		GetTileZ(tile) != z
	);

	v = FindVehicleBetween(starttile, tile, z);
	if (v != NULL) {
		_error_message = v->type == VEH_Train ?
			STR_5000_TRAIN_IN_TUNNEL : STR_5001_ROAD_VEHICLE_IN_TUNNEL;
		return INVALID_TILE;
	}

	if (length != NULL) *length = len;
	return tile;
}

static inline bool CheckAllowRemoveTunnel(TileIndex tile)
{
	/* Floods can remove anything as well as the scenario editor */
	if (_current_player == OWNER_WATER || _game_mode == GM_EDITOR) return true;
	/* Obviously if the bridge/tunnel belongs to us, or no-one, we can remove it */
	if (CheckTileOwnership(tile) || IsTileOwner(tile, OWNER_NONE)) return true;
	/* Otherwise we can only remove town-owned stuff with extra patch-settings, or cheat */
	if (IsTileOwner(tile, OWNER_TOWN) && (_patches.extra_dynamite || _cheats.magic_bulldozer.value)) return true;
	return false;
}

static int32 DoClearTunnel(TileIndex tile, uint32 flags)
{
	Town *t = NULL;
	TileIndex endtile;
	uint length;

	SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);

	if (!CheckAllowRemoveTunnel(tile)) return CMD_ERROR;

	endtile = CheckTunnelBusy(tile, &length);
	if (endtile == INVALID_TILE) return CMD_ERROR;

	_build_tunnel_endtile = endtile;

	if (IsTileOwner(tile, OWNER_TOWN) && _game_mode != GM_EDITOR) {
		t = ClosestTownFromTile(tile, (uint)-1); // town penalty rating

		/* Check if you are allowed to remove the tunnel owned by a town
		 * Removal depends on difficulty settings */
		if (!CheckforTownRating(flags, t, TUNNELBRIDGE_REMOVE)) {
			SetDParam(0, t->index);
			return_cmd_error(STR_2009_LOCAL_AUTHORITY_REFUSES);
		}
	}

	if (flags & DC_EXEC) {
		// We first need to request the direction before calling DoClearSquare
		//  else the direction is always 0.. dah!! ;)
		DiagDirection dir = GetTunnelDirection(tile);
		Track track;

		// Adjust the town's player rating. Do this before removing the tile owner info.
		if (IsTileOwner(tile, OWNER_TOWN) && _game_mode != GM_EDITOR)
			ChangeTownRating(t, RATING_TUNNEL_BRIDGE_DOWN_STEP, RATING_TUNNEL_BRIDGE_MINIMUM);

		DoClearSquare(tile);
		DoClearSquare(endtile);
		UpdateSignalsOnSegment(tile, ReverseDiagDir(dir));
		UpdateSignalsOnSegment(endtile, dir);
		track = AxisToTrack(DiagDirToAxis(dir));
		YapfNotifyTrackLayoutChange(tile, track);
		YapfNotifyTrackLayoutChange(endtile, track);
	}
	return _price.clear_tunnel * (length + 1);
}



static int32 ClearTile_Tunnel(TileIndex tile, byte flags)
{
	assert(IsTunnelTile(tile));
	if (flags & DC_AUTO) return_cmd_error(STR_5006_MUST_DEMOLISH_TUNNEL_FIRST);
	return DoClearTunnel(tile, flags);
}


int32 DoConvertTunnelRail(TileIndex tile, RailType totype, bool exec)
{
	TileIndex endtile;

	if (GetTunnelTransportType(tile) == TRANSPORT_RAIL) {
		uint length;

		if (!CheckTileOwnership(tile)) return CMD_ERROR;

		if (GetRailType(tile) == totype) return CMD_ERROR;

		// 'hidden' elrails can't be downgraded to normal rail when elrails are disabled
		if (_patches.disable_elrails && totype == RAILTYPE_RAIL && GetRailType(tile) == RAILTYPE_ELECTRIC) return CMD_ERROR;

		endtile = CheckTunnelBusy(tile, &length);
		if (endtile == INVALID_TILE) return CMD_ERROR;

		if (exec) {
			Track track;
			SetRailType(tile, totype);
			SetRailType(endtile, totype);
			MarkTileDirtyByTile(tile);
			MarkTileDirtyByTile(endtile);

			track = AxisToTrack(DiagDirToAxis(GetTunnelDirection(tile)));
			YapfNotifyTrackLayoutChange(tile, track);
			YapfNotifyTrackLayoutChange(endtile, track);
		}
		return (length + 1) * (_price.build_rail >> 1);
	} else {
		return CMD_ERROR;
	}
}

/**
 * Draws a tunnel tile.
 * Please note that in this code, "roads" are treated as railtype 1, whilst the real railtypes are 0, 2 and 3
 */
static void DrawTile_Tunnel(TileInfo *ti)
{
	uint32 image;

	if (GetTunnelTransportType(ti->tile) == TRANSPORT_RAIL) {
		image = GetRailTypeInfo(GetRailType(ti->tile))->base_sprites.tunnel;
	} else {
		image = SPR_TUNNEL_ENTRY_REAR_ROAD;
	}

	if (HasTunnelSnowOrDesert(ti->tile)) image += 32;

	image += GetTunnelDirection(ti->tile) * 2;
	DrawGroundSprite(image);
	if (GetRailType(ti->tile) == RAILTYPE_ELECTRIC) DrawCatenary(ti);

	AddSortableSpriteToDraw(image+1, ti->x + TILE_SIZE - 1, ti->y + TILE_SIZE - 1, 1, 1, 8, (byte)ti->z);
	DrawBridgeMiddle(ti);
}


/** Gets the absolute z coordinate of a point inside a tunnel tile
 *  When we're on the track (that means between position 5 and 10)
 *  on the coordinate perpendicular to the track it returns only the
 *  base height of the tile (because the track is horizontal).
 *  Outside this range (from 0 to 4 and from 11 to 15) it returns the
 *  "true" Z coordinate of the tile by taking the slope into account
 *  @param tile The index of the tile we are talking about
 *  @param x Absolute or relative x coordinate
 *  @param y Absolute or relative y coordinate
 *  @return Absolute z coordinate
 */
static uint GetSlopeZ_Tunnel(TileIndex tile, uint x, uint y)
{
	uint z, pos;
	Slope tileh = GetTileSlope(tile, &z);

	x &= 0xF;
	y &= 0xF;

	pos = (DiagDirToAxis(GetTunnelDirection(tile)) == AXIS_X ? y : x);

	// In the tunnel entrance?
	if (5 <= pos && pos <= 10) return z;

	return z + GetPartialZ(x, y, tileh);
}


static Slope GetSlopeTileh_Tunnel(TileIndex tile, Slope tileh)
{
	return tileh;
}


static void GetAcceptedCargo_Tunnel(TileIndex tile, AcceptedCargo ac)
{
	/* not used */
}

static void GetTileDesc_Tunnel(TileIndex tile, TileDesc *td)
{
	td->str = (GetTunnelTransportType(tile) == TRANSPORT_RAIL) ?  STR_5017_RAILROAD_TUNNEL : STR_5018_ROAD_TUNNEL;
	td->owner = GetTileOwner(tile);
}

static void AnimateTile_Tunnel(TileIndex tile)
{
	/* not used */
}

static void TileLoop_Tunnel(TileIndex tile)
{
	bool snow_or_desert = HasTunnelSnowOrDesert(tile);
	switch (_opt.landscape) {
		case LT_HILLY:
			if (snow_or_desert != (GetTileZ(tile) > _opt.snow_line)) {
				SetTunnelSnowOrDesert(tile, !snow_or_desert);
				MarkTileDirtyByTile(tile);
			}
			break;

		case LT_DESERT:
			if (GetTropicZone(tile) == TROPICZONE_DESERT && !snow_or_desert) {
				SetTunnelSnowOrDesert(tile, true);
				MarkTileDirtyByTile(tile);
			}
			break;
	}
}


static void ClickTile_Tunnel(TileIndex tile)
{
	/* not used */
}

static uint32 GetTileTrackStatus_Tunnel(TileIndex tile, TransportType mode)
{
	if (GetTunnelTransportType(tile) != mode) return 0;
	return AxisToTrackBits(DiagDirToAxis(GetTunnelDirection(tile))) * 0x101;
}

static void ChangeTileOwner_Tunnel(TileIndex tile, PlayerID old_player, PlayerID new_player)
{
	if (!IsTileOwner(tile, old_player)) return;

	if (new_player != PLAYER_SPECTATOR) {
		SetTileOwner(tile, new_player);
	} else {
		DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR);
	}
}


static const byte _tunnel_fractcoord_1[4]    = {0x8E, 0x18, 0x81, 0xE8};
static const byte _tunnel_fractcoord_2[4]    = {0x81, 0x98, 0x87, 0x38};
static const byte _tunnel_fractcoord_3[4]    = {0x82, 0x88, 0x86, 0x48};
static const byte _exit_tunnel_track[4]      = {1, 2, 1, 2};

static const byte _road_exit_tunnel_state[4] = {8, 9, 0, 1};
static const byte _road_exit_tunnel_frame[4] = {2, 7, 9, 4};

static const byte _tunnel_fractcoord_4[4]    = {0x52, 0x85, 0x98, 0x29};
static const byte _tunnel_fractcoord_5[4]    = {0x92, 0x89, 0x58, 0x25};
static const byte _tunnel_fractcoord_6[4]    = {0x92, 0x89, 0x56, 0x45};
static const byte _tunnel_fractcoord_7[4]    = {0x52, 0x85, 0x96, 0x49};

static uint32 VehicleEnter_Tunnel(Vehicle *v, TileIndex tile, int x, int y)
{
	int z = GetSlopeZ(x, y) - v->z_pos;

	byte fc;
	DiagDirection dir;
	DiagDirection vdir;

	if (myabs(z) > 2) return 8;

	if (v->type == VEH_Train) {
		fc = (x & 0xF) + (y << 4);

		dir = GetTunnelDirection(tile);
		vdir = DirToDiagDir(v->direction);

		if (v->u.rail.track != 0x40 && dir == vdir) {
			if (IsFrontEngine(v) && fc == _tunnel_fractcoord_1[dir]) {
				if (!PlayVehicleSound(v, VSE_TUNNEL) && v->spritenum < 4) {
					SndPlayVehicleFx(SND_05_TRAIN_THROUGH_TUNNEL, v);
				}
				return 0;
			}
			if (fc == _tunnel_fractcoord_2[dir]) {
				v->tile = tile;
				v->u.rail.track = 0x40;
				v->vehstatus |= VS_HIDDEN;
				return 4;
			}
		}

		if (dir == ReverseDiagDir(vdir) && fc == _tunnel_fractcoord_3[dir] && z == 0) {
			/* We're at the tunnel exit ?? */
			v->tile = tile;
			v->u.rail.track = _exit_tunnel_track[dir];
			assert(v->u.rail.track);
			v->vehstatus &= ~VS_HIDDEN;
			return 4;
		}
	} else if (v->type == VEH_Road) {
		fc = (x & 0xF) + (y << 4);
		dir = GetTunnelDirection(tile);
		vdir = DirToDiagDir(v->direction);

		// Enter tunnel?
		if (v->u.road.state != 0xFF && dir == vdir) {
			if (fc == _tunnel_fractcoord_4[dir] ||
					fc == _tunnel_fractcoord_5[dir]) {
				v->tile = tile;
				v->u.road.state = 0xFF;
				v->vehstatus |= VS_HIDDEN;
				return 4;
			} else {
				return 0;
			}
		}

		if (dir == ReverseDiagDir(vdir) && (
					/* We're at the tunnel exit ?? */
					fc == _tunnel_fractcoord_6[dir] ||
					fc == _tunnel_fractcoord_7[dir]
				) &&
				z == 0) {
			v->tile = tile;
			v->u.road.state = _road_exit_tunnel_state[dir];
			v->u.road.frame = _road_exit_tunnel_frame[dir];
			v->vehstatus &= ~VS_HIDDEN;
			return 4;
		}
	}
	return 0;
}


const TileTypeProcs _tile_type_tunnel_procs = {
	DrawTile_Tunnel,           /* draw_tile_proc */
	GetSlopeZ_Tunnel,          /* get_slope_z_proc */
	ClearTile_Tunnel,          /* clear_tile_proc */
	GetAcceptedCargo_Tunnel,   /* get_accepted_cargo_proc */
	GetTileDesc_Tunnel,        /* get_tile_desc_proc */
	GetTileTrackStatus_Tunnel, /* get_tile_track_status_proc */
	ClickTile_Tunnel,          /* click_tile_proc */
	AnimateTile_Tunnel,        /* animate_tile_proc */
	TileLoop_Tunnel,           /* tile_loop_clear */
	ChangeTileOwner_Tunnel,    /* change_tile_owner_clear */
	NULL,                            /* get_produced_cargo_proc */
	VehicleEnter_Tunnel,       /* vehicle_enter_tile_proc */
	GetSlopeTileh_Tunnel,      /* get_slope_tileh_proc */
};