Initial support for rail custom bridge heads

pull/73/head
Jonathan G Rennison 6 years ago
parent 25af12814b
commit 77362b829a

@ -1473,10 +1473,76 @@
</tr>
</table>
</li>
<li style="color: blue">m2 bits 2..0: rail bridge heads track reserved for pbs
<table style="color: blue">
<tr>
<td><tt>0</tt>&nbsp; </td>
<td>not reserved</td>
</tr>
<tr>
<td><tt>1</tt>&nbsp; </td>
<td>X direction</td>
</tr>
<tr>
<td><tt>2</tt>&nbsp; </td>
<td>Y direction</td>
</tr>
<tr>
<td><tt>3</tt>&nbsp; </td>
<td>north corner (W-E)</td>
</tr>
<tr>
<td><tt>4</tt>&nbsp; </td>
<td>south corner (W-E)</td>
</tr>
<tr>
<td><tt>5</tt>&nbsp; </td>
<td>west corner (N-S)</td>
</tr>
<tr>
<td><tt>6</tt>&nbsp; </td>
<td>east corner (N-S)</td>
</tr>
</table>
</li>
<li style="color: blue">m2 bit 3: rail bridge heads opposite track is reserved, too</li>
<li>m3 bits 7..4: <a href="#OwnershipInfo">owner</a> of tram</li>
<li>m3 bits 3..0: <a href="#TrackType">track type</a> for railway</li>
<li>m5 bit 4: pbs reservation state for railway</li>
<li>m5 bits 7 clear: tunnel entrance/exit</li>
<li style="color: blue">m4 bits 5..0: rail bridge heads track layout: bit set = track present:
<table style="color: blue">
<tr>
<td nowrap valign=top>bit 0: </td>
<td align=left>in the X direction</td>
</tr>
<tr>
<td nowrap valign=top>bit 1: </td>
<td align=left>in the Y direction</td>
</tr>
<tr>
<td nowrap valign=top>bit 2: </td>
<td align=left>in the north corner (direction W-E)</td>
</tr>
<tr>
<td nowrap valign=top>bit 3: </td>
<td align=left>in the south corner (direction W-E)</td>
</tr>
<tr>
<td nowrap valign=top>bit 4: </td>
<td align=left>in the west corner (direction N-S)</td>
</tr>
<tr>
<td nowrap valign=top>bit 5: </td>
<td align=left>in the east corner (direction N-S)</td>
</tr>
</table>
</li>
<li>m5 bit 4: pbs reservation state for railway (tunnel only)</li>
<li>m5 bit 7 clear: tunnel entrance/exit</li>
<li>m5 bit 7 set: bridge ramp
<ul>
<li>m6 bits 5..2: <a name="BridgeType">bridge type</a>:

@ -347,7 +347,7 @@ the array so you can quickly see what is used and what is not.
<td class="bits">-inherit-</td>
<td class="bits"><span class="free">OOOO OOOO</span> <span class="used_p">PPPP PPPP</span></td>
<td class="bits">-inherit-</td>
<td class="bits">-inherit-</td>
<td class="bits"><span class="free">OO</span><span class="used_p">PP PPPP</span></td>
<td class="bits">-inherit-</td>
<td class="bits"><span class="free">OO</span>XX XX<span class="free">OO</span></td>
<td class="bits">-inherit-</td>

@ -171,9 +171,22 @@ static inline void MakeRoadBridgeRamp(TileIndex t, Owner o, Owner owner_road, Ow
* @param d the direction this ramp must be facing
* @param r the rail type of the bridge
*/
static inline void MakeRailBridgeRamp(TileIndex t, Owner o, BridgeType bridgetype, DiagDirection d, RailType r)
static inline void MakeRailBridgeRamp(TileIndex t, Owner o, BridgeType bridgetype, DiagDirection d, RailType r, bool upgrade)
{
// Backup custom bridgehead data.
uint custom_bridge_head_reservation_backup = GB(_m[t].m2, 0, 4);
uint custom_bridge_head_tracks_backup = GB(_m[t].m4, 0, 6);
MakeBridgeRamp(t, o, bridgetype, d, TRANSPORT_RAIL, r);
if (upgrade) {
// Restore custom bridgehead data if we're upgrading an existing bridge.
SB(_m[t].m2, 0, 4, custom_bridge_head_reservation_backup);
SB(_m[t].m4, 0, 6, custom_bridge_head_tracks_backup);
} else {
// Set bridge head tracks to axial track only.
SB(_m[t].m4, 0, 6, DiagDirToDiagTrackBits(d));
}
}
/**
@ -255,4 +268,195 @@ static inline void SetCustomBridgeHeadRoadBits(TileIndex t, RoadType rt, RoadBit
}
}
/**
* Checks if this tile is a rail bridge head
* @param t The tile to analyze
* @return true if it is a rail bridge head
*/
static inline bool IsRailBridgeHeadTile(TileIndex t)
{
return IsBridgeTile(t) && (TransportType)GB(_m[t].m5, 2, 2) == TRANSPORT_RAIL;
}
/**
* Checks if this tile is a flat rail bridge head
* @param t The tile to analyze
* @return true if it is a flat rail bridge head
*/
static inline bool IsFlatRailBridgeHeadTile(TileIndex t)
{
return IsRailBridgeHeadTile(t) && HasBridgeFlatRamp(GetTileSlope(t), DiagDirToAxis((DiagDirection)GB(_m[t].m5, 0, 2)));
}
/**
* Returns the track bits for a (possibly custom) rail bridge head
* @param tile the tile to get the track bits from
* @pre IsRailBridgeHeadTile(t)
* @return road bits for the bridge head
*/
static inline TrackBits GetCustomBridgeHeadTrackBits(TileIndex t)
{
assert(IsRailBridgeHeadTile(t));
return (TrackBits)GB(_m[t].m4, 0, 6);
}
/**
* Sets the road track for a (possibly custom) rail bridge head
* @param t the tile to set the track bits of
* @param b the new track bits for the tile
* @pre IsRailBridgeHeadTile(t)
*/
static inline void SetCustomBridgeHeadTrackBits(TileIndex t, TrackBits b)
{
assert(IsRailBridgeHeadTile(t));
SB(_m[t].m4, 0, 6, b);
}
/**
* Checks if this rail bridge head is a custom bridge head
* @param t The tile to analyze
* @pre IsRailBridgeHeadTile(t)
* @return true if it is a custom bridge head
*/
static inline bool IsRailCustomBridgeHead(TileIndex t)
{
assert(IsRailBridgeHeadTile(t));
return GetCustomBridgeHeadTrackBits(t) != DiagDirToDiagTrackBits((DiagDirection)GB(_m[t].m5, 0, 2));
}
/**
* Checks if this tile is a rail bridge head with a custom bridge head
* @param t The tile to analyze
* @return true if it is a rail bridge head with a custom bridge head
*/
static inline bool IsRailCustomBridgeHeadTile(TileIndex t)
{
return IsRailBridgeHeadTile(t) && IsRailCustomBridgeHead(t);
}
/**
* Checks if this tile is a bridge head with a custom bridge head
* @param t The tile to analyze
* @return true if it is a bridge head with a custom bridge head
*/
static inline bool IsCustomBridgeHeadTile(TileIndex t)
{
return IsRailCustomBridgeHeadTile(t) || IsRoadCustomBridgeHeadTile(t);
}
/**
* Get the reserved track bits for a rail bridge head
* @pre IsRailBridgeHeadTile(t)
* @param t the tile
* @return reserved track bits
*/
static inline TrackBits GetBridgeReservationTrackBits(TileIndex t)
{
assert(IsRailBridgeHeadTile(t));
byte track_b = GB(_m[t].m2, 0, 3);
Track track = (Track)(track_b - 1); // map array saves Track+1
if (track_b == 0) return TRACK_BIT_NONE;
return (TrackBits)(TrackToTrackBits(track) | (HasBit(_m[t].m2, 3) ? TrackToTrackBits(TrackToOppositeTrack(track)) : 0));
}
/**
* Sets the reserved track bits of the rail bridge head
* @pre IsRailBridgeHeadTile(t)
* @param t the tile to change
* @param b the track bits
*/
static inline void SetBridgeReservationTrackBits(TileIndex t, TrackBits b)
{
assert(IsRailBridgeHeadTile(t));
assert(!TracksOverlap(b));
Track track = RemoveFirstTrack(&b);
SB(_m[t].m2, 0, 3, track == INVALID_TRACK ? 0 : track + 1);
SB(_m[t].m2, 3, 1, (byte)(b != TRACK_BIT_NONE));
}
/**
* Try to reserve a specific track on a rail bridge head tile
* @pre IsRailBridgeHeadTile(t) && HasBit(GetCustomBridgeHeadTrackBits(tile), t)
* @param tile the tile
* @param t the rack to reserve
* @return true if successful
*/
static inline bool TryReserveRailBridgeHead(TileIndex tile, Track t)
{
assert(IsRailBridgeHeadTile(tile));
assert(HasBit(GetCustomBridgeHeadTrackBits(tile), t));
TrackBits bits = TrackToTrackBits(t);
TrackBits res = GetBridgeReservationTrackBits(tile);
if ((res & bits) != TRACK_BIT_NONE) return false; // already reserved
res |= bits;
if (TracksOverlap(res)) return false; // crossing reservation present
SetBridgeReservationTrackBits(tile, res);
return true;
}
/**
* Lift the reservation of a specific track on a rail bridge head tile
* @pre IsRailBridgeHeadTile(t) && HasBit(GetCustomBridgeHeadTrackBits(tile), t)
* @param tile the tile
* @param t the track to free
*/
static inline void UnreserveRailBridgeHeadTrack(TileIndex tile, Track t)
{
assert(IsRailBridgeHeadTile(tile));
assert(HasBit(GetCustomBridgeHeadTrackBits(tile), t));
TrackBits res = GetBridgeReservationTrackBits(tile);
res &= ~TrackToTrackBits(t);
SetBridgeReservationTrackBits(tile, res);
}
/**
* Get the possible track bits of the bridge head tile onto/across the bridge
* @pre IsRailBridgeHeadTile(t)
* @param t the tile
* @return reservation state
*/
static inline TrackBits GetAcrossBridgePossibleTrackBits(TileIndex t)
{
assert(IsRailBridgeHeadTile(t));
return DiagdirReachesTracks(ReverseDiagDir((DiagDirection)GB(_m[t].m5, 0, 2)));
}
/**
* Get the reserved track bits of the bridge head tile onto/across the bridge
* @pre IsBridgeTile(t) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return reservation state
*/
static inline TrackBits GetAcrossBridgeReservationTrackBits(TileIndex t)
{
return GetBridgeReservationTrackBits(t) & GetAcrossBridgePossibleTrackBits(t);
}
/**
* Get the reservation state of the bridge head tile onto/across the bridge
* @pre IsBridgeTile(t) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return reservation state
*/
static inline bool HasAcrossBridgeReservation(TileIndex t)
{
return GetAcrossBridgeReservationTrackBits(t) != TRACK_BIT_NONE;
}
/**
* Lift the reservation of a specific track on a rail bridge head tile
* @pre IsRailBridgeHeadTile(t)
* @param tile the tile
*/
static inline void UnreserveAcrossRailBridgeHead(TileIndex tile)
{
assert(IsRailBridgeHeadTile(tile));
TrackBits res = GetAcrossBridgeReservationTrackBits(tile);
if (res != TRACK_BIT_NONE) {
SetBridgeReservationTrackBits(tile, GetBridgeReservationTrackBits(tile) & ~res);
}
}
#endif /* BRIDGE_MAP_H */

@ -103,7 +103,7 @@ static TrackBits GetRailTrackBitsUniversal(TileIndex t, byte *override)
if (override != NULL && (IsTunnel(t) || GetTunnelBridgeLength(t, GetOtherBridgeEnd(t)) > 0)) {
*override = 1 << GetTunnelBridgeDirection(t);
}
return DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t));
return GetTunnelBridgeTrackBits(t);
case MP_ROAD:
if (!IsLevelCrossing(t)) return TRACK_BIT_NONE;
@ -199,6 +199,8 @@ static void AdjustTileh(TileIndex tile, Slope *tileh)
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
if (IsTunnel(tile)) {
*tileh = SLOPE_STEEP; // XXX - Hack to make tunnel entrances to always have a pylon
} else if (IsRailCustomBridgeHeadTile(tile)) {
/* no change */
} else if (*tileh != SLOPE_FLAT) {
*tileh = SLOPE_FLAT;
} else {

@ -352,7 +352,7 @@ Slope GetFoundationSlope(TileIndex tile, int *z)
bool HasFoundationNW(TileIndex tile, Slope slope_here, uint z_here)
{
if (IsRoadCustomBridgeHeadTile(tile) && GetTunnelBridgeDirection(tile) == DIAGDIR_NW) return false;
if (IsCustomBridgeHeadTile(tile) && GetTunnelBridgeDirection(tile) == DIAGDIR_NW) return false;
int z;
@ -371,7 +371,7 @@ bool HasFoundationNW(TileIndex tile, Slope slope_here, uint z_here)
bool HasFoundationNE(TileIndex tile, Slope slope_here, uint z_here)
{
if (IsRoadCustomBridgeHeadTile(tile) && GetTunnelBridgeDirection(tile) == DIAGDIR_NE) return false;
if (IsCustomBridgeHeadTile(tile) && GetTunnelBridgeDirection(tile) == DIAGDIR_NE) return false;
int z;

@ -1703,6 +1703,8 @@ STR_CONFIG_SETTING_REVERSE_AT_SIGNALS_HELPTEXT :Allow trains to
STR_CONFIG_SETTING_ENABLE_ROAD_CUSTOM_BRIDGE_HEADS :Enable road custom bridge heads: {STRING2}
STR_CONFIG_SETTING_ENABLE_ROAD_CUSTOM_BRIDGE_HEADS_HELPTEXT :Allow road bridges to have custom, non-straight flat entry/exit tiles
STR_CONFIG_SETTING_ENABLE_RAIL_CUSTOM_BRIDGE_HEADS :Enable rail custom bridge heads: {STRING2}
STR_CONFIG_SETTING_ENABLE_RAIL_CUSTOM_BRIDGE_HEADS_HELPTEXT :Allow rail bridges to have custom, non-straight flat entry/exit tiles
STR_CONFIG_SETTING_QUERY_CAPTION :{WHITE}Change setting value

@ -209,7 +209,7 @@ static uint32 GetRailContinuationInfo(TileIndex tile)
/* With tunnels and bridges the tile has tracks, but they are not necessarily connected
* with the next tile because the ramp is not going in the right direction. */
if (IsTileType(neighbour_tile, MP_TUNNELBRIDGE) && GetTunnelBridgeDirection(neighbour_tile) != *diagdir) {
if (IsTileType(neighbour_tile, MP_TUNNELBRIDGE) && GetTunnelBridgeDirection(neighbour_tile) == ReverseDiagDir(*diagdir)) {
continue;
}

@ -125,7 +125,7 @@ struct CFollowTrackT
m_old_td = old_td;
m_err = EC_NONE;
assert(((TrackStatusToTrackdirBits(GetTileTrackStatus(m_old_tile, TT(), IsRoadTT() ? RoadVehicle::From(m_veh)->compatible_roadtypes : 0)) & TrackdirToTrackdirBits(m_old_td)) != 0) ||
(IsTram() && GetSingleTramBit(m_old_tile) != INVALID_DIAGDIR)); // Disable the assertion for single tram bits
(IsTram() && GetSingleTramBit(m_old_tile) != INVALID_DIAGDIR) || (IsRailTT() && IsRailCustomBridgeHeadTile(m_old_tile))); // Disable the assertion for single tram bits
m_exitdir = TrackdirToExitdir(m_old_td);
if (ForcedReverse()) return true;
if (!CanExitOldTile()) return false;
@ -153,7 +153,7 @@ struct CFollowTrackT
return false;
}
if (!Allow90degTurns()) {
if (!Allow90degTurns() && m_tiles_skipped == 0) {
m_new_td_bits &= (TrackdirBits)~(int)TrackdirCrossesTrackdirs(m_old_td);
if (m_new_td_bits == TRACKDIR_BIT_NONE) {
m_err = EC_90DEG;
@ -216,7 +216,7 @@ protected:
m_tiles_skipped = GetTunnelBridgeLength(m_new_tile, m_old_tile);
return;
}
if (!IsRoadCustomBridgeHeadTile(m_old_tile)) assert(ReverseDiagDir(enterdir) == m_exitdir);
if (!IsRoadCustomBridgeHeadTile(m_old_tile) && !IsRailCustomBridgeHeadTile(m_old_tile)) assert(ReverseDiagDir(enterdir) == m_exitdir);
}
/* normal or station tile, do one step */
@ -365,13 +365,22 @@ protected:
}
}
} else { // IsBridge(m_new_tile)
DiagDirection ramp_enderdir = GetTunnelBridgeDirection(m_new_tile);
if (!m_is_bridge && ramp_enderdir == ReverseDiagDir(m_exitdir)) {
m_err = EC_NO_WAY;
return false;
}
if (!m_is_bridge && IsRoadTT() && IsRoadCustomBridgeHeadTile(m_new_tile)) {
if (!(DiagDirToRoadBits(ReverseDiagDir(m_exitdir)) & GetCustomBridgeHeadRoadBits(m_new_tile, IsTram() ? ROADTYPE_TRAM : ROADTYPE_ROAD))) {
m_err = EC_NO_WAY;
return false;
}
} else if (!m_is_bridge && IsRailTT() && IsRailCustomBridgeHeadTile(m_new_tile)) {
if (!(DiagdirReachesTracks(m_exitdir) & GetCustomBridgeHeadTrackBits(m_new_tile))) {
m_err = EC_NO_WAY;
return false;
}
} else if (!m_is_bridge) {
DiagDirection ramp_enderdir = GetTunnelBridgeDirection(m_new_tile);
if (ramp_enderdir != m_exitdir) {
m_err = EC_NO_WAY;
return false;

@ -564,7 +564,7 @@ bool YapfTrainCheckReverse(const Train *v)
int reverse_penalty = 0;
if (v->track == TRACK_BIT_WORMHOLE) {
if (v->track & TRACK_BIT_WORMHOLE) {
/* front in tunnel / on bridge */
DiagDirection dir_into_wormhole = GetTunnelBridgeDirection(tile);
@ -579,7 +579,7 @@ bool YapfTrainCheckReverse(const Train *v)
reverse_penalty -= DistanceManhattan(cur_tile, tile) * YAPF_TILE_LENGTH;
}
if (last_veh->track == TRACK_BIT_WORMHOLE) {
if (last_veh->track & TRACK_BIT_WORMHOLE) {
/* back in tunnel / on bridge */
DiagDirection dir_into_wormhole = GetTunnelBridgeDirection(tile_rev);

@ -81,7 +81,8 @@ void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool
*/
bool TryReserveRailTrack(TileIndex tile, Track t, bool trigger_stations)
{
assert((GetTileTrackStatus(tile, TRANSPORT_RAIL, 0) & TrackToTrackBits(t)) != 0);
assert_msg((TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0)) & TrackToTrackBits(t)) != 0,
"%X, %X, %X, %X", TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0)), tile, t, TrackToTrackBits(t));
if (_settings_client.gui.show_track_reservation) {
/* show the reserved rail if needed */
@ -123,9 +124,14 @@ bool TryReserveRailTrack(TileIndex tile, Track t, bool trigger_stations)
break;
case MP_TUNNELBRIDGE:
if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL && !GetTunnelBridgeReservationTrackBits(tile)) {
SetTunnelBridgeReservation(tile, true);
return true;
if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL) {
if (IsTunnel(tile) && !HasTunnelReservation(tile)) {
SetTunnelReservation(tile, true);
return true;
}
if (IsBridge(tile)) {
return TryReserveRailBridgeHead(tile, t);
}
}
break;
@ -177,7 +183,13 @@ void UnreserveRailTrack(TileIndex tile, Track t)
break;
case MP_TUNNELBRIDGE:
if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL) SetTunnelBridgeReservation(tile, false);
if (GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL) {
if (IsTunnel(tile)) {
SetTunnelReservation(tile, false);
} else {
UnreserveRailBridgeHeadTrack(tile, t);
}
}
break;
default:
@ -270,7 +282,7 @@ static Vehicle *FindTrainOnTrackEnum(Vehicle *v, void *data)
if (v->type != VEH_TRAIN || (v->vehstatus & VS_CRASHED)) return NULL;
Train *t = Train::From(v);
if (t->track == TRACK_BIT_WORMHOLE || HasBit((TrackBits)t->track, TrackdirToTrack(info->res.trackdir))) {
if (t->track & TRACK_BIT_WORMHOLE || HasBit((TrackBits)t->track, TrackdirToTrack(info->res.trackdir))) {
t = t->First();
/* ALWAYS return the lowest ID (anti-desync!) */

@ -424,6 +424,25 @@ static CommandCost CheckRailSlope(Slope tileh, TrackBits rail_bits, TrackBits ex
return CommandCost(EXPENSES_CONSTRUCTION, f_new != f_old ? _price[PR_BUILD_FOUNDATION] : (Money)0);
}
bool IsValidFlatRailBridgeHeadTrackBits(Slope normalised_slope, DiagDirection bridge_direction, TrackBits tracks)
{
/* bridge_direction c1 c2
* 0 0 1
* 1 0 3
* 2 2 3
* 3 2 1
*/
const Corner c1 = (Corner) (bridge_direction & 2);
const Corner c2 = (Corner) (((bridge_direction + 1) & 2) + 1);
auto test_corner = [&](Corner c) -> bool {
if (normalised_slope & SlopeWithOneCornerRaised(c)) return true;
Slope effective_slope = normalised_slope | SlopeWithOneCornerRaised(OppositeCorner(c));
assert(effective_slope < lengthof(_valid_tracks_on_leveled_foundation));
return (_valid_tracks_on_leveled_foundation[effective_slope] & tracks) == tracks;
};
return test_corner(c1) && test_corner(c2);
}
/* Validate functions for rail building */
static inline bool ValParamTrackOrientation(Track track)
{
@ -443,6 +462,7 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u
{
RailType railtype = Extract<RailType, 0, 4>(p1);
Track track = Extract<Track, 0, 3>(p2);
bool disable_custom_bridge_heads = HasBit(p2, 4);
CommandCost cost(EXPENSES_CONSTRUCTION);
if (!ValParamRailtype(railtype) || !ValParamTrackOrientation(track)) return CMD_ERROR;
@ -497,6 +517,52 @@ CommandCost CmdBuildSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1, u
break;
}
case MP_TUNNELBRIDGE: {
CommandCost ret = CheckTileOwnership(tile);
if (ret.Failed()) return ret;
if (disable_custom_bridge_heads || !_settings_game.construction.rail_custom_bridge_heads || !IsFlatRailBridgeHeadTile(tile)) return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); // just get appropriate error message
if (!IsCompatibleRail(GetRailType(tile), railtype)) return_cmd_error(STR_ERROR_IMPOSSIBLE_TRACK_COMBINATION);
if (GetRailType(tile) != railtype && !HasPowerOnRail(railtype, GetRailType(tile))) return_cmd_error(STR_ERROR_CAN_T_CONVERT_RAIL);
const DiagDirection entrance_dir = GetTunnelBridgeDirection(tile);
const TrackBits axial_track = DiagDirToDiagTrackBits(entrance_dir);
const TrackBits existing = GetCustomBridgeHeadTrackBits(tile);
const TrackBits future = existing | trackbit;
if (existing == future) return_cmd_error(STR_ERROR_ALREADY_BUILT);
if (flags & DC_NO_RAIL_OVERLAP) {
if (future != TRACK_BIT_HORZ && future != TRACK_BIT_VERT) {
return_cmd_error(STR_ERROR_IMPOSSIBLE_TRACK_COMBINATION);
}
}
if ((trackbit & ~axial_track) && !_settings_game.construction.build_on_slopes) {
return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
}
/* Steep slopes behave the same as slopes with one corner raised. */
const Slope normalised_tileh = IsSteepSlope(tileh) ? SlopeWithOneCornerRaised(GetHighestSlopeCorner(tileh)) : tileh;
if (!IsValidFlatRailBridgeHeadTrackBits(normalised_tileh, GetTunnelBridgeDirection(tile), future)) {
return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
}
const TileIndex other_end = GetOtherTunnelBridgeEnd(tile);
ret = TunnelBridgeIsFree(tile, other_end);
if (ret.Failed()) return ret;
if (flags & DC_EXEC) {
SetCustomBridgeHeadTrackBits(tile, future);
Company::Get(GetTileOwner(tile))->infrastructure.rail[GetRailType(tile)] += GetTunnelBridgeHeadOnlyRailInfrastructureCountFromTrackBits(future) - GetTunnelBridgeHeadOnlyRailInfrastructureCountFromTrackBits(existing);
DirtyCompanyInfrastructureWindows(_current_company);
}
break;
}
case MP_ROAD: {
/* Level crossings may only be built on these slopes */
if (!HasBit(VALID_LEVEL_CROSSING_SLOPES, tileh)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION);
@ -710,6 +776,44 @@ CommandCost CmdRemoveSingleRail(TileIndex tile, DoCommandFlag flags, uint32 p1,
break;
}
case MP_TUNNELBRIDGE: {
CommandCost ret = CheckTileOwnership(tile);
if (ret.Failed()) return ret;
if (!IsFlatRailBridgeHeadTile(tile) || GetCustomBridgeHeadTrackBits(tile) == DiagDirToDiagTrackBits(GetTunnelBridgeDirection(tile))) {
return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); // just get appropriate error message
}
const TrackBits present = GetCustomBridgeHeadTrackBits(tile);
if ((present & trackbit) == 0) return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
if (present == (TRACK_BIT_X | TRACK_BIT_Y)) crossing = true;
const TrackBits future = present ^ trackbit;
if ((GetAcrossBridgePossibleTrackBits(tile) & future) == 0) return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); // just get appropriate error message
const TileIndex other_end = GetOtherTunnelBridgeEnd(tile);
ret = TunnelBridgeIsFree(tile, other_end);
if (ret.Failed()) return ret;
cost.AddCost(RailClearCost(GetRailType(tile)));
if (flags & DC_EXEC) {
owner = GetTileOwner(tile);
if (HasReservedTracks(tile, trackbit)) {
v = GetTrainForReservation(tile, track);
if (v != NULL) FreeTrainTrackReservation(v);
}
SetCustomBridgeHeadTrackBits(tile, future);
Company::Get(GetTileOwner(tile))->infrastructure.rail[GetRailType(tile)] -= GetTunnelBridgeHeadOnlyRailInfrastructureCountFromTrackBits(present) - GetTunnelBridgeHeadOnlyRailInfrastructureCountFromTrackBits(future);
DirtyCompanyInfrastructureWindows(_current_company);
}
break;
}
default: return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK);
}
@ -1224,13 +1328,27 @@ static bool CheckSignalAutoFill(TileIndex &tile, Trackdir &trackdir, int &signal
TileIndex orig_tile = tile; // backup old value
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return false;
if (GetTunnelBridgeDirection(tile) != TrackdirToExitdir(trackdir)) return false;
signal_ctr += IsDiagonalTrackdir(trackdir) ? 2 : 1;
if (GetTunnelBridgeDirection(tile) == TrackdirToExitdir(trackdir)) {
/* Skip to end of tunnel or bridge
* note that tile is a parameter by reference, so it must be updated */
tile = GetOtherTunnelBridgeEnd(tile);
signal_ctr += GetTunnelBridgeLength(orig_tile, tile) * 2;
/* Check for track bits on the new tile */
trackdirbits = TrackStatusToTrackdirBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0));
if (TracksOverlap(TrackdirBitsToTrackBits(trackdirbits))) return false;
trackdirbits &= TrackdirReachesTrackdirs(trackdir);
/* Skip to end of tunnel or bridge
* note that tile is a parameter by reference, so it must be updated */
tile = GetOtherTunnelBridgeEnd(tile);
/* Get the first track dir */
trackdir = RemoveFirstTrackdir(&trackdirbits);
signal_ctr += (GetTunnelBridgeLength(orig_tile, tile) + 2) * 2;
/* Any left? It's a junction so we stop */
if (trackdirbits != TRACKDIR_BIT_NONE) return false;
signal_ctr += IsDiagonalTrackdir(trackdir) ? 2 : 1;
}
return true;
}
@ -1599,6 +1717,24 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
SmallVector<Train *, 2> vehicles_affected;
auto find_train_reservations = [&vehicles_affected, &totype](TileIndex tile, TrackBits reserved) {
Track track;
while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) {
Train *v = GetTrainForReservation(tile, track);
if (v != NULL && !HasPowerOnRail(v->railtype, totype)) {
/* No power on new rail type, reroute. */
FreeTrainTrackReservation(v);
*vehicles_affected.Append() = v;
}
}
};
auto yapf_notify_track_change = [](TileIndex tile, TrackBits tracks) {
while (tracks != TRACK_BIT_NONE) {
YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks));
}
};
/* Vehicle on the tile when not converting Rail <-> ElRail
* Tunnels and bridges have special check later */
if (tt != MP_TUNNELBRIDGE) {
@ -1610,16 +1746,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
}
}
if (flags & DC_EXEC) { // we can safely convert, too
TrackBits reserved = GetReservedTrackbits(tile);
Track track;
while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) {
Train *v = GetTrainForReservation(tile, track);
if (v != NULL && !HasPowerOnRail(v->railtype, totype)) {
/* No power on new rail type, reroute. */
FreeTrainTrackReservation(v);
*vehicles_affected.Append() = v;
}
}
find_train_reservations(tile, GetReservedTrackbits(tile));
/* Update the company infrastructure counters. */
if (!IsRailStationTile(tile) || !IsStationTileBlocked(tile)) {
@ -1660,10 +1787,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
default: // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS
if (flags & DC_EXEC) {
/* notify YAPF about the track layout change */
TrackBits tracks = GetTrackBits(tile);
while (tracks != TRACK_BIT_NONE) {
YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks));
}
yapf_notify_track_change(tile, GetTrackBits(tile));
}
cost.AddCost(RailConvertCost(type, totype) * CountBits(GetTrackBits(tile)));
break;
@ -1692,22 +1816,18 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
}
}
uint middle_len = GetTunnelBridgeLength(tile, endtile);
uint num_raw_pieces = middle_len + CountBits(GetTunnelBridgeTrackBits(tile)) + CountBits(GetTunnelBridgeTrackBits(endtile));
if (flags & DC_EXEC) {
Track track = DiagDirToDiagTrack(GetTunnelBridgeDirection(tile));
if (HasTunnelBridgeReservation(tile)) {
Train *v = GetTrainForReservation(tile, track);
if (v != NULL && !HasPowerOnRail(v->railtype, totype)) {
/* No power on new rail type, reroute. */
FreeTrainTrackReservation(v);
*vehicles_affected.Append() = v;
}
}
find_train_reservations(tile, GetTunnelBridgeReservationTrackBits(tile));
find_train_reservations(endtile, GetTunnelBridgeReservationTrackBits(endtile));
/* Update the company infrastructure counters. */
uint num_pieces = (GetTunnelBridgeLength(tile, endtile) + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR;
uint num_infra_pieces = (middle_len* TUNNELBRIDGE_TRACKBIT_FACTOR) + GetTunnelBridgeHeadOnlyRailInfrastructureCount(tile) + GetTunnelBridgeHeadOnlyRailInfrastructureCount(endtile);
Company *c = Company::Get(GetTileOwner(tile));
c->infrastructure.rail[GetRailType(tile)] -= num_pieces;
c->infrastructure.rail[totype] += num_pieces;
c->infrastructure.rail[GetRailType(tile)] -= num_infra_pieces;
c->infrastructure.rail[totype] += num_infra_pieces;
DirtyCompanyInfrastructureWindows(c->index);
SetRailType(tile, totype);
@ -1716,8 +1836,9 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
FindVehicleOnPos(tile, &affected_trains, &UpdateTrainPowerProc);
FindVehicleOnPos(endtile, &affected_trains, &UpdateTrainPowerProc);
YapfNotifyTrackLayoutChange(tile, track);
YapfNotifyTrackLayoutChange(endtile, track);
/* notify YAPF about the track layout change */
yapf_notify_track_change(tile, GetTunnelBridgeTrackBits(tile));
yapf_notify_track_change(endtile, GetTunnelBridgeTrackBits(endtile));
if (IsBridge(tile)) {
MarkBridgeDirty(tile);
@ -1727,7 +1848,7 @@ CommandCost CmdConvertRail(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
}
}
cost.AddCost((GetTunnelBridgeLength(tile, endtile) + 2) * RailConvertCost(type, totype));
cost.AddCost(num_raw_pieces * RailConvertCost(type, totype));
break;
}
@ -2061,10 +2182,19 @@ static inline void DrawTrackSprite(SpriteID sprite, PaletteID pal, const TileInf
DrawGroundSprite(sprite, pal, NULL, 0, (ti->tileh & s) ? -8 : 0);
}
static RailGroundType GetRailOrBridgeGroundType(TileInfo *ti) {
if (IsTileType(ti->tile, MP_TUNNELBRIDGE)) {
return HasTunnelBridgeSnowOrDesert(ti->tile) ? RAIL_GROUND_ICE_DESERT : RAIL_GROUND_GRASS;
} else {
return GetRailGroundType(ti->tile);
}
}
static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailtypeInfo *rti)
{
RailGroundType rgt = GetRailGroundType(ti->tile);
Foundation f = GetRailFoundation(ti->tileh, track);
const bool is_bridge = IsTileType(ti->tile, MP_TUNNELBRIDGE);
RailGroundType rgt = GetRailOrBridgeGroundType(ti);
Foundation f = is_bridge ? FOUNDATION_LEVELED : GetRailFoundation(ti->tileh, track);
Corner halftile_corner = CORNER_INVALID;
if (IsNonContinuousFoundation(f)) {
@ -2103,7 +2233,10 @@ static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailtypeIn
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
SpriteID ground = GetCustomRailSprite(rti, ti->tile, RTSG_GROUND);
TrackBits pbs = _settings_client.gui.show_track_reservation ? GetRailReservationTrackBits(ti->tile) : TRACK_BIT_NONE;
TrackBits pbs = TRACK_BIT_NONE;
if (_settings_client.gui.show_track_reservation) {
pbs = is_bridge ? GetTunnelBridgeReservationTrackBits(ti->tile) : GetRailReservationTrackBits(ti->tile);
}
if (track == TRACK_BIT_NONE) {
/* Half-tile foundation, no track here? */
@ -2213,7 +2346,7 @@ static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailtypeIn
* @param ti TileInfo
* @param track TrackBits to draw
*/
static void DrawTrackBits(TileInfo *ti, TrackBits track)
void DrawTrackBits(TileInfo *ti, TrackBits track)
{
const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
@ -2222,8 +2355,9 @@ static void DrawTrackBits(TileInfo *ti, TrackBits track)
return;
}
RailGroundType rgt = GetRailGroundType(ti->tile);
Foundation f = GetRailFoundation(ti->tileh, track);
const bool is_bridge = IsTileType(ti->tile, MP_TUNNELBRIDGE);
RailGroundType rgt = GetRailOrBridgeGroundType(ti);
Foundation f = is_bridge ? FOUNDATION_LEVELED : GetRailFoundation(ti->tileh, track);
Corner halftile_corner = CORNER_INVALID;
if (IsNonContinuousFoundation(f)) {
@ -2314,7 +2448,7 @@ static void DrawTrackBits(TileInfo *ti, TrackBits track)
/* PBS debugging, draw reserved tracks darker */
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation) {
/* Get reservation, but mask track on halftile slope */
TrackBits pbs = GetRailReservationTrackBits(ti->tile) & track;
TrackBits pbs = (is_bridge ? GetTunnelBridgeReservationTrackBits(ti->tile) : GetRailReservationTrackBits(ti->tile)) & track;
if (pbs & TRACK_BIT_X) {
if (ti->tileh == SLOPE_FLAT || ti->tileh == SLOPE_ELEVATED) {
DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH);

@ -1212,6 +1212,17 @@ bool AfterLoadGame()
}
}
if (!SlXvIsFeaturePresent(XSLFI_CUSTOM_BRIDGE_HEADS, 2)) {
/* change map bits for rail bridge heads */
for (TileIndex t = 0; t < map_size; t++) {
if (IsBridgeTile(t) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL) {
SetCustomBridgeHeadTrackBits(t, DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)));
SetBridgeReservationTrackBits(t, HasBit(_m[t].m5, 4) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE);
ClrBit(_m[t].m5, 4);
}
}
}
/* Elrails got added in rev 24 */
if (IsSavegameVersionBefore(24)) {
RailType min_rail = RAILTYPE_ELECTRIC;
@ -1924,7 +1935,7 @@ bool AfterLoadGame()
break;
case MP_TUNNELBRIDGE: // Clear PBS reservation on tunnels/bridges
if (GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL) SetTunnelBridgeReservation(t, false);
if (GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL) UnreserveAcrossRailTunnelBridge(t);
break;
default: break;

@ -201,12 +201,11 @@ void AfterLoadCompanyStats()
/* Count each tunnel/bridge TUNNELBRIDGE_TRACKBIT_FACTOR times to simulate
* the higher structural maintenance needs, and don't forget the end tiles. */
const uint middle_len = GetTunnelBridgeLength(tile, other_end) * TUNNELBRIDGE_TRACKBIT_FACTOR;
const uint len = middle_len + (2 * TUNNELBRIDGE_TRACKBIT_FACTOR);
switch (GetTunnelBridgeTransportType(tile)) {
case TRANSPORT_RAIL:
c = Company::GetIfValid(GetTileOwner(tile));
if (c != NULL) c->infrastructure.rail[GetRailType(tile)] += len;
if (c != NULL) c->infrastructure.rail[GetRailType(tile)] += middle_len + GetTunnelBridgeHeadOnlyRailInfrastructureCount(tile) + GetTunnelBridgeHeadOnlyRailInfrastructureCount(other_end);
break;
case TRANSPORT_ROAD: {
@ -216,7 +215,7 @@ void AfterLoadCompanyStats()
case TRANSPORT_WATER:
c = Company::GetIfValid(GetTileOwner(tile));
if (c != NULL) c->infrastructure.water += len;
if (c != NULL) c->infrastructure.water += middle_len + (2 * TUNNELBRIDGE_TRACKBIT_FACTOR);
break;
default:

@ -45,7 +45,7 @@ std::vector<uint32> _sl_xv_discardable_chunk_ids; ///< list of chunks
static const uint32 _sl_xv_slxi_chunk_version = 0; ///< current version os SLXI chunk
const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
{ XSLFI_CUSTOM_BRIDGE_HEADS, XSCF_NULL, 1, 1, "custom_bridge_heads", NULL, NULL, NULL },
{ XSLFI_CUSTOM_BRIDGE_HEADS, XSCF_NULL, 2, 2, "custom_bridge_heads", NULL, NULL, NULL },
{ XSLFI_NULL, XSCF_NULL, 0, 0, NULL, NULL, NULL, NULL },// This is the end marker
};

@ -312,6 +312,7 @@ struct ConstructionSettings {
uint8 extra_tree_placement; ///< (dis)allow building extra trees in-game
uint8 command_pause_level; ///< level/amount of commands that can't be executed while paused
uint8 road_custom_bridge_heads; ///< allow construction of road custom bridge heads
uint8 rail_custom_bridge_heads; ///< allow construction of rail custom bridge heads
uint32 terraform_per_64k_frames; ///< how many tile heights may, over a long period, be terraformed per 65536 frames?
uint16 terraform_frame_burst; ///< how many tile heights may, over a short period, be terraformed?

@ -374,22 +374,45 @@ static SigFlags ExploreSegment(Owner owner)
case MP_TUNNELBRIDGE: {
if (GetTileOwner(tile) != owner) continue;
if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) continue;
DiagDirection dir = GetTunnelBridgeDirection(tile);
DiagDirection tunnel_bridge_dir = GetTunnelBridgeDirection(tile);
TrackBits tracks = GetTunnelBridgeTrackBits(tile);
TrackBits across_tracks = GetAcrossTunnelBridgeTrackBits(tile);
auto check_train_present = [tile, tracks, across_tracks](DiagDirection enterdir) -> bool {
if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) {
if (_enterdir_to_trackbits[enterdir] & across_tracks) {
return EnsureNoTrainOnTrackBits(tile, TRACK_BIT_WORMHOLE | across_tracks).Failed();
} else {
return EnsureNoTrainOnTrackBits(tile, tracks & (~across_tracks)).Failed();
}
} else {
return HasVehicleOnPos(tile, NULL, &TrainOnTileEnum);
}
};
TrackBits tracks_masked = (TrackBits)(tracks & _enterdir_to_trackbits[enterdir == INVALID_DIAGDIR ? tunnel_bridge_dir : enterdir]); // only incidating trackbits
if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) tracks = tracks_masked;
if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
enterdir = dir;
exitdir = ReverseDiagDir(dir);
tile += TileOffsByDiagDir(exitdir); // just skip to next tile
} else { // NOT incoming from the wormhole!
if (ReverseDiagDir(enterdir) != dir) continue;
if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
enterdir = INVALID_DIAGDIR;
exitdir = INVALID_DIAGDIR;
if (!(flags & SF_TRAIN) && check_train_present(tunnel_bridge_dir)) flags |= SF_TRAIN;
enterdir = tunnel_bridge_dir;
} else if (enterdir != tunnel_bridge_dir) { // NOT incoming from the wormhole!
if (tracks_masked == TRACK_BIT_NONE) continue; // no incidating track
if (!(flags & SF_TRAIN) && check_train_present(enterdir)) flags |= SF_TRAIN;
}
for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { // test all possible exit directions
if (dir != enterdir && (tracks & _enterdir_to_trackbits[dir])) { // any track incidating?
if (dir == tunnel_bridge_dir) {
if (!MaybeAddToTodoSet(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR, tile, INVALID_DIAGDIR)) return flags | SF_FULL;
} else {
TileIndex newtile = tile + TileOffsByDiagDir(dir); // new tile to check
DiagDirection newdir = ReverseDiagDir(dir); // direction we are entering from
if (!MaybeAddToTodoSet(newtile, newdir, tile, dir)) return flags | SF_FULL;
}
}
}
continue; // continue the while() loop
}
break;
default:
continue; // continue the while() loop
@ -489,16 +512,20 @@ static SigSegState UpdateSignalsInBuffer(Owner owner)
* train entering/leaving block, train leaving depot...
*/
switch (GetTileType(tile)) {
case MP_TUNNELBRIDGE:
case MP_TUNNELBRIDGE: {
/* 'optimization assert' - do not try to update signals when it is not needed */
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile)));
_tbdset.Add(tile, INVALID_DIAGDIR); // we can safely start from wormhole centre
_tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR);
break;
if (IsTunnel(tile)) assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile)));
TrackBits across = GetAcrossTunnelBridgeTrackBits(tile);
if (dir == INVALID_DIAGDIR || _enterdir_to_trackbits[dir] & across) {
_tbdset.Add(tile, INVALID_DIAGDIR); // we can safely start from wormhole centre
_tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR);
break;
}
}
/* FALL THROUGH */
case MP_RAILWAY:
if (IsRailDepot(tile)) {
if (IsRailDepotTile(tile)) {
/* 'optimization assert' do not try to update signals in other cases */
assert(dir == INVALID_DIAGDIR || dir == GetRailDepotDirection(tile));
_tbdset.Add(tile, INVALID_DIAGDIR); // start from depot inside
@ -591,8 +618,13 @@ void AddTrackToSignalBuffer(TileIndex tile, Track track, Owner owner)
_last_owner = owner;
_globset.Add(tile, _search_dir_1[track]);
_globset.Add(tile, _search_dir_2[track]);
DiagDirection wormhole_dir = IsTileType(tile, MP_TUNNELBRIDGE) ? GetTunnelBridgeDirection(tile) : INVALID_DIAGDIR;
auto add_dir = [&](DiagDirection dir) {
_globset.Add(tile, dir == wormhole_dir ? INVALID_DIAGDIR : dir);
};
add_dir(_search_dir_1[track]);
add_dir(_search_dir_2[track]);
if (_globset.Items() >= SIG_GLOB_UPDATE) {
/* too many items, force update */

@ -1238,6 +1238,15 @@ str = STR_CONFIG_SETTING_ENABLE_ROAD_CUSTOM_BRIDGE_HEADS
strhelp = STR_CONFIG_SETTING_ENABLE_ROAD_CUSTOM_BRIDGE_HEADS_HELPTEXT
patxname = ""custom_bridge_heads.construction.road_custom_bridge_heads""
[SDT_BOOL]
base = GameSettings
var = construction.rail_custom_bridge_heads
def = true
cat = SC_BASIC
str = STR_CONFIG_SETTING_ENABLE_RAIL_CUSTOM_BRIDGE_HEADS
strhelp = STR_CONFIG_SETTING_ENABLE_RAIL_CUSTOM_BRIDGE_HEADS_HELPTEXT
patxname = ""custom_bridge_heads.construction.rail_custom_bridge_heads""
[SDT_BOOL]
base = GameSettings
var = station.adjacent_stations

@ -246,7 +246,7 @@ protected: // These functions should not be called outside acceleration code.
inline byte GetAirDragArea() const
{
/* Air drag is higher in tunnels due to the limited cross-section. */
return (this->track == TRACK_BIT_WORMHOLE && this->vehstatus & VS_HIDDEN) ? 28 : 14;
return (this->track & TRACK_BIT_WORMHOLE && this->vehstatus & VS_HIDDEN) ? 28 : 14;
}
/**

@ -427,7 +427,7 @@ int Train::GetCurrentMaxSpeed() const
}
/* Vehicle is on the middle part of a bridge. */
if (u->track == TRACK_BIT_WORMHOLE && !(u->vehstatus & VS_HIDDEN)) {
if (u->track & TRACK_BIT_WORMHOLE && !(u->vehstatus & VS_HIDDEN)) {
max_speed = min(max_speed, GetBridgeSpec(GetBridgeType(u->tile))->speed);
}
}
@ -1568,17 +1568,17 @@ static void UpdateStatusAfterSwap(Train *v)
if (v->track != TRACK_BIT_DEPOT) v->direction = ReverseDir(v->direction);
/* Call the proper EnterTile function unless we are in a wormhole. */
if (v->track != TRACK_BIT_WORMHOLE) {
if (!(v->track & TRACK_BIT_WORMHOLE)) {
VehicleEnterTile(v, v->tile, v->x_pos, v->y_pos);
} else {
/* VehicleEnter_TunnelBridge() sets TRACK_BIT_WORMHOLE when the vehicle
/* VehicleEnter_TunnelBridge() may set TRACK_BIT_WORMHOLE when the vehicle
* is on the last bit of the bridge head (frame == TILE_SIZE - 1).
* If we were swapped with such a vehicle, we have set TRACK_BIT_WORMHOLE,
* when we shouldn't have. Check if this is the case. */
TileIndex vt = TileVirtXY(v->x_pos, v->y_pos);
if (IsTileType(vt, MP_TUNNELBRIDGE)) {
VehicleEnterTile(v, vt, v->x_pos, v->y_pos);
if (v->track != TRACK_BIT_WORMHOLE && IsBridgeTile(v->tile)) {
if (!(v->track & TRACK_BIT_WORMHOLE) && IsBridgeTile(v->tile)) {
/* We have just left the wormhole, possibly set the
* "goingdown" bit. UpdateInclination() can be used
* because we are at the border of the tile. */
@ -1875,7 +1875,7 @@ void ReverseTrainDirection(Train *v)
/* TrainExitDir does not always produce the desired dir for depots and
* tunnels/bridges that is needed for UpdateSignalsOnSegment. */
DiagDirection dir = TrainExitDir(v->direction, v->track);
if (IsRailDepotTile(v->tile) || IsTileType(v->tile, MP_TUNNELBRIDGE)) dir = INVALID_DIAGDIR;
if (IsRailDepotTile(v->tile) || (IsTileType(v->tile, MP_TUNNELBRIDGE) && (v->track & TRACK_BIT_WORMHOLE || dir == GetTunnelBridgeDirection(v->tile)))) dir = INVALID_DIAGDIR;
if (UpdateSignalsOnSegment(v->tile, dir, v->owner) == SIGSEG_PBS || _settings_game.pf.reserve_paths) {
/* If we are currently on a tile with conventional signals, we can't treat the
@ -2216,17 +2216,15 @@ static bool CheckTrainStayInDepot(Train *v)
*/
static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_dir)
{
DiagDirection dir = TrackdirToExitdir(track_dir);
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
/* Are we just leaving a tunnel/bridge? */
if (GetTunnelBridgeDirection(tile) == ReverseDiagDir(dir)) {
if (TrackdirExitsTunnelBridge(tile, track_dir)) {
TileIndex end = GetOtherTunnelBridgeEnd(tile);
if (TunnelBridgeIsFree(tile, end, v).Succeeded()) {
if (TunnelBridgeIsFree(tile, end, v, true).Succeeded()) {
/* Free the reservation only if no other train is on the tiles. */
SetTunnelBridgeReservation(tile, false);
SetTunnelBridgeReservation(end, false);
UnreserveAcrossRailTunnelBridge(tile);
UnreserveAcrossRailTunnelBridge(end);
if (_settings_client.gui.show_track_reservation) {
if (IsBridge(tile)) {
@ -2237,8 +2235,11 @@ static void ClearPathReservation(const Train *v, TileIndex tile, Trackdir track_
}
}
}
} else if (TrackdirToExitdir(track_dir) != GetTunnelBridgeDirection(tile)) {
UnreserveRailTrack(tile, TrackdirToTrack(track_dir));
}
} else if (IsRailStationTile(tile)) {
DiagDirection dir = TrackdirToExitdir(track_dir);
TileIndex new_tile = TileAddByDiagDir(tile, dir);
/* If the new tile is not a further tile of the same station, we
* clear the reservation for the whole platform. */
@ -2764,7 +2765,7 @@ bool TryPathReserve(Train *v, bool mark_as_stuck, bool first_tile_okay)
static bool CheckReverseTrain(const Train *v)
{
if (_settings_game.difficulty.line_reverse_mode != 0 ||
v->track == TRACK_BIT_DEPOT || v->track == TRACK_BIT_WORMHOLE ||
v->track == TRACK_BIT_DEPOT || v->track & TRACK_BIT_WORMHOLE ||
!(v->direction & 1)) {
return false;
}
@ -2924,15 +2925,18 @@ static bool TrainMovedChangeSignals(TileIndex tile, DiagDirection dir)
void Train::ReserveTrackUnderConsist() const
{
for (const Train *u = this; u != NULL; u = u->Next()) {
switch (u->track) {
case TRACK_BIT_WORMHOLE:
if (u->track & TRACK_BIT_WORMHOLE) {
if (IsRailCustomBridgeHeadTile(u->tile)) {
/* reserve the first available track */
TrackBits bits = GetAcrossBridgePossibleTrackBits(u->tile) & GetCustomBridgeHeadTrackBits(u->tile);
Track first_track = RemoveFirstTrack(&bits);
assert(IsValidTrack(first_track));
TryReserveRailTrack(u->tile, first_track);
} else {
TryReserveRailTrack(u->tile, DiagDirToDiagTrack(GetTunnelBridgeDirection(u->tile)));
break;
case TRACK_BIT_DEPOT:
break;
default:
TryReserveRailTrack(u->tile, TrackBitsToTrack(u->track));
break;
}
} else if (u->track != TRACK_BIT_DEPOT) {
TryReserveRailTrack(u->tile, TrackBitsToTrack(u->track));
}
}
}
@ -2957,7 +2961,7 @@ uint Train::Crash(bool flooded)
if (IsTileType(v->tile, MP_TUNNELBRIDGE)) {
/* ClearPathReservation will not free the wormhole exit
* if the train has just entered the wormhole. */
SetTunnelBridgeReservation(GetOtherTunnelBridgeEnd(v->tile), false);
UnreserveAcrossRailTunnelBridge(GetOtherTunnelBridgeEnd(v->tile));
}
}
@ -3064,14 +3068,14 @@ static bool CheckTrainCollision(Train *v)
/* can't collide in depot */
if (v->track == TRACK_BIT_DEPOT) return false;
assert(v->track == TRACK_BIT_WORMHOLE || TileVirtXY(v->x_pos, v->y_pos) == v->tile);
assert(v->track & TRACK_BIT_WORMHOLE || TileVirtXY(v->x_pos, v->y_pos) == v->tile);
TrainCollideChecker tcc;
tcc.v = v;
tcc.num = 0;
/* find colliding vehicles */
if (v->track == TRACK_BIT_WORMHOLE) {
if (v->track & TRACK_BIT_WORMHOLE) {
FindVehicleOnPos(v->tile, &tcc, FindTrainCollideEnum);
FindVehicleOnPos(GetOtherTunnelBridgeEnd(v->tile), &tcc, FindTrainCollideEnum);
} else {
@ -3116,14 +3120,37 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
Train *first = v->First();
Train *prev;
bool direction_changed = false; // has direction of any part changed?
Direction old_direction = INVALID_DIR;
TrackBits old_trackbits = INVALID_TRACK_BIT;
auto notify_direction_changed = [&](Direction old_direction, Direction new_direction) {
if (prev == NULL && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
const AccelerationSlowdownParams *asp = &_accel_slowdown[GetRailTypeInfo(v->railtype)->acceleration_type];
DirDiff diff = DirDifference(old_direction, new_direction);
v->cur_speed -= (diff == DIRDIFF_45RIGHT || diff == DIRDIFF_45LEFT ? asp->small_turn : asp->large_turn) * v->cur_speed >> 8;
}
direction_changed = true;
};
/* For every vehicle after and including the given vehicle */
for (prev = v->Previous(); v != nomove; prev = v, v = v->Next()) {
old_direction = v->direction;
old_trackbits = v->track;
DiagDirection enterdir = DIAGDIR_BEGIN;
bool update_signals_crossing = false; // will we update signals or crossing state?
GetNewVehiclePosResult gp = GetNewVehiclePos(v);
if (v->track != TRACK_BIT_WORMHOLE) {
if (!(v->track & TRACK_BIT_WORMHOLE) && gp.old_tile != gp.new_tile &&
IsRailBridgeHeadTile(gp.old_tile) && DiagdirBetweenTiles(gp.old_tile, gp.new_tile) == GetTunnelBridgeDirection(gp.old_tile)) {
/* left a bridge headtile into a wormhole */
Direction old_direction = v->direction;
uint32 r = VehicleEnterTile(v, gp.old_tile, gp.x, gp.y); // NB: old tile, the bridge head which the train just left
if (HasBit(r, VETS_CANNOT_ENTER)) {
goto invalid_rail;
}
if (old_direction != v->direction) notify_direction_changed(old_direction, v->direction);
}
if (!(v->track & TRACK_BIT_WORMHOLE)) {
/* Not inside tunnel */
if (gp.old_tile == gp.new_tile) {
/* Staying in the old tile */
@ -3145,6 +3172,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
/* The new position is the end of the platform */
TrainEnterStation(v, r >> VETS_STATION_ID_OFFSET);
}
if (old_direction != v->direction) notify_direction_changed(old_direction, v->direction);
}
} else {
/* A new tile is about to be entered. */
@ -3153,9 +3181,11 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
enterdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile);
assert(IsValidDiagDirection(enterdir));
enter_new_tile:
/* Get the status of the tracks in the new tile and mask
* away the bits that aren't reachable. */
TrackStatus ts = GetTileTrackStatus(gp.new_tile, TRANSPORT_RAIL, 0, ReverseDiagDir(enterdir));
TrackStatus ts = GetTileTrackStatus(gp.new_tile, TRANSPORT_RAIL, 0, (v->track & TRACK_BIT_WORMHOLE) ? INVALID_DIAGDIR : ReverseDiagDir(enterdir));
TrackdirBits reachable_trackdirs = DiagdirReachesTrackdirs(enterdir);
TrackdirBits trackdirbits = TrackStatusToTrackdirBits(ts) & reachable_trackdirs;
@ -3165,7 +3195,11 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
if (_settings_game.pf.forbid_90_deg && prev == NULL) {
/* We allow wagons to make 90 deg turns, because forbid_90_deg
* can be switched on halfway a turn */
bits &= ~TrackCrossesTracks(FindFirstTrack(v->track));
if (!(v->track & TRACK_BIT_WORMHOLE)) {
bits &= ~TrackCrossesTracks(FindFirstTrack(v->track));
} else if (v->track & TRACK_BIT_MASK) {
bits &= ~TrackCrossesTracks(FindFirstTrack(v->track & TRACK_BIT_MASK));
}
}
if (bits == TRACK_BIT_NONE) goto invalid_rail;
@ -3240,9 +3274,9 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
}
} else {
/* The wagon is active, simply follow the prev vehicle. */
if (prev->tile == gp.new_tile) {
if (TileVirtXY(prev->x_pos, prev->y_pos) == gp.new_tile) {
/* Choose the same track as prev */
if (prev->track == TRACK_BIT_WORMHOLE) {
if (prev->track & TRACK_BIT_WORMHOLE) {
/* Vehicles entering tunnels enter the wormhole earlier than for bridges.
* However, just choose the track into the wormhole. */
assert(IsTunnel(prev->tile));
@ -3264,7 +3298,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
{TRACK_BIT_NONE, TRACK_BIT_RIGHT, TRACK_BIT_X, TRACK_BIT_UPPER},
{TRACK_BIT_RIGHT, TRACK_BIT_NONE, TRACK_BIT_LOWER, TRACK_BIT_Y }
};
DiagDirection exitdir = DiagdirBetweenTiles(gp.new_tile, prev->tile);
DiagDirection exitdir = DiagdirBetweenTiles(gp.new_tile, TileVirtXY(prev->x_pos, prev->y_pos));
assert(IsValidDiagDirection(exitdir));
chosen_track = _connecting_track[enterdir][exitdir];
}
@ -3284,7 +3318,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
Direction chosen_dir = (Direction)b[2];
/* Call the landscape function and tell it that the vehicle entered the tile */
uint32 r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y);
uint32 r = (v->track & TRACK_BIT_WORMHOLE) ? 0 : VehicleEnterTile(v, gp.new_tile, gp.x, gp.y);
if (HasBit(r, VETS_CANNOT_ENTER)) {
goto invalid_rail;
}
@ -3315,12 +3349,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
update_signals_crossing = true;
if (chosen_dir != v->direction) {
if (prev == NULL && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
const AccelerationSlowdownParams *asp = &_accel_slowdown[GetRailTypeInfo(v->railtype)->acceleration_type];
DirDiff diff = DirDifference(v->direction, chosen_dir);
v->cur_speed -= (diff == DIRDIFF_45RIGHT || diff == DIRDIFF_45LEFT ? asp->small_turn : asp->large_turn) * v->cur_speed >> 8;
}
direction_changed = true;
notify_direction_changed(v->direction, chosen_dir);
v->direction = chosen_dir;
}
@ -3343,6 +3372,10 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
} else {
if (IsTileType(gp.new_tile, MP_TUNNELBRIDGE) && HasBit(VehicleEnterTile(v, gp.new_tile, gp.x, gp.y), VETS_ENTERED_WORMHOLE)) {
/* Perform look-ahead on tunnel exit. */
if (IsRailCustomBridgeHeadTile(gp.new_tile)) {
enterdir = ReverseDiagDir(GetTunnelBridgeDirection(gp.new_tile));
goto enter_new_tile;
}
if (v->IsFrontEngine()) {
TryReserveRailTrack(gp.new_tile, DiagDirToDiagTrack(GetTunnelBridgeDirection(gp.new_tile)));
CheckNextTrainTile(v);
@ -3416,6 +3449,11 @@ invalid_rail:
if (prev != NULL) error("Disconnecting train");
reverse_train_direction:
if ((v->track ^ old_trackbits) & TRACK_BIT_WORMHOLE) {
/* Entering/exiting wormhole failed/aborted, back out changes to vehicle direction and track */
v->track = old_trackbits;
v->direction = old_direction;
}
if (reverse) {
v->wait_counter = 0;
v->cur_speed = 0;
@ -3426,6 +3464,21 @@ reverse_train_direction:
return false;
}
static TrackBits GetTrackbitsFromCrashedVehicle(Train *t)
{
TrackBits train_tbits = t->track;
if (train_tbits & TRACK_BIT_WORMHOLE) {
/* Vehicle is inside a wormhole, v->track contains no useful value then. */
train_tbits = GetAcrossTunnelBridgeReservationTrackBits(t->tile);
if (train_tbits != TRACK_BIT_NONE) return train_tbits;
/* Pick the first available tunnel/bridge head track which could be reserved */
train_tbits = GetAcrossTunnelBridgeTrackBits(t->tile);
return train_tbits ^ KillFirstBit(train_tbits);
} else {
return train_tbits;
}
}
/**
* Collect trackbits of all crashed train vehicles on a tile
* @param v Vehicle passed from Find/HasVehicleOnPos()
@ -3437,12 +3490,8 @@ static Vehicle *CollectTrackbitsFromCrashedVehiclesEnum(Vehicle *v, void *data)
TrackBits *trackbits = (TrackBits *)data;
if (v->type == VEH_TRAIN && (v->vehstatus & VS_CRASHED) != 0) {
TrackBits train_tbits = Train::From(v)->track;
if (train_tbits == TRACK_BIT_WORMHOLE) {
/* Vehicle is inside a wormhole, v->track contains no useful value then. */
*trackbits |= DiagDirToDiagTrackBits(GetTunnelBridgeDirection(v->tile));
} else if (train_tbits != TRACK_BIT_DEPOT) {
*trackbits |= train_tbits;
if (Train::From(v)->track != TRACK_BIT_DEPOT) {
*trackbits |= GetTrackbitsFromCrashedVehicle(Train::From(v));
}
}
@ -3479,18 +3528,14 @@ static void DeleteLastWagon(Train *v)
}
/* 'v' shouldn't be accessed after it has been deleted */
TrackBits trackbits = v->track;
TileIndex tile = v->tile;
Owner owner = v->owner;
const TrackBits orig_trackbits = v->track;
TrackBits trackbits = GetTrackbitsFromCrashedVehicle(v);
const TileIndex tile = v->tile;
const Owner owner = v->owner;
delete v;
v = NULL; // make sure nobody will try to read 'v' anymore
if (trackbits == TRACK_BIT_WORMHOLE) {
/* Vehicle is inside a wormhole, v->track contains no useful value then. */
trackbits = DiagDirToDiagTrackBits(GetTunnelBridgeDirection(tile));
}
Track track = TrackBitsToTrack(trackbits);
if (HasReservedTracks(tile, trackbits)) {
UnreserveRailTrack(tile, track);
@ -3509,7 +3554,7 @@ static void DeleteLastWagon(Train *v)
if (IsLevelCrossingTile(tile)) UpdateLevelCrossing(tile);
/* Update signals */
if (IsTileType(tile, MP_TUNNELBRIDGE) || IsRailDepotTile(tile)) {
if ((orig_trackbits & TRACK_BIT_WORMHOLE) || IsRailDepotTile(tile)) {
UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, owner);
} else {
SetSignalsOnBothDir(tile, track, owner);
@ -3533,7 +3578,7 @@ static void ChangeTrainDirRandomly(Train *v)
/* Refrain from updating the z position of the vehicle when on
* a bridge, because UpdateInclination() will put the vehicle under
* the bridge in that case */
if (v->track != TRACK_BIT_WORMHOLE) {
if (!(v->track & TRACK_BIT_WORMHOLE)) {
v->UpdatePosition();
v->UpdateInclination(false, true);
} else {
@ -3648,7 +3693,7 @@ static bool TrainApproachingLineEnd(Train *v, bool signal, bool reverse)
static bool TrainCanLeaveTile(const Train *v)
{
/* Exit if inside a tunnel/bridge or a depot */
if (v->track == TRACK_BIT_WORMHOLE || v->track == TRACK_BIT_DEPOT) return false;
if (v->track & TRACK_BIT_WORMHOLE || v->track == TRACK_BIT_DEPOT) return false;
TileIndex tile = v->tile;
@ -3656,6 +3701,13 @@ static bool TrainCanLeaveTile(const Train *v)
if (IsTileType(tile, MP_TUNNELBRIDGE)) {
DiagDirection dir = GetTunnelBridgeDirection(tile);
if (DiagDirToDir(dir) == v->direction) return false;
if (IsRailCustomBridgeHeadTile(tile) && TrainExitDir(v->direction, v->track) == dir) {
if (_settings_game.pf.forbid_90_deg && v->Previous() == NULL && GetTunnelBridgeLength(tile, GetOtherTunnelBridgeEnd(tile)) == 0) {
/* Check for 90 degree turn on zero-length bridge span */
if (!(GetCustomBridgeHeadTrackBits(tile) & ~TrackCrossesTracks(FindFirstTrack(v->track)))) return true;
}
return false;
}
}
/* entering a depot? */
@ -4042,6 +4094,8 @@ Trackdir Train::GetVehicleTrackdir() const
if (this->track == TRACK_BIT_WORMHOLE) {
/* train in tunnel or on bridge, so just use his direction and assume a diagonal track */
return DiagDirToDiagTrackdir(DirToDiagDir(this->direction));
} else if (this->track & TRACK_BIT_WORMHOLE) {
return TrackDirectionToTrackdir(FindFirstTrack(this->track & TRACK_BIT_MASK), this->direction);
}
return TrackDirectionToTrackdir(FindFirstTrack(this->track), this->direction);

@ -37,6 +37,40 @@ static inline bool IsTunnelTile(TileIndex t)
return IsTileType(t, MP_TUNNELBRIDGE) && IsTunnel(t);
}
/**
* Checks if this tile is a rail tunnel
* @param t the tile that might be a rail tunnel
* @return true if it is a rail tunnel
*/
static inline bool IsRailTunnelTile(TileIndex t)
{
return IsTunnelTile(t) && (TransportType)GB(_m[t].m5, 2, 2) == TRANSPORT_RAIL;
}
/**
* Get the reservation state of the rail tunnel
* @pre IsRailTunnelTile(t)
* @param t the tile
* @return reservation state
*/
static inline bool HasTunnelReservation(TileIndex t)
{
assert(IsRailTunnelTile(t));
return HasBit(_m[t].m5, 4);
}
/**
* Set the reservation state of the rail tunnel
* @pre IsRailTunnelTile(t)
* @param t the tile
* @param b the reservation state
*/
static inline void SetTunnelReservation(TileIndex t, bool b)
{
assert(IsRailTunnelTile(t));
SB(_m[t].m5, 4, 1, b ? 1 : 0);
}
TileIndex GetOtherTunnelEnd(TileIndex);
bool IsTunnelInWay(TileIndex, int z);
bool IsTunnelInWayDir(TileIndex tile, int z, DiagDirection dir);

@ -52,6 +52,7 @@ TileIndex _build_tunnel_endtile; ///< The end of a tunnel; as hidden return from
/** Z position of the bridge sprites relative to bridge height (downwards) */
static const int BRIDGE_Z_START = 3;
extern void DrawTrackBits(TileInfo *ti, TrackBits track);
extern void DrawRoadBits(TileInfo *ti);
extern const RoadBits _invalid_tileh_slopes_road[2][15];
@ -315,7 +316,6 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u
int z_end;
Slope tileh_start = GetTileSlope(tile_start, &z_start);
Slope tileh_end = GetTileSlope(tile_end, &z_end);
bool pbs_reservation = false;
CommandCost terraform_cost_north = CheckBridgeSlopeNorth(direction, &tileh_start, &z_start);
CommandCost terraform_cost_south = CheckBridgeSlopeSouth(direction, &tileh_end, &z_end);
@ -369,15 +369,6 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u
is_new_owner = (owner == OWNER_NONE);
if (is_new_owner) owner = company;
switch (transport_type) {
case TRANSPORT_RAIL:
/* Keep the reservation, the path stays valid. */
pbs_reservation = HasTunnelBridgeReservation(tile_start);
break;
default: break;
}
is_upgrade = true;
} else {
/* Build a new bridge. */
@ -491,11 +482,9 @@ CommandCost CmdBuildBridge(TileIndex end_tile, DoCommandFlag flags, uint32 p1, u
switch (transport_type) {
case TRANSPORT_RAIL:
/* Add to company infrastructure count if required. */
if (is_new_owner && c != NULL) c->infrastructure.rail[railtype] += (bridge_len + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR;
MakeRailBridgeRamp(tile_start, owner, bridge_type, dir, railtype);
MakeRailBridgeRamp(tile_end, owner, bridge_type, ReverseDiagDir(dir), railtype);
SetTunnelBridgeReservation(tile_start, pbs_reservation);
SetTunnelBridgeReservation(tile_end, pbs_reservation);
MakeRailBridgeRamp(tile_start, owner, bridge_type, dir, railtype, is_upgrade);
MakeRailBridgeRamp(tile_end, owner, bridge_type, ReverseDiagDir(dir), railtype, is_upgrade);
if (is_new_owner && c != NULL) c->infrastructure.rail[railtype] += (bridge_len * TUNNELBRIDGE_TRACKBIT_FACTOR) + GetTunnelBridgeHeadOnlyRailInfrastructureCount(tile_start) + GetTunnelBridgeHeadOnlyRailInfrastructureCount(tile_end);
break;
case TRANSPORT_ROAD: {
@ -834,7 +823,7 @@ static CommandCost DoClearTunnel(TileIndex tile, DoCommandFlag flags)
Owner owner = GetTileOwner(tile);
Train *v = NULL;
if (HasTunnelBridgeReservation(tile)) {
if (HasTunnelReservation(tile)) {
v = GetTrainForReservation(tile, track);
if (v != NULL) FreeTrainTrackReservation(v);
}
@ -909,24 +898,48 @@ static CommandCost DoClearBridge(TileIndex tile, DoCommandFlag flags)
ChangeTownRating(t, RATING_TUNNEL_BRIDGE_DOWN_STEP, RATING_TUNNEL_BRIDGE_MINIMUM, flags);
}
CommandCost cost(EXPENSES_CONSTRUCTION);
const bool rail = GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL;
TrackBits tile_tracks = TRACK_BIT_NONE;
TrackBits endtile_tracks = TRACK_BIT_NONE;
if (rail) {
tile_tracks = GetCustomBridgeHeadTrackBits(tile);
endtile_tracks = GetCustomBridgeHeadTrackBits(endtile);
cost.AddCost(RailClearCost(GetRailType(tile)) * (CountBits(tile_tracks) + CountBits(endtile_tracks) - 2));
}
Money base_cost = (GetTunnelBridgeTransportType(tile) != TRANSPORT_WATER) ? _price[PR_CLEAR_BRIDGE] : _price[PR_CLEAR_AQUEDUCT];
uint len = GetTunnelBridgeLength(tile, endtile) + 2; // Don't forget the end tiles.
uint middle_len = GetTunnelBridgeLength(tile, endtile);
uint len = middle_len + 2; // Don't forget the end tiles.
cost.AddCost(len * base_cost);
if (flags & DC_EXEC) {
/* read this value before actual removal of bridge */
bool rail = GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL;
Owner owner = GetTileOwner(tile);
int height = GetBridgeHeight(tile);
Train *v = NULL;
SmallVector<Train *, 2> vehicles_affected;
if (rail && HasTunnelBridgeReservation(tile)) {
v = GetTrainForReservation(tile, DiagDirToDiagTrack(direction));
if (v != NULL) FreeTrainTrackReservation(v);
if (rail) {
auto find_train_reservations = [&vehicles_affected](TileIndex tile) {
TrackBits reserved = GetBridgeReservationTrackBits(tile);
Track track;
while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) {
Train *v = GetTrainForReservation(tile, track);
if (v != NULL) {
FreeTrainTrackReservation(v);
*vehicles_affected.Append() = v;
}
}
};
find_train_reservations(tile);
find_train_reservations(endtile);
}
/* Update company infrastructure counts. */
if (rail) {
if (Company::IsValidID(owner)) Company::Get(owner)->infrastructure.rail[GetRailType(tile)] -= len * TUNNELBRIDGE_TRACKBIT_FACTOR;
if (Company::IsValidID(owner)) Company::Get(owner)->infrastructure.rail[GetRailType(tile)] -= (middle_len * TUNNELBRIDGE_TRACKBIT_FACTOR) + GetTunnelBridgeHeadOnlyRailInfrastructureCount(tile) + GetTunnelBridgeHeadOnlyRailInfrastructureCount(endtile);
} else if (GetTunnelBridgeTransportType(tile) == TRANSPORT_ROAD) {
SubtractRoadTunnelBridgeInfrastructure(tile, endtile);
} else { // Aqueduct
@ -948,18 +961,28 @@ static CommandCost DoClearBridge(TileIndex tile, DoCommandFlag flags)
if (rail) {
/* cannot use INVALID_DIAGDIR for signal update because the bridge doesn't exist anymore */
AddSideToSignalBuffer(tile, ReverseDiagDir(direction), owner);
AddSideToSignalBuffer(endtile, direction, owner);
Track track = DiagDirToDiagTrack(direction);
YapfNotifyTrackLayoutChange(tile, track);
YapfNotifyTrackLayoutChange(endtile, track);
auto notify_track_change = [owner](TileIndex tile, DiagDirection direction, TrackBits tracks) {
auto check_dir = [&](DiagDirection d) {
if (DiagdirReachesTracks(d) & tracks) AddSideToSignalBuffer(tile, d, owner);
};
check_dir(ChangeDiagDir(direction, DIAGDIRDIFF_90RIGHT));
check_dir(ChangeDiagDir(direction, DIAGDIRDIFF_REVERSE));
check_dir(ChangeDiagDir(direction, DIAGDIRDIFF_90LEFT));
while (tracks != TRACK_BIT_NONE) {
YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks));
}
};
notify_track_change(tile, direction, tile_tracks);
notify_track_change(endtile, ReverseDiagDir(direction), endtile_tracks);
if (v != NULL) TryPathReserve(v, true);
for (uint i = 0; i < vehicles_affected.Length(); ++i) {
TryPathReserve(vehicles_affected[i], true);
}
}
}
return CommandCost(EXPENSES_CONSTRUCTION, len * base_cost);
return cost;
}
/**
@ -1202,7 +1225,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
}
/* PBS debugging, draw reserved tracks darker */
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasTunnelBridgeReservation(ti->tile)) {
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasTunnelReservation(ti->tile)) {
if (rti->UsesOverlay()) {
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
DrawGroundSprite(overlay + RTO_X + DiagDirToAxis(tunnelbridge_direction), PALETTE_CRASH);
@ -1241,6 +1264,14 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
DrawBridgeMiddle(ti);
return;
}
if (transport_type == TRANSPORT_RAIL && IsRailCustomBridgeHead(ti->tile)) {
DrawTrackBits(ti, GetCustomBridgeHeadTrackBits(ti->tile));
if (HasRailCatenaryDrawn(GetRailType(ti->tile))) {
DrawRailCatenary(ti);
}
DrawBridgeMiddle(ti);
return;
}
const PalSpriteID *psid;
int base_offset;
@ -1324,7 +1355,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
}
/* PBS debugging, draw reserved tracks darker */
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasTunnelBridgeReservation(ti->tile)) {
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && GetBridgeReservationTrackBits(ti->tile) != TRACK_BIT_NONE) {
if (rti->UsesOverlay()) {
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
@ -1489,7 +1520,7 @@ void DrawBridgeMiddle(const TileInfo *ti)
}
}
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && !IsInvisibilitySet(TO_BRIDGES) && HasTunnelBridgeReservation(rampnorth)) {
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && !IsInvisibilitySet(TO_BRIDGES) && (HasAcrossBridgeReservation(rampnorth) || HasAcrossBridgeReservation(rampsouth))) {
if (rti->UsesOverlay()) {
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
AddSortableSpriteToDraw(overlay + RTO_X + axis, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, bridge_z, IsTransparencySet(TO_BRIDGES));
@ -1556,7 +1587,7 @@ static int GetSlopePixelZ_TunnelBridge(TileIndex tile, uint x, uint y)
/* In the tunnel entrance? */
if (5 <= pos && pos <= 10) return z;
} else { // IsBridge(tile)
if (IsRoadCustomBridgeHeadTile(tile)) {
if (IsCustomBridgeHeadTile(tile)) {
return z + TILE_HEIGHT + (IsSteepSlope(tileh) ? TILE_HEIGHT : 0);
}
@ -1587,7 +1618,7 @@ static int GetSlopePixelZ_TunnelBridge(TileIndex tile, uint x, uint y)
static Foundation GetFoundation_TunnelBridge(TileIndex tile, Slope tileh)
{
if (IsRoadCustomBridgeHeadTile(tile)) return FOUNDATION_LEVELED;
if (IsCustomBridgeHeadTile(tile)) return FOUNDATION_LEVELED;
return IsTunnel(tile) ? FOUNDATION_NONE : GetBridgeFoundation(tileh, DiagDirToAxis(GetTunnelBridgeDirection(tile)));
}
@ -1677,15 +1708,14 @@ static TrackStatus GetTileTrackStatus_TunnelBridge(TileIndex tile, TransportType
DiagDirection dir = GetTunnelBridgeDirection(tile);
if (side != INVALID_DIAGDIR && side == dir) return 0;
if (mode == TRANSPORT_ROAD && IsRoadCustomBridgeHeadTile(tile)) {
if (side != INVALID_DIAGDIR && side == dir) return 0;
TrackBits bits = TRACK_BIT_NONE;
if (sub_mode & ROADTYPES_TRAM) bits |= _road_trackbits[GetCustomBridgeHeadRoadBits(tile, ROADTYPE_TRAM)];
if (sub_mode & ROADTYPES_ROAD) bits |= _road_trackbits[GetCustomBridgeHeadRoadBits(tile, ROADTYPE_ROAD)];
return CombineTrackStatus(TrackBitsToTrackdirBits(bits), TRACKDIR_BIT_NONE);
}
if (side != INVALID_DIAGDIR && side != ReverseDiagDir(dir)) return 0;
return CombineTrackStatus(TrackBitsToTrackdirBits(DiagDirToDiagTrackBits(dir)), TRACKDIR_BIT_NONE);
return CombineTrackStatus(TrackBitsToTrackdirBits(mode == TRANSPORT_RAIL ? GetTunnelBridgeTrackBits(tile) : DiagDirToDiagTrackBits(dir)), TRACKDIR_BIT_NONE);
}
static void UpdateRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end, bool add) {
@ -1743,9 +1773,6 @@ void SubtractRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end) {
static void ChangeTileOwner_TunnelBridge(TileIndex tile, Owner old_owner, Owner new_owner)
{
const TileIndex other_end = GetOtherTunnelBridgeEnd(tile);
/* Set number of pieces to zero if it's the southern tile as we
* don't want to update the infrastructure counts twice. */
const uint num_pieces = tile < other_end ? (GetTunnelBridgeLength(tile, other_end) + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR : 0;
const TransportType tt = GetTunnelBridgeTransportType(tile);
if (tt == TRANSPORT_ROAD && tile < other_end) {
@ -1772,9 +1799,15 @@ static void ChangeTileOwner_TunnelBridge(TileIndex tile, Owner old_owner, Owner
Company *old = Company::Get(old_owner);
if (tt == TRANSPORT_RAIL) {
/* Set number of middle pieces to zero if it's the southern tile as we
* don't want to update the infrastructure counts twice. */
const uint num_pieces = GetTunnelBridgeHeadOnlyRailInfrastructureCount(tile) + (tile < other_end ? GetTunnelBridgeLength(tile, other_end) * TUNNELBRIDGE_TRACKBIT_FACTOR : 0);
old->infrastructure.rail[GetRailType(tile)] -= num_pieces;
if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.rail[GetRailType(tile)] += num_pieces;
} else if (tt == TRANSPORT_WATER) {
/* Set number of pieces to zero if it's the southern tile as we
* don't want to update the infrastructure counts twice. */
const uint num_pieces = tile < other_end ? (GetTunnelBridgeLength(tile, other_end) + 2) * TUNNELBRIDGE_TRACKBIT_FACTOR : 0;
old->infrastructure.water -= num_pieces;
if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.water += num_pieces;
}
@ -1813,23 +1846,31 @@ extern const byte _tunnel_visibility_frame[DIAGDIR_END] = {12, 8, 8, 12};
static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex tile, int x, int y)
{
int z = GetSlopePixelZ(x, y) - v->z_pos;
if (abs(z) > 2) return VETSB_CANNOT_ENTER;
/* Direction into the wormhole */
const DiagDirection dir = GetTunnelBridgeDirection(tile);
/* Direction of the vehicle */
const DiagDirection vdir = DirToDiagDir(v->direction);
/* New position of the vehicle on the tile */
byte pos = (DiagDirToAxis(vdir) == AXIS_X ? x : y) & TILE_UNIT_MASK;
int pos = (DiagDirToAxis(dir) == AXIS_X ? x - (TileX(tile) * TILE_SIZE) : y - (TileY(tile) * TILE_SIZE));
/* Number of units moved by the vehicle since entering the tile */
byte frame = (vdir == DIAGDIR_NE || vdir == DIAGDIR_NW) ? TILE_SIZE - 1 - pos : pos;
int frame = (dir == DIAGDIR_NE || dir == DIAGDIR_NW) ? TILE_SIZE - 1 - pos : pos;
if (frame > (int) TILE_SIZE || frame < 0) return VETSB_CANNOT_ENTER;
if (frame == TILE_SIZE) {
TileIndexDiffC offset = TileIndexDiffCByDiagDir(ReverseDiagDir(dir));
x += offset.x;
y += offset.y;
}
int z = GetSlopePixelZ(x, y) - v->z_pos;
if (abs(z) > 2) return VETSB_CANNOT_ENTER;
if (IsTunnel(tile)) {
/* Direction of the vehicle */
const DiagDirection vdir = DirToDiagDir(v->direction);
if (v->type == VEH_TRAIN) {
Train *t = Train::From(v);
if (t->track != TRACK_BIT_WORMHOLE && dir == vdir) {
if (!(t->track & TRACK_BIT_WORMHOLE) && dir == vdir) {
if (t->IsFrontEngine() && frame == TUNNEL_SOUND_FRAME) {
if (!PlayVehicleSound(t, VSE_TUNNEL) && RailVehInfo(t->engine_type)->engclass == 0) {
SndPlayVehicleFx(SND_05_TRAIN_THROUGH_TUNNEL, v);
@ -1844,7 +1885,7 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
}
}
if (dir == ReverseDiagDir(vdir) && frame == TILE_SIZE - _tunnel_visibility_frame[dir] && z == 0) {
if (dir == ReverseDiagDir(vdir) && frame == (int) (_tunnel_visibility_frame[dir] - 1) && z == 0) {
/* We're at the tunnel exit ?? */
t->tile = tile;
t->track = DiagDirToDiagTrackBits(vdir);
@ -1870,7 +1911,7 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
}
/* We're at the tunnel exit ?? */
if (dir == ReverseDiagDir(vdir) && frame == TILE_SIZE - _tunnel_visibility_frame[dir] && z == 0) {
if (dir == ReverseDiagDir(vdir) && frame == (int) (_tunnel_visibility_frame[dir] - 1) && z == 0) {
rv->tile = tile;
rv->state = DiagDirToDiagTrackdir(vdir);
rv->frame = frame;
@ -1888,11 +1929,12 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
first->cur_speed = min(first->cur_speed, spd);
}
if (vdir == dir) {
/* Vehicle enters bridge at the last frame inside this tile. */
if (frame != TILE_SIZE - 1) return VETSB_CONTINUE;
const Direction bridge_dir = DiagDirToDir(dir);
if (v->direction == bridge_dir) {
switch (v->type) {
case VEH_TRAIN: {
/* Trains enter bridge at the first frame beyond this tile. */
if (frame != TILE_SIZE) return VETSB_CONTINUE;
Train *t = Train::From(v);
t->track = TRACK_BIT_WORMHOLE;
ClrBit(t->gv_flags, GVF_GOINGUP_BIT);
@ -1901,6 +1943,8 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
}
case VEH_ROAD: {
/* Non-train vehicles enter bridge at the last frame inside this tile. */
if (frame != TILE_SIZE - 1) return VETSB_CONTINUE;
RoadVehicle *rv = RoadVehicle::From(v);
if (IsRoadCustomBridgeHeadTile(tile)) {
RoadBits bits = ROAD_NONE;
@ -1916,28 +1960,35 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
}
case VEH_SHIP:
/* Non-train vehicles enter bridge at the last frame inside this tile. */
if (frame != TILE_SIZE - 1) return VETSB_CONTINUE;
Ship::From(v)->state = TRACK_BIT_WORMHOLE;
break;
default: NOT_REACHED();
}
return VETSB_ENTERED_WORMHOLE;
} else if (vdir == ReverseDiagDir(dir)) {
v->tile = tile;
} else if (v->direction == ReverseDir(bridge_dir)) {
switch (v->type) {
case VEH_TRAIN: {
Train *t = Train::From(v);
if (t->track == TRACK_BIT_WORMHOLE) {
t->track = DiagDirToDiagTrackBits(vdir);
if (t->track & TRACK_BIT_WORMHOLE) {
if (IsRailCustomBridgeHeadTile(tile)) {
return VETSB_ENTERED_WORMHOLE;
} else {
v->tile = tile;
t->track = DiagDirToDiagTrackBits(DirToDiagDir(v->direction));
}
return VETSB_ENTERED_WORMHOLE;
}
break;
}
case VEH_ROAD: {
v->tile = tile;
RoadVehicle *rv = RoadVehicle::From(v);
if (rv->state == RVSB_WORMHOLE) {
rv->state = DiagDirToDiagTrackdir(vdir);
rv->state = DiagDirToDiagTrackdir(DirToDiagDir(v->direction));
rv->frame = 0;
return VETSB_ENTERED_WORMHOLE;
}
@ -1945,9 +1996,10 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
}
case VEH_SHIP: {
v->tile = tile;
Ship *ship = Ship::From(v);
if (ship->state == TRACK_BIT_WORMHOLE) {
ship->state = DiagDirToDiagTrackBits(vdir);
ship->state = DiagDirToDiagTrackBits(DirToDiagDir(v->direction));
return VETSB_ENTERED_WORMHOLE;
}
break;
@ -1955,6 +2007,29 @@ static VehicleEnterTileStatus VehicleEnter_TunnelBridge(Vehicle *v, TileIndex ti
default: NOT_REACHED();
}
} else if (v->type == VEH_TRAIN && IsRailCustomBridgeHeadTile(tile)) {
DirDiff dir_diff = DirDifference(v->direction, bridge_dir);
DirDiff reverse_dir_diff = DirDifference(v->direction, ReverseDir(bridge_dir));
if (dir_diff == DIRDIFF_45RIGHT || dir_diff == DIRDIFF_45LEFT) {
if (frame != TILE_SIZE) return VETSB_CONTINUE;
Train *t = Train::From(v);
TileIndex other = GetOtherTunnelBridgeEnd(tile);
if (GetTunnelBridgeLength(tile, other) == 0 && IsRailCustomBridgeHead(other)) {
t->track |= TRACK_BIT_WORMHOLE;
} else {
t->direction = bridge_dir;
t->track = TRACK_BIT_WORMHOLE;
}
ClrBit(t->gv_flags, GVF_GOINGUP_BIT);
ClrBit(t->gv_flags, GVF_GOINGDOWN_BIT);
return VETSB_ENTERED_WORMHOLE;
}
if (reverse_dir_diff == DIRDIFF_45RIGHT || reverse_dir_diff == DIRDIFF_45LEFT) {
Train *t = Train::From(v);
if (t->track & TRACK_BIT_WORMHOLE) return VETSB_ENTERED_WORMHOLE;
}
}
}
return VETSB_CONTINUE;
@ -1980,6 +2055,16 @@ static CommandCost TerraformTile_TunnelBridge(TileIndex tile, DoCommandFlag flag
return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
}
}
if (IsRailCustomBridgeHeadTile(tile)) {
extern bool IsValidFlatRailBridgeHeadTrackBits(Slope normalised_slope, DiagDirection bridge_direction, TrackBits tracks);
/* Steep slopes behave the same as slopes with one corner raised. */
const Slope normalised_tileh_new = IsSteepSlope(tileh_new) ? SlopeWithOneCornerRaised(GetHighestSlopeCorner(tileh_new)) : tileh_new;
if (!IsValidFlatRailBridgeHeadTrackBits(normalised_tileh_new, direction, GetCustomBridgeHeadTrackBits(tile))) {
return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
}
}
/* Check if new slope is valid for bridges in general (so we can safely call GetBridgeFoundation()) */
if ((direction == DIAGDIR_NW) || (direction == DIAGDIR_NE)) {

@ -14,6 +14,8 @@
#include "bridge_map.h"
#include "tunnel_map.h"
#include "track_func.h"
#include "core/bitmath_func.hpp"
/**
@ -83,42 +85,160 @@ static inline TileIndex GetOtherTunnelBridgeEnd(TileIndex t)
return IsTunnel(t) ? GetOtherTunnelEnd(t) : GetOtherBridgeEnd(t);
}
/**
* Get the track bits for a rail tunnel/bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return reserved track bits
*/
static inline TrackBits GetTunnelBridgeTrackBits(TileIndex t)
{
if (IsTunnel(t)) {
return DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t));
} else {
return GetCustomBridgeHeadTrackBits(t);
}
}
/**
* Get the track bits for a rail tunnel/bridge onto/across the tunnel/bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return reserved track bits
*/
static inline TrackBits GetAcrossTunnelBridgeTrackBits(TileIndex t)
{
if (IsTunnel(t)) {
return DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t));
} else {
return GetCustomBridgeHeadTrackBits(t) & GetAcrossBridgePossibleTrackBits(t);
}
}
/**
* Get the reserved track bits for a rail tunnel/bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return reserved track bits
*/
static inline TrackBits GetTunnelBridgeReservationTrackBits(TileIndex t)
{
if (IsTunnel(t)) {
return HasTunnelReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE;
} else {
return GetBridgeReservationTrackBits(t);
}
}
/**
* Get the reservation state of the rail tunnel/bridge
* Get the reserved track bits for a rail tunnel/bridge onto/across the tunnel/bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return reserved track bits
*/
static inline TrackBits GetAcrossTunnelBridgeReservationTrackBits(TileIndex t)
{
if (IsTunnel(t)) {
return HasTunnelReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE;
} else {
return GetAcrossBridgeReservationTrackBits(t);
}
}
/**
* Get whether there are reserved track bits for a rail tunnel/bridge onto/across the tunnel/bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return whether there are reserved track bits
*/
static inline bool HasAcrossTunnelBridgeReservation(TileIndex t)
{
if (IsTunnel(t)) {
return HasTunnelReservation(t);
} else {
return GetAcrossBridgeReservationTrackBits(t) != TRACK_BIT_NONE;
}
}
/**
* Get the rail infrastructure count of a rail tunnel/bridge head tile (excluding the tunnel/bridge middle)
* @param bits the track bits
* @return rail infrastructure count
*/
static inline uint GetTunnelBridgeHeadOnlyRailInfrastructureCountFromTrackBits(TrackBits bits)
{
uint pieces = CountBits(bits);
if (TracksOverlap(bits)) pieces *= pieces;
return (TUNNELBRIDGE_TRACKBIT_FACTOR / 2) * (1 + pieces);
}
/**
* Get the rail infrastructure count of a rail tunnel/bridge head tile (excluding the tunnel/bridge middle)
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return rail infrastructure count
*/
static inline uint GetTunnelBridgeHeadOnlyRailInfrastructureCount(TileIndex t)
{
return IsBridge(t) ? GetTunnelBridgeHeadOnlyRailInfrastructureCountFromTrackBits(GetTunnelBridgeTrackBits(t)) : TUNNELBRIDGE_TRACKBIT_FACTOR;
}
/**
* Check if the given track direction on a rail bridge head tile enters the bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @param td track direction
* @return reservation state
*/
static inline bool HasTunnelBridgeReservation(TileIndex t)
static inline bool TrackdirEntersTunnelBridge(TileIndex t, Trackdir td)
{
assert(IsTileType(t, MP_TUNNELBRIDGE));
assert(GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL);
return HasBit(_m[t].m5, 4);
return TrackdirToExitdir(td) == GetTunnelBridgeDirection(t);
}
/**
* Set the reservation state of the rail tunnel/bridge
* Check if the given track direction on a rail bridge head tile exits the bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @param b the reservation state
* @param td track direction
* @return reservation state
*/
static inline void SetTunnelBridgeReservation(TileIndex t, bool b)
static inline bool TrackdirExitsTunnelBridge(TileIndex t, Trackdir td)
{
assert(IsTileType(t, MP_TUNNELBRIDGE));
assert(GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL);
SB(_m[t].m5, 4, 1, b ? 1 : 0);
return TrackdirToExitdir(ReverseTrackdir(td)) == GetTunnelBridgeDirection(t);
}
/**
* Get the reserved track bits for a rail tunnel/bridge
* Check if the given track on a rail bridge head tile enters/exits the bridge
* @pre IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL
* @param t the tile
* @return reserved track bits
* @param tile the tile
* @param t track
* @return reservation state
*/
static inline TrackBits GetTunnelBridgeReservationTrackBits(TileIndex t)
static inline bool IsTrackAcrossTunnelBridge(TileIndex tile, Track t)
{
assert(IsTileType(tile, MP_TUNNELBRIDGE));
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
return DiagdirReachesTracks(ReverseDiagDir(GetTunnelBridgeDirection(tile))) & TrackToTrackBits(t);
}
/**
* Lift the reservation of a specific track on a tunnel or rail bridge head tile
* @pre IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL
* @param tile the tile
*/
static inline void UnreserveAcrossRailTunnelBridge(TileIndex tile)
{
return HasTunnelBridgeReservation(t) ? DiagDirToDiagTrackBits(GetTunnelBridgeDirection(t)) : TRACK_BIT_NONE;
assert(IsTileType(tile, MP_TUNNELBRIDGE));
assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
if (IsTunnel(tile)) {
SetTunnelReservation(tile, false);
} else {
UnreserveAcrossRailBridgeHead(tile);
}
}
void AddRoadTunnelBridgeInfrastructure(TileIndex begin, TileIndex end);

@ -525,11 +525,23 @@ CommandCost EnsureNoVehicleOnGround(TileIndex tile)
return CommandCost();
}
struct GetVehicleTunnelBridgeProcData {
const Vehicle *v;
TileIndex t;
bool across_only;
};
/** Procedure called for every vehicle found in tunnel/bridge in the hash map */
static Vehicle *GetVehicleTunnelBridgeProc(Vehicle *v, void *data)
{
const GetVehicleTunnelBridgeProcData *info = (GetVehicleTunnelBridgeProcData*) data;
if (v->type != VEH_TRAIN && v->type != VEH_ROAD && v->type != VEH_SHIP) return NULL;
if (v == (const Vehicle *)data) return NULL;
if (v == info->v) return NULL;
if (v->type == VEH_TRAIN && info->across_only && IsBridge(info->t)) {
TrackBits vehicle_track = Train::From(v)->track;
if (!(vehicle_track & TRACK_BIT_WORMHOLE) && !(GetAcrossBridgePossibleTrackBits(info->t) & vehicle_track)) return NULL;
}
return v;
}
@ -539,16 +551,24 @@ static Vehicle *GetVehicleTunnelBridgeProc(Vehicle *v, void *data)
* @param tile first end
* @param endtile second end
* @param ignore Ignore this vehicle when searching
* @param across_only Only find vehicles which are passing across the bridge/tunnel or on connecting bridge head track pieces
* @return Succeeded command (if tunnel/bridge is free) or failed command (if a vehicle is using the tunnel/bridge).
*/
CommandCost TunnelBridgeIsFree(TileIndex tile, TileIndex endtile, const Vehicle *ignore)
CommandCost TunnelBridgeIsFree(TileIndex tile, TileIndex endtile, const Vehicle *ignore, bool across_only)
{
/* Value v is not safe in MP games, however, it is used to generate a local
* error message only (which may be different for different machines).
* Such a message does not affect MP synchronisation.
*/
Vehicle *v = VehicleFromPos(tile, const_cast<Vehicle *>(ignore), &GetVehicleTunnelBridgeProc, true);
if (v == NULL) v = VehicleFromPos(endtile, const_cast<Vehicle *>(ignore), &GetVehicleTunnelBridgeProc, true);
GetVehicleTunnelBridgeProcData data;
data.v = ignore;
data.t = tile;
data.across_only = across_only;
Vehicle *v = VehicleFromPos(tile, &data, &GetVehicleTunnelBridgeProc, true);
if (v == NULL) {
data.t = endtile;
v = VehicleFromPos(endtile, &data, &GetVehicleTunnelBridgeProc, true);
}
if (v != NULL) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY + v->type);
return CommandCost();
@ -561,6 +581,12 @@ static Vehicle *EnsureNoTrainOnTrackProc(Vehicle *v, void *data)
if (v->type != VEH_TRAIN) return NULL;
Train *t = Train::From(v);
if (rail_bits & TRACK_BIT_WORMHOLE) {
if (t->track & TRACK_BIT_WORMHOLE) return v;
rail_bits &= ~TRACK_BIT_WORMHOLE;
} else if (t->track & TRACK_BIT_WORMHOLE) {
return NULL;
}
if ((t->track != rail_bits) && !TracksOverlap(t->track | rail_bits)) return NULL;
return v;

@ -60,7 +60,7 @@ byte GetBestFittingSubType(Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_t
void ViewportAddVehicles(DrawPixelInfo *dpi);
void ShowNewGrfVehicleError(EngineID engine, StringID part1, StringID part2, GRFBugs bug_type, bool critical);
CommandCost TunnelBridgeIsFree(TileIndex tile, TileIndex endtile, const Vehicle *ignore = NULL);
CommandCost TunnelBridgeIsFree(TileIndex tile, TileIndex endtile, const Vehicle *ignore = NULL, bool across_only = false);
void DecreaseVehicleValue(Vehicle *v);
void CheckVehicleBreakdown(Vehicle *v);

@ -84,6 +84,7 @@
#include "linkgraph/linkgraph_gui.h"
#include "viewport_sprite_sorter.h"
#include "bridge_map.h"
#include "tunnelbridge_map.h"
#include <map>
@ -976,23 +977,38 @@ static const HighLightStyle _autorail_type[6][2] = {
*/
static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
{
SpriteID image;
PaletteID pal;
FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
int offset;
bool bridge_head_mode = false;
FoundationPart foundation_part = FOUNDATION_PART_NORMAL;
Slope autorail_tileh = RemoveHalftileSlope(ti->tileh);
if (IsHalftileSlope(ti->tileh)) {
static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U };
Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
if (autorail_type != _lower_rail[halftile_corner]) {
foundation_part = FOUNDATION_PART_HALFTILE;
/* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
autorail_tileh = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
if (IsFlatRailBridgeHeadTile(ti->tile)) {
extern bool IsValidFlatRailBridgeHeadTrackBits(Slope normalised_slope, DiagDirection bridge_direction, TrackBits tracks);
offset = _AutorailTilehSprite[SLOPE_FLAT][autorail_type];
const Slope real_tileh = GetTileSlope(ti->tile, nullptr);
const Slope normalised_tileh = IsSteepSlope(real_tileh) ? SlopeWithOneCornerRaised(GetHighestSlopeCorner(real_tileh)) : real_tileh;
if (!IsValidFlatRailBridgeHeadTrackBits(normalised_tileh, GetTunnelBridgeDirection(ti->tile), TrackToTrackBits((Track) autorail_type))) {
offset = -offset;
}
if (!IsRailCustomBridgeHead(ti->tile)) {
bridge_head_mode = true;
}
} else {
Slope autorail_tileh = RemoveHalftileSlope(ti->tileh);
if (IsHalftileSlope(ti->tileh)) {
static const uint _lower_rail[4] = { 5U, 2U, 4U, 3U };
Corner halftile_corner = GetHalftileSlopeCorner(ti->tileh);
if (autorail_type != _lower_rail[halftile_corner]) {
foundation_part = FOUNDATION_PART_HALFTILE;
/* Here we draw the highlights of the "three-corners-raised"-slope. That looks ok to me. */
autorail_tileh = SlopeWithThreeCornersRaised(OppositeCorner(halftile_corner));
}
}
offset = _AutorailTilehSprite[autorail_tileh][autorail_type];
}
offset = _AutorailTilehSprite[autorail_tileh][autorail_type];
SpriteID image;
PaletteID pal;
if (offset >= 0) {
image = SPR_AUTORAIL_BASE + offset;
pal = PAL_NONE;
@ -1001,7 +1017,11 @@ static void DrawAutorailSelection(const TileInfo *ti, uint autorail_type)
pal = PALETTE_SEL_TILE_RED;
}
DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
if (bridge_head_mode) {
AddSortableSpriteToDraw(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti->x, ti->y, 16, 16, 0, ti->z + 15);
} else {
DrawSelectionSprite(image, _thd.make_square_red ? PALETTE_SEL_TILE_RED : pal, ti, 7, foundation_part);
}
}
/**

Loading…
Cancel
Save