mirror of
https://github.com/JGRennison/OpenTTD-patches.git
synced 2024-11-17 21:25:40 +00:00
b618b75c9b
-Feature: Bridges can now be placed above: Any railway track combination (excluding depots and waypoints) Any road combination (excluding depots) Clear tiles (duh), including fields Tunnel entrances Bridge heads Thanks to Tron for idea and implementation, KUDr for the yapf synchronization and many others for hours of testing There are still a number of visual problems remaining, especially when electric railways are on or under the bridge. DO NOT REPORT THOSE BUGS FOR THE TIME BEING please.
2027 lines
57 KiB
C
2027 lines
57 KiB
C
/* $Id$ */
|
|
|
|
#include "stdafx.h"
|
|
#include "openttd.h"
|
|
#include "bridge_map.h"
|
|
#include "debug.h"
|
|
#include "functions.h"
|
|
#include "rail_map.h"
|
|
#include "road_map.h"
|
|
#include "table/sprites.h"
|
|
#include "table/strings.h"
|
|
#include "map.h"
|
|
#include "tile.h"
|
|
#include "town_map.h"
|
|
#include "tunnel_map.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 "waypoint.h"
|
|
#include "rail.h"
|
|
#include "railtypes.h" // include table for railtypes
|
|
#include "newgrf.h"
|
|
#include "yapf/yapf.h"
|
|
#include "newgrf_callbacks.h"
|
|
#include "newgrf_station.h"
|
|
|
|
const byte _track_sloped_sprites[14] = {
|
|
14, 15, 22, 13,
|
|
0, 21, 17, 12,
|
|
23, 0, 18, 20,
|
|
19, 16
|
|
};
|
|
|
|
void ShowTrainDepotWindow(TileIndex tile);
|
|
|
|
/* 4
|
|
* ---------
|
|
* |\ /|
|
|
* | \ 1/ |
|
|
* | \ / |
|
|
* | \ / |
|
|
* 16| \ |32
|
|
* | / \2 |
|
|
* | / \ |
|
|
* | / \ |
|
|
* |/ \|
|
|
* ---------
|
|
* 8
|
|
*/
|
|
|
|
|
|
|
|
/* 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_TILE_NORMAL && type != RAIL_TILE_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_TILE_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_HORZ || future == TRACK_BIT_VERT;
|
|
} else {
|
|
/* Normally, we may overlap and any combination is valid */
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
const TrackBits _valid_tileh_slopes[2][15] = {
|
|
|
|
// set of normal ones
|
|
{
|
|
TRACK_BIT_ALL,
|
|
TRACK_BIT_RIGHT,
|
|
TRACK_BIT_UPPER,
|
|
TRACK_BIT_X,
|
|
|
|
TRACK_BIT_LEFT,
|
|
0,
|
|
TRACK_BIT_Y,
|
|
TRACK_BIT_LOWER,
|
|
|
|
TRACK_BIT_LOWER,
|
|
TRACK_BIT_Y,
|
|
0,
|
|
TRACK_BIT_LEFT,
|
|
|
|
TRACK_BIT_X,
|
|
TRACK_BIT_UPPER,
|
|
TRACK_BIT_RIGHT,
|
|
},
|
|
|
|
// allowed rail for an evenly raised platform
|
|
{
|
|
0,
|
|
TRACK_BIT_LEFT,
|
|
TRACK_BIT_LOWER,
|
|
TRACK_BIT_Y | TRACK_BIT_LOWER | TRACK_BIT_LEFT,
|
|
|
|
TRACK_BIT_RIGHT,
|
|
TRACK_BIT_ALL,
|
|
TRACK_BIT_X | TRACK_BIT_LOWER | TRACK_BIT_RIGHT,
|
|
TRACK_BIT_ALL,
|
|
|
|
TRACK_BIT_UPPER,
|
|
TRACK_BIT_X | TRACK_BIT_UPPER | TRACK_BIT_LEFT,
|
|
TRACK_BIT_ALL,
|
|
TRACK_BIT_ALL,
|
|
|
|
TRACK_BIT_Y | TRACK_BIT_UPPER | TRACK_BIT_RIGHT,
|
|
TRACK_BIT_ALL,
|
|
TRACK_BIT_ALL
|
|
}
|
|
};
|
|
|
|
uint GetRailFoundation(Slope tileh, TrackBits bits)
|
|
{
|
|
uint i;
|
|
|
|
if ((~_valid_tileh_slopes[0][tileh] & bits) == 0) return 0;
|
|
if ((~_valid_tileh_slopes[1][tileh] & bits) == 0) return tileh;
|
|
|
|
switch (bits) {
|
|
case TRACK_BIT_X: i = 0; break;
|
|
case TRACK_BIT_Y: i = 1; break;
|
|
default: return 0;
|
|
}
|
|
switch (tileh) {
|
|
case SLOPE_W: i += 0; break;
|
|
case SLOPE_S: i += 2; break;
|
|
case SLOPE_E: i += 4; break;
|
|
case SLOPE_N: i += 6; break;
|
|
default: return 0;
|
|
}
|
|
return i + 15;
|
|
}
|
|
|
|
|
|
static uint32 CheckRailSlope(Slope tileh, TrackBits rail_bits, TrackBits existing, TileIndex tile)
|
|
{
|
|
// never allow building on top of steep tiles
|
|
if (!IsSteepSlope(tileh)) {
|
|
rail_bits |= existing;
|
|
|
|
// don't allow building on the lower side of a coast
|
|
if (IsTileType(tile, MP_WATER) &&
|
|
~_valid_tileh_slopes[1][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_X || rail_bits == TRACK_BIT_Y) &&
|
|
(tileh == SLOPE_W || tileh == SLOPE_S || tileh == SLOPE_E || tileh == SLOPE_N)
|
|
)) { // partly up
|
|
if (existing != 0) {
|
|
return 0;
|
|
} else if (!_patches.build_on_slopes || _is_old_ai_player) {
|
|
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 tile tile to build on
|
|
* @param p1 railtype of being built piece (normal, mono, maglev)
|
|
* @param p2 rail track to build
|
|
*/
|
|
int32 CmdBuildSingleRail(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
Slope tileh;
|
|
Track track = (Track)p2;
|
|
TrackBits trackbit;
|
|
int32 cost = 0;
|
|
int32 ret;
|
|
|
|
if (!ValParamRailtype(p1) || !ValParamTrackOrientation(track)) return CMD_ERROR;
|
|
|
|
tileh = GetTileSlope(tile, NULL);
|
|
trackbit = TrackToTrackBits(track);
|
|
|
|
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
|
|
|
|
switch (GetTileType(tile)) {
|
|
case MP_RAILWAY:
|
|
if (!CheckTrackCombination(tile, trackbit, flags) ||
|
|
!EnsureNoVehicle(tile)) {
|
|
return CMD_ERROR;
|
|
}
|
|
if (GetRailTileType(tile) == RAIL_TILE_DEPOT_WAYPOINT ||
|
|
!IsTileOwner(tile, _current_player) ||
|
|
GetRailType(tile) != p1) {
|
|
// Get detailed error message
|
|
return DoCommand(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) {
|
|
SetRailGroundType(tile, RAIL_GROUND_BARREN);
|
|
_m[tile].m5 |= trackbit;
|
|
}
|
|
break;
|
|
|
|
case MP_STREET:
|
|
#define M(x) (1 << (x))
|
|
/* Level crossings may only be built on these slopes */
|
|
if (!HASBIT(M(SLOPE_SEN) | M(SLOPE_ENW) | M(SLOPE_NWS) | M(SLOPE_NS) | M(SLOPE_WSE) | M(SLOPE_EW) | M(SLOPE_FLAT), tileh)) {
|
|
return_cmd_error(STR_1000_LAND_SLOPED_IN_WRONG_DIRECTION);
|
|
}
|
|
#undef M
|
|
|
|
if (!EnsureNoVehicle(tile)) return CMD_ERROR;
|
|
|
|
if (GetRoadTileType(tile) == ROAD_TILE_NORMAL && (
|
|
(track == TRACK_X && GetRoadBits(tile) == ROAD_Y) ||
|
|
(track == TRACK_Y && GetRoadBits(tile) == ROAD_X)
|
|
)) {
|
|
if (flags & DC_EXEC) {
|
|
MakeRoadCrossing(tile, GetTileOwner(tile), _current_player, (track == TRACK_X ? AXIS_Y : AXIS_X), p1, GetTownIndex(tile));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (IsLevelCrossing(tile) && GetCrossingRailBits(tile) == trackbit) {
|
|
return_cmd_error(STR_1007_ALREADY_BUILT);
|
|
}
|
|
/* FALLTHROUGH */
|
|
|
|
default:
|
|
ret = CheckRailSlope(tileh, trackbit, 0, tile);
|
|
if (CmdFailed(ret)) return ret;
|
|
cost += ret;
|
|
|
|
ret = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
|
|
if (CmdFailed(ret)) return ret;
|
|
cost += ret;
|
|
|
|
if (flags & DC_EXEC) MakeRailNormal(tile, _current_player, trackbit, p1);
|
|
break;
|
|
}
|
|
|
|
if (flags & DC_EXEC) {
|
|
MarkTileDirtyByTile(tile);
|
|
SetSignalsOnBothDir(tile, track);
|
|
YapfNotifyTrackLayoutChange(tile, track);
|
|
}
|
|
|
|
return cost + _price.build_rail;
|
|
}
|
|
|
|
/** Remove a single piece of track
|
|
* @param tile tile to remove track from
|
|
* @param p1 unused
|
|
* @param p2 rail orientation
|
|
*/
|
|
int32 CmdRemoveSingleRail(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
Track track = (Track)p2;
|
|
TrackBits trackbit;
|
|
int32 cost = _price.remove_rail;
|
|
bool crossing = false;
|
|
|
|
if (!ValParamTrackOrientation(p2)) return CMD_ERROR;
|
|
trackbit = TrackToTrackBits(track);
|
|
|
|
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
|
|
|
|
switch (GetTileType(tile)) {
|
|
case MP_STREET: {
|
|
if (!IsLevelCrossing(tile) ||
|
|
GetCrossingRailBits(tile) != trackbit ||
|
|
(_current_player != OWNER_WATER && !CheckTileOwnership(tile)) ||
|
|
!EnsureNoVehicle(tile)) {
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
if (flags & DC_EXEC) {
|
|
MakeRoadNormal(tile, GetCrossingRoadOwner(tile), GetCrossingRoadBits(tile), GetTownIndex(tile));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MP_RAILWAY: {
|
|
TrackBits present;
|
|
|
|
if (!IsPlainRailTile(tile) ||
|
|
(_current_player != OWNER_WATER && !CheckTileOwnership(tile)) ||
|
|
!EnsureNoVehicle(tile)) {
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
present = GetTrackBits(tile);
|
|
if ((present & trackbit) == 0) return CMD_ERROR;
|
|
if (present == (TRACK_BIT_X | TRACK_BIT_Y)) crossing = true;
|
|
|
|
/* Charge extra to remove signals on the track, if they are there */
|
|
if (HasSignalOnTrack(tile, track))
|
|
cost += DoCommand(tile, track, 0, flags, CMD_REMOVE_SIGNALS);
|
|
|
|
if (flags & DC_EXEC) {
|
|
present ^= trackbit;
|
|
if (present == 0) {
|
|
DoClearSquare(tile);
|
|
} else {
|
|
SetTrackBits(tile, present);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: return CMD_ERROR;
|
|
}
|
|
|
|
if (flags & DC_EXEC) {
|
|
MarkTileDirtyByTile(tile);
|
|
if (crossing) {
|
|
/* crossing is set when only TRACK_BIT_X and TRACK_BIT_Y are set. As we
|
|
* are removing one of these pieces, we'll need to update signals for
|
|
* both directions explicitly, as after the track is removed it won't
|
|
* 'connect' with the other piece. */
|
|
SetSignalsOnBothDir(tile, TRACK_X);
|
|
SetSignalsOnBothDir(tile, TRACK_Y);
|
|
YapfNotifyTrackLayoutChange(tile, TRACK_X);
|
|
YapfNotifyTrackLayoutChange(tile, TRACK_Y);
|
|
} else {
|
|
SetSignalsOnBothDir(tile, track);
|
|
YapfNotifyTrackLayoutChange(tile, track);
|
|
}
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
|
|
|
|
static const TileIndexDiffC _trackdelta[] = {
|
|
{ -1, 0 }, { 0, 1 }, { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, 1 },
|
|
{ 0, 0 },
|
|
{ 0, 0 },
|
|
{ 1, 0 }, { 0, -1 }, { 0, -1 }, { 1, 0 }, { 0, -1 }, { -1, 0 },
|
|
{ 0, 0 },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
|
|
static int32 ValidateAutoDrag(Trackdir *trackdir, TileIndex start, TileIndex end)
|
|
{
|
|
int x = TileX(start);
|
|
int y = TileY(start);
|
|
int ex = TileX(end);
|
|
int ey = TileY(end);
|
|
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 = _trackdelta[*trackdir].x;
|
|
trdy = _trackdelta[*trackdir].y;
|
|
|
|
if (!IsDiagonalTrackdir(*trackdir)) {
|
|
trdx += _trackdelta[*trackdir ^ 1].x;
|
|
trdy += _trackdelta[*trackdir ^ 1].y;
|
|
}
|
|
|
|
// 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 = _trackdelta[*trackdir].x;
|
|
trdy = _trackdelta[*trackdir].y;
|
|
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 tile 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(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
int32 ret, total_cost = 0;
|
|
Track track = (Track)GB(p2, 4, 3);
|
|
Trackdir trackdir;
|
|
byte mode = HASBIT(p2, 7);
|
|
RailType railtype = (RailType)GB(p2, 0, 4);
|
|
TileIndex end_tile;
|
|
|
|
if (!ValParamRailtype(railtype) || !ValParamTrackOrientation(track)) return CMD_ERROR;
|
|
if (p1 >= MapSize()) return CMD_ERROR;
|
|
end_tile = p1;
|
|
trackdir = TrackToTrackdir(track);
|
|
|
|
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
|
|
|
|
if (CmdFailed(ValidateAutoDrag(&trackdir, tile, end_tile))) return CMD_ERROR;
|
|
|
|
if (flags & DC_EXEC) SndPlayTileFx(SND_20_SPLAT_2, tile);
|
|
|
|
for (;;) {
|
|
ret = DoCommand(tile, railtype, 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;
|
|
_error_message = INVALID_STRING_ID;
|
|
} else
|
|
total_cost += ret;
|
|
|
|
if (tile == end_tile) break;
|
|
|
|
tile += ToTileIndexDiff(_trackdelta[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(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
return CmdRailTrackHelper(tile, flags, p1, CLRBIT(p2, 7));
|
|
}
|
|
|
|
/** Build rail on a stretch of track.
|
|
* Stub for the unified rail builder/remover
|
|
* @see CmdRailTrackHelper
|
|
*/
|
|
int32 CmdRemoveRailroadTrack(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
return CmdRailTrackHelper(tile, flags, p1, SETBIT(p2, 7));
|
|
}
|
|
|
|
/** Build a train depot
|
|
* @param tile position of the train depot
|
|
* @param p1 rail type
|
|
* @param p2 entrance direction (DiagDirection)
|
|
*
|
|
* @todo When checking for the tile slope,
|
|
* distingush between "Flat land required" and "land sloped in wrong direction"
|
|
*/
|
|
int32 CmdBuildTrainDepot(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
Depot *d;
|
|
int32 cost, ret;
|
|
Slope 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 != SLOPE_FLAT && (
|
|
_is_old_ai_player ||
|
|
!_patches.build_on_slopes ||
|
|
IsSteepSlope(tileh) ||
|
|
!CanBuildDepotByTileh(p2, tileh)
|
|
)) {
|
|
return_cmd_error(STR_0007_FLAT_LAND_REQUIRED);
|
|
}
|
|
|
|
ret = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
|
|
if (CmdFailed(ret)) return CMD_ERROR;
|
|
cost = ret;
|
|
|
|
if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) return_cmd_error(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST);
|
|
|
|
d = AllocateDepot();
|
|
if (d == NULL) return CMD_ERROR;
|
|
|
|
if (flags & DC_EXEC) {
|
|
MakeRailDepot(tile, _current_player, p2, p1);
|
|
MarkTileDirtyByTile(tile);
|
|
|
|
d->xy = tile;
|
|
d->town_index = ClosestTownFromTile(tile, (uint)-1)->index;
|
|
|
|
UpdateSignalsOnSegment(tile, p2);
|
|
YapfNotifyTrackLayoutChange(tile, TrackdirToTrack(DiagdirToDiagTrackdir(p2)));
|
|
}
|
|
|
|
return cost + _price.build_train_depot;
|
|
}
|
|
|
|
/** Build signals, alternate between double/single, signal/semaphore,
|
|
* pre/exit/combo-signals, and what-else not
|
|
* @param tile tile where to build the signals
|
|
* @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(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
SignalVariant sigvar;
|
|
bool pre_signal;
|
|
Track track = (Track)(p1 & 0x7);
|
|
int32 cost;
|
|
|
|
// Same bit, used in different contexts
|
|
sigvar = HASBIT(p1, 3) ? SIG_SEMAPHORE : SIG_ELECTRIC;
|
|
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;
|
|
|
|
/* 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_HORZ &&
|
|
trackbits != TRACK_BIT_VERT
|
|
)
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION);
|
|
|
|
if (!HasSignalOnTrack(tile, track)) {
|
|
// build new signals
|
|
cost = _price.build_signals;
|
|
} else {
|
|
if (p2 != 0 && sigvar != GetSignalVariant(tile)) {
|
|
// 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_TILE_SIGNALS) {
|
|
// there are no signals at all on this tile yet
|
|
_m[tile].m5 |= RAIL_TILE_SIGNALS; // change into signals
|
|
_m[tile].m2 |= 0xF0; // all signals are on
|
|
_m[tile].m3 &= ~0xF0; // no signals built by default
|
|
SetSignalType(tile, SIGTYPE_NORMAL);
|
|
SetSignalVariant(tile, sigvar);
|
|
}
|
|
|
|
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 -> ...
|
|
SignalType type = GetSignalType(tile);
|
|
|
|
SetSignalType(tile, type == SIGTYPE_COMBO ? SIGTYPE_NORMAL : type + 1);
|
|
} else {
|
|
CycleSignalSide(tile, track);
|
|
}
|
|
}
|
|
} 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);
|
|
SetSignalVariant(tile, sigvar);
|
|
}
|
|
|
|
MarkTileDirtyByTile(tile);
|
|
SetSignalsOnBothDir(tile, track);
|
|
YapfNotifyTrackLayoutChange(tile, track);
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
|
|
/** Build many signals by dragging; AutoSignals
|
|
* @param tile 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(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
int32 ret, total_cost, signal_ctr;
|
|
byte signals;
|
|
bool error = true;
|
|
TileIndex end_tile;
|
|
|
|
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;
|
|
end_tile = p1;
|
|
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;
|
|
|
|
if (CmdFailed(ValidateAutoDrag(&trackdir, tile, end_tile))) 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_TILE_SIGNALS && GetTrackBits(tile) != 0) { /* XXX: GetTrackBits check useless? */
|
|
signals = _m[tile].m3 & SignalOnTrack(track);
|
|
if (signals == 0) signals = SignalOnTrack(track); /* Can this actually occur? */
|
|
|
|
// copy signal/semaphores style (independent of CTRL)
|
|
semaphores = (GetSignalVariant(tile) == SIG_ELECTRIC ? 0 : 8);
|
|
} 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(tile, 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;
|
|
_error_message = INVALID_STRING_ID;
|
|
} else {
|
|
error = false;
|
|
total_cost += ret;
|
|
}
|
|
}
|
|
|
|
if (tile == end_tile) break;
|
|
|
|
tile += ToTileIndexDiff(_trackdelta[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(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
return CmdSignalTrackHelper(tile, flags, p1, p2);
|
|
}
|
|
|
|
/** Remove signals
|
|
* @param tile coordinates where signal is being deleted from
|
|
* @param p1 track to remove signal from (Track enum)
|
|
*/
|
|
int32 CmdRemoveSingleSignal(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
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 (GB(_m[tile].m3, 4, 4) == 0) {
|
|
SB(_m[tile].m2, 4, 4, 0);
|
|
SB(_m[tile].m5, 6, 2, RAIL_TILE_NORMAL >> 6); // XXX >> because the constant is meant for direct application, not use with SB
|
|
SetSignalVariant(tile, SIG_ELECTRIC); // remove any possible semaphores
|
|
}
|
|
|
|
SetSignalsOnBothDir(tile, track);
|
|
YapfNotifyTrackLayoutChange(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(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
return CmdSignalTrackHelper(tile, flags, p1, SETBIT(p2, 0));
|
|
}
|
|
|
|
typedef int32 DoConvertRailProc(TileIndex tile, RailType totype, bool exec);
|
|
|
|
static int32 DoConvertRail(TileIndex tile, RailType totype, bool exec)
|
|
{
|
|
if (!CheckTileOwnership(tile)) return CMD_ERROR;
|
|
|
|
if (!EnsureNoVehicle(tile) && (!IsCompatibleRail(GetRailType(tile), totype) || IsPlainRailTile(tile))) return CMD_ERROR;
|
|
|
|
// tile is already of requested type?
|
|
if (GetRailType(tile) == totype) return CMD_ERROR;
|
|
|
|
// change type.
|
|
if (exec) {
|
|
TrackBits tracks;
|
|
SetRailType(tile, totype);
|
|
MarkTileDirtyByTile(tile);
|
|
|
|
// notify YAPF about the track layout change
|
|
for (tracks = GetTrackBits(tile); tracks != TRACK_BIT_NONE; tracks = KILL_FIRST_BIT(tracks))
|
|
YapfNotifyTrackLayoutChange(tile, FIND_FIRST_BIT(tracks));
|
|
|
|
/* Update build vehicle window related to this depot */
|
|
if (IsTileDepotType(tile, TRANSPORT_RAIL)) {
|
|
Window *w = FindWindowById(WC_BUILD_VEHICLE, tile);
|
|
if (w != NULL) {
|
|
WP(w,buildtrain_d).railtype = totype;
|
|
SetWindowDirty(w);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _price.build_rail / 2;
|
|
}
|
|
|
|
extern int32 DoConvertStationRail(TileIndex tile, RailType totype, bool exec);
|
|
extern int32 DoConvertStreetRail(TileIndex tile, RailType totype, bool exec);
|
|
extern int32 DoConvertTunnelBridgeRail(TileIndex tile, RailType totype, bool exec);
|
|
|
|
/** Convert one rail type to the other. You can convert normal rail to
|
|
* monorail/maglev easily or vice-versa.
|
|
* @param tile end tile of rail conversion drag
|
|
* @param p1 start tile of drag
|
|
* @param p2 new railtype to convert to
|
|
*/
|
|
int32 CmdConvertRail(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
|
|
{
|
|
int32 ret, cost, money;
|
|
int ex;
|
|
int ey;
|
|
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
|
|
ex = TileX(tile);
|
|
ey = TileY(tile);
|
|
sx = TileX(p1);
|
|
sy = TileY(p1);
|
|
if (ex < sx) intswap(ex, sx);
|
|
if (ey < sy) intswap(ey, sy);
|
|
|
|
money = GetAvailableMoneyForCommand();
|
|
cost = 0;
|
|
ret = 0;
|
|
|
|
for (x = sx; x <= ex; ++x) {
|
|
for (y = sy; y <= ey; ++y) {
|
|
TileIndex tile = TileXY(x, y);
|
|
DoConvertRailProc* proc;
|
|
|
|
switch (GetTileType(tile)) {
|
|
case MP_RAILWAY: proc = DoConvertRail; break;
|
|
case MP_STATION: proc = DoConvertStationRail; break;
|
|
case MP_STREET: proc = DoConvertStreetRail; break;
|
|
case MP_TUNNELBRIDGE: proc = DoConvertTunnelBridgeRail; break;
|
|
default: continue;
|
|
}
|
|
|
|
ret = proc(tile, p2, false);
|
|
if (CmdFailed(ret)) continue;
|
|
cost += ret;
|
|
|
|
if (flags & DC_EXEC) {
|
|
money -= ret;
|
|
if (money < 0) {
|
|
_additional_cash_required = ret;
|
|
return cost - ret;
|
|
}
|
|
proc(tile, p2, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (cost == 0) ? ret : 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) {
|
|
DiagDirection dir = GetRailDepotDirection(tile);
|
|
|
|
DoDeleteDepot(tile);
|
|
UpdateSignalsOnSegment(tile, dir);
|
|
YapfNotifyTrackLayoutChange(tile, TrackdirToTrack(DiagdirToDiagTrackdir(dir)));
|
|
}
|
|
|
|
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 (GetRailTileType(tile) == RAIL_TILE_DEPOT_WAYPOINT) {
|
|
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)) {
|
|
/* XXX: Why the fuck do we remove these thow signals first? */
|
|
case RAIL_TILE_SIGNALS:
|
|
if (HasSignalOnTrack(tile, TRACK_X)) {
|
|
ret = DoCommand(tile, TRACK_X, 0, flags, CMD_REMOVE_SIGNALS);
|
|
if (CmdFailed(ret)) return CMD_ERROR;
|
|
cost += ret;
|
|
}
|
|
if (HasSignalOnTrack(tile, TRACK_LOWER)) {
|
|
ret = DoCommand(tile, TRACK_LOWER, 0, flags, CMD_REMOVE_SIGNALS);
|
|
if (CmdFailed(ret)) 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_TILE_NORMAL: {
|
|
uint i;
|
|
|
|
for (i = 0; m5 != 0; i++, m5 >>= 1) {
|
|
if (m5 & 1) {
|
|
ret = DoCommand(tile, 0, i, flags, CMD_REMOVE_SINGLE_RAIL);
|
|
if (CmdFailed(ret)) return CMD_ERROR;
|
|
cost += ret;
|
|
}
|
|
}
|
|
return cost;
|
|
}
|
|
|
|
case RAIL_TILE_DEPOT_WAYPOINT:
|
|
if (GetRailTileSubtype(tile) == RAIL_SUBTYPE_DEPOT) {
|
|
return RemoveTrainDepot(tile, flags);
|
|
} else {
|
|
return RemoveTrainWaypoint(tile, flags, false);
|
|
}
|
|
|
|
default:
|
|
return CMD_ERROR;
|
|
}
|
|
}
|
|
|
|
#include "table/track_land.h"
|
|
|
|
static void DrawSingleSignal(TileIndex tile, byte condition, uint image, uint pos)
|
|
{
|
|
bool side = _opt.road_side & _patches.signal_side;
|
|
static const Point SignalPositions[2][12] = {
|
|
{ /* Signals on the left side */
|
|
/* LEFT LEFT RIGHT RIGHT UPPER UPPER */
|
|
{ 8, 5}, {14, 1}, { 1, 14}, { 9, 11}, { 1, 0}, { 3, 10},
|
|
/* LOWER LOWER X X Y Y */
|
|
{11, 4}, {14, 14}, {11, 3}, { 4, 13}, { 3, 4}, {11, 13}
|
|
}, { /* Signals on the right side */
|
|
/* LEFT LEFT RIGHT RIGHT UPPER UPPER */
|
|
{14, 1}, {12, 10}, { 4, 6}, { 1, 14}, {10, 4}, { 0, 1},
|
|
/* LOWER LOWER X X Y Y */
|
|
{14, 14}, { 5, 12}, {11, 13}, { 4, 3}, {13, 4}, { 3, 11}
|
|
}
|
|
};
|
|
|
|
static const SpriteID SignalBase[2][2][4] = {
|
|
{ /* Signals on left side */
|
|
{ 0x4FB, 0x1323, 0x1333, 0x1343}, /* light signals */
|
|
{ 0x1353, 0x1363, 0x1373, 0x1383} /* semaphores */
|
|
}, { /* Signals on right side */
|
|
{ 0x4FB, 0x1323, 0x1333, 0x1343}, /* light signals */
|
|
{ 0x1446, 0x1456, 0x1466, 0x1476} /* semaphores */
|
|
/* | | | | */
|
|
/* normal, entry, exit, combo */
|
|
}
|
|
};
|
|
|
|
uint x = TileX(tile) * TILE_SIZE + SignalPositions[side][pos].x;
|
|
uint y = TileY(tile) * TILE_SIZE + SignalPositions[side][pos].y;
|
|
|
|
SpriteID sprite;
|
|
|
|
/* _signal_base is set by our NewGRF Action 5 loader. If it is 0 then we
|
|
* just draw the standard signals, else we get the offset from _signal_base
|
|
* and draw that sprite. All the signal sprites are loaded sequentially. */
|
|
if (_signal_base == 0 || (GetSignalType(tile) == 0 && GetSignalVariant(tile) == SIG_ELECTRIC)) {
|
|
sprite = SignalBase[side][GetSignalVariant(tile)][GetSignalType(tile)] + image + condition;
|
|
} else {
|
|
sprite = _signal_base + (GetSignalType(tile) - 1) * 16 + GetSignalVariant(tile) * 64 + image + condition;
|
|
}
|
|
|
|
AddSortableSpriteToDraw(sprite, x, y, 1, 1, 10, GetSlopeZ(x,y));
|
|
}
|
|
|
|
static uint32 _drawtile_track_palette;
|
|
|
|
|
|
static void DrawTrackFence_NW(const TileInfo *ti)
|
|
{
|
|
uint32 image = 0x515;
|
|
if (ti->tileh != SLOPE_FLAT) image = (ti->tileh & SLOPE_S) ? 0x519 : 0x51B;
|
|
AddSortableSpriteToDraw(image | _drawtile_track_palette,
|
|
ti->x, ti->y + 1, 16, 1, 4, ti->z);
|
|
}
|
|
|
|
static void DrawTrackFence_SE(const TileInfo *ti)
|
|
{
|
|
uint32 image = 0x515;
|
|
if (ti->tileh != SLOPE_FLAT) image = (ti->tileh & SLOPE_S) ? 0x519 : 0x51B;
|
|
AddSortableSpriteToDraw(image | _drawtile_track_palette,
|
|
ti->x, ti->y + TILE_SIZE - 1, 16, 1, 4, ti->z);
|
|
}
|
|
|
|
static void DrawTrackFence_NW_SE(const TileInfo *ti)
|
|
{
|
|
DrawTrackFence_NW(ti);
|
|
DrawTrackFence_SE(ti);
|
|
}
|
|
|
|
static void DrawTrackFence_NE(const TileInfo *ti)
|
|
{
|
|
uint32 image = 0x516;
|
|
if (ti->tileh != SLOPE_FLAT) image = (ti->tileh & SLOPE_S) ? 0x51A : 0x51C;
|
|
AddSortableSpriteToDraw(image | _drawtile_track_palette,
|
|
ti->x + 1, ti->y, 1, 16, 4, ti->z);
|
|
}
|
|
|
|
static void DrawTrackFence_SW(const TileInfo *ti)
|
|
{
|
|
uint32 image = 0x516;
|
|
if (ti->tileh != SLOPE_FLAT) image = (ti->tileh & SLOPE_S) ? 0x51A : 0x51C;
|
|
AddSortableSpriteToDraw(image | _drawtile_track_palette,
|
|
ti->x + TILE_SIZE - 1, ti->y, 1, 16, 4, ti->z);
|
|
}
|
|
|
|
static void DrawTrackFence_NE_SW(const TileInfo *ti)
|
|
{
|
|
DrawTrackFence_NE(ti);
|
|
DrawTrackFence_SW(ti);
|
|
}
|
|
|
|
static void DrawTrackFence_NS_1(const TileInfo *ti)
|
|
{
|
|
int z = ti->z;
|
|
if (ti->tileh & SLOPE_W) z += TILE_HEIGHT;
|
|
AddSortableSpriteToDraw(0x517 | _drawtile_track_palette,
|
|
ti->x + TILE_SIZE / 2, ti->y + TILE_SIZE / 2, 1, 1, 4, z);
|
|
}
|
|
|
|
static void DrawTrackFence_NS_2(const TileInfo *ti)
|
|
{
|
|
int z = ti->z;
|
|
if (ti->tileh & SLOPE_E) z += TILE_HEIGHT;
|
|
AddSortableSpriteToDraw(0x517 | _drawtile_track_palette,
|
|
ti->x + TILE_SIZE / 2, ti->y + TILE_SIZE / 2, 1, 1, 4, z);
|
|
}
|
|
|
|
static void DrawTrackFence_WE_1(const TileInfo *ti)
|
|
{
|
|
int z = ti->z;
|
|
if (ti->tileh & SLOPE_N) z += TILE_HEIGHT;
|
|
AddSortableSpriteToDraw(0x518 | _drawtile_track_palette,
|
|
ti->x + TILE_SIZE / 2, ti->y + TILE_SIZE / 2, 1, 1, 4, z);
|
|
}
|
|
|
|
static void DrawTrackFence_WE_2(const TileInfo *ti)
|
|
{
|
|
int z = ti->z;
|
|
if (ti->tileh & SLOPE_S) z += TILE_HEIGHT;
|
|
AddSortableSpriteToDraw(0x518 | _drawtile_track_palette,
|
|
ti->x + TILE_SIZE / 2, ti->y + TILE_SIZE / 2, 1, 1, 4, z);
|
|
}
|
|
|
|
static void DetTrackDrawProc_Null(const TileInfo *ti)
|
|
{
|
|
/* nothing should be here */
|
|
}
|
|
|
|
typedef void DetailedTrackProc(const TileInfo *ti);
|
|
static DetailedTrackProc* const _detailed_track_proc[] = {
|
|
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 offset,
|
|
const TileInfo* ti,
|
|
byte x, byte y, byte z,
|
|
byte xsize, byte ysize, byte zsize)
|
|
{
|
|
if (image & PALETTE_MODIFIER_COLOR) image |= _drawtile_track_palette;
|
|
image += offset;
|
|
if (_display_opt & DO_TRANS_BUILDINGS) MAKE_TRANSPARENT(image);
|
|
AddSortableSpriteToDraw(image, ti->x + x, ti->y + y, xsize, ysize, zsize, ti->z + z);
|
|
}
|
|
|
|
/**
|
|
* Draw ground sprite and track bits
|
|
* @param ti TileInfo
|
|
* @param track TrackBits to draw
|
|
* @param earth Draw as earth
|
|
* @param snow Draw as snow
|
|
* @param flat Always draw foundation
|
|
*/
|
|
static void DrawTrackBits(TileInfo* ti, TrackBits track, bool flat)
|
|
{
|
|
const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
|
|
PalSpriteID image;
|
|
bool junction = false;
|
|
bool earth = IsBarrenRailGround(ti->tile);
|
|
bool snow = IsSnowRailGround(ti->tile);
|
|
|
|
// Select the sprite to use.
|
|
(image = rti->base_sprites.track_y, track == TRACK_BIT_Y) ||
|
|
(image++, track == TRACK_BIT_X) ||
|
|
(image++, track == TRACK_BIT_UPPER) ||
|
|
(image++, track == TRACK_BIT_LOWER) ||
|
|
(image++, track == TRACK_BIT_RIGHT) ||
|
|
(image++, track == TRACK_BIT_LEFT) ||
|
|
(image++, track == TRACK_BIT_CROSS) ||
|
|
|
|
(image = rti->base_sprites.track_ns, track == TRACK_BIT_HORZ) ||
|
|
(image++, track == TRACK_BIT_VERT) ||
|
|
|
|
(junction = true, false) ||
|
|
(image = rti->base_sprites.ground, (track & TRACK_BIT_3WAY_NE) == 0) ||
|
|
(image++, (track & TRACK_BIT_3WAY_SW) == 0) ||
|
|
(image++, (track & TRACK_BIT_3WAY_NW) == 0) ||
|
|
(image++, (track & TRACK_BIT_3WAY_SE) == 0) ||
|
|
(image++, true);
|
|
|
|
if (ti->tileh != SLOPE_FLAT) {
|
|
int foundation;
|
|
|
|
if (flat) {
|
|
foundation = ti->tileh;
|
|
} else {
|
|
foundation = GetRailFoundation(ti->tileh, track);
|
|
}
|
|
|
|
if (foundation != 0)
|
|
DrawFoundation(ti, foundation);
|
|
|
|
// DrawFoundation() modifies ti.
|
|
// Default sloped sprites..
|
|
if (ti->tileh != SLOPE_FLAT)
|
|
image = _track_sloped_sprites[ti->tileh - 1] + rti->base_sprites.track_y;
|
|
}
|
|
|
|
if (earth) {
|
|
image = (image & SPRITE_MASK) | PALETTE_TO_BARE_LAND; // Use brown palette
|
|
} else if (snow) {
|
|
image += rti->snow_offset;
|
|
}
|
|
|
|
DrawGroundSprite(image);
|
|
|
|
// Draw track pieces individually for junction tiles
|
|
if (junction) {
|
|
if (track & TRACK_BIT_X) DrawGroundSprite(rti->base_sprites.single_y);
|
|
if (track & TRACK_BIT_Y) DrawGroundSprite(rti->base_sprites.single_x);
|
|
if (track & TRACK_BIT_UPPER) DrawGroundSprite(rti->base_sprites.single_n);
|
|
if (track & TRACK_BIT_LOWER) DrawGroundSprite(rti->base_sprites.single_s);
|
|
if (track & TRACK_BIT_LEFT) DrawGroundSprite(rti->base_sprites.single_w);
|
|
if (track & TRACK_BIT_RIGHT) DrawGroundSprite(rti->base_sprites.single_e);
|
|
}
|
|
|
|
if (GetRailType(ti->tile) == RAILTYPE_ELECTRIC) DrawCatenary(ti);
|
|
|
|
}
|
|
|
|
static void DrawSignals(TileIndex tile, TrackBits rails)
|
|
{
|
|
#define MAYBE_DRAW_SIGNAL(x,y,z) if (IsSignalPresent(tile, x)) DrawSingleSignal(tile, GetSingleSignalState(tile, x), y - 0x4FB, z)
|
|
|
|
if (!(rails & TRACK_BIT_Y)) {
|
|
if (!(rails & TRACK_BIT_X)) {
|
|
if (rails & TRACK_BIT_LEFT) {
|
|
MAYBE_DRAW_SIGNAL(2, 0x509, 0);
|
|
MAYBE_DRAW_SIGNAL(3, 0x507, 1);
|
|
}
|
|
if (rails & TRACK_BIT_RIGHT) {
|
|
MAYBE_DRAW_SIGNAL(0, 0x509, 2);
|
|
MAYBE_DRAW_SIGNAL(1, 0x507, 3);
|
|
}
|
|
if (rails & TRACK_BIT_UPPER) {
|
|
MAYBE_DRAW_SIGNAL(3, 0x505, 4);
|
|
MAYBE_DRAW_SIGNAL(2, 0x503, 5);
|
|
}
|
|
if (rails & 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);
|
|
}
|
|
}
|
|
|
|
static void DrawTile_Track(TileInfo *ti)
|
|
{
|
|
const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
|
|
PalSpriteID image;
|
|
|
|
_drawtile_track_palette = SPRITE_PALETTE(PLAYER_SPRITE_COLOR(GetTileOwner(ti->tile)));
|
|
|
|
if (GetRailTileType(ti->tile) != RAIL_TILE_DEPOT_WAYPOINT) {
|
|
TrackBits rails = GetTrackBits(ti->tile);
|
|
|
|
DrawTrackBits(ti, rails, false);
|
|
|
|
if (_display_opt & DO_FULL_DETAIL) _detailed_track_proc[GetRailGroundType(ti->tile)](ti);
|
|
|
|
/* draw signals also? */
|
|
if (GetRailTileType(ti->tile) == RAIL_TILE_SIGNALS) DrawSignals(ti->tile, rails);
|
|
|
|
} else {
|
|
/* draw depots / waypoints */
|
|
const DrawTrackSeqStruct *drss;
|
|
bool is_depot = GetRailTileSubtype(ti->tile) == RAIL_SUBTYPE_DEPOT;
|
|
|
|
if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, ti->tileh);
|
|
|
|
if (IsRailWaypoint(ti->tile) && IsCustomWaypoint(ti->tile)) {
|
|
// look for customization
|
|
byte stat_id = GetWaypointByTile(ti->tile)->stat_id;
|
|
const StationSpec *statspec = GetCustomStationSpec(STAT_CLASS_WAYP, stat_id);
|
|
|
|
if (statspec != NULL) {
|
|
DrawTileSeqStruct const *seq;
|
|
// emulate station tile - open with building
|
|
uint tile = 2;
|
|
const DrawTileSprites *cust;
|
|
Station *st = ComposeWaypointStation(ti->tile);
|
|
|
|
uint32 relocation = GetCustomStationRelocation(statspec, st, ti->tile);
|
|
|
|
if (HASBIT(statspec->callbackmask, CBM_CUSTOM_LAYOUT)) {
|
|
uint16 callback = GetStationCallback(CBID_STATION_SPRITE_LAYOUT, 0, 0, statspec, st, ti->tile);
|
|
if (callback != CALLBACK_FAILED) tile = callback;
|
|
}
|
|
|
|
if (statspec->renderdata == NULL) {
|
|
cust = GetStationTileLayout(tile);
|
|
} else {
|
|
cust = &statspec->renderdata[(tile < statspec->tiles ? tile : 0) + GetWaypointAxis(ti->tile)];
|
|
}
|
|
|
|
/* If there is no sprite layout, we fall back to the default waypoint graphics. */
|
|
if (cust != NULL && cust->seq != NULL) {
|
|
image = cust->ground_sprite;
|
|
if (HASBIT(image, 31)) {
|
|
CLRBIT(image, 31);
|
|
image += GetCustomStationGroundRelocation(statspec, st, ti->tile);
|
|
image += rti->custom_ground_offset;
|
|
} else {
|
|
image += rti->total_offset;
|
|
}
|
|
|
|
DrawGroundSprite(image);
|
|
|
|
if (GetRailType(ti->tile) == RAILTYPE_ELECTRIC) DrawCatenary(ti);
|
|
|
|
foreach_draw_tile_seq(seq, cust->seq) {
|
|
DrawSpecialBuilding(
|
|
seq->image + relocation, 0, ti,
|
|
seq->delta_x, seq->delta_y, seq->delta_z,
|
|
seq->width, seq->height, seq->unk
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
drss = is_depot ? _track_depot_layout_table[GetRailDepotDirection(ti->tile)] : _track_waypoint_layout_table[GetWaypointAxis(ti->tile)];
|
|
|
|
image = drss++->image;
|
|
/* @note This is kind of an ugly hack, as the PALETTE_MODIFIER_COLOR indicates
|
|
* whether the sprite is railtype dependent. Rewrite this asap */
|
|
if (image & PALETTE_MODIFIER_COLOR) image = (image & SPRITE_MASK) + rti->total_offset;
|
|
|
|
// adjust ground tile for desert
|
|
// (don't adjust for arctic depots, because snow in depots looks weird)
|
|
// type >= 4 means waypoints
|
|
if (IsSnowRailGround(ti->tile) && (_opt.landscape == LT_DESERT || !is_depot)) {
|
|
if (image != SPR_FLAT_GRASS_TILE) {
|
|
image += rti->snow_offset; // tile with tracks
|
|
} else {
|
|
image = SPR_FLAT_SNOWY_TILE; // flat ground
|
|
}
|
|
}
|
|
|
|
DrawGroundSprite(image);
|
|
|
|
if (GetRailType(ti->tile) == RAILTYPE_ELECTRIC) DrawCatenary(ti);
|
|
|
|
for (; drss->image != 0; drss++) {
|
|
DrawSpecialBuilding(
|
|
drss->image, is_depot ? rti->total_offset : 0, ti,
|
|
drss->subcoord_x, drss->subcoord_y, 0,
|
|
drss->width, drss->height, 0x17
|
|
);
|
|
}
|
|
}
|
|
DrawBridgeMiddle(ti);
|
|
}
|
|
|
|
void DrawTrainDepotSprite(int x, int y, int image, RailType railtype)
|
|
{
|
|
uint32 ormod, img;
|
|
const RailtypeInfo *rti = GetRailTypeInfo(railtype);
|
|
const DrawTrackSeqStruct *dtss;
|
|
|
|
ormod = PLAYER_SPRITE_COLOR(_local_player);
|
|
|
|
dtss = _track_depot_layout_table[image];
|
|
|
|
x += 33;
|
|
y += 17;
|
|
|
|
img = dtss++->image;
|
|
/* @note This is kind of an ugly hack, as the PALETTE_MODIFIER_COLOR indicates
|
|
* whether the sprite is railtype dependent. Rewrite this asap */
|
|
if (img & PALETTE_MODIFIER_COLOR) img = (img & SPRITE_MASK) + rti->total_offset;
|
|
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 + rti->total_offset, x + pt.x, y + pt.y);
|
|
}
|
|
}
|
|
|
|
void DrawDefaultWaypointSprite(int x, int y, RailType railtype)
|
|
{
|
|
const DrawTrackSeqStruct *dtss = _track_waypoint_layout_table[0];
|
|
const RailtypeInfo *rti = GetRailTypeInfo(railtype);
|
|
uint32 img;
|
|
|
|
img = dtss++->image;
|
|
if (img & PALETTE_MODIFIER_COLOR) img = (img & SPRITE_MASK) + rti->total_offset;
|
|
DrawSprite(img, x, y);
|
|
|
|
for (; dtss->image != 0; dtss++) {
|
|
Point pt = RemapCoords(dtss->subcoord_x, dtss->subcoord_y, 0);
|
|
img = dtss->image;
|
|
if (img & PALETTE_MODIFIER_COLOR) img |= PLAYER_SPRITE_COLOR(_local_player);
|
|
DrawSprite(img, x + pt.x, y + pt.y);
|
|
}
|
|
}
|
|
|
|
typedef struct SetSignalsData {
|
|
int cur;
|
|
int cur_stack;
|
|
bool stop;
|
|
bool has_presignal;
|
|
|
|
// 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];
|
|
|
|
// 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)
|
|
{
|
|
if (!IsTileType(tile, MP_RAILWAY)) return false;
|
|
|
|
// the tile has signals?
|
|
if (HasSignalOnTrack(tile, TrackdirToTrack(track))) {
|
|
if (HasSignalOnTrackdir(tile, ReverseTrackdir(track))) {
|
|
// 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++;
|
|
}
|
|
|
|
// remember if this block has a presignal.
|
|
ssd->has_presignal |= IsPresignalEntry(tile);
|
|
}
|
|
|
|
if (HasSignalOnTrackdir(tile, track) && IsPresignalExit(tile)) {
|
|
// this is an exit signal that points out from the segment
|
|
ssd->presignal_exits++;
|
|
if (GetSignalStateByTrackdir(tile, track) != SIGNAL_STATE_RED)
|
|
ssd->presignal_exits_free++;
|
|
}
|
|
|
|
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)
|
|
{
|
|
const SignalVehicleCheckStruct* dest = data;
|
|
|
|
if (v->type != VEH_Train) return NULL;
|
|
|
|
/* Wrong tile, or no train? Not a match */
|
|
if (v->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 */
|
|
static bool SignalVehicleCheck(TileIndex tile, uint track)
|
|
{
|
|
SignalVehicleCheckStruct dest;
|
|
|
|
dest.tile = tile;
|
|
dest.track = track;
|
|
|
|
/* Locate vehicles in tunnels or on bridges */
|
|
if (IsTunnelTile(tile) || IsBridgeTile(tile)) {
|
|
TileIndex end;
|
|
DiagDirection direction;
|
|
|
|
if (IsTunnelTile(tile)) {
|
|
end = GetOtherTunnelEnd(tile);
|
|
direction = GetTunnelDirection(tile);
|
|
} else {
|
|
end = GetOtherBridgeEnd(tile);
|
|
direction = GetBridgeRampDirection(tile);
|
|
}
|
|
|
|
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(end, &dest, SignalVehicleCheckProc) != NULL) return true;
|
|
|
|
// now check all tiles from start to end for a warping vehicle
|
|
// NOTE: the hashes for tiles may overlap, so this could maybe be optimised a bit by not checking every tile?
|
|
dest.track = 0x40;
|
|
for (; tile != end; 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;
|
|
const 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
|
|
|
|
// then mark the signals in the segment accordingly
|
|
for (i = 0; i != ssd->cur; i++) {
|
|
TileIndex tile = ssd->tile[i];
|
|
byte bit = SignalAgainstTrackdir(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 (IsPresignalEntry(tile)) {
|
|
int ex = ssd->presignal_exits, exfree = ssd->presignal_exits_free;
|
|
|
|
// subtract for dual combo signals so they don't count themselves
|
|
if (IsPresignalExit(tile) && HasSignalOnTrackdir(tile, ssd->bit[i])) {
|
|
ex--;
|
|
if (GetSignalStateByTrackdir(tile, ssd->bit[i]) != SIGNAL_STATE_RED) 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 (IsPresignalExit(tile)) {
|
|
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, DiagDirection direction)
|
|
{
|
|
SetSignalsData ssd;
|
|
int result = -1;
|
|
|
|
ssd.cur_stack = 0;
|
|
|
|
for (;;) {
|
|
// go through one segment and update all signals pointing into that segment.
|
|
ssd.cur = ssd.presignal_exits = ssd.presignal_exits_free = 0;
|
|
ssd.has_presignal = false;
|
|
|
|
FollowTrack(tile, 0xC000 | TRANSPORT_RAIL, direction, (TPFEnumProc*)SetSignalsEnumProc, SetSignalsAfterProc, &ssd);
|
|
ChangeSignalStates(&ssd);
|
|
|
|
// remember the result only for the first iteration.
|
|
if (result < 0) {
|
|
// stay in depot while segment is occupied or while all presignal exits are blocked
|
|
result = ssd.stop || (ssd.presignal_exits > 0 && ssd.presignal_exits_free == 0);
|
|
}
|
|
|
|
// if any exit signals were changed, we need to keep going to modify the stuff behind those.
|
|
if (ssd.cur_stack == 0) 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 result != 0;
|
|
}
|
|
|
|
void SetSignalsOnBothDir(TileIndex tile, byte track)
|
|
{
|
|
static const DiagDirection _search_dir_1[] = {
|
|
DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_SE
|
|
};
|
|
static const DiagDirection _search_dir_2[] = {
|
|
DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE
|
|
};
|
|
|
|
UpdateSignalsOnSegment(tile, _search_dir_1[track]);
|
|
UpdateSignalsOnSegment(tile, _search_dir_2[track]);
|
|
}
|
|
|
|
static uint GetSlopeZ_Track(const TileInfo* ti)
|
|
{
|
|
Slope tileh = ti->tileh;
|
|
uint z = ti->z;
|
|
|
|
if (tileh == SLOPE_FLAT) return z;
|
|
if (GetRailTileType(ti->tile) == RAIL_TILE_DEPOT_WAYPOINT) {
|
|
return z + TILE_HEIGHT;
|
|
} else {
|
|
uint f = GetRailFoundation(ti->tileh, GetTrackBits(ti->tile));
|
|
|
|
if (f != 0) {
|
|
if (f < 15) return z + TILE_HEIGHT; // leveled foundation
|
|
tileh = _inclined_tileh[f - 15]; // inclined foundation
|
|
}
|
|
return z + GetPartialZ(ti->x & 0xF, ti->y & 0xF, tileh);
|
|
}
|
|
}
|
|
|
|
static Slope GetSlopeTileh_Track(TileIndex tile, Slope tileh)
|
|
{
|
|
if (tileh == SLOPE_FLAT) return SLOPE_FLAT;
|
|
if (GetRailTileType(tile) == RAIL_TILE_DEPOT_WAYPOINT) {
|
|
return SLOPE_FLAT;
|
|
} else {
|
|
uint f = GetRailFoundation(tileh, GetTrackBits(tile));
|
|
|
|
if (f == 0) return tileh;
|
|
if (f < 15) return SLOPE_FLAT; // leveled foundation
|
|
return _inclined_tileh[f - 15]; // inclined foundation
|
|
}
|
|
}
|
|
|
|
static void GetAcceptedCargo_Track(TileIndex tile, AcceptedCargo ac)
|
|
{
|
|
/* not used */
|
|
}
|
|
|
|
static void AnimateTile_Track(TileIndex tile)
|
|
{
|
|
/* not used */
|
|
}
|
|
|
|
static void TileLoop_Track(TileIndex tile)
|
|
{
|
|
RailGroundType old_ground = GetRailGroundType(tile);
|
|
RailGroundType new_ground = old_ground;
|
|
bool quick_return = false;
|
|
|
|
switch (_opt.landscape) {
|
|
case LT_HILLY:
|
|
if (GetTileZ(tile) > _opt.snow_line) {
|
|
new_ground = RAIL_GROUND_ICE_DESERT;
|
|
quick_return = true;
|
|
}
|
|
break;
|
|
|
|
case LT_DESERT:
|
|
if (GetTropicZone(tile) == TROPICZONE_DESERT) {
|
|
new_ground = RAIL_GROUND_ICE_DESERT;
|
|
quick_return = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (new_ground != old_ground) {
|
|
SetRailGroundType(tile, new_ground);
|
|
MarkTileDirtyByTile(tile);
|
|
}
|
|
|
|
if (quick_return) return;
|
|
|
|
// Don't continue tile loop for depots
|
|
if (GetRailTileType(tile) == RAIL_TILE_DEPOT_WAYPOINT) return;
|
|
|
|
new_ground = RAIL_GROUND_GRASS;
|
|
|
|
if (old_ground != RAIL_GROUND_BARREN) { /* wait until bottom is green */
|
|
/* determine direction of fence */
|
|
TrackBits rail = GetTrackBits(tile);
|
|
|
|
switch (rail) {
|
|
case TRACK_BIT_UPPER: new_ground = RAIL_GROUND_FENCE_HORIZ1; break;
|
|
case TRACK_BIT_LOWER: new_ground = RAIL_GROUND_FENCE_HORIZ2; break;
|
|
case TRACK_BIT_LEFT: new_ground = RAIL_GROUND_FENCE_VERT1; break;
|
|
case TRACK_BIT_RIGHT: new_ground = RAIL_GROUND_FENCE_VERT2; break;
|
|
|
|
default: {
|
|
PlayerID owner = GetTileOwner(tile);
|
|
|
|
if (rail == (TRACK_BIT_LOWER | TRACK_BIT_RIGHT) || (
|
|
(rail & TRACK_BIT_3WAY_NW) == 0 &&
|
|
(rail & TRACK_BIT_X)
|
|
)) {
|
|
TileIndex n = tile + TileDiffXY(0, -1);
|
|
TrackBits nrail = GetTrackBits(n);
|
|
|
|
if (!IsTileType(n, MP_RAILWAY) ||
|
|
!IsTileOwner(n, owner) ||
|
|
nrail == TRACK_BIT_UPPER ||
|
|
nrail == TRACK_BIT_LEFT) {
|
|
new_ground = RAIL_GROUND_FENCE_NW;
|
|
}
|
|
}
|
|
|
|
if (rail == (TRACK_BIT_UPPER | TRACK_BIT_LEFT) || (
|
|
(rail & TRACK_BIT_3WAY_SE) == 0 &&
|
|
(rail & TRACK_BIT_X)
|
|
)) {
|
|
TileIndex n = tile + TileDiffXY(0, 1);
|
|
TrackBits nrail = GetTrackBits(n);
|
|
|
|
if (!IsTileType(n, MP_RAILWAY) ||
|
|
!IsTileOwner(n, owner) ||
|
|
nrail == TRACK_BIT_LOWER ||
|
|
nrail == TRACK_BIT_RIGHT) {
|
|
new_ground = (new_ground == RAIL_GROUND_FENCE_NW) ?
|
|
RAIL_GROUND_FENCE_SENW : RAIL_GROUND_FENCE_SE;
|
|
}
|
|
}
|
|
|
|
if (rail == (TRACK_BIT_LOWER | TRACK_BIT_LEFT) || (
|
|
(rail & TRACK_BIT_3WAY_NE) == 0 &&
|
|
(rail & TRACK_BIT_Y)
|
|
)) {
|
|
TileIndex n = tile + TileDiffXY(-1, 0);
|
|
TrackBits nrail = GetTrackBits(n);
|
|
|
|
if (!IsTileType(n, MP_RAILWAY) ||
|
|
!IsTileOwner(n, owner) ||
|
|
nrail == TRACK_BIT_UPPER ||
|
|
nrail == TRACK_BIT_RIGHT) {
|
|
new_ground = RAIL_GROUND_FENCE_NE;
|
|
}
|
|
}
|
|
|
|
if (rail == (TRACK_BIT_UPPER | TRACK_BIT_RIGHT) || (
|
|
(rail & TRACK_BIT_3WAY_SW) == 0 &&
|
|
(rail & TRACK_BIT_Y)
|
|
)) {
|
|
TileIndex n = tile + TileDiffXY(1, 0);
|
|
TrackBits nrail = GetTrackBits(n);
|
|
|
|
if (!IsTileType(n, MP_RAILWAY) ||
|
|
!IsTileOwner(n, owner) ||
|
|
nrail == TRACK_BIT_LOWER ||
|
|
nrail == TRACK_BIT_LEFT) {
|
|
new_ground = (new_ground == RAIL_GROUND_FENCE_NE) ?
|
|
RAIL_GROUND_FENCE_NESW : RAIL_GROUND_FENCE_SW;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (old_ground != new_ground) {
|
|
SetRailGroundType(tile, new_ground);
|
|
MarkTileDirtyByTile(tile);
|
|
}
|
|
}
|
|
|
|
|
|
static uint32 GetTileTrackStatus_Track(TileIndex tile, TransportType mode)
|
|
{
|
|
byte a;
|
|
uint16 b;
|
|
|
|
if (mode != TRANSPORT_RAIL) return 0;
|
|
|
|
if (GetRailTileType(tile) != RAIL_TILE_DEPOT_WAYPOINT) {
|
|
TrackBits rails = GetTrackBits(tile);
|
|
uint32 ret = rails * 0x101;
|
|
|
|
if (GetRailTileType(tile) != RAIL_TILE_SIGNALS) {
|
|
if (rails == TRACK_BIT_CROSS) 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 |= 0x07100000;
|
|
if ((b & 0x20) == 0) ret |= 0x20080000;
|
|
if ((b & 0x10) == 0) ret |= 0x08200000;
|
|
}
|
|
return ret;
|
|
} else {
|
|
if (GetRailTileSubtype(tile) == RAIL_SUBTYPE_DEPOT) {
|
|
return (DiagDirToAxis(GetRailDepotDirection(tile)) == AXIS_X ? TRACK_BIT_X : TRACK_BIT_Y) * 0x101;
|
|
} else {
|
|
return GetRailWaypointBits(tile) * 0x101;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ClickTile_Track(TileIndex tile)
|
|
{
|
|
if (IsTileDepotType(tile, TRANSPORT_RAIL)) {
|
|
ShowTrainDepotWindow(tile);
|
|
} else if (IsRailWaypoint(tile)) {
|
|
ShowRenameWaypointWindow(GetWaypointByTile(tile));
|
|
}
|
|
}
|
|
|
|
static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
|
|
{
|
|
td->owner = GetTileOwner(tile);
|
|
switch (GetRailTileType(tile)) {
|
|
case RAIL_TILE_NORMAL:
|
|
td->str = STR_1021_RAILROAD_TRACK;
|
|
break;
|
|
|
|
case RAIL_TILE_SIGNALS: {
|
|
const StringID signal_type[] = {
|
|
STR_RAILROAD_TRACK_WITH_NORMAL_SIGNALS,
|
|
STR_RAILROAD_TRACK_WITH_PRESIGNALS,
|
|
STR_RAILROAD_TRACK_WITH_EXITSIGNALS,
|
|
STR_RAILROAD_TRACK_WITH_COMBOSIGNALS
|
|
};
|
|
|
|
td->str = signal_type[GetSignalType(tile)];
|
|
break;
|
|
}
|
|
|
|
case RAIL_TILE_DEPOT_WAYPOINT:
|
|
default:
|
|
td->str = (GetRailTileSubtype(tile) == RAIL_SUBTYPE_DEPOT) ?
|
|
STR_1023_RAILROAD_TRAIN_DEPOT : STR_LANDINFO_WAYPOINT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ChangeTileOwner_Track(TileIndex tile, PlayerID old_player, PlayerID new_player)
|
|
{
|
|
if (!IsTileOwner(tile, old_player)) return;
|
|
|
|
if (new_player != OWNER_SPECTATOR) {
|
|
SetTileOwner(tile, new_player);
|
|
} else {
|
|
DoCommand(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 uint32 VehicleEnter_Track(Vehicle *v, TileIndex tile, int x, int y)
|
|
{
|
|
byte fract_coord;
|
|
byte fract_coord_leave;
|
|
DiagDirection 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 = GetRailDepotDirection(tile);
|
|
|
|
/* 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 (DiagDirToDir(ReverseDiagDir(dir)) == v->direction) {
|
|
/* enter the depot */
|
|
v->u.rail.track = 0x80,
|
|
v->vehstatus |= VS_HIDDEN; /* hide it */
|
|
v->direction = ReverseDir(v->direction);
|
|
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 (DiagDirToDir(dir) == v->direction) {
|
|
/* leave the depot? */
|
|
if ((v = v->next) != NULL) {
|
|
v->vehstatus &= ~VS_HIDDEN;
|
|
v->u.rail.track = (DiagDirToAxis(dir) == AXIS_X ? 1 : 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 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 */
|
|
GetSlopeTileh_Track, /* get_slope_tileh_proc */
|
|
};
|