OpenTTD-patches/rail_cmd.c
celestar a3739aecdf (svn r2702) -Codechange: Cleaned up the sprite code and replaced many magic numbers
by enums. There remains work in gfx.c to move the "transparency" and
"recolor" bits around to make space for more sprites. However, 2800
additional sprites can now be loaded. There also remains cleanup and
Doxygen work on many of the header files.
2005-07-24 15:56:31 +00:00

2301 lines
63 KiB
C

/* $Id$ */
#include "stdafx.h"
#include "openttd.h"
#include "debug.h"
#include "functions.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "map.h"
#include "tile.h"
#include "vehicle.h"
#include "viewport.h"
#include "command.h"
#include "pathfind.h"
#include "engine.h"
#include "town.h"
#include "sound.h"
#include "station.h"
#include "sprite.h"
#include "depot.h"
#include "pbs.h"
#include "waypoint.h"
#include "npf.h"
#include "rail.h"
extern uint16 _custom_sprites_base;
void ShowTrainDepotWindow(TileIndex tile);
/* Format of rail map5 byte.
* 00 abcdef => Normal rail
* 01 abcdef => Rail with signals
* 10 ?????? => Unused
* 11 ????dd => Depot
*
* abcdef is a bitmask, which contains ones for all present tracks. Below the
* value for each track is given.
*/
/* 4
* ---------
* |\ /|
* | \ 1/ |
* | \ / |
* | \ / |
* 16| \ |32
* | / \2 |
* | / \ |
* | / \ |
* |/ \|
* ---------
* 8
*/
// Constants for lower part of Map2 byte.
enum RailMap2Lower4 {
RAIL_MAP2LO_GROUND_MASK = 0xF,
RAIL_GROUND_BROWN = 0,
RAIL_GROUND_GREEN = 1,
RAIL_GROUND_FENCE_NW = 2,
RAIL_GROUND_FENCE_SE = 3,
RAIL_GROUND_FENCE_SENW = 4,
RAIL_GROUND_FENCE_NE = 5,
RAIL_GROUND_FENCE_SW = 6,
RAIL_GROUND_FENCE_NESW = 7,
RAIL_GROUND_FENCE_VERT1 = 8,
RAIL_GROUND_FENCE_VERT2 = 9,
RAIL_GROUND_FENCE_HORIZ1 = 10,
RAIL_GROUND_FENCE_HORIZ2 = 11,
RAIL_GROUND_ICE_DESERT = 12,
};
/* MAP2 byte: abcd???? => Signal On? Same coding as map3lo
* MAP3LO byte: abcd???? => Signal Exists?
* a and b are for diagonals, upper and left,
* one for each direction. (ie a == NE->SW, b ==
* SW->NE, or v.v., I don't know. b and c are
* similar for lower and right.
* MAP2 byte: ????abcd => Type of ground.
* MAP3LO byte: ????abcd => Type of rail.
* MAP5: 00abcdef => rail
* 01abcdef => rail w/ signals
* 10uuuuuu => unused
* 11uuuudd => rail depot
*/
static bool CheckTrackCombination(TileIndex tile, TrackBits to_build, uint flags)
{
RailTileType type = GetRailTileType(tile);
TrackBits current; /* The current track layout */
TrackBits future; /* The track layout we want to build */
_error_message = STR_1001_IMPOSSIBLE_TRACK_COMBINATION;
if (type != RAIL_TYPE_NORMAL && type != RAIL_TYPE_SIGNALS)
return false; /* Cannot build anything on depots and checkpoints */
/* So, we have a tile with tracks on it (and possibly signals). Let's see
* what tracks first */
current = GetTrackBits(tile);
future = current | to_build;
/* Are we really building something new? */
if (current == future) {
/* Nothing new is being built */
_error_message = STR_1007_ALREADY_BUILT;
return false;
}
/* Let's see if we may build this */
if ((flags & DC_NO_RAIL_OVERLAP) || type == RAIL_TYPE_SIGNALS) {
/* If we are not allowed to overlap (flag is on for ai players or we have
* signals on the tile), check that */
return
future == (TRACK_BIT_UPPER | TRACK_BIT_LOWER) ||
future == (TRACK_BIT_LEFT | TRACK_BIT_RIGHT);
} else {
/* Normally, we may overlap and any combination is valid */
return true;
}
}
static const byte _valid_tileh_slopes[4][15] = {
// set of normal ones
{
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_RIGHT,
TRACK_BIT_UPPER,
TRACK_BIT_DIAG1,
TRACK_BIT_LEFT,
0,
TRACK_BIT_DIAG2,
TRACK_BIT_LOWER,
TRACK_BIT_LOWER,
TRACK_BIT_DIAG2,
0,
TRACK_BIT_LEFT,
TRACK_BIT_DIAG1,
TRACK_BIT_UPPER,
TRACK_BIT_RIGHT,
},
// allowed rail for an evenly raised platform
{
0,
TRACK_BIT_LEFT,
TRACK_BIT_LOWER,
TRACK_BIT_DIAG2 | TRACK_BIT_LOWER | TRACK_BIT_LEFT,
TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1 | TRACK_BIT_LOWER | TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_UPPER,
TRACK_BIT_DIAG1 | TRACK_BIT_UPPER | TRACK_BIT_LEFT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG2 | TRACK_BIT_UPPER | TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
},
// allowed rail on coast tile
{
0,
TRACK_BIT_LEFT,
TRACK_BIT_LOWER,
TRACK_BIT_DIAG2|TRACK_BIT_LEFT|TRACK_BIT_LOWER,
TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_RIGHT|TRACK_BIT_LOWER,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_UPPER,
TRACK_BIT_DIAG1|TRACK_BIT_LEFT|TRACK_BIT_UPPER,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG2|TRACK_BIT_RIGHT|TRACK_BIT_UPPER,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
TRACK_BIT_DIAG1|TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LOWER|TRACK_BIT_LEFT|TRACK_BIT_RIGHT,
},
// valid railway crossings on slopes
{
1, 0, 0, // 0, 1, 2
0, 0, 1, // 3, 4, 5
0, 1, 0, // 6, 7, 8
0, 1, 1, // 9, 10, 11
0, 1, 1, // 12, 13, 14
}
};
uint GetRailFoundation(uint tileh, uint bits)
{
int i;
if ((~_valid_tileh_slopes[0][tileh] & bits) == 0)
return 0;
if ((~_valid_tileh_slopes[1][tileh] & bits) == 0)
return tileh;
if ( ((i=0, tileh == 1) || (i+=2, tileh == 2) || (i+=2, tileh == 4) || (i+=2, tileh == 8)) && (bits == TRACK_BIT_DIAG1 || (i++, bits == TRACK_BIT_DIAG2)))
return i + 15;
return 0;
}
//
static uint32 CheckRailSlope(uint tileh, TrackBits rail_bits, TrackBits existing, TileIndex tile)
{
// never allow building on top of steep tiles
if (!IsSteepTileh(tileh)) {
rail_bits |= existing;
// don't allow building on the lower side of a coast
if (IsTileType(tile, MP_WATER) &&
~_valid_tileh_slopes[2][tileh] & rail_bits) {
return_cmd_error(STR_3807_CAN_T_BUILD_ON_WATER);
}
// no special foundation
if ((~_valid_tileh_slopes[0][tileh] & rail_bits) == 0)
return 0;
if ((~_valid_tileh_slopes[1][tileh] & rail_bits) == 0 || ( // whole tile is leveled up
(rail_bits == TRACK_BIT_DIAG1 || rail_bits == TRACK_BIT_DIAG2) &&
(tileh == 1 || tileh == 2 || tileh == 4 || tileh == 8)
)) { // partly up
if (existing != 0) {
return 0;
} else if (!_patches.build_on_slopes ||
(_is_ai_player && !_patches.ainew_active)) {
return_cmd_error(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION);
} else {
return _price.terraform;
}
}
}
return_cmd_error(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION);
}
/* Validate functions for rail building */
static inline bool ValParamTrackOrientation(Track track) {return IsValidTrack(track);}
/** Build a single piece of rail
* @param x,y coordinates on where to build
* @param p1 railtype of being built piece (normal, mono, maglev)
* @param p2 rail track to build
*/
int32 CmdBuildSingleRail(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
TileIndex tile;
uint tileh;
uint m5; /* XXX: Used only as a cache, should probably be removed? */
Track track = (Track)p2;
TrackBits trackbit;
int32 cost = 0;
int32 ret;
if (!ValParamRailtype(p1) || !ValParamTrackOrientation(track)) return CMD_ERROR;
tile = TileVirtXY(x, y);
tileh = GetTileSlope(tile, NULL);
m5 = _m[tile].m5;
trackbit = TrackToTrackBits(track);
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
switch (GetTileType(tile)) {
case MP_TUNNELBRIDGE:
if ((m5 & 0xC0) != 0xC0 || // not bridge middle part?
(m5 & 0x01 ? 1 : 2) != trackbit) { // wrong direction?
// Get detailed error message
return DoCommandByTile(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
}
switch (m5 & 0x38) { // what's under the bridge?
case 0x00: // clear land
ret = CheckRailSlope(tileh, trackbit, 0, tile);
if (CmdFailed(ret)) return ret;
cost += ret;
if (flags & DC_EXEC) {
SetTileOwner(tile, _current_player);
_m[tile].m3 &= ~0x0F;
_m[tile].m3 |= p1;
_m[tile].m5 = (m5 & 0xC7) | 0x20; // railroad under bridge
}
break;
case 0x20: // rail already there
return_cmd_error(STR_1007_ALREADY_BUILT);
default:
// Get detailed error message
return DoCommandByTile(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
}
break;
case MP_RAILWAY:
if (!CheckTrackCombination(tile, trackbit, flags) ||
!EnsureNoVehicle(tile)) {
return CMD_ERROR;
}
if (m5 & RAIL_TYPE_SPECIAL ||
!IsTileOwner(tile, _current_player) ||
(_m[tile].m3 & 0xFU) != p1) {
// Get detailed error message
return DoCommandByTile(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
}
ret = CheckRailSlope(tileh, trackbit, GetTrackBits(tile), tile);
if (CmdFailed(ret)) return ret;
cost += ret;
if (flags & DC_EXEC) {
_m[tile].m2 &= ~RAIL_MAP2LO_GROUND_MASK; // Bare land
_m[tile].m5 = m5 | trackbit;
}
break;
case MP_STREET:
if (!_valid_tileh_slopes[3][tileh]) // prevent certain slopes
return_cmd_error(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION);
if (!EnsureNoVehicle(tile)) return CMD_ERROR;
if ((m5 & 0xF0) == 0 && ( // normal road?
(track == TRACK_DIAG1 && m5 == 0x05) ||
(track == TRACK_DIAG2 && m5 == 0x0A) // correct direction?
)) {
if (flags & DC_EXEC) {
_m[tile].m3 = GetTileOwner(tile);
SetTileOwner(tile, _current_player);
_m[tile].m4 = p1;
_m[tile].m5 = 0x10 | (track == TRACK_DIAG1 ? 0x08 : 0x00); // level crossing
}
break;
}
if (IsLevelCrossing(tile) && (m5 & 0x08 ? TRACK_DIAG1 : TRACK_DIAG2) == track)
return_cmd_error(STR_1007_ALREADY_BUILT);
/* FALLTHROUGH */
default:
ret = CheckRailSlope(tileh, trackbit, 0, tile);
if (CmdFailed(ret)) return ret;
cost += ret;
ret = DoCommandByTile(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
if (CmdFailed(ret)) return ret;
cost += ret;
if (flags & DC_EXEC) {
SetTileType(tile, MP_RAILWAY);
SetTileOwner(tile, _current_player);
_m[tile].m2 = 0; // Bare land
_m[tile].m3 = p1; // No signals, rail type
_m[tile].m5 = trackbit;
}
break;
}
if (flags & DC_EXEC) {
MarkTileDirtyByTile(tile);
SetSignalsOnBothDir(tile, track);
}
return cost + _price.build_rail;
}
static const byte _signals_table[] = {
0x40, 0x40, 0x40, 0x10, 0x80, 0x20, 0, 0, // direction 1
0x80, 0x80, 0x80, 0x20, 0x40, 0x10, 0, 0 // direction 2
};
static const byte _signals_table_other[] = {
0x80, 0x80, 0x80, 0x20, 0x40, 0x10, 0, 0, // direction 1
0x40, 0x40, 0x40, 0x10, 0x80, 0x20, 0, 0 // direction 2
};
static const byte _signals_table_both[] = {
0xC0, 0xC0, 0xC0, 0x30, 0xC0, 0x30, 0, 0, // both directions combined
0xC0, 0xC0, 0xC0, 0x30, 0xC0, 0x30, 0, 0
};
/** Remove a single piece of track
* @param x,y coordinates for removal of track
* @param p1 unused
* @param p2 rail orientation
*/
int32 CmdRemoveSingleRail(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
Track track = (Track)p2;
TrackBits trackbit;
uint tileh;
TileIndex tile;
byte m5;
int32 cost = _price.remove_rail;
if (!ValParamTrackOrientation(p2)) return CMD_ERROR;
trackbit = TrackToTrackBits(track);
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
tile = TileVirtXY(x, y);
tileh = GetTileSlope(tile, NULL);
if (!IsTileType(tile, MP_TUNNELBRIDGE) && !IsTileType(tile, MP_STREET) && !IsTileType(tile, MP_RAILWAY))
return CMD_ERROR;
if (_current_player != OWNER_WATER && !CheckTileOwnership(tile))
return CMD_ERROR;
// allow building rail under bridge
if (!IsTileType(tile, MP_TUNNELBRIDGE) && !EnsureNoVehicle(tile))
return CMD_ERROR;
switch(GetTileType(tile))
{
case MP_TUNNELBRIDGE:
if (!EnsureNoVehicleZ(tile, TilePixelHeight(tile)))
return CMD_ERROR;
if ((_m[tile].m5 & 0xF8) != 0xE0)
return CMD_ERROR;
if ( ((_m[tile].m5 & 1) ? 1 : 2) != trackbit )
return CMD_ERROR;
if (!(flags & DC_EXEC))
return _price.remove_rail;
SetTileOwner(tile, OWNER_NONE);
_m[tile].m5 = _m[tile].m5 & 0xC7;
break;
case MP_STREET:
if (!(_m[tile].m5 & 0xF0))
return CMD_ERROR;
if (_m[tile].m5 & 0xE0)
return CMD_ERROR;
/* This is a crossing, let's check if the direction is correct */
if (_m[tile].m5 & 8) {
m5 = 5;
if (track != TRACK_DIAG1)
return CMD_ERROR;
} else {
m5 = 10;
if (track != TRACK_DIAG2)
return CMD_ERROR;
}
if (!(flags & DC_EXEC))
return _price.remove_rail;
_m[tile].m5 = m5;
SetTileOwner(tile, _m[tile].m3);
_m[tile].m2 = 0;
break;
case MP_RAILWAY:
if (!IsPlainRailTile(tile))
return CMD_ERROR;
/* See if the track to remove is actually there */
if (!(GetTrackBits(tile) & trackbit))
return CMD_ERROR;
/* Charge extra to remove signals on the track, if they are there */
if (HasSignalOnTrack(tile, track))
cost += DoCommand(x, y, track, 0, flags, CMD_REMOVE_SIGNALS);
if (!(flags & DC_EXEC))
return cost;
/* We remove the trackbit here. */
_m[tile].m5 &= ~trackbit;
/* Unreserve track for PBS */
if (PBSTileReserved(tile) & trackbit)
PBSClearTrack(tile, track);
if (GetTrackBits(tile) == 0) {
/* The tile has no tracks left, it is no longer a rail tile */
DoClearSquare(tile);
/* XXX: This is an optimisation, right? Is it really worth the ugly goto? */
goto skip_mark_dirty;
}
break;
default:
assert(0);
}
/* mark_dirty */
MarkTileDirtyByTile(tile);
skip_mark_dirty:;
SetSignalsOnBothDir(tile, track);
return cost;
}
static const struct {
int8 xinc[16];
int8 yinc[16];
} _railbit = {{
// 0 1 2 3 4 5
-16, 0,-16, 0, 16, 0, 0, 0,
16, 0, 0, 16, 0,-16, 0, 0,
},{
0, 16, 0, 16, 0, 16, 0, 0,
0,-16,-16, 0,-16, 0, 0, 0,
}};
static int32 ValidateAutoDrag(Trackdir *trackdir, int x, int y, int ex, int ey)
{
int dx, dy, trdx, trdy;
if (!ValParamTrackOrientation(*trackdir)) return CMD_ERROR;
// calculate delta x,y from start to end tile
dx = ex - x;
dy = ey - y;
// calculate delta x,y for the first direction
trdx = _railbit.xinc[*trackdir];
trdy = _railbit.yinc[*trackdir];
if (!IsDiagonalTrackdir(*trackdir)) {
trdx += _railbit.xinc[*trackdir ^ 1];
trdy += _railbit.yinc[*trackdir ^ 1];
}
// validate the direction
while (((trdx <= 0) && (dx > 0)) || ((trdx >= 0) && (dx < 0)) ||
((trdy <= 0) && (dy > 0)) || ((trdy >= 0) && (dy < 0))) {
if (!HASBIT(*trackdir, 3)) { // first direction is invalid, try the other
SETBIT(*trackdir, 3); // reverse the direction
trdx = -trdx;
trdy = -trdy;
} else // other direction is invalid too, invalid drag
return CMD_ERROR;
}
// (for diagonal tracks, this is already made sure of by above test), but:
// for non-diagonal tracks, check if the start and end tile are on 1 line
if (!IsDiagonalTrackdir(*trackdir)) {
trdx = _railbit.xinc[*trackdir];
trdy = _railbit.yinc[*trackdir];
if ((abs(dx) != abs(dy)) && (abs(dx) + abs(trdy) != abs(dy) + abs(trdx)))
return CMD_ERROR;
}
return 0;
}
/** Build a stretch of railroad tracks.
* @param x,y start tile of drag
* @param p1 end tile of drag
* @param p2 various bitstuffed elements
* - p2 = (bit 0-3) - railroad type normal/maglev (0 = normal, 1 = mono, 2 = maglev)
* - p2 = (bit 4-6) - track-orientation, valid values: 0-5 (Track enum)
* - p2 = (bit 7) - 0 = build, 1 = remove tracks
*/
static int32 CmdRailTrackHelper(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
int ex, ey;
int32 ret, total_cost = 0;
Track track = (Track)GB(p2, 4, 3);
Trackdir trackdir;
byte mode = HASBIT(p2, 7);
if (!ValParamRailtype(p2 & 0x3) || !ValParamTrackOrientation(track)) return CMD_ERROR;
if (p1 > MapSize()) return CMD_ERROR;
trackdir = TrackToTrackdir(track);
/* unpack end point */
ex = TileX(p1) * TILE_SIZE;
ey = TileY(p1) * TILE_SIZE;
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
if (CmdFailed(ValidateAutoDrag(&trackdir, x, y, ex, ey))) return CMD_ERROR;
if (flags & DC_EXEC) SndPlayTileFx(SND_20_SPLAT_2, TileVirtXY(x, y));
for(;;) {
ret = DoCommand(x, y, p2 & 0x3, TrackdirToTrack(trackdir), flags, (mode == 0) ? CMD_BUILD_SINGLE_RAIL : CMD_REMOVE_SINGLE_RAIL);
if (CmdFailed(ret)) {
if ((_error_message != STR_1007_ALREADY_BUILT) && (mode == 0))
break;
} else
total_cost += ret;
if (x == ex && y == ey)
break;
x += _railbit.xinc[trackdir];
y += _railbit.yinc[trackdir];
// toggle railbit for the non-diagonal tracks
if (!IsDiagonalTrackdir(trackdir)) trackdir ^= 1;
}
return (total_cost == 0) ? CMD_ERROR : total_cost;
}
/** Build rail on a stretch of track.
* Stub for the unified rail builder/remover
* @see CmdRailTrackHelper
*/
int32 CmdBuildRailroadTrack(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
return CmdRailTrackHelper(x, y, flags, p1, CLRBIT(p2, 7));
}
/** Build rail on a stretch of track.
* Stub for the unified rail builder/remover
* @see CmdRailTrackHelper
*/
int32 CmdRemoveRailroadTrack(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
return CmdRailTrackHelper(x, y, flags, p1, SETBIT(p2, 7));
}
/** Build a train depot
* @param x,y position of the train depot
* @param p1 rail type
* @param p2 depot direction (0 through 3), where 0 is NE, 1 is SE, 2 is SW, 3 is NW
*
* @todo When checking for the tile slope,
* distingush between "Flat land required" and "land sloped in wrong direction"
*/
int32 CmdBuildTrainDepot(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
Depot *d;
TileIndex tile = TileVirtXY(x, y);
int32 cost, ret;
uint tileh;
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
if (!EnsureNoVehicle(tile)) return CMD_ERROR;
/* check railtype and valid direction for depot (0 through 3), 4 in total */
if (!ValParamRailtype(p1) || p2 > 3) return CMD_ERROR;
tileh = GetTileSlope(tile, NULL);
/* Prohibit construction if
The tile is non-flat AND
1) The AI is "old-school"
2) build-on-slopes is disabled
3) the tile is steep i.e. spans two height levels
4) the exit points in the wrong direction
*/
if (tileh != 0 && (
(!_patches.ainew_active && _is_ai_player) ||
!_patches.build_on_slopes ||
IsSteepTileh(tileh) ||
!CanBuildDepotByTileh(p2, tileh)
)
) {
return_cmd_error(STR_0007_FLAT_LAND_REQUIRED);
}
ret = DoCommandByTile(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
if (CmdFailed(ret)) return CMD_ERROR;
cost = ret;
d = AllocateDepot();
if (d == NULL)
return CMD_ERROR;
if (flags & DC_EXEC) {
if (_current_player == _local_player) _last_built_train_depot_tile = tile;
ModifyTile(tile,
MP_SETTYPE(MP_RAILWAY) |
MP_MAP3LO | MP_MAPOWNER_CURRENT | MP_MAP5,
p1, /* map3_lo */
p2 | RAIL_TYPE_DEPOT_WAYPOINT /* map5 */
);
d->xy = tile;
d->town_index = ClosestTownFromTile(tile, (uint)-1)->index;
SetSignalsOnBothDir(tile, (p2&1) ? 2 : 1);
}
return cost + _price.build_train_depot;
}
/** Build signals, alternate between double/single, signal/semaphore,
* pre/exit/combo-signals, and what-else not
* @param x,y coordinates where signals is being built
* @param p1 various bitstuffed elements
* - p1 = (bit 0-2) - track-orientation, valid values: 0-5 (Track enum)
* - p1 = (bit 3) - choose semaphores/signals or cycle normal/pre/exit/combo depending on context
* @param p2 used for CmdBuildManySignals() to copy direction of first signal
* TODO: p2 should be replaced by two bits for "along" and "against" the track.
*/
int32 CmdBuildSingleSignal(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
TileIndex tile = TileVirtXY(x, y);
bool semaphore;
bool pre_signal;
Track track = (Track)(p1 & 0x7);
byte m5;
int32 cost;
// Same bit, used in different contexts
semaphore = pre_signal = HASBIT(p1, 3);
if (!ValParamTrackOrientation(track) || !IsTileType(tile, MP_RAILWAY) || !EnsureNoVehicle(tile))
return CMD_ERROR;
/* Protect against invalid signal copying */
if (p2 != 0 && (p2 & SignalOnTrack(track)) == 0) return CMD_ERROR;
m5 = _m[tile].m5;
/* You can only build signals on plain rail tiles, and the selected track must exist */
if (!IsPlainRailTile(tile) || !HasTrack(tile, track)) return CMD_ERROR;
if (!CheckTileOwnership(tile)) return CMD_ERROR;
_error_message = STR_1005_NO_SUITABLE_RAILROAD_TRACK;
{
/* See if this is a valid track combination for signals, (ie, no overlap) */
TrackBits trackbits = GetTrackBits(tile);
if (KILL_FIRST_BIT(trackbits) != 0 && /* More than one track present */
trackbits != (TRACK_BIT_UPPER | TRACK_BIT_LOWER) && /* Horizontal parallel, non-intersecting tracks */
trackbits != (TRACK_BIT_LEFT | TRACK_BIT_RIGHT) /* Vertical parallel, non-intersecting tracks */
)
return CMD_ERROR;
}
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
if (!HasSignalOnTrack(tile, track)) {
// build new signals
cost = _price.build_signals;
} else {
if (p2 != 0 && semaphore != HasSemaphores(tile, track)) {
// convert signals <-> semaphores
cost = _price.build_signals + _price.remove_signals;
} else {
// it is free to change orientation/pre-exit-combo signals
cost = 0;
}
}
if (flags & DC_EXEC) {
if (GetRailTileType(tile) != RAIL_TYPE_SIGNALS) {
// there are no signals at all on this tile yet
_m[tile].m5 |= RAIL_TYPE_SIGNALS; // change into signals
_m[tile].m2 |= 0xF0; // all signals are on
_m[tile].m3 &= ~0xF0; // no signals built by default
_m[tile].m4 = semaphore ? 0x08 : 0;
}
if (p2 == 0) {
if (!HasSignalOnTrack(tile, track)) {
// build new signals
_m[tile].m3 |= SignalOnTrack(track);
} else {
if (pre_signal) {
// cycle between normal -> pre -> exit -> combo -> pbs ->...
byte type = ((GetSignalType(tile, track) + 1) % 5);
_m[tile].m4 &= ~0x07;
_m[tile].m4 |= type ;
} else {
// cycle between two-way -> one-way -> one-way -> ...
/* TODO: Rewrite switch into something more general */
switch (track) {
case TRACK_LOWER:
case TRACK_RIGHT: {
byte signal = (_m[tile].m3 - 0x10) & 0x30;
if (signal == 0) signal = 0x30;
_m[tile].m3 &= ~0x30;
_m[tile].m3 |= signal;
break;
}
default: {
byte signal = (_m[tile].m3 - 0x40) & 0xC0;
if (signal == 0) signal = 0xC0;
_m[tile].m3 &= ~0xC0;
_m[tile].m3 |= signal;
break;
}
}
}
}
} else {
/* If CmdBuildManySignals is called with copying signals, just copy the
* direction of the first signal given as parameter by CmdBuildManySignals */
_m[tile].m3 &= ~SignalOnTrack(track);
_m[tile].m3 |= p2 & SignalOnTrack(track);
// convert between signal<->semaphores when dragging
if (semaphore)
SETBIT(_m[tile].m4, 3);
else
CLRBIT(_m[tile].m4, 3);
}
MarkTileDirtyByTile(tile);
SetSignalsOnBothDir(tile, track);
}
return cost;
}
/** Build many signals by dragging; AutoSignals
* @param x,y start tile of drag
* @param p1 end tile of drag
* @param p2 various bitstuffed elements
* - p2 = (bit 0) - 0 = build, 1 = remove signals
* - p2 = (bit 3) - 0 = signals, 1 = semaphores
* - p2 = (bit 4- 6) - track-orientation, valid values: 0-5 (Track enum)
* - p2 = (bit 24-31) - user defined signals_density
*/
static int32 CmdSignalTrackHelper(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
int ex, ey;
int32 ret, total_cost, signal_ctr;
byte signals;
TileIndex tile = TileVirtXY(x, y);
bool error = true;
int mode = p2 & 0x1;
Track track = GB(p2, 4, 3);
Trackdir trackdir = TrackToTrackdir(track);
byte semaphores = (HASBIT(p2, 3)) ? 8 : 0;
byte signal_density = (p2 >> 24);
if (p1 > MapSize()) return CMD_ERROR;
if (signal_density == 0 || signal_density > 20) return CMD_ERROR;
if (!IsTileType(tile, MP_RAILWAY))
return CMD_ERROR;
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
/* for vertical/horizontal tracks, double the given signals density
* since the original amount will be too dense (shorter tracks) */
if (!IsDiagonalTrack(track))
signal_density *= 2;
// unpack end tile
ex = TileX(p1) * TILE_SIZE;
ey = TileY(p1) * TILE_SIZE;
if (CmdFailed(ValidateAutoDrag(&trackdir, x, y, ex, ey))) return CMD_ERROR;
track = TrackdirToTrack(trackdir); /* trackdir might have changed, keep track in sync */
// copy the signal-style of the first rail-piece if existing
if (GetRailTileType(tile) == RAIL_TYPE_SIGNALS && GetTrackBits(tile) != 0) { /* XXX: GetTrackBits check useless? */
signals = _m[tile].m3 & SignalOnTrack(track);
if (signals == 0) signals = SignalOnTrack(track); /* Can this actually occur? */
semaphores = (HasSemaphores(tile, track) ? 8 : 0); // copy signal/semaphores style (independent of CTRL)
} else // no signals exist, drag a two-way signal stretch
signals = SignalOnTrack(track);
/* signal_ctr - amount of tiles already processed
* signals_density - patch setting to put signal on every Nth tile (double space on |, -- tracks)
**********
* trackdir - trackdir to build with autorail
* semaphores - semaphores or signals
* signals - is there a signal/semaphore on the first tile, copy its style (two-way/single-way)
and convert all others to semaphore/signal
* mode - 1 remove signals, 0 build signals */
signal_ctr = total_cost = 0;
for (;;) {
// only build/remove signals with the specified density
if ((signal_ctr % signal_density) == 0 ) {
ret = DoCommand(x, y, TrackdirToTrack(trackdir) | semaphores, signals, flags, (mode == 1) ? CMD_REMOVE_SIGNALS : CMD_BUILD_SIGNALS);
/* Abort placement for any other error than NOT_SUITABLE_TRACK
* This includes vehicles on track, competitor's tracks, etc. */
if (CmdFailed(ret)) {
if (_error_message != STR_1005_NO_SUITABLE_RAILROAD_TRACK && mode != 1) return CMD_ERROR;
} else {
error = false;
total_cost += ret;
}
}
if (ex == x && ey == y) break; // reached end of drag
x += _railbit.xinc[trackdir];
y += _railbit.yinc[trackdir];
signal_ctr++;
// toggle railbit for the non-diagonal tracks (|, -- tracks)
if (!IsDiagonalTrackdir(trackdir)) trackdir ^= 1;
}
return (error) ? CMD_ERROR : total_cost;
}
/** Build signals on a stretch of track.
* Stub for the unified signal builder/remover
* @see CmdSignalTrackHelper
*/
int32 CmdBuildSignalTrack(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
return CmdSignalTrackHelper(x, y, flags, p1, p2);
}
/** Remove signals
* @param x,y coordinates where signal is being deleted from
* @param p1 track to remove signal from (Track enum)
*/
int32 CmdRemoveSingleSignal(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
TileIndex tile = TileVirtXY(x, y);
Track track = (Track)(p1 & 0x7);
if (!ValParamTrackOrientation(track) || !IsTileType(tile, MP_RAILWAY) || !EnsureNoVehicle(tile))
return CMD_ERROR;
if (!HasSignalOnTrack(tile, track)) // no signals on track?
return CMD_ERROR;
/* Only water can remove signals from anyone */
if (_current_player != OWNER_WATER && !CheckTileOwnership(tile)) return CMD_ERROR;
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
/* Do it? */
if (flags & DC_EXEC) {
_m[tile].m3 &= ~SignalOnTrack(track);
/* removed last signal from tile? */
if ((_m[tile].m3 & 0xF0) == 0) {
_m[tile].m5 &= ~RAIL_TYPE_SIGNALS;
_m[tile].m2 &= ~0xF0;
CLRBIT(_m[tile].m4, 3); // remove any possible semaphores
}
SetSignalsOnBothDir(tile, track);
MarkTileDirtyByTile(tile);
}
return _price.remove_signals;
}
/** Remove signals on a stretch of track.
* Stub for the unified signal builder/remover
* @see CmdSignalTrackHelper
*/
int32 CmdRemoveSignalTrack(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
return CmdSignalTrackHelper(x, y, flags, p1, SETBIT(p2, 0));
}
typedef int32 DoConvertRailProc(TileIndex tile, uint totype, bool exec);
static int32 DoConvertRail(TileIndex tile, uint totype, bool exec)
{
if (!CheckTileOwnership(tile) || !EnsureNoVehicle(tile))
return CMD_ERROR;
// tile is already of requested type?
if ( GetRailType(tile) == totype)
return CMD_ERROR;
// change type.
if (exec) {
_m[tile].m3 = (_m[tile].m3 & 0xF0) + totype;
MarkTileDirtyByTile(tile);
}
return _price.build_rail / 2;
}
extern int32 DoConvertStationRail(TileIndex tile, uint totype, bool exec);
extern int32 DoConvertStreetRail(TileIndex tile, uint totype, bool exec);
extern int32 DoConvertTunnelBridgeRail(TileIndex tile, uint totype, bool exec);
/** Convert one rail type to the other. You can convert normal rail to
* monorail/maglev easily or vice-versa.
* @param ex,ey end tile of rail conversion drag
* @param p1 start tile of drag
* @param p2 new railtype to convert to
*/
int32 CmdConvertRail(int ex, int ey, uint32 flags, uint32 p1, uint32 p2)
{
int32 ret, cost, money;
int sx, sy, x, y;
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
if (!ValParamRailtype(p2)) return CMD_ERROR;
if (p1 > MapSize()) return CMD_ERROR;
// make sure sx,sy are smaller than ex,ey
sx = TileX(p1) * TILE_SIZE;
sy = TileY(p1) * TILE_SIZE;
if (ex < sx) intswap(ex, sx);
if (ey < sy) intswap(ey, sy);
money = GetAvailableMoneyForCommand();
cost = 0;
for (x = sx; x <= ex; x += TILE_SIZE) {
for (y = sy; y <= ey; y += TILE_SIZE) {
TileIndex tile = TileVirtXY(x, y);
DoConvertRailProc *proc;
if (IsTileType(tile, MP_RAILWAY)) proc = DoConvertRail;
else if (IsTileType(tile, MP_STATION)) proc = DoConvertStationRail;
else if (IsTileType(tile, MP_STREET)) proc = DoConvertStreetRail;
else if (IsTileType(tile, MP_TUNNELBRIDGE)) proc = DoConvertTunnelBridgeRail;
else continue;
ret = proc(tile, p2, false);
if (CmdFailed(ret)) continue;
cost += ret;
if (flags & DC_EXEC) {
if ( (money -= ret) < 0) { _additional_cash_required = ret; return cost - ret; }
proc(tile, p2, true);
}
}
}
return (cost == 0) ? CMD_ERROR : cost;
}
static int32 RemoveTrainDepot(TileIndex tile, uint32 flags)
{
if (!CheckTileOwnership(tile) && _current_player != OWNER_WATER)
return CMD_ERROR;
if (!EnsureNoVehicle(tile))
return CMD_ERROR;
if (flags & DC_EXEC) {
int track = TrackdirToTrack(DiagdirToDiagTrackdir(GetDepotDirection(tile, TRANSPORT_RAIL)));
DoDeleteDepot(tile);
SetSignalsOnBothDir(tile, track);
}
return _price.remove_train_depot;
}
static int32 ClearTile_Track(TileIndex tile, byte flags)
{
int32 cost;
int32 ret;
byte m5;
m5 = _m[tile].m5;
if (flags & DC_AUTO) {
if (m5 & RAIL_TYPE_SPECIAL)
return_cmd_error(STR_2004_BUILDING_MUST_BE_DEMOLISHED);
if (!IsTileOwner(tile, _current_player))
return_cmd_error(STR_1024_AREA_IS_OWNED_BY_ANOTHER);
return_cmd_error(STR_1008_MUST_REMOVE_RAILROAD_TRACK);
}
cost = 0;
switch (GetRailTileType(tile)) {
case RAIL_TYPE_SIGNALS:
if (_m[tile].m3 & _signals_table_both[0]) {
ret = DoCommandByTile(tile, 0, 0, flags, CMD_REMOVE_SIGNALS);
if (ret == CMD_ERROR) return CMD_ERROR;
cost += ret;
}
if (_m[tile].m3 & _signals_table_both[3]) {
ret = DoCommandByTile(tile, 3, 0, flags, CMD_REMOVE_SIGNALS);
if (ret == CMD_ERROR) return CMD_ERROR;
cost += ret;
}
m5 &= TRACK_BIT_MASK;
if (!(flags & DC_EXEC)) {
for (; m5 != 0; m5 >>= 1) if (m5 & 1) cost += _price.remove_rail;
return cost;
}
/* FALLTHROUGH */
case RAIL_TYPE_NORMAL: {
uint i;
for (i = 0; m5 != 0; i++, m5 >>= 1) {
if (m5 & 1) {
ret = DoCommandByTile(tile, 0, i, flags, CMD_REMOVE_SINGLE_RAIL);
if (ret == CMD_ERROR) return CMD_ERROR;
cost += ret;
}
}
return cost;
}
case RAIL_TYPE_DEPOT_WAYPOINT:
switch (m5 & RAIL_SUBTYPE_MASK) {
case RAIL_SUBTYPE_DEPOT:
return RemoveTrainDepot(tile, flags);
case RAIL_SUBTYPE_WAYPOINT:
return RemoveTrainWaypoint(tile, flags, false);
default:
return CMD_ERROR;
}
default:
return CMD_ERROR;
}
}
#include "table/track_land.h"
// used for presignals
static const SpriteID _signal_base_sprites[32] = {
0x4FB,
0x1323,
0x1333,
0x1343,
// pbs signals
0x1393,
0x13A3, // not used (yet?)
0x13B3, // not used (yet?)
0x13C3, // not used (yet?)
// semaphores
0x1353,
0x1363,
0x1373,
0x1383,
// pbs semaphores
0x13D3,
0x13E3, // not used (yet?)
0x13F3, // not used (yet?)
0x1403, // not used (yet?)
// mirrored versions
0x4FB,
0x1323,
0x1333,
0x1343,
// pbs signals
0x1393,
0x13A3, // not used (yet?)
0x13B3, // not used (yet?)
0x13C3, // not used (yet?)
// semaphores
0x1446,
0x1456,
0x1466,
0x1476,
// pbs semaphores
0x14C6,
0x14D6, // not used (yet?)
0x14E6, // not used (yet?)
0x14F6, // not used (yet?)
};
// used to determine the side of the road for the signal
static const byte _signal_position[24] = {
/* original: left side position */
0x58,0x1E,0xE1,0xB9,0x01,0xA3,0x4B,0xEE,0x3B,0xD4,0x43,0xBD,
/* patch: ride side position */
0x1E,0xAC,0x64,0xE1,0x4A,0x10,0xEE,0xC5,0xDB,0x34,0x4D,0xB3
};
static void DrawSignalHelper(TileInfo *ti, byte condition, uint32 image_and_pos)
{
bool otherside = _opt.road_side & _patches.signal_side;
uint v = _signal_position[(image_and_pos & 0xF) + (otherside ? 12 : 0)];
uint x = ti->x | (v&0xF);
uint y = ti->y | (v>>4);
uint sprite = _signal_base_sprites[(_m[ti->tile].m4 & 0xF) + (otherside ? 0x10 : 0)] + (image_and_pos>>4) + ((condition != 0) ? 1 : 0);
AddSortableSpriteToDraw(sprite, x, y, 1, 1, 10, GetSlopeZ(x,y));
}
static uint32 _drawtile_track_palette;
static void DrawTrackFence_NW(TileInfo *ti)
{
uint32 image = 0x515;
if (ti->tileh != 0) {
image = 0x519;
if (!(ti->tileh & 2)) {
image = 0x51B;
}
}
AddSortableSpriteToDraw(image | _drawtile_track_palette,
ti->x, ti->y+1, 16, 1, 4, ti->z);
}
static void DrawTrackFence_SE(TileInfo *ti)
{
uint32 image = 0x515;
if (ti->tileh != 0) {
image = 0x519;
if (!(ti->tileh & 2)) {
image = 0x51B;
}
}
AddSortableSpriteToDraw(image | _drawtile_track_palette,
ti->x, ti->y+15, 16, 1, 4, ti->z);
}
static void DrawTrackFence_NW_SE(TileInfo *ti)
{
DrawTrackFence_NW(ti);
DrawTrackFence_SE(ti);
}
static void DrawTrackFence_NE(TileInfo *ti)
{
uint32 image = 0x516;
if (ti->tileh != 0) {
image = 0x51A;
if (!(ti->tileh & 2)) {
image = 0x51C;
}
}
AddSortableSpriteToDraw(image | _drawtile_track_palette,
ti->x+1, ti->y, 1, 16, 4, ti->z);
}
static void DrawTrackFence_SW(TileInfo *ti)
{
uint32 image = 0x516;
if (ti->tileh != 0) {
image = 0x51A;
if (!(ti->tileh & 2)) {
image = 0x51C;
}
}
AddSortableSpriteToDraw(image | _drawtile_track_palette,
ti->x+15, ti->y, 1, 16, 4, ti->z);
}
static void DrawTrackFence_NE_SW(TileInfo *ti)
{
DrawTrackFence_NE(ti);
DrawTrackFence_SW(ti);
}
static void DrawTrackFence_NS_1(TileInfo *ti)
{
int z = ti->z;
if (ti->tileh & 1)
z += 8;
AddSortableSpriteToDraw(0x517 | _drawtile_track_palette,
ti->x + 8, ti->y + 8, 1, 1, 4, z);
}
static void DrawTrackFence_NS_2(TileInfo *ti)
{
int z = ti->z;
if (ti->tileh & 4)
z += 8;
AddSortableSpriteToDraw(0x517 | _drawtile_track_palette,
ti->x + 8, ti->y + 8, 1, 1, 4, z);
}
static void DrawTrackFence_WE_1(TileInfo *ti)
{
int z = ti->z;
if (ti->tileh & 8)
z += 8;
AddSortableSpriteToDraw(0x518 | _drawtile_track_palette,
ti->x + 8, ti->y + 8, 1, 1, 4, z);
}
static void DrawTrackFence_WE_2(TileInfo *ti)
{
int z = ti->z;
if (ti->tileh & 2)
z += 8;
AddSortableSpriteToDraw(0x518 | _drawtile_track_palette,
ti->x + 8, ti->y + 8, 1, 1, 4, z);
}
static void DetTrackDrawProc_Null(TileInfo *ti)
{
/* nothing should be here */
}
typedef void DetailedTrackProc(TileInfo *ti);
DetailedTrackProc * const _detailed_track_proc[16] = {
DetTrackDrawProc_Null,
DetTrackDrawProc_Null,
DrawTrackFence_NW,
DrawTrackFence_SE,
DrawTrackFence_NW_SE,
DrawTrackFence_NE,
DrawTrackFence_SW,
DrawTrackFence_NE_SW,
DrawTrackFence_NS_1,
DrawTrackFence_NS_2,
DrawTrackFence_WE_1,
DrawTrackFence_WE_2,
DetTrackDrawProc_Null,
DetTrackDrawProc_Null,
DetTrackDrawProc_Null,
DetTrackDrawProc_Null,
};
static void DrawSpecialBuilding(uint32 image, uint32 tracktype_offs,
TileInfo *ti,
byte x, byte y, byte z,
byte xsize, byte ysize, byte zsize)
{
if (image & PALETTE_MODIFIER_COLOR)
image |= _drawtile_track_palette;
image += tracktype_offs;
if (_display_opt & DO_TRANS_BUILDINGS) // show transparent depots
MAKE_TRANSPARENT(image);
AddSortableSpriteToDraw(image, ti->x + x, ti->y + y, xsize, ysize, zsize, ti->z + z);
}
/* Arrangement of the sprites
* 1) Single track in Y direction
* 2) Northern and southern trackbits set
* 3) "Basis" for 3-way switch
* 4) Single rail in Y direction without ground sprite
* 5) as above, X direction
* 6) as above, nortern track
* 7) as above, southern track
* 8) as above, eastern track
* 9) as above, western track
* 10) the offset of the snow sprites
*/
static SpriteID RailSpriteIDs[] = {
SPR_RAIL_TRACK_Y,
SPR_RAIL_TRACK_N_S,
SPR_RAIL_TRACK_BASE,
SPR_RAIL_SINGLE_Y,
SPR_RAIL_SINGLE_X,
SPR_RAIL_SINGLE_NORTH,
SPR_RAIL_SINGLE_SOUTH,
SPR_RAIL_SINGLE_EAST,
SPR_RAIL_SINGLE_WEST,
SPR_RAIL_SNOW_OFFSET,
};
static SpriteID MonoSpriteIDs[] = {
SPR_MONO_TRACK_Y,
SPR_MONO_TRACK_N_S,
SPR_MONO_TRACK_BASE,
SPR_MONO_SINGLE_Y,
SPR_MONO_SINGLE_X,
SPR_MONO_SINGLE_NORTH,
SPR_MONO_SINGLE_SOUTH,
SPR_MONO_SINGLE_EAST,
SPR_MONO_SINGLE_WEST,
SPR_MONO_SNOW_OFFSET,
};
static SpriteID MaglevSpriteIDs[] = {
SPR_MGLV_TRACK_Y,
SPR_MGLV_TRACK_N_S,
SPR_MGLV_TRACK_BASE,
SPR_MGLV_SINGLE_Y,
SPR_MGLV_SINGLE_X,
SPR_MGLV_SINGLE_NORTH,
SPR_MGLV_SINGLE_SOUTH,
SPR_MGLV_SINGLE_EAST,
SPR_MGLV_SINGLE_WEST,
SPR_MGLV_SNOW_OFFSET,
};
/** Sprite reference enum */
enum {
TRACK_Y,
TRACK_N_S,
TRACK_BASE,
SINGLE_Y,
SINGLE_X,
SINGLE_NORTH,
SINGLE_SOUTH,
SINGLE_EAST,
SINGLE_WEST,
SNOW_OFFSET,
};
/** Contains the pointers to the arrays *SpriteIDs.
* There, all the Sprites are recorded that the
* Track Draw system requireds. Note: Pointer arrangement
* must match the tracktype number
*/
static SpriteID *TrackSpriteIDs[RAILTYPE_END] = {
RailSpriteIDs,
MonoSpriteIDs,
MaglevSpriteIDs
};
static void DrawTile_Track(TileInfo *ti)
{
uint32 tracktype_offs;
byte m5;
SpriteID *TrackSet = TrackSpriteIDs[GetRailType(ti->tile)];
uint32 image; //XXX ok why the hell is SpriteID 16 bit when all the drawing routines need 32?
_drawtile_track_palette = SPRITE_PALETTE(PLAYER_SPRITE_COLOR(GetTileOwner(ti->tile)));
tracktype_offs = (_m[ti->tile].m3 & 0xF) * TRACKTYPE_SPRITE_PITCH;
m5 = (byte)ti->map5;
if (!(m5 & RAIL_TYPE_SPECIAL)) {
bool special;
m5 &= TRACK_BIT_MASK;
special = false;
// select the sprite to use based on the map5 byte.
(image = TrackSet[TRACK_Y], m5 == TRACK_BIT_DIAG2) ||
(image++, m5 == TRACK_BIT_DIAG1) ||
(image++, m5 == TRACK_BIT_UPPER) ||
(image++, m5 == TRACK_BIT_LOWER) ||
(image++, m5 == TRACK_BIT_RIGHT) ||
(image++, m5 == TRACK_BIT_LEFT) ||
(image++, m5 == (TRACK_BIT_DIAG1|TRACK_BIT_DIAG2)) ||
(image = TrackSet[TRACK_N_S], m5 == (TRACK_BIT_UPPER|TRACK_BIT_LOWER)) ||
(image++, m5 == (TRACK_BIT_LEFT|TRACK_BIT_RIGHT)) ||
(special=true, false) ||
(image = TrackSet[TRACK_BASE], !(m5 & (TRACK_BIT_RIGHT|TRACK_BIT_UPPER|TRACK_BIT_DIAG1))) ||
(image++, !(m5 & (TRACK_BIT_LEFT|TRACK_BIT_LOWER|TRACK_BIT_DIAG1))) ||
(image++, !(m5 & (TRACK_BIT_LEFT|TRACK_BIT_UPPER|TRACK_BIT_DIAG2))) ||
(image++, !(m5 & (TRACK_BIT_RIGHT|TRACK_BIT_LOWER|TRACK_BIT_DIAG2))) ||
(image++, true);
if (ti->tileh != 0) {
int f = GetRailFoundation(ti->tileh, ti->map5 & 0x3F);
if (f) DrawFoundation(ti, f);
// default sloped sprites..
if (ti->tileh != 0) image = _track_sloped_sprites[ti->tileh - 1] + TrackSet[TRACK_Y];
}
if ((_m[ti->tile].m2 & RAIL_MAP2LO_GROUND_MASK)==RAIL_GROUND_BROWN) {
image = (image & SPRITE_MASK) | PALETTE_TO_BARE_LAND; // use a brown palette
} else if ((_m[ti->tile].m2 & RAIL_MAP2LO_GROUND_MASK)==RAIL_GROUND_ICE_DESERT) {
image += TrackSet[SNOW_OFFSET];
}
DrawGroundSprite(image);
if (special) {
if (m5 & TRACK_BIT_DIAG1) DrawGroundSprite(TrackSet[SINGLE_Y]);
if (m5 & TRACK_BIT_DIAG2) DrawGroundSprite(TrackSet[SINGLE_X]);
if (m5 & TRACK_BIT_UPPER) DrawGroundSprite(TrackSet[SINGLE_NORTH]);
if (m5 & TRACK_BIT_LOWER) DrawGroundSprite(TrackSet[SINGLE_SOUTH]);
if (m5 & TRACK_BIT_LEFT) DrawGroundSprite(TrackSet[SINGLE_WEST]);
if (m5 & TRACK_BIT_RIGHT) DrawGroundSprite(TrackSet[SINGLE_EAST]);
}
if (_debug_pbs_level >= 1) {
byte pbs = PBSTileReserved(ti->tile);
if (pbs & TRACK_BIT_DIAG1) DrawGroundSprite(TrackSet[SINGLE_Y] | PALETTE_CRASH);
if (pbs & TRACK_BIT_DIAG2) DrawGroundSprite(TrackSet[SINGLE_X] | PALETTE_CRASH);
if (pbs & TRACK_BIT_UPPER) DrawGroundSprite(TrackSet[SINGLE_NORTH] | PALETTE_CRASH);
if (pbs & TRACK_BIT_LOWER) DrawGroundSprite(TrackSet[SINGLE_SOUTH] | PALETTE_CRASH);
if (pbs & TRACK_BIT_LEFT) DrawGroundSprite(TrackSet[SINGLE_WEST] | PALETTE_CRASH);
if (pbs & TRACK_BIT_RIGHT) DrawGroundSprite(TrackSet[SINGLE_EAST] | PALETTE_CRASH);
}
if (_display_opt & DO_FULL_DETAIL) {
_detailed_track_proc[_m[ti->tile].m2 & RAIL_MAP2LO_GROUND_MASK](ti);
}
/* draw signals also? */
if (!(ti->map5 & RAIL_TYPE_SIGNALS))
return;
{
byte m23;
m23 = (_m[ti->tile].m3 >> 4) | (_m[ti->tile].m2 & 0xF0);
#define HAS_SIGNAL(x) (m23 & (byte)(0x1 << (x)))
#define ISON_SIGNAL(x) (m23 & (byte)(0x10 << (x)))
#define MAYBE_DRAW_SIGNAL(x,y,z) if (HAS_SIGNAL(x)) DrawSignalHelper(ti, ISON_SIGNAL(x), ((y-0x4FB) << 4)|(z))
if (!(m5 & TRACK_BIT_DIAG2)) {
if (!(m5 & TRACK_BIT_DIAG1)) {
if (m5 & TRACK_BIT_LEFT) {
MAYBE_DRAW_SIGNAL(2, 0x509, 0);
MAYBE_DRAW_SIGNAL(3, 0x507, 1);
}
if (m5 & TRACK_BIT_RIGHT) {
MAYBE_DRAW_SIGNAL(0, 0x509, 2);
MAYBE_DRAW_SIGNAL(1, 0x507, 3);
}
if (m5 & TRACK_BIT_UPPER) {
MAYBE_DRAW_SIGNAL(3, 0x505, 4);
MAYBE_DRAW_SIGNAL(2, 0x503, 5);
}
if (m5 & TRACK_BIT_LOWER) {
MAYBE_DRAW_SIGNAL(1, 0x505, 6);
MAYBE_DRAW_SIGNAL(0, 0x503, 7);
}
} else {
MAYBE_DRAW_SIGNAL(3, 0x4FB, 8);
MAYBE_DRAW_SIGNAL(2, 0x4FD, 9);
}
} else {
MAYBE_DRAW_SIGNAL(3, 0x4FF, 10);
MAYBE_DRAW_SIGNAL(2, 0x501, 11);
}
}
} else {
/* draw depots / waypoints */
const DrawTrackSeqStruct *drss;
byte type = m5 & 0x3F; // 0-3: depots, 4-5: waypoints
if (!(m5 & (RAIL_TILE_TYPE_MASK&~RAIL_TYPE_SPECIAL)))
/* XXX: There used to be "return;" here, but since I could not find out
* why this would ever occur, I put assert(0) here. Let's see if someone
* complains about it. If not, we'll remove this check. (Matthijs). */
assert(0);
if (ti->tileh != 0) { DrawFoundation(ti, ti->tileh); }
if (IsRailWaypoint(m5) && _m[ti->tile].m3 & 16) {
// look for customization
StationSpec *stat = GetCustomStation(STAT_CLASS_WAYP, _m[ti->tile].m4);
if (stat) {
DrawTileSeqStruct const *seq;
// emulate station tile - open with building
DrawTileSprites *cust = &stat->renderdata[2 + (m5 & 0x1)];
uint32 relocation = GetCustomStationRelocation(stat, ComposeWaypointStation(ti->tile), 0);
int railtype=(_m[ti->tile].m3 & 0xF);
/* We don't touch the 0x8000 bit. In all this
* waypoint code, it is used to indicate that
* we should offset by railtype, but we always
* do that for custom ground sprites and never
* for station sprites. And in the drawing
* code, it is used to indicate that the sprite
* should be drawn in company colors, and it's
* up to the GRF file to decide that. */
image = cust->ground_sprite;
image += railtype*((image<_custom_sprites_base)?TRACKTYPE_SPRITE_PITCH:1);
DrawGroundSprite(image);
foreach_draw_tile_seq(seq, cust->seq) {
uint32 image = seq->image + relocation;
DrawSpecialBuilding(image, 0, ti,
seq->delta_x, seq->delta_y, seq->delta_z,
seq->width, seq->height, seq->unk);
}
return;
}
}
drss = _track_depot_layout_table[type];
image = drss++->image;
if (image & PALETTE_MODIFIER_COLOR) image = (image & SPRITE_MASK) + tracktype_offs;
// adjust ground tile for desert
// (don't adjust for arctic depots, because snow in depots looks weird)
if ((_m[ti->tile].m2 & RAIL_MAP2LO_GROUND_MASK)==RAIL_GROUND_ICE_DESERT && (_opt.landscape == LT_DESERT || type>=4))
{
if(image!=3981)
image += 26; // tile with tracks
else
image = 4550; // flat ground
}
DrawGroundSprite(image);
if (_debug_pbs_level >= 1) {
byte pbs = PBSTileReserved(ti->tile);
if (pbs & TRACK_BIT_DIAG1) DrawGroundSprite((0x3ED + tracktype_offs) | PALETTE_CRASH);
if (pbs & TRACK_BIT_DIAG2) DrawGroundSprite((0x3EE + tracktype_offs) | PALETTE_CRASH);
if (pbs & TRACK_BIT_UPPER) DrawGroundSprite((0x3EF + tracktype_offs) | PALETTE_CRASH);
if (pbs & TRACK_BIT_LOWER) DrawGroundSprite((0x3F0 + tracktype_offs) | PALETTE_CRASH);
if (pbs & TRACK_BIT_LEFT) DrawGroundSprite((0x3F2 + tracktype_offs) | PALETTE_CRASH);
if (pbs & TRACK_BIT_RIGHT) DrawGroundSprite((0x3F1 + tracktype_offs) | PALETTE_CRASH);
}
while ((image=drss->image) != 0) {
DrawSpecialBuilding(image, type < 4 ? tracktype_offs : 0, ti,
drss->subcoord_x, drss->subcoord_y, 0,
drss->width, drss->height, 0x17);
drss++;
}
}
}
void DrawTrainDepotSprite(int x, int y, int image, int railtype)
{
uint32 ormod, img;
const DrawTrackSeqStruct *dtss;
/* baseimage */
railtype *= TRACKTYPE_SPRITE_PITCH;
ormod = PLAYER_SPRITE_COLOR(_local_player);
dtss = _track_depot_layout_table[image];
x+=33;
y+=17;
img = dtss++->image;
if (img & PALETTE_MODIFIER_COLOR) img = (img & SPRITE_MASK) + railtype;
DrawSprite(img, x, y);
for (; dtss->image != 0; dtss++) {
Point pt = RemapCoords(dtss->subcoord_x, dtss->subcoord_y, 0);
image = dtss->image;
if (image & PALETTE_MODIFIER_COLOR) image |= ormod;
DrawSprite(image + railtype, x + pt.x, y + pt.y);
}
}
typedef struct SetSignalsData {
int cur;
int cur_stack;
bool stop;
bool has_presignal;
bool has_pbssignal;
// lowest 2 bits = amount of pbs signals in the block, clamped at 2
// bit 2 = there is a pbs entry signal in this block
// bit 3 = there is a pbs exit signal in this block
// presignal info
int presignal_exits;
int presignal_exits_free;
// these are used to keep track of the signals that change.
byte bit[NUM_SSD_ENTRY];
TileIndex tile[NUM_SSD_ENTRY];
int pbs_cur;
// these are used to keep track of all signals in the block
TileIndex pbs_tile[NUM_SSD_ENTRY];
// these are used to keep track of the stack that modifies presignals recursively
TileIndex next_tile[NUM_SSD_STACK];
byte next_dir[NUM_SSD_STACK];
} SetSignalsData;
static bool SetSignalsEnumProc(TileIndex tile, SetSignalsData *ssd, int track, uint length, byte *state)
{
// the tile has signals?
if (IsTileType(tile, MP_RAILWAY)) {
if (HasSignalOnTrack(tile, TrackdirToTrack(track))) {
if ((_m[tile].m3 & _signals_table[track]) != 0) {
// yes, add the signal to the list of signals
if (ssd->cur != NUM_SSD_ENTRY) {
ssd->tile[ssd->cur] = tile; // remember the tile index
ssd->bit[ssd->cur] = track; // and the controlling bit number
ssd->cur++;
}
if (PBSIsPbsSignal(tile, ReverseTrackdir(track)))
SETBIT(ssd->has_pbssignal, 2);
// remember if this block has a presignal.
ssd->has_presignal |= (_m[tile].m4&1);
}
if (PBSIsPbsSignal(tile, ReverseTrackdir(track)) || PBSIsPbsSignal(tile, track)) {
byte num = ssd->has_pbssignal & 3;
num = clamp(num + 1, 0, 2);
ssd->has_pbssignal &= ~3;
ssd->has_pbssignal |= num;
}
if ((_m[tile].m3 & _signals_table_both[track]) != 0) {
ssd->pbs_tile[ssd->pbs_cur] = tile; // remember the tile index
ssd->pbs_cur++;
}
if (_m[tile].m3&_signals_table_other[track]) {
if (_m[tile].m4&2) {
// this is an exit signal that points out from the segment
ssd->presignal_exits++;
if ((_m[tile].m2&_signals_table_other[track]) != 0)
ssd->presignal_exits_free++;
}
if (PBSIsPbsSignal(tile, track))
SETBIT(ssd->has_pbssignal, 3);
}
return true;
} else if (IsTileDepotType(tile, TRANSPORT_RAIL))
return true; // don't look further if the tile is a depot
}
return false;
}
/* Struct to parse data from VehicleFromPos to SignalVehicleCheckProc */
typedef struct SignalVehicleCheckStruct {
TileIndex tile;
uint track;
} SignalVehicleCheckStruct;
static void *SignalVehicleCheckProc(Vehicle *v, void *data)
{
SignalVehicleCheckStruct *dest = data;
TileIndex tile;
if (v->type != VEH_Train)
return NULL;
/* Find the tile outside the tunnel, for signalling */
if (v->u.rail.track == 0x40)
tile = GetVehicleOutOfTunnelTile(v);
else
tile = v->tile;
/* Wrong tile, or no train? Not a match */
if (tile != dest->tile)
return NULL;
/* Are we on the same piece of track? */
if (dest->track & (v->u.rail.track + (v->u.rail.track<<8)))
return v;
return NULL;
}
/* Special check for SetSignalsAfterProc, to see if there is a vehicle on this tile */
bool SignalVehicleCheck(TileIndex tile, uint track)
{
SignalVehicleCheckStruct dest;
dest.tile = tile;
dest.track = track;
/** @todo "Hackish" fix for the tunnel problems. This is needed because a tunnel
* is some kind of invisible black hole, and there is some special magic going
* on in there. This 'workaround' can be removed once the maprewrite is done.
*/
if (GetTileType(tile)==MP_TUNNELBRIDGE && ((_m[tile].m5 & 0xF0)==0)) {
// It is a tunnel we're checking, we need to do some special stuff
// because VehicleFromPos will not find the vihicle otherwise
byte direction = _m[tile].m5 & 3;
FindLengthOfTunnelResult flotr;
flotr = FindLengthOfTunnel(tile, direction);
dest.track = 1 << (direction & 1); // get the trackbit the vehicle would have if it has not entered the tunnel yet (ie is still visible)
// check for a vehicle with that trackdir on the start tile of the tunnel
if (VehicleFromPos(tile, &dest, SignalVehicleCheckProc) != NULL) return true;
// check for a vehicle with that trackdir on the end tile of the tunnel
if (VehicleFromPos(flotr.tile, &dest, SignalVehicleCheckProc) != NULL) return true;
// now check all tiles from start to end for a "hidden" vehicle
// NOTE: the hashes for tiles may overlap, so this could maybe be optimised a bit by not checking every tile?
dest.track = 0x40; // trackbit for vehicles "hidden" inside a tunnel
for (; tile != flotr.tile; tile += TileOffsByDir(direction)) {
if (VehicleFromPos(tile, &dest, SignalVehicleCheckProc) != NULL)
return true;
}
// no vehicle found
return false;
}
return VehicleFromPos(tile, &dest, SignalVehicleCheckProc) != NULL;
}
static void SetSignalsAfterProc(TrackPathFinder *tpf)
{
SetSignalsData *ssd = tpf->userdata;
TrackPathFinderLink *link;
uint offs;
uint i;
ssd->stop = false;
/* Go through all the PF tiles */
for (i = 0; i < lengthof(tpf->hash_head); i++) {
/* Empty hash item */
if (tpf->hash_head[i] == 0)
continue;
/* If 0x8000 is not set, there is only 1 item */
if (!(tpf->hash_head[i] & 0x8000)) {
/* Check if there is a vehicle on this tile */
if (SignalVehicleCheck(tpf->hash_tile[i], tpf->hash_head[i])) {
ssd->stop = true;
return;
}
} else {
/* There are multiple items, where hash_tile points to the first item in the list */
offs = tpf->hash_tile[i];
do {
/* Find the next item */
link = PATHFIND_GET_LINK_PTR(tpf, offs);
/* Check if there is a vehicle on this tile */
if (SignalVehicleCheck(link->tile, link->flags)) {
ssd->stop = true;
return;
}
/* Goto the next item */
} while ((offs=link->next) != 0xFFFF);
}
}
}
static const byte _dir_from_track[14] = {
0,1,0,1,2,1, 0,0,
2,3,3,2,3,0,
};
static void ChangeSignalStates(SetSignalsData *ssd)
{
int i;
// thinking about presignals...
// the presignal is green if,
// if no train is in the segment AND
// there is at least one green exit signal OR
// there are no exit signals in the segment
// convert the block to pbs, if needed
if (_patches.auto_pbs_placement && !(ssd->stop) && (ssd->has_pbssignal == 0xE) && !ssd->has_presignal && (ssd->presignal_exits == 0)) // 0xE means at least 2 pbs signals, and at least 1 entry and 1 exit, see comments ssd->has_pbssignal
for(i=0; i!=ssd->pbs_cur; i++) {
TileIndex tile = ssd->pbs_tile[i];
_m[tile].m4 &= ~0x07;
_m[tile].m4 |= 0x04;
MarkTileDirtyByTile(tile);
};
// then mark the signals in the segment accordingly
for(i=0; i!=ssd->cur; i++) {
TileIndex tile = ssd->tile[i];
byte bit = _signals_table[ssd->bit[i]];
uint16 m2 = _m[tile].m2;
// presignals don't turn green if there is at least one presignal exit and none are free
if (_m[tile].m4 & 1) {
int ex = ssd->presignal_exits, exfree = ssd->presignal_exits_free;
// subtract for dual combo signals so they don't count themselves
if (_m[tile].m4&2 && _m[tile].m3&_signals_table_other[ssd->bit[i]]) {
ex--;
if ((_m[tile].m2&_signals_table_other[ssd->bit[i]]) != 0) exfree--;
}
// if we have exits and none are free, make red.
if (ex && !exfree) goto make_red;
}
// check if the signal is unaffected.
if (ssd->stop) {
make_red:
// turn red
if ( (bit&m2) == 0 )
continue;
} else {
// turn green
if ( (bit&m2) != 0 )
continue;
}
/* Update signals on the other side of this exit-combo signal; it changed. */
if (_m[tile].m4 & 2 ) {
if (ssd->cur_stack != NUM_SSD_STACK) {
ssd->next_tile[ssd->cur_stack] = tile;
ssd->next_dir[ssd->cur_stack] = _dir_from_track[ssd->bit[i]];
ssd->cur_stack++;
} else {
printf("NUM_SSD_STACK too small\n"); /// @todo WTF is this???
}
}
// it changed, so toggle it
_m[tile].m2 = m2 ^ bit;
MarkTileDirtyByTile(tile);
}
}
bool UpdateSignalsOnSegment(TileIndex tile, byte direction)
{
SetSignalsData ssd;
int result = -1;
ssd.cur_stack = 0;
direction>>=1;
for(;;) {
// go through one segment and update all signals pointing into that segment.
ssd.cur = ssd.pbs_cur = ssd.presignal_exits = ssd.presignal_exits_free = 0;
ssd.has_presignal = false;
ssd.has_pbssignal = false;
FollowTrack(tile, 0xC000 | TRANSPORT_RAIL, direction, (TPFEnumProc*)SetSignalsEnumProc, SetSignalsAfterProc, &ssd);
ChangeSignalStates(&ssd);
// remember the result only for the first iteration.
if (result < 0) result = ssd.stop;
// if any exit signals were changed, we need to keep going to modify the stuff behind those.
if(!ssd.cur_stack)
break;
// one or more exit signals were changed, so we need to update another segment too.
tile = ssd.next_tile[--ssd.cur_stack];
direction = ssd.next_dir[ssd.cur_stack];
}
return (bool)result;
}
void SetSignalsOnBothDir(TileIndex tile, byte track)
{
static const byte _search_dir_1[6] = {1, 3, 1, 3, 5, 3};
static const byte _search_dir_2[6] = {5, 7, 7, 5, 7, 1};
UpdateSignalsOnSegment(tile, _search_dir_1[track]);
UpdateSignalsOnSegment(tile, _search_dir_2[track]);
}
static uint GetSlopeZ_Track(TileInfo *ti)
{
uint z = ti->z;
int th = ti->tileh;
// check if it's a foundation
if (ti->tileh != 0) {
if ((ti->map5 & 0x80) == 0) {
uint f = GetRailFoundation(ti->tileh, ti->map5 & 0x3F);
if (f != 0) {
if (f < 15) {
// leveled foundation
return z + 8;
}
// inclined foundation
th = _inclined_tileh[f - 15];
}
} else if ((ti->map5 & 0xC0) == 0xC0) {
// depot or waypoint
return z + 8;
}
return GetPartialZ(ti->x&0xF, ti->y&0xF, th) + z;
}
return z;
}
static uint GetSlopeTileh_Track(TileInfo *ti)
{
// check if it's a foundation
if (ti->tileh != 0) {
if ((ti->map5 & 0x80) == 0) {
uint f = GetRailFoundation(ti->tileh, ti->map5 & 0x3F);
if (f != 0) {
if (f < 15) {
// leveled foundation
return 0;
}
// inclined foundation
return _inclined_tileh[f - 15];
}
} else if ((ti->map5 & 0xC0) == 0xC0) {
// depot or waypoint
return 0;
}
}
return ti->tileh;
}
static void GetAcceptedCargo_Track(TileIndex tile, AcceptedCargo ac)
{
/* not used */
}
static void AnimateTile_Track(TileIndex tile)
{
/* not used */
}
static void TileLoop_Track(TileIndex tile)
{
byte a2;
byte rail;
uint16 m2;
byte owner;
m2 = _m[tile].m2 & 0xF;
/* special code for alps landscape */
if (_opt.landscape == LT_HILLY) {
/* convert into snow? */
if (GetTileZ(tile) > _opt.snow_line) {
a2 = RAIL_GROUND_ICE_DESERT;
goto modify_me;
}
/* special code for desert landscape */
} else if (_opt.landscape == LT_DESERT) {
/* convert into desert? */
if (GetMapExtraBits(tile) == 1) {
a2 = RAIL_GROUND_ICE_DESERT;
goto modify_me;
}
}
// Don't continue tile loop for depots
if (_m[tile].m5 & RAIL_TYPE_SPECIAL)
return;
a2 = RAIL_GROUND_GREEN;
if (m2 != RAIL_GROUND_BROWN) { /* wait until bottom is green */
/* determine direction of fence */
rail = _m[tile].m5 & TRACK_BIT_MASK;
if (rail == TRACK_BIT_UPPER) {
a2 = RAIL_GROUND_FENCE_HORIZ1;
} else if (rail == TRACK_BIT_LOWER) {
a2 = RAIL_GROUND_FENCE_HORIZ2;
} else if (rail == TRACK_BIT_LEFT) {
a2 = RAIL_GROUND_FENCE_VERT1;
} else if (rail == TRACK_BIT_RIGHT) {
a2 = RAIL_GROUND_FENCE_VERT2;
} else {
owner = GetTileOwner(tile);
if ( (!(rail&(TRACK_BIT_DIAG2|TRACK_BIT_UPPER|TRACK_BIT_LEFT)) && (rail&TRACK_BIT_DIAG1)) || rail==(TRACK_BIT_LOWER|TRACK_BIT_RIGHT)) {
if (!IsTileType(tile + TileDiffXY(0, -1), MP_RAILWAY) ||
!IsTileOwner(tile + TileDiffXY(0, -1), owner) ||
(_m[tile + TileDiffXY(0, -1)].m5 == TRACK_BIT_UPPER || _m[tile + TileDiffXY(0, -1)].m5 == TRACK_BIT_LEFT))
a2 = RAIL_GROUND_FENCE_NW;
}
if ( (!(rail&(TRACK_BIT_DIAG2|TRACK_BIT_LOWER|TRACK_BIT_RIGHT)) && (rail&TRACK_BIT_DIAG1)) || rail==(TRACK_BIT_UPPER|TRACK_BIT_LEFT)) {
if (!IsTileType(tile + TileDiffXY(0, 1), MP_RAILWAY) ||
!IsTileOwner(tile + TileDiffXY(0, 1), owner) ||
(_m[tile + TileDiffXY(0, 1)].m5 == TRACK_BIT_LOWER || _m[tile + TileDiffXY(0, 1)].m5 == TRACK_BIT_RIGHT))
a2 = (a2 == RAIL_GROUND_FENCE_NW) ? RAIL_GROUND_FENCE_SENW : RAIL_GROUND_FENCE_SE;
}
if ( (!(rail&(TRACK_BIT_DIAG1|TRACK_BIT_UPPER|TRACK_BIT_RIGHT)) && (rail&TRACK_BIT_DIAG2)) || rail==(TRACK_BIT_LOWER|TRACK_BIT_LEFT)) {
if (!IsTileType(tile + TileDiffXY(-1, 0), MP_RAILWAY) ||
!IsTileOwner(tile + TileDiffXY(-1, 0), owner) ||
(_m[tile + TileDiffXY(-1, 0)].m5 == TRACK_BIT_UPPER || _m[tile + TileDiffXY(-1, 0)].m5 == TRACK_BIT_RIGHT))
a2 = RAIL_GROUND_FENCE_NE;
}
if ( (!(rail&(TRACK_BIT_DIAG1|TRACK_BIT_LOWER|TRACK_BIT_LEFT)) && (rail&TRACK_BIT_DIAG2)) || rail==(TRACK_BIT_UPPER|TRACK_BIT_RIGHT)) {
if (!IsTileType(tile + TileDiffXY(1, 0), MP_RAILWAY) ||
!IsTileOwner(tile + TileDiffXY(1, 0), owner) ||
(_m[tile + TileDiffXY(1, 0)].m5 == TRACK_BIT_LOWER || _m[tile + TileDiffXY(1, 0)].m5 == TRACK_BIT_LEFT))
a2 = (a2 == RAIL_GROUND_FENCE_NE) ? RAIL_GROUND_FENCE_NESW : RAIL_GROUND_FENCE_SW;
}
}
}
modify_me:;
/* tile changed? */
if ( m2 != a2) {
_m[tile].m2 = (_m[tile].m2 & ~RAIL_MAP2LO_GROUND_MASK) | a2;
MarkTileDirtyByTile(tile);
}
}
static uint32 GetTileTrackStatus_Track(TileIndex tile, TransportType mode)
{
byte m5, a;
uint16 b;
uint32 ret;
if (mode != TRANSPORT_RAIL)
return 0;
m5 = _m[tile].m5;
if (!(m5 & RAIL_TYPE_SPECIAL)) {
ret = (m5 | (m5 << 8)) & 0x3F3F;
if (!(m5 & RAIL_TYPE_SIGNALS)) {
if ( (ret & 0xFF) == 3)
/* Diagonal crossing? */
ret |= 0x40;
} else {
/* has_signals */
a = _m[tile].m3;
b = _m[tile].m2;
b &= a;
/* When signals are not present (in neither
* direction), we pretend them to be green. (So if
* signals are only one way, the other way will
* implicitely become `red' */
if ((a & 0xC0) == 0) { b |= 0xC0; }
if ((a & 0x30) == 0) { b |= 0x30; }
if ( (b & 0x80) == 0) ret |= 0x10070000;
if ( (b & 0x40) == 0) ret |= 0x7100000;
if ( (b & 0x20) == 0) ret |= 0x20080000;
if ( (b & 0x10) == 0) ret |= 0x8200000;
}
} else if (m5 & 0x40) {
static const byte _train_spec_tracks[6] = {1,2,1,2,1,2};
m5 = _train_spec_tracks[m5 & 0x3F];
ret = (m5 << 8) + m5;
} else
return 0;
return ret;
}
static void ClickTile_Track(TileIndex tile)
{
if (IsTileDepotType(tile, TRANSPORT_RAIL))
ShowTrainDepotWindow(tile);
else if (IsRailWaypoint(_m[tile].m5))
ShowRenameWaypointWindow(GetWaypointByTile(tile));
}
static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
{
td->owner = GetTileOwner(tile);
switch (GetRailTileType(tile)) {
case RAIL_TYPE_NORMAL:
td->str = STR_1021_RAILROAD_TRACK;
break;
case RAIL_TYPE_SIGNALS: {
const StringID signal_type[7] = {
STR_RAILROAD_TRACK_WITH_NORMAL_SIGNALS,
STR_RAILROAD_TRACK_WITH_PRESIGNALS,
STR_RAILROAD_TRACK_WITH_EXITSIGNALS,
STR_RAILROAD_TRACK_WITH_COMBOSIGNALS,
STR_RAILROAD_TRACK_WITH_PBSSIGNALS,
STR_NULL, STR_NULL
};
td->str = signal_type[_m[tile].m4 & 0x7];
break;
}
case RAIL_TYPE_DEPOT_WAYPOINT:
default:
td->str = ((_m[tile].m5 & RAIL_SUBTYPE_MASK) == RAIL_SUBTYPE_DEPOT) ?
STR_1023_RAILROAD_TRAIN_DEPOT : STR_LANDINFO_WAYPOINT;
break;
}
}
static void ChangeTileOwner_Track(TileIndex tile, byte old_player, byte new_player)
{
if (!IsTileOwner(tile, old_player)) return;
if (new_player != 255) {
SetTileOwner(tile, new_player);
} else {
DoCommandByTile(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR);
}
}
static const byte _fractcoords_behind[4] = { 0x8F, 0x8, 0x80, 0xF8 };
static const byte _fractcoords_enter[4] = { 0x8A, 0x48, 0x84, 0xA8 };
static const byte _deltacoord_leaveoffset[8] = {
-1, 0, 1, 0, /* x */
0, 1, 0, -1 /* y */
};
static const byte _enter_directions[4] = {5, 7, 1, 3};
static const byte _leave_directions[4] = {1, 3, 5, 7};
static const byte _depot_track_mask[4] = {1, 2, 1, 2};
static uint32 VehicleEnter_Track(Vehicle *v, TileIndex tile, int x, int y)
{
byte fract_coord;
byte fract_coord_leave;
int dir;
int length;
// this routine applies only to trains in depot tiles
if (v->type != VEH_Train || !IsTileDepotType(tile, TRANSPORT_RAIL))
return 0;
/* depot direction */
dir = GetDepotDirection(tile, TRANSPORT_RAIL);
/* calculate the point where the following wagon should be activated */
/* this depends on the length of the current vehicle */
length = v->u.rail.cached_veh_length;
fract_coord_leave =
((_fractcoords_enter[dir] & 0x0F) + // x
(length + 1) * _deltacoord_leaveoffset[dir]) +
(((_fractcoords_enter[dir] >> 4) + // y
((length + 1) * _deltacoord_leaveoffset[dir+4])) << 4);
fract_coord = (x & 0xF) + ((y & 0xF) << 4);
if (_fractcoords_behind[dir] == fract_coord) {
/* make sure a train is not entering the tile from behind */
return 8;
} else if (_fractcoords_enter[dir] == fract_coord) {
if (_enter_directions[dir] == v->direction) {
/* enter the depot */
if (v->next == NULL)
PBSClearTrack(v->tile, FIND_FIRST_BIT(v->u.rail.track));
v->u.rail.track = 0x80,
v->vehstatus |= VS_HIDDEN; /* hide it */
v->direction ^= 4;
if (v->next == NULL)
TrainEnterDepot(v, tile);
v->tile = tile;
InvalidateWindow(WC_VEHICLE_DEPOT, tile);
return 4;
}
} else if (fract_coord_leave == fract_coord) {
if (_leave_directions[dir] == v->direction) {
/* leave the depot? */
if ((v=v->next) != NULL) {
v->vehstatus &= ~VS_HIDDEN;
v->u.rail.track = _depot_track_mask[dir];
assert(v->u.rail.track);
}
}
}
return 0;
}
void InitializeRail(void)
{
_last_built_train_depot_tile = 0;
}
const TileTypeProcs _tile_type_rail_procs = {
DrawTile_Track, /* draw_tile_proc */
GetSlopeZ_Track, /* get_slope_z_proc */
ClearTile_Track, /* clear_tile_proc */
GetAcceptedCargo_Track, /* get_accepted_cargo_proc */
GetTileDesc_Track, /* get_tile_desc_proc */
GetTileTrackStatus_Track, /* get_tile_track_status_proc */
ClickTile_Track, /* click_tile_proc */
AnimateTile_Track, /* animate_tile_proc */
TileLoop_Track, /* tile_loop_clear */
ChangeTileOwner_Track, /* change_tile_owner_clear */
NULL, /* get_produced_cargo_proc */
VehicleEnter_Track, /* vehicle_enter_tile_proc */
NULL, /* vehicle_leave_tile_proc */
GetSlopeTileh_Track, /* get_slope_tileh_proc */
};