From b2ef6c0de8455afdec9ec33b70e7ea14d2de2099 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sat, 28 Aug 2021 12:51:06 +0100 Subject: [PATCH] Add implementation of multi-aspect signalling and GRF support Requires realistic braking See: #247 --- docs/landscape.html | 4 + docs/landscape_grid.html | 6 +- docs/newgrf-additions-nml.html | 34 ++ docs/newgrf-additions.html | 32 ++ src/bridge_map.h | 2 + src/command.cpp | 3 + src/infrastructure.cpp | 9 + src/misc.cpp | 1 + src/newgrf.cpp | 16 + src/newgrf.h | 7 + src/newgrf_railtype.cpp | 18 +- src/newgrf_railtype.h | 2 +- src/openttd.cpp | 2 + src/pbs.cpp | 5 + src/rail.h | 5 + src/rail_cmd.cpp | 33 +- src/rail_map.h | 16 +- src/saveload/afterload.cpp | 4 + src/saveload/extended_ver_sl.cpp | 2 +- src/saveload/misc_sl.cpp | 3 + src/settings.cpp | 1 + src/signal.cpp | 562 ++++++++++++++++++++++++++++++- src/signal_func.h | 20 ++ src/table/newgrf_debug_data.h | 14 + src/table/railtypes.h | 12 + src/train_cmd.cpp | 71 +++- src/tunnelbridge_cmd.cpp | 44 ++- src/tunnelbridge_map.h | 24 ++ 28 files changed, 917 insertions(+), 35 deletions(-) diff --git a/docs/landscape.html b/docs/landscape.html index 966c476bf8..308b55e7f5 100644 --- a/docs/landscape.html +++ b/docs/landscape.html @@ -508,6 +508,8 @@
  • m3 bits 7..4: bit set = signal 3..0 present
  • m4 bits 7..4: bit clear = signal 3..0 shows red
  • +
  • m7 bits 5..3: signal aspect for signal 0 or 1 (only valid if signal is present and not red, and multi-aspect signalling is in effect)
  • +
  • m7 bits 2..0: signal aspect for signal 2 or 3 (only valid if signal is present and not red, and multi-aspect signalling is in effect)
  • m2 bits 8..10: track reserved for pbs @@ -1740,6 +1742,8 @@
  • m6 bit 0: set = entrance signal shows green, clear = entrance signal shows red
  • m2 bit 15: for bridge entrances only: storage for visual red/green state of signals starting from 15 is allocated outside the map array
  • m2 bits 14..4: for bridge entrances only: for signals 0..10 on bridge, signal is visually red if corresponding bit in 4..14 is set
  • +
  • m3 bits 5..3: entrance signal aspect (only valid if signal is present and not red, and multi-aspect signalling is in effect)
  • +
  • m3 bits 2..0: exit signal aspect (only valid if signal is present and not red, and multi-aspect signalling is in effect)
  • m5 bits 3..2: transport type diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html index 02a4df7017..cf5000358e 100644 --- a/docs/landscape_grid.html +++ b/docs/landscape_grid.html @@ -104,7 +104,7 @@ the array so you can quickly see what is used and what is not. OOOO XXXX OO XXXXXX OOOO OOOO - OOOO OOOO + OOOO OOOO OOOO PPPP PPXX XXXX @@ -113,6 +113,7 @@ the array so you can quickly see what is used and what is not. XXXX OOOO XXXX XXXX O1 XXXXXX + OO PPPPPP depot @@ -120,6 +121,7 @@ the array so you can quickly see what is used and what is not. OOOO OOOO OOOO XXXX 11OX OOXX + OOOO OOOO OOOO OOOO OOXX XXXX @@ -285,7 +287,7 @@ the array so you can quickly see what is used and what is not. tunnel entrance OOO XXXXX PPPP PPPP PPPP PPPP - XXXX OOOO + XXXX OOOO
    OO PPPPPP OOXX XXXX OPPX XX XX PPOO OOPP diff --git a/docs/newgrf-additions-nml.html b/docs/newgrf-additions-nml.html index 05a6b06436..087cf68b38 100644 --- a/docs/newgrf-additions-nml.html +++ b/docs/newgrf-additions-nml.html @@ -66,6 +66,23 @@ If the OpenTTD version does not support this property/feature, then the property would ordinarily be ignored/skipped and no recolouring would be done. + extra_aspects0 - 6 + + The value is the number of additional signal aspects to use (e.g. 4-aspect signalling should use a value of 2).
    + When set, the lowest byte of extra_callback_info2 (signal state) may have the given number of additional values starting from 02: + + + + + + + +
    ValueMeaning
    00Red signal
    01Green signal
    021st extra aspect (e.g. yellow)
    032nd extra aspect (e.g. double yellow)
    ...Further extra aspects...
    +
    + The provided value is currently clamped to be within the range 0 - 6 (inclusive).
    + N.B. Realistic braking must be enabled for additional signal aspects to be used + + disable_realistic_braking0 or 1 When this property is set realistic braking is disabled for trains of this railtype even when realistic braking is otherwise in effect. @@ -161,6 +178,23 @@ + extra_aspects0 - 6 + + The value is the number of additional signal aspects to use (e.g. 4-aspect signalling should use a value of 2).
    + When set, the lowest byte of extra_callback_info2 (signal state) may have the given number of additional values starting from 02: + + + + + + + +
    ValueMeaning
    00Red signal
    01Green signal
    021st extra aspect (e.g. yellow)
    032nd extra aspect (e.g. double yellow)
    ...Further extra aspects...
    +
    + The provided value is currently clamped to be within the range 0 - 6 (inclusive).
    + N.B. Realistic braking must be enabled for additional signal aspects to be used + +

    Custom signal sprites example: diff --git a/docs/newgrf-additions.html b/docs/newgrf-additions.html index 8b8c209e6c..8e9887f137 100644 --- a/docs/newgrf-additions.html +++ b/docs/newgrf-additions.html @@ -241,6 +241,22 @@

    The property length is 1 byte. 0 is disabled (default). 1 is enabled.

    This is indicated by the feature name: action0_railtype_recolour, version 1

    +

    Set number of additional signal aspects (mappable property: railtype_extra_aspects)

    +

    This applies to Action 2/3 - Railtype custom signal sprites.
    + The value is the number of additional signal aspects to use (e.g. 4-aspect signalling should use a value of 2).
    + When set, the lowest byte of variable 0x18 (SS: signal state) may have the given number of additional values starting from 02: + + + + + + + +
    ValueMeaning
    00Red signal
    01Green signal
    021st extra aspect (e.g. yellow)
    032nd extra aspect (e.g. double yellow)
    ...Further extra aspects...

    +

    The property length is 1 byte.
    + The provided value is currently clamped to be within the range 0 - 6 (inclusive).

    +

    N.B. Realistic braking must be enabled for additional signal aspects to be used.

    +

    This is indicated by the feature name: action0_railtype_extra_aspects, version 1

    Disable use of realistic braking with this rail type (mappable property: railtype_disable_realistic_braking)

    When this property is set realistic braking is disabled for trains of this railtype even when realistic braking is otherwise in effect.
    The property length is 1 byte. 0 is realistic braking is not disabled for this railtype. 1 is disable realistic braking for this railtype. @@ -319,6 +335,22 @@ The Action 0 Id field is not used, the value is ignored.

    This is indicated by the feature name: action0_signals_recolour, version 1

    +

    Set number of additional signal aspects (mappable property: signals_extra_aspects)

    +

    This applies to Action 2/3 Signals (Feature 0E) custom signal sprites for this GRF.
    + The value is the number of additional signal aspects to use (e.g. 4-aspect signalling should use a value of 2).
    + When set, the lowest byte of variable 0x18 (SS: signal state) may have the given number of additional values starting from 02: + + + + + + + +
    ValueMeaning
    00Red signal
    01Green signal
    021st extra aspect (e.g. yellow)
    032nd extra aspect (e.g. double yellow)
    ...Further extra aspects...

    +

    The property length is 1 byte.
    + The provided value is currently clamped to be within the range 0 - 6 (inclusive).

    +

    N.B. Realistic braking must be enabled for additional signal aspects to be used.

    +

    This is indicated by the feature name: action0_signals_extra_aspects, version 1



    Variational Action 2 - Stations

    diff --git a/src/bridge_map.h b/src/bridge_map.h index 2f67e2b37d..b6f37cb36b 100644 --- a/src/bridge_map.h +++ b/src/bridge_map.h @@ -170,6 +170,7 @@ static inline void MakeRailBridgeRamp(TileIndex t, Owner o, BridgeType bridgetyp { /* Backup bridge signal and custom bridgehead data. */ auto m2_backup = _m[t].m2; + auto m3_backup = _m[t].m3; auto m4_backup = _m[t].m4; auto m5_backup = _m[t].m5; auto m6_backup = _me[t].m6; @@ -182,6 +183,7 @@ static inline void MakeRailBridgeRamp(TileIndex t, Owner o, BridgeType bridgetyp if (upgrade) { /* Restore bridge signal and custom bridgehead data if we're upgrading an existing bridge. */ _m[t].m2 = m2_backup; + SB(_m[t].m3, 0, 6, GB(m3_backup, 0, 6)); SB(_m[t].m4, 0, 6, GB(m4_backup, 0, 6)); SB(_m[t].m5, 4, 3, GB(m5_backup, 4, 3)); SB(_me[t].m6, 0, 2, GB(m6_backup, 0, 2)); diff --git a/src/command.cpp b/src/command.cpp index b89b996e73..e0c9cb0242 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -29,6 +29,7 @@ #include "scope_info.h" #include "core/random_func.hpp" #include "settings_func.h" +#include "signal_func.h" #include #include "table/strings.h" @@ -1087,6 +1088,7 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, /* It could happen we removed rail, thus gained money, and deleted something else. * So make sure the signal buffer is empty even in this case */ UpdateSignalsInBuffer(); + if (_extra_aspects > 0) FlushDeferredAspectUpdates(); SetDParam(0, _additional_cash_required); return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY)); } @@ -1101,6 +1103,7 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, /* update signals if needed */ UpdateSignalsInBuffer(); + if (_extra_aspects > 0) FlushDeferredAspectUpdates(); return_dcpi(res2); } diff --git a/src/infrastructure.cpp b/src/infrastructure.cpp index f3948e54f0..f29c74880a 100644 --- a/src/infrastructure.cpp +++ b/src/infrastructure.cpp @@ -350,8 +350,17 @@ void UpdateAllBlockSignals(Owner owner) } while (bits != TRACK_BIT_NONE); } else if (IsLevelCrossingTile(tile) && (owner == INVALID_OWNER || GetTileOwner(tile) == owner)) { UpdateLevelCrossing(tile); + } else if (IsTunnelBridgeWithSignalSimulation(tile)) { + if (IsTunnelBridgeSignalSimulationExit(tile)) { + AddSideToSignalBuffer(tile, INVALID_DIAGDIR, GetTileOwner(tile)); + } + if (_extra_aspects > 0 && IsTunnelBridgeSignalSimulationEntrance(tile) && GetTunnelBridgeEntranceSignalState(tile) == SIGNAL_STATE_GREEN) { + SetTunnelBridgeEntranceSignalAspect(tile, 0); + UpdateAspectDeferred(tile, GetTunnelBridgeEntranceTrackdir(tile)); + } } } while (++tile != MapSize()); UpdateSignalsInBuffer(); + FlushDeferredAspectUpdates(); } diff --git a/src/misc.cpp b/src/misc.cpp index 249eaaad9e..58eb04cb0d 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -95,6 +95,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin _game_load_tick_skip_counter = 0; _game_load_time = 0; _extra_station_names_used = 0; + _extra_aspects = 0; _loadgame_DBGL_data.clear(); if (reset_settings) MakeNewgameSettingsLive(); diff --git a/src/newgrf.cpp b/src/newgrf.cpp index aba7b06539..ce5a55e54f 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -4088,6 +4088,11 @@ static ChangeInfoResult SignalsChangeInfo(uint id, int numinfo, int prop, const SB(_cur.grffile->new_signal_ctrl_flags, NSCF_RECOLOUR_ENABLED, 1, (buf->ReadByte() != 0 ? 1 : 0)); break; + case A0RPI_SIGNALS_EXTRA_ASPECTS: + if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break; + _cur.grffile->new_signal_extra_aspects = std::min(buf->ReadByte(), NEW_SIGNALS_MAX_EXTRA_ASPECT); + break; + default: ret = HandleAction0PropertyDefault(buf, prop); break; @@ -4426,6 +4431,11 @@ static ChangeInfoResult RailTypeChangeInfo(uint id, int numinfo, int prop, const SB(rti->ctrl_flags, RTCF_RECOLOUR_ENABLED, 1, (buf->ReadByte() != 0 ? 1 : 0)); break; + case A0RPI_RAILTYPE_EXTRA_ASPECTS: + if (MappedPropertyLengthMismatch(buf, 1, mapping_entry)) break; + rti->signal_extra_aspects = std::min(buf->ReadByte(), NEW_SIGNALS_MAX_EXTRA_ASPECT); + break; + default: ret = HandleAction0PropertyDefault(buf, prop); break; @@ -4510,6 +4520,7 @@ static ChangeInfoResult RailTypeReserveInfo(uint id, int numinfo, int prop, cons case A0RPI_RAILTYPE_ENABLE_RESTRICTED_SIGNALS: case A0RPI_RAILTYPE_DISABLE_REALISTIC_BRAKING: case A0RPI_RAILTYPE_ENABLE_SIGNAL_RECOLOUR: + case A0RPI_RAILTYPE_EXTRA_ASPECTS: buf->Skip(buf->ReadExtendedByte()); break; @@ -8480,11 +8491,13 @@ static const GRFFeatureInfo _grf_feature_list[] = { GRFFeatureInfo("action0_railtype_restricted_signals", 1), GRFFeatureInfo("action0_railtype_disable_realistic_braking", 1), GRFFeatureInfo("action0_railtype_recolour", 1), + GRFFeatureInfo("action0_railtype_extra_aspects", 1), GRFFeatureInfo("action0_roadtype_extra_flags", 1), GRFFeatureInfo("action0_global_extra_station_names", 1), GRFFeatureInfo("action0_signals_programmable_signals", 1), GRFFeatureInfo("action0_signals_restricted_signals", 1), GRFFeatureInfo("action0_signals_recolour", 1), + GRFFeatureInfo("action0_signals_extra_aspects", 1), GRFFeatureInfo("action3_signals_custom_signal_sprites", 1), GRFFeatureInfo(), }; @@ -8606,12 +8619,14 @@ static const GRFPropertyMapDefinition _grf_action0_remappable_properties[] = { GRFPropertyMapDefinition(GSF_RAILTYPES, A0RPI_RAILTYPE_ENABLE_RESTRICTED_SIGNALS, "railtype_enable_restricted_signals"), GRFPropertyMapDefinition(GSF_RAILTYPES, A0RPI_RAILTYPE_DISABLE_REALISTIC_BRAKING, "railtype_disable_realistic_braking"), GRFPropertyMapDefinition(GSF_RAILTYPES, A0RPI_RAILTYPE_ENABLE_SIGNAL_RECOLOUR, "railtype_enable_signal_recolour"), + GRFPropertyMapDefinition(GSF_RAILTYPES, A0RPI_RAILTYPE_EXTRA_ASPECTS, "railtype_extra_aspects"), GRFPropertyMapDefinition(GSF_ROADTYPES, A0RPI_ROADTYPE_EXTRA_FLAGS, "roadtype_extra_flags"), GRFPropertyMapDefinition(GSF_TRAMTYPES, A0RPI_ROADTYPE_EXTRA_FLAGS, "roadtype_extra_flags"), GRFPropertyMapDefinition(GSF_GLOBALVAR, A0RPI_GLOBALVAR_EXTRA_STATION_NAMES, "global_extra_station_names"), GRFPropertyMapDefinition(GSF_SIGNALS, A0RPI_SIGNALS_ENABLE_PROGRAMMABLE_SIGNALS, "signals_enable_programmable_signals"), GRFPropertyMapDefinition(GSF_SIGNALS, A0RPI_SIGNALS_ENABLE_RESTRICTED_SIGNALS, "signals_enable_restricted_signals"), GRFPropertyMapDefinition(GSF_SIGNALS, A0RPI_SIGNALS_ENABLE_SIGNAL_RECOLOUR, "signals_enable_signal_recolour"), + GRFPropertyMapDefinition(GSF_SIGNALS, A0RPI_SIGNALS_EXTRA_ASPECTS, "signals_extra_aspects"), GRFPropertyMapDefinition(), }; @@ -9409,6 +9424,7 @@ GRFFile::GRFFile(const GRFConfig *config) this->new_signals_group = nullptr; this->new_signal_ctrl_flags = 0; + this->new_signal_extra_aspects = 0; /* Mark price_base_multipliers as 'not set' */ for (Price i = PR_BEGIN; i < PR_END; i++) { diff --git a/src/newgrf.h b/src/newgrf.h index f00696337e..1c1f26b6b9 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -119,11 +119,13 @@ enum Action0RemapPropertyIds { A0RPI_RAILTYPE_ENABLE_RESTRICTED_SIGNALS, A0RPI_RAILTYPE_DISABLE_REALISTIC_BRAKING, A0RPI_RAILTYPE_ENABLE_SIGNAL_RECOLOUR, + A0RPI_RAILTYPE_EXTRA_ASPECTS, A0RPI_ROADTYPE_EXTRA_FLAGS, A0RPI_GLOBALVAR_EXTRA_STATION_NAMES, A0RPI_SIGNALS_ENABLE_PROGRAMMABLE_SIGNALS, A0RPI_SIGNALS_ENABLE_RESTRICTED_SIGNALS, A0RPI_SIGNALS_ENABLE_SIGNAL_RECOLOUR, + A0RPI_SIGNALS_EXTRA_ASPECTS, }; enum GRFPropertyMapFallbackMode { @@ -227,6 +229,10 @@ enum NewSignalCtrlFlags { NSCF_RECOLOUR_ENABLED = 3, ///< Recolour sprites enabled }; +enum { + NEW_SIGNALS_MAX_EXTRA_ASPECT = 6, +}; + /** New signal control flags. */ enum NewSignalAction3ID { NSA3ID_CUSTOM_SIGNALS = 0, ///< Action 3 ID for custom signal sprites @@ -285,6 +291,7 @@ struct GRFFile : ZeroedMemoryAllocator { const SpriteGroup *new_signals_group; ///< New signals sprite group byte new_signal_ctrl_flags; ///< Ctrl flags for new signals + byte new_signal_extra_aspects; ///< Number of extra aspects for new signals GRFFile(const struct GRFConfig *config); ~GRFFile(); diff --git a/src/newgrf_railtype.cpp b/src/newgrf_railtype.cpp index 71f07b739f..50593c6fea 100644 --- a/src/newgrf_railtype.cpp +++ b/src/newgrf_railtype.cpp @@ -115,13 +115,21 @@ SpriteID GetCustomRailSprite(const RailtypeInfo *rti, TileIndex tile, RailTypeSp return group->GetResult(); } -static PalSpriteID GetRailTypeCustomSignalSprite(const RailtypeInfo *rti, TileIndex tile, SignalType type, SignalVariant var, SignalState state, bool gui, bool restricted) +inline uint8 RemapAspect(uint8 aspect, uint8 extra_aspects) +{ + if (likely(extra_aspects == 0 || _extra_aspects == 0)) return std::min(aspect, 1); + if (aspect == 0) return 0; + if (aspect >= extra_aspects + 1) return 1; + return aspect + 1; +} + +static PalSpriteID GetRailTypeCustomSignalSprite(const RailtypeInfo *rti, TileIndex tile, SignalType type, SignalVariant var, uint8 aspect, bool gui, bool restricted) { if (rti->group[RTSG_SIGNALS] == nullptr) return { 0, PAL_NONE }; if (type == SIGTYPE_PROG && !HasBit(rti->ctrl_flags, RTCF_PROGSIG)) return { 0, PAL_NONE }; uint32 param1 = gui ? 0x10 : 0x00; - uint32 param2 = (type << 16) | (var << 8) | state; + uint32 param2 = (type << 16) | (var << 8) | RemapAspect(aspect, rti->signal_extra_aspects); if (restricted && HasBit(rti->ctrl_flags, RTCF_RESTRICTEDSIG)) SetBit(param2, 24); RailTypeResolverObject object(rti, tile, TCX_NORMAL, RTSG_SIGNALS, param1, param2); @@ -142,16 +150,16 @@ static PalSpriteID GetRailTypeCustomSignalSprite(const RailtypeInfo *rti, TileIn * @param gui Is the sprite being used on the map or in the GUI? * @return The sprite to draw. */ -CustomSignalSpriteResult GetCustomSignalSprite(const RailtypeInfo *rti, TileIndex tile, SignalType type, SignalVariant var, SignalState state, bool gui, bool restricted) +CustomSignalSpriteResult GetCustomSignalSprite(const RailtypeInfo *rti, TileIndex tile, SignalType type, SignalVariant var, uint8 aspect, bool gui, bool restricted) { - PalSpriteID spr = GetRailTypeCustomSignalSprite(rti, tile, type, var, state, gui, restricted); + PalSpriteID spr = GetRailTypeCustomSignalSprite(rti, tile, type, var, aspect, gui, restricted); if (spr.sprite != 0) return { spr, HasBit(rti->ctrl_flags, RTCF_PROGSIG) }; for (const GRFFile *grf : _new_signals_grfs) { if (type == SIGTYPE_PROG && !HasBit(grf->new_signal_ctrl_flags, NSCF_PROGSIG)) continue; uint32 param1 = gui ? 0x10 : 0x00; - uint32 param2 = (type << 16) | (var << 8) | state; + uint32 param2 = (type << 16) | (var << 8) | RemapAspect(aspect, grf->new_signal_extra_aspects); if (restricted && HasBit(grf->new_signal_ctrl_flags, NSCF_RESTRICTEDSIG)) SetBit(param2, 24); NewSignalsResolverObject object(grf, tile, TCX_NORMAL, param1, param2); diff --git a/src/newgrf_railtype.h b/src/newgrf_railtype.h index 6c478c4590..8b8eb63e23 100644 --- a/src/newgrf_railtype.h +++ b/src/newgrf_railtype.h @@ -61,7 +61,7 @@ struct CustomSignalSpriteResult { }; SpriteID GetCustomRailSprite(const RailtypeInfo *rti, TileIndex tile, RailTypeSpriteGroup rtsg, TileContext context = TCX_NORMAL, uint *num_results = nullptr); -CustomSignalSpriteResult GetCustomSignalSprite(const RailtypeInfo *rti, TileIndex tile, SignalType type, SignalVariant var, SignalState state, bool gui = false, bool restricted = false); +CustomSignalSpriteResult GetCustomSignalSprite(const RailtypeInfo *rti, TileIndex tile, SignalType type, SignalVariant var, uint8 aspect, bool gui = false, bool restricted = false); RailType GetRailTypeTranslation(uint8 railtype, const GRFFile *grffile); uint8 GetReverseRailTypeTranslation(RailType railtype, const GRFFile *grffile); diff --git a/src/openttd.cpp b/src/openttd.cpp index 87f1434f99..d9f3536a69 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -465,6 +465,7 @@ static void ShutdownGame() _game_load_tick_skip_counter = 0; _game_load_time = 0; _extra_station_names_used = 0; + _extra_aspects = 0; _loadgame_DBGL_data.clear(); _loadgame_DBGC_data.clear(); } @@ -1860,6 +1861,7 @@ void StateGameLoop() UpdateStateChecksum(c->money); } } + if (_extra_aspects > 0) FlushDeferredAspectUpdates(); assert(IsLocalCompany()); } diff --git a/src/pbs.cpp b/src/pbs.cpp index 9ba7ad2cbb..5c5cf70f61 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -85,6 +85,10 @@ bool TryReserveRailTrackdir(TileIndex tile, Trackdir td, bool trigger_stations) if (success && HasPbsSignalOnTrackdir(tile, td)) { SetSignalStateByTrackdir(tile, td, SIGNAL_STATE_GREEN); MarkSingleSignalDirty(tile, td); + if (_extra_aspects > 0) { + SetSignalAspect(tile, TrackdirToTrack(td), 0); + UpdateAspectDeferred(tile, td); + } } return success; } @@ -240,6 +244,7 @@ void UnreserveRailTrack(TileIndex tile, Track t) if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgeEffectivelyPBS(tile) && IsTrackAcrossTunnelBridge(tile, t)) { if (IsTunnelBridgePBS(tile)) { SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); + if (_extra_aspects > 0) PropagateAspectChange(tile, GetTunnelBridgeExitTrackdir(tile), 0); } else { UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, GetTileOwner(tile)); } diff --git a/src/rail.h b/src/rail.h index 33a7c2feb5..b3689b37ff 100644 --- a/src/rail.h +++ b/src/rail.h @@ -224,6 +224,11 @@ public: */ byte ctrl_flags; + /** + * Signal extra aspects + */ + uint8 signal_extra_aspects; + /** * Cost multiplier for building this rail type */ diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index a4a82c8bfd..640c4b59f9 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -70,7 +70,7 @@ void ResetRailTypes() {0,0,0,0,0,0,0,0,{}}, {0,0,0,0,0,0,0,0}, {0,0,0,0,0,0}, - 0, RAILTYPES_NONE, RAILTYPES_NONE, RAILTYPES_NONE, 0, 0, 0, RTFB_NONE, 0, 0, 0, 0, 0, 0, + 0, RAILTYPES_NONE, RAILTYPES_NONE, RAILTYPES_NONE, 0, 0, 0, RTFB_NONE, 0, 0, 0, 0, 0, 0, 0, RailTypeLabelList(), 0, 0, RAILTYPES_NONE, RAILTYPES_NONE, 0, {}, {} }; for (; i < lengthof(_railtypes); i++) _railtypes[i] = empty_railtype; @@ -113,8 +113,8 @@ void ResolveRailTypeGUISprites(RailtypeInfo *rti) for (SignalType type = SIGTYPE_NORMAL; type < SIGTYPE_END; type = (SignalType)(type + 1)) { for (SignalVariant var = SIG_ELECTRIC; var <= SIG_SEMAPHORE; var = (SignalVariant)(var + 1)) { - PalSpriteID red = GetCustomSignalSprite(rti, INVALID_TILE, type, var, SIGNAL_STATE_RED, true).sprite; - PalSpriteID green = GetCustomSignalSprite(rti, INVALID_TILE, type, var, SIGNAL_STATE_GREEN, true).sprite; + PalSpriteID red = GetCustomSignalSprite(rti, INVALID_TILE, type, var, 0, true).sprite; + PalSpriteID green = GetCustomSignalSprite(rti, INVALID_TILE, type, var, 255, true).sprite; if (red.sprite != 0) { rti->gui_sprites.signals[type][var][0] = { red.sprite + SIGNAL_TO_SOUTH, red.pal }; } else { @@ -1392,6 +1392,10 @@ static void SetupBridgeTunnelSignalSimulation(TileIndex entrance, TileIndex exit SetTunnelBridgeSignalSimulationEntrance(entrance); SetTunnelBridgeEntranceSignalState(entrance, SIGNAL_STATE_GREEN); SetTunnelBridgeSignalSimulationExit(exit); + if (_extra_aspects > 0) { + SetTunnelBridgeEntranceSignalAspect(entrance, 0); + UpdateAspectDeferred(entrance, GetTunnelBridgeEntranceTrackdir(entrance)); + } } static void ReReserveTrainPath(Train *v) @@ -1504,6 +1508,10 @@ CommandCost CmdBuildSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1, SetTunnelBridgeSignalSimulationEntrance(t); SetTunnelBridgeEntranceSignalState(t, SIGNAL_STATE_GREEN); SetTunnelBridgeSignalSimulationExit(t); + if (_extra_aspects > 0) { + SetTunnelBridgeEntranceSignalAspect(t, 0); + UpdateAspectDeferred(t, GetTunnelBridgeEntranceTrackdir(t)); + } }; if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { @@ -2673,12 +2681,27 @@ static const int SIGNAL_DIRTY_RIGHT = 14 * ZOOM_LVL_BASE; static const int SIGNAL_DIRTY_TOP = 30 * ZOOM_LVL_BASE; static const int SIGNAL_DIRTY_BOTTOM = 5 * ZOOM_LVL_BASE; -void DrawSingleSignal(TileIndex tile, const RailtypeInfo *rti, Track track, SignalState condition, SignalOffsets image, uint pos, SignalType type, SignalVariant variant, bool show_restricted) +void DrawSingleSignal(TileIndex tile, const RailtypeInfo *rti, Track track, SignalState condition, SignalOffsets image, uint pos, SignalType type, + SignalVariant variant, bool show_restricted, bool exit_signal = false) { uint x, y; GetSignalXY(tile, pos, x, y); - const CustomSignalSpriteResult result = GetCustomSignalSprite(rti, tile, type, variant, condition, false, show_restricted); + uint8 aspect; + if (condition == SIGNAL_STATE_GREEN) { + aspect = 1; + if (_extra_aspects > 0) { + if (IsPlainRailTile(tile)) { + aspect = GetSignalAspect(tile, track); + } else if (IsTunnelBridgeWithSignalSimulation(tile)) { + aspect = exit_signal? GetTunnelBridgeExitSignalAspect(tile) : GetTunnelBridgeEntranceSignalAspect(tile); + } + } + } else { + aspect = 0; + } + + const CustomSignalSpriteResult result = GetCustomSignalSprite(rti, tile, type, variant, aspect, false, show_restricted); SpriteID sprite = result.sprite.sprite; PaletteID pal = PAL_NONE; bool is_custom_sprite = (sprite != 0); diff --git a/src/rail_map.h b/src/rail_map.h index 83b427a42e..fd96e8d961 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -352,7 +352,7 @@ static inline bool IsPresignalProgrammable(TileIndex t, Track track) /** One-way signals can't be passed the 'wrong' way. */ static inline bool IsOnewaySignal(TileIndex t, Track track) { - return GetSignalType(t, track) != SIGTYPE_PBS; + return IsOnewaySignal(GetSignalType(t, track)); } static inline void CycleSignalSide(TileIndex t, Track track) @@ -378,6 +378,20 @@ static inline void SetSignalVariant(TileIndex t, Track track, SignalVariant v) if (track == INVALID_TRACK) SB(_m[t].m2, 7, 1, v); } +static inline uint8 GetSignalAspect(TileIndex t, Track track) +{ + assert_tile(GetRailTileType(t) == RAIL_TILE_SIGNALS, t); + byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 3 : 0; + return GB(_me[t].m7, pos, 3); +} + +static inline void SetSignalAspect(TileIndex t, Track track, uint8 aspect) +{ + assert_tile(GetRailTileType(t) == RAIL_TILE_SIGNALS, t); + byte pos = (track == TRACK_LOWER || track == TRACK_RIGHT) ? 3 : 0; + SB(_me[t].m7, pos, 3, aspect); +} + /** * Set the states of the signals (Along/AgainstTrackDir) * @param tile the tile to set the states for diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index a78beefaf8..a10485da66 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -3976,6 +3976,8 @@ bool AfterLoadGame() extern void YapfCheckRailSignalPenalties(); YapfCheckRailSignalPenalties(); + UpdateExtraAspectsVariable(); + if (_networking && !_network_server) { SlProcessVENC(); } @@ -4050,6 +4052,8 @@ void ReloadNewGRFData() } } + UpdateExtraAspectsVariable(); + /* Update company statistics. */ AfterLoadCompanyStats(); /* Check and update house and town values */ diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 5ade36cb14..8cfad01739 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -146,7 +146,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_ANIMATED_TILE_EXTRA, XSCF_NULL, 1, 1, "animated_tile_extra", nullptr, nullptr, nullptr }, { XSLFI_NEWGRF_INFO_EXTRA, XSCF_NULL, 1, 1, "newgrf_info_extra", nullptr, nullptr, nullptr }, { XSLFI_INDUSTRY_CARGO_ADJ, XSCF_IGNORABLE_UNKNOWN, 1, 1, "industry_cargo_adj", nullptr, nullptr, nullptr }, - { XSLFI_REALISTIC_TRAIN_BRAKING,XSCF_NULL, 3, 3, "realistic_train_braking", nullptr, nullptr, "VLKA" }, + { XSLFI_REALISTIC_TRAIN_BRAKING,XSCF_NULL, 4, 4, "realistic_train_braking", nullptr, nullptr, "VLKA" }, { XSLFI_INFLATION_FIXED_DATES, XSCF_IGNORABLE_ALL, 1, 1, "inflation_fixed_dates", nullptr, nullptr, nullptr }, { XSLFI_WATER_FLOODING, XSCF_NULL, 2, 2, "water_flooding", nullptr, nullptr, nullptr }, { XSLFI_MORE_HOUSES, XSCF_NULL, 2, 2, "more_houses", nullptr, nullptr, nullptr }, diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp index 29a5c80835..af7e8c4207 100644 --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -26,6 +26,7 @@ extern TileIndex _cur_tileloop_tile; extern uint16 _disaster_delay; extern byte _trees_tick_ctr; +extern uint8 _extra_aspects; /* Keep track of current game position */ int _saved_scrollpos_x; @@ -95,6 +96,7 @@ static const SaveLoadGlobVarList _date_desc[] = { SLEG_CONDVAR(_pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION), SLEG_CONDVAR_X(_game_events_overall, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GAME_EVENTS)), SLEG_CONDVAR_X(_road_layout_change_counter, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_LAYOUT_CHANGE_CTR)), + SLEG_CONDVAR_X(_extra_aspects, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_REALISTIC_TRAIN_BRAKING, 4)), SLE_CONDNULL(4, SLV_11, SLV_120), SLEG_END() }; @@ -124,6 +126,7 @@ static const SaveLoadGlobVarList _date_check_desc[] = { SLE_CONDNULL(1, SLV_4, SL_MAX_VERSION), // _pause_mode SLE_CONDNULL_X(4, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_GAME_EVENTS)), // _game_events_overall SLE_CONDNULL_X(4, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ROAD_LAYOUT_CHANGE_CTR)), // _road_layout_change_counter + SLE_CONDNULL_X(1, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_REALISTIC_TRAIN_BRAKING, 4)), // _extra_aspects SLE_CONDNULL(4, SLV_11, SLV_120), SLEG_END() }; diff --git a/src/settings.cpp b/src/settings.cpp index 6609d7679a..e9ce4ef8dd 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1199,6 +1199,7 @@ static bool TrainBrakingModelChanged(int32 p1) } } + UpdateExtraAspectsVariable(); UpdateAllBlockSignals(); InvalidateWindowData(WC_BUILD_SIGNAL, 0); diff --git a/src/signal.cpp b/src/signal.cpp index 34968059de..db3bb993f0 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -20,9 +20,14 @@ #include "programmable_signals.h" #include "error.h" #include "infrastructure_func.h" +#include "tunnelbridge.h" +#include "bridge_signal_map.h" +#include "newgrf_newsignals.h" #include "safeguards.h" +uint8 _extra_aspects = 0; + /// List of signals dependent upon this one typedef std::vector SignalDependencyList; @@ -197,6 +202,7 @@ public: }; static SmallSet _tbuset("_tbuset"); ///< set of signals that will be updated +static SmallSet _tbpset("_tbpset"); ///< set of PBS signals to update the aspect of static SmallSet _tbdset("_tbdset"); ///< set of open nodes in current signal block static SmallSet _globset("_globset"); ///< set of places to be updated in following runs @@ -285,10 +291,14 @@ struct SigInfo { flags = SF_NONE; num_exits = 0; num_green = 0; + out_signal_tile = INVALID_TILE; + out_signal_trackdir = INVALID_TRACKDIR; } SigFlags flags; uint num_exits; uint num_green; + TileIndex out_signal_tile; + Trackdir out_signal_trackdir; }; /** @@ -354,18 +364,28 @@ static SigInfo ExploreSegment(Owner owner) if (HasSignalOnTrackdir(tile, reversedir)) { if (IsPbsSignalNonExtended(sig)) { info.flags |= SF_PBS; + if (_extra_aspects > 0 && GetSignalStateByTrackdir(tile, reversedir) == SIGNAL_STATE_GREEN) { + _tbpset.Add(tile, reversedir); + } } else if (!_tbuset.Add(tile, reversedir)) { info.flags |= SF_FULL; return info; } } - if (HasSignalOnTrackdir(tile, trackdir) && !IsOnewaySignal(tile, track)) info.flags |= SF_PBS; - /* if it is a presignal EXIT in OUR direction, count it */ - if (IsPresignalExit(tile, track) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit - info.num_exits++; - if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { // found green presignal exit - info.num_green++; + if (HasSignalOnTrackdir(tile, trackdir)) { + if (!IsOnewaySignal(sig)) info.flags |= SF_PBS; + if (_extra_aspects > 0) { + info.out_signal_tile = tile; + info.out_signal_trackdir = trackdir; + } + + /* if it is a presignal EXIT in OUR direction, count it */ + if (IsExitSignal(sig)) { // found presignal exit + info.num_exits++; + if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { // found green presignal exit + info.num_green++; + } } } @@ -455,11 +475,19 @@ static SigInfo ExploreSegment(Owner owner) if (IsTunnelBridgeSignalSimulationExit(tile)) { if (IsTunnelBridgePBS(tile)) { info.flags |= SF_PBS; + if (_extra_aspects > 0 && GetTunnelBridgeExitSignalState(tile) == SIGNAL_STATE_GREEN) { + Trackdir exit_td = GetTunnelBridgeExitTrackdir(tile, tunnel_bridge_dir); + _tbpset.Add(tile, exit_td); + } } else if (!_tbuset.Add(tile, INVALID_TRACKDIR)) { info.flags |= SF_FULL; return info; } } + if (_extra_aspects > 0 && IsTunnelBridgeSignalSimulationEntrance(tile)) { + info.out_signal_tile = tile; + info.out_signal_trackdir = GetTunnelBridgeEntranceTrackdir(tile, tunnel_bridge_dir); + } if (!(info.flags & SF_TRAIN)) { if (HasVehicleOnPos(tile, VEH_TRAIN, reinterpret_cast((uintptr_t)tile), &TrainInWormholeTileEnum)) info.flags |= SF_TRAIN; if (!(info.flags & SF_TRAIN) && IsTunnelBridgeSignalSimulationExit(tile)) { @@ -510,6 +538,162 @@ static SigInfo ExploreSegment(Owner owner) return info; } +static uint8 GetSignalledTunnelBridgeEntranceForwardAspect(TileIndex tile, TileIndex tile_exit) +{ + if (!IsTunnelBridgeSignalSimulationEntrance(tile)) return 0; + const uint spacing = GetTunnelBridgeSignalSimulationSpacing(tile); + const uint signal_count = GetTunnelBridgeLength(tile, tile_exit) / spacing; + if (IsBridge(tile)) { + uint8 aspect = 0; + for (uint i = 0; i < signal_count; i++) { + if (GetBridgeEntranceSimulatedSignalState(tile, i) == SIGNAL_STATE_GREEN) { + aspect++; + } else { + return std::min(aspect, _extra_aspects + 1); + } + } + if (GetTunnelBridgeExitSignalState(tile_exit) == SIGNAL_STATE_GREEN) aspect += GetTunnelBridgeExitSignalAspect(tile_exit); + return std::min(aspect, _extra_aspects + 1); + } else { + int free_tiles = GetAvailableFreeTilesInSignalledTunnelBridge(tile, tile_exit, tile); + if (free_tiles == INT_MAX) { + uint aspect = signal_count; + if (GetTunnelBridgeExitSignalState(tile_exit) == SIGNAL_STATE_GREEN) aspect += GetTunnelBridgeExitSignalAspect(tile_exit); + return std::min(aspect, _extra_aspects + 1); + } else { + if (free_tiles < (int)spacing) return 0; + return std::min((free_tiles / spacing) - 1, _extra_aspects + 1); + } + } +} + +uint8 GetForwardAspectFollowingTrack(TileIndex tile, Trackdir trackdir) +{ + Owner owner = GetTileOwner(tile); + DiagDirection exitdir = TrackdirToExitdir(trackdir); + DiagDirection enterdir = ReverseDiagDir(exitdir); + if (IsTileType(tile, MP_TUNNELBRIDGE) && TrackdirEntersTunnelBridge(tile, trackdir)) { + TileIndex other = GetOtherTunnelBridgeEnd(tile); + if (IsTunnelBridgeWithSignalSimulation(tile)) { + return GetSignalledTunnelBridgeEntranceForwardAspect(tile, other); + } + tile = other; + } else { + tile += TileOffsByDiagDir(exitdir); + } + while (true) { + switch (GetTileType(tile)) { + case MP_RAILWAY: { + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return 0; + + if (IsRailDepot(tile)) { + return 0; + } + + TrackBits tracks = GetTrackBits(tile); // trackbits of tile + TrackBits tracks_masked = (TrackBits)(tracks & _enterdir_to_trackbits[enterdir]); // only incidating trackbits + + if (tracks_masked == TRACK_BIT_NONE) return 0; // no incidating track + if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) tracks = tracks_masked; + + if (!HasAtMostOneBit(tracks)) { + TrackBits reserved_bits = GetRailReservationTrackBits(tile) & tracks_masked; + if (reserved_bits == TRACK_BIT_NONE) return 0; // no reservation on junction + tracks = reserved_bits; + } + + Track track = (Track)FIND_FIRST_BIT(tracks); + trackdir = TrackEnterdirToTrackdir(track, ReverseDiagDir(enterdir)); + + if (HasSignals(tile)) { + if (HasSignalOnTrack(tile, track)) { // now check whole track, not trackdir + if (HasSignalOnTrackdir(tile, trackdir)) { + if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_RED) return 0; + return GetSignalAspect(tile, track); + } else if (IsOnewaySignal(tile, track)) { + return 0; // one-way signal facing the wrong way + } + } + } + + exitdir = TrackdirToExitdir(trackdir); + enterdir = ReverseDiagDir(exitdir); + tile += TileOffsByDiagDir(exitdir); + + break; + } + + case MP_STATION: + if (!HasStationRail(tile)) return 0; + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return 0; + if (DiagDirToAxis(enterdir) != GetRailStationAxis(tile)) return 0; // different axis + if (IsStationTileBlocked(tile)) return 0; // 'eye-candy' station tile + + tile += TileOffsByDiagDir(exitdir); + break; + + case MP_ROAD: + if (!IsLevelCrossing(tile)) return 0; + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return 0; + if (DiagDirToAxis(enterdir) == GetCrossingRoadAxis(tile)) return 0; // different axis + + tile += TileOffsByDiagDir(exitdir); + break; + + case MP_TUNNELBRIDGE: { + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return 0; + if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return 0; + + TrackBits tracks = GetTunnelBridgeTrackBits(tile); // trackbits of tile + TrackBits tracks_masked = (TrackBits)(tracks & _enterdir_to_trackbits[enterdir]); // only incidating trackbits + + if (tracks_masked == TRACK_BIT_NONE) return 0; // no incidating track + if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) tracks = tracks_masked; + + if (!HasAtMostOneBit(tracks)) { + TrackBits reserved_bits = GetTunnelBridgeReservationTrackBits(tile) & tracks_masked; + if (reserved_bits == TRACK_BIT_NONE) return 0; // no reservation on junction + tracks = reserved_bits; + } + + Track track = (Track)FIND_FIRST_BIT(tracks); + trackdir = TrackEnterdirToTrackdir(track, ReverseDiagDir(enterdir)); + + if (IsTunnelBridgeWithSignalSimulation(tile) && HasTrack(GetAcrossTunnelBridgeTrackBits(tile), track)) { + return GetSignalAspectGeneric(tile, trackdir); + } + + if (TrackdirEntersTunnelBridge(tile, trackdir)) { + tile = GetOtherTunnelBridgeEnd(tile); + enterdir = GetTunnelBridgeDirection(tile); + exitdir = ReverseDiagDir(enterdir); + } else { + exitdir = TrackdirToExitdir(trackdir); + enterdir = ReverseDiagDir(exitdir); + tile += TileOffsByDiagDir(exitdir); + } + break; + } + + default: + return 0; + } + } +} + +static uint8 GetForwardAspect(const SigInfo &info, TileIndex tile, Trackdir trackdir) +{ + if (info.flags & SF_JUNCTION) { + return GetForwardAspectFollowingTrack(tile, trackdir); + } else { + return (info.out_signal_tile != INVALID_TILE) ? GetSignalAspectGeneric(info.out_signal_tile, info.out_signal_trackdir) : 0; + } +} + +static uint8 GetForwardAspectAndIncrement(const SigInfo &info, TileIndex tile, Trackdir trackdir) +{ + return std::min(GetForwardAspect(info, tile, trackdir) + 1, _extra_aspects + 1); +} /** * Update signals around segment in _tbuset @@ -529,13 +713,42 @@ static void UpdateSignalsAroundSegment(SigInfo info) while (_tbuset.Get(&tile, &trackdir)) { if (IsTileType(tile, MP_TUNNELBRIDGE) && IsTunnelBridgeSignalSimulationExit(tile)) { - if (IsTunnelBridgePBS(tile) || (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && HasAcrossTunnelBridgeReservation(tile))) continue; + if (IsTunnelBridgePBS(tile) || (_settings_game.vehicle.train_braking_model == TBM_REALISTIC && HasAcrossTunnelBridgeReservation(tile))) { + if (_extra_aspects > 0 && GetTunnelBridgeExitSignalState(tile) == SIGNAL_STATE_GREEN) { + Trackdir exit_td = GetTunnelBridgeExitTrackdir(tile); + uint8 aspect = GetForwardAspectAndIncrement(info, tile, exit_td); + if (aspect != GetTunnelBridgeExitSignalAspect(tile)) { + SetTunnelBridgeExitSignalAspect(tile, aspect); + MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + PropagateAspectChange(tile, exit_td, aspect); + } + } + continue; + } SignalState old_state = GetTunnelBridgeExitSignalState(tile); SignalState new_state = (info.flags & SF_TRAIN) ? SIGNAL_STATE_RED : SIGNAL_STATE_GREEN; + bool refresh = false; if (old_state != new_state) { SetTunnelBridgeExitSignalState(tile, new_state); - MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + refresh = true; + } + if (_extra_aspects > 0) { + const uint8 current_aspect = (old_state == SIGNAL_STATE_GREEN) ? GetTunnelBridgeExitSignalAspect(tile) : 0; + uint8 aspect; + if (new_state == SIGNAL_STATE_GREEN) { + aspect = GetForwardAspectAndIncrement(info, tile, trackdir); + } else { + aspect = 0; + } + if (aspect != current_aspect || old_state != new_state) { + if (new_state == SIGNAL_STATE_GREEN) SetTunnelBridgeExitSignalAspect(tile, aspect); + refresh = true; + Trackdir exit_td = GetTunnelBridgeExitTrackdir(tile); + PropagateAspectChange(tile, exit_td, aspect); + } } + if (refresh) MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + continue; } @@ -546,7 +759,18 @@ static void UpdateSignalsAroundSegment(SigInfo info) SignalState newstate = SIGNAL_STATE_GREEN; /* don't change signal state if tile is reserved in realistic braking mode */ - if ((_settings_game.vehicle.train_braking_model == TBM_REALISTIC && HasBit(GetRailReservationTrackBits(tile), track))) continue; + if ((_settings_game.vehicle.train_braking_model == TBM_REALISTIC && HasBit(GetRailReservationTrackBits(tile), track))) { + if (_extra_aspects > 0 && GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { + uint8 aspect = GetForwardAspectAndIncrement(info, tile, trackdir); + uint8 old_aspect = GetSignalAspect(tile, track); + if (aspect != old_aspect) { + SetSignalAspect(tile, track, aspect); + if (old_aspect != 0) MarkSingleSignalDirty(tile, trackdir); + PropagateAspectChange(tile, trackdir, aspect); + } + } + continue; + } /* determine whether the new state is red */ if (info.flags & SF_TRAIN) { @@ -587,8 +811,29 @@ static void UpdateSignalsAroundSegment(SigInfo info) } } + bool refresh = false; + const SignalState current_state = GetSignalStateByTrackdir(tile, trackdir); + + if (_extra_aspects > 0) { + const uint8 current_aspect = (current_state == SIGNAL_STATE_GREEN) ? GetSignalAspect(tile, track) : 0; + uint8 aspect; + if (newstate == SIGNAL_STATE_GREEN) { + aspect = 1; + if (info.out_signal_tile != INVALID_TILE) { + aspect = std::min(GetSignalAspectGeneric(info.out_signal_tile, info.out_signal_trackdir) + 1, _extra_aspects + 1); + } + } else { + aspect = 0; + } + if (aspect != current_aspect || newstate != current_state) { + SetSignalAspect(tile, track, aspect); + refresh = true; + PropagateAspectChange(tile, trackdir, aspect); + } + } + /* only when the state changes */ - if (newstate != GetSignalStateByTrackdir(tile, trackdir)) { + if (newstate != current_state) { if (IsExitSignal(sig)) { /* for pre-signal exits, add block to the global set */ DiagDirection exitdir = TrackdirToExitdir(ReverseTrackdir(trackdir)); @@ -598,10 +843,32 @@ static void UpdateSignalsAroundSegment(SigInfo info) MarkDependencidesForUpdate(SignalReference(tile, track)); } SetSignalStateByTrackdir(tile, trackdir, newstate); + refresh = true; + } + if (refresh) { MarkSingleSignalDirty(tile, trackdir); } } + while (_tbpset.Get(&tile, &trackdir)) { + uint8 aspect = GetForwardAspectAndIncrement(info, tile, trackdir); + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + uint8 old_aspect = GetTunnelBridgeExitSignalAspect(tile); + if (aspect != old_aspect) { + SetTunnelBridgeExitSignalAspect(tile, aspect); + if (old_aspect != 0) MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + PropagateAspectChange(tile, trackdir, aspect); + } + } else { + uint8 old_aspect = GetSignalAspect(tile, track); + Track track = TrackdirToTrack(trackdir); + if (aspect != old_aspect) { + SetSignalAspect(tile, track, aspect); + if (old_aspect != 0) MarkSingleSignalDirty(tile, trackdir); + PropagateAspectChange(tile, trackdir, aspect); + } + } + } } @@ -609,6 +876,7 @@ static void UpdateSignalsAroundSegment(SigInfo info) static inline void ResetSets() { _tbuset.Reset(); + _tbpset.Reset(); _tbdset.Reset(); _globset.Reset(); } @@ -943,3 +1211,277 @@ void CheckRemoveSignal(TileIndex tile, Track track) _signal_dependencies.erase(i); } } + +uint8 GetSignalAspectGeneric(TileIndex tile, Trackdir trackdir) +{ + switch (GetTileType(tile)) { + case MP_RAILWAY: + if (HasSignalOnTrackdir(tile, trackdir) && GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { + return GetSignalAspect(tile, TrackdirToTrack(trackdir)); + } + break; + + case MP_TUNNELBRIDGE: + if (IsTunnelBridgeSignalSimulationEntrance(tile) && TrackdirEntersTunnelBridge(tile, trackdir)) { + return (GetTunnelBridgeEntranceSignalState(tile) == SIGNAL_STATE_GREEN) ? GetTunnelBridgeEntranceSignalAspect(tile) : 0; + } + if (IsTunnelBridgeSignalSimulationExit(tile) && TrackdirExitsTunnelBridge(tile, trackdir)) { + return (GetTunnelBridgeExitSignalState(tile) == SIGNAL_STATE_GREEN) ? GetTunnelBridgeExitSignalAspect(tile) : 0; + } + break; + + default: + break; + } + + return 0; +} + +static void RefreshBridgeOnExitAspectChange(TileIndex entrance, TileIndex exit) +{ + const uint simulated_wormhole_signals = GetTunnelBridgeSignalSimulationSpacing(entrance); + const uint bridge_length = GetTunnelBridgeLength(entrance, exit); + const TileIndexDiffC offset = TileIndexDiffCByDiagDir(GetTunnelBridgeDirection(entrance)); + const TileIndexDiff diff = TileDiffXY(offset.x * simulated_wormhole_signals, offset.y * simulated_wormhole_signals); + const uint signal_count = bridge_length / simulated_wormhole_signals; + TileIndex tile = entrance; + for (uint i = signal_count; i > 0; i--) { + tile += diff; + if (i <= 2) MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + } +} + +void PropagateAspectChange(TileIndex tile, Trackdir trackdir, uint8 aspect) +{ + aspect = std::min(aspect + 1, _extra_aspects + 1); + Owner owner = GetTileOwner(tile); + DiagDirection exitdir = TrackdirToExitdir(ReverseTrackdir(trackdir)); + DiagDirection enterdir = ReverseDiagDir(exitdir); + if (IsTileType(tile, MP_TUNNELBRIDGE) && TrackdirExitsTunnelBridge(tile, trackdir)) { + TileIndex other = GetOtherTunnelBridgeEnd(tile); + if (IsBridge(tile)) RefreshBridgeOnExitAspectChange(other, tile); + aspect = std::min(GetSignalledTunnelBridgeEntranceForwardAspect(other, tile) + 1, _extra_aspects + 1); + tile = other; + } else { + tile += TileOffsByDiagDir(exitdir); + } + while (true) { + switch (GetTileType(tile)) { + case MP_RAILWAY: { + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return; + + if (IsRailDepot(tile)) { + return; + } + + TrackBits tracks = GetTrackBits(tile); // trackbits of tile + TrackBits tracks_masked = (TrackBits)(tracks & _enterdir_to_trackbits[enterdir]); // only incidating trackbits + + if (tracks_masked == TRACK_BIT_NONE) return; // no incidating track + if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) tracks = tracks_masked; + + if (!HasAtMostOneBit(tracks)) { + TrackBits reserved_bits = GetRailReservationTrackBits(tile) & tracks_masked; + if (reserved_bits == TRACK_BIT_NONE) return; // no reservation on junction + tracks = reserved_bits; + } + + Track track = (Track)FIND_FIRST_BIT(tracks); + trackdir = TrackEnterdirToTrackdir(track, ReverseDiagDir(enterdir)); + + if (HasSignals(tile)) { + if (HasSignalOnTrack(tile, track)) { // now check whole track, not trackdir + Trackdir reversedir = ReverseTrackdir(trackdir); + + if (HasSignalOnTrackdir(tile, reversedir)) { + if (GetSignalStateByTrackdir(tile, reversedir) == SIGNAL_STATE_RED) return; + if (GetSignalAspect(tile, track) == aspect) return; // aspect already correct + SetSignalAspect(tile, track, aspect); + MarkSingleSignalDirty(tile, reversedir); + aspect = std::min(aspect + 1, _extra_aspects + 1); + } else if (IsOnewaySignal(tile, track)) { + return; // one-way signal facing the wrong way + } + } + } + + exitdir = TrackdirToExitdir(trackdir); + enterdir = ReverseDiagDir(exitdir); + tile += TileOffsByDiagDir(exitdir); + + break; + } + + case MP_STATION: + if (!HasStationRail(tile)) return; + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return; + if (DiagDirToAxis(enterdir) != GetRailStationAxis(tile)) return; // different axis + if (IsStationTileBlocked(tile)) return; // 'eye-candy' station tile + + tile += TileOffsByDiagDir(exitdir); + break; + + case MP_ROAD: + if (!IsLevelCrossing(tile)) return; + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return; + if (DiagDirToAxis(enterdir) == GetCrossingRoadAxis(tile)) return; // different axis + + tile += TileOffsByDiagDir(exitdir); + break; + + case MP_TUNNELBRIDGE: { + if (!IsOneSignalBlock(owner, GetTileOwner(tile))) return; + if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) return; + + TrackBits tracks = GetTunnelBridgeTrackBits(tile); // trackbits of tile + TrackBits tracks_masked = (TrackBits)(tracks & _enterdir_to_trackbits[enterdir]); // only incidating trackbits + + if (tracks_masked == TRACK_BIT_NONE) return; // no incidating track + if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) tracks = tracks_masked; + + if (!HasAtMostOneBit(tracks)) { + TrackBits reserved_bits = GetTunnelBridgeReservationTrackBits(tile) & tracks_masked; + if (reserved_bits == TRACK_BIT_NONE) return; // no reservation on junction + tracks = reserved_bits; + } + + Track track = (Track)FIND_FIRST_BIT(tracks); + trackdir = TrackEnterdirToTrackdir(track, ReverseDiagDir(enterdir)); + + if (TrackdirEntersTunnelBridge(tile, trackdir)) { + TileIndex other = GetOtherTunnelBridgeEnd(tile); + if (IsTunnelBridgeWithSignalSimulation(tile)) { + /* exit signal */ + if (!IsTunnelBridgeSignalSimulationExit(tile) || GetTunnelBridgeExitSignalState(tile) != SIGNAL_STATE_GREEN) return; + if (GetTunnelBridgeExitSignalAspect(tile) == aspect) return; + SetTunnelBridgeExitSignalAspect(tile, aspect); + MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + if (IsBridge(tile)) RefreshBridgeOnExitAspectChange(other, tile); + aspect = std::min(GetSignalledTunnelBridgeEntranceForwardAspect(other, tile) + 1, _extra_aspects + 1); + } + enterdir = GetTunnelBridgeDirection(other); + exitdir = ReverseDiagDir(enterdir); + tile = other; + } else { + if (TrackdirEntersTunnelBridge(tile, ReverseTrackdir(trackdir))) { + if (IsTunnelBridgeWithSignalSimulation(tile)) { + /* entrance signal */ + if (!IsTunnelBridgeSignalSimulationEntrance(tile) || GetTunnelBridgeEntranceSignalState(tile) != SIGNAL_STATE_GREEN) return; + if (GetTunnelBridgeEntranceSignalAspect(tile) == aspect) return; + SetTunnelBridgeEntranceSignalAspect(tile, aspect); + MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + aspect = std::min(aspect + 1, _extra_aspects + 1); + } + } + exitdir = TrackdirToExitdir(trackdir); + enterdir = ReverseDiagDir(exitdir); + tile += TileOffsByDiagDir(exitdir); + } + break; + } + + default: + return; + } + } +} + +static std::vector> _deferred_aspect_updates; + +void UpdateAspectDeferred(TileIndex tile, Trackdir trackdir) +{ + _deferred_aspect_updates.push_back({ tile, trackdir }); +} + +void FlushDeferredAspectUpdates() +{ + /* Iterate in reverse order to reduce backtracking when updating the aspects of a new reservation */ + for (auto iter = _deferred_aspect_updates.rbegin(); iter != _deferred_aspect_updates.rend(); ++iter) { + TileIndex tile = iter->first; + Trackdir trackdir = iter->second; + switch (GetTileType(tile)) { + case MP_RAILWAY: + if (HasSignalOnTrackdir(tile, trackdir) && GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN && GetSignalAspect(tile, TrackdirToTrack(trackdir)) == 0) { + uint8 aspect = GetForwardAspectFollowingTrackAndIncrement(tile, trackdir); + SetSignalAspect(tile, TrackdirToTrack(trackdir), aspect); + PropagateAspectChange(tile, trackdir, aspect); + } + break; + + case MP_TUNNELBRIDGE: + if (IsTunnelBridgeSignalSimulationEntrance(tile) && TrackdirEntersTunnelBridge(tile, trackdir) && + GetTunnelBridgeEntranceSignalState(tile) == SIGNAL_STATE_GREEN && GetTunnelBridgeEntranceSignalAspect(tile) == 0) { + uint8 aspect = GetForwardAspectFollowingTrackAndIncrement(tile, trackdir); + SetTunnelBridgeEntranceSignalAspect(tile, aspect); + PropagateAspectChange(tile, trackdir, aspect); + } + if (IsTunnelBridgeSignalSimulationExit(tile) && TrackdirExitsTunnelBridge(tile, trackdir) && + GetTunnelBridgeExitSignalState(tile) == SIGNAL_STATE_GREEN && GetTunnelBridgeExitSignalAspect(tile) == 0) { + uint8 aspect = GetForwardAspectFollowingTrackAndIncrement(tile, trackdir); + SetTunnelBridgeExitSignalAspect(tile, aspect); + PropagateAspectChange(tile, trackdir, aspect); + } + break; + + default: + break; + } + } + _deferred_aspect_updates.clear(); +} + +void UpdateAllSignalAspects() +{ + for (TileIndex tile = 0; tile != MapSize(); ++tile) { + if (IsTileType(tile, MP_RAILWAY) && HasSignals(tile)) { + TrackBits bits = GetTrackBits(tile); + do { + Track track = RemoveFirstTrack(&bits); + if (HasSignalOnTrack(tile, track)) { + Trackdir trackdir = TrackToTrackdir(track); + if (!HasSignalOnTrackdir(tile, trackdir)) trackdir = ReverseTrackdir(trackdir); + if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { + uint8 aspect = GetForwardAspectFollowingTrackAndIncrement(tile, trackdir); + SetSignalAspect(tile, track, aspect); + PropagateAspectChange(tile, trackdir, aspect); + } + } + } while (bits != TRACK_BIT_NONE); + } else if (IsTunnelBridgeWithSignalSimulation(tile)) { + if (IsTunnelBridgeSignalSimulationEntrance(tile) && GetTunnelBridgeEntranceSignalState(tile) == SIGNAL_STATE_GREEN) { + Trackdir trackdir = GetTunnelBridgeEntranceTrackdir(tile); + uint8 aspect = GetForwardAspectFollowingTrackAndIncrement(tile, trackdir); + SetTunnelBridgeEntranceSignalAspect(tile, aspect); + PropagateAspectChange(tile, trackdir, aspect); + } + if (IsTunnelBridgeSignalSimulationExit(tile) && GetTunnelBridgeExitSignalState(tile) == SIGNAL_STATE_GREEN) { + Trackdir trackdir = GetTunnelBridgeExitTrackdir(tile); + uint8 aspect = GetForwardAspectFollowingTrackAndIncrement(tile, trackdir); + SetTunnelBridgeExitSignalAspect(tile, aspect); + PropagateAspectChange(tile, trackdir, aspect); + } + } + } +} + +void UpdateExtraAspectsVariable() +{ + uint8 new_extra_aspects = 0; + + if (_settings_game.vehicle.train_braking_model == TBM_REALISTIC) { + for (RailType r = RAILTYPE_BEGIN; r != RAILTYPE_END; r++) { + const RailtypeInfo *rti = GetRailTypeInfo(r); + new_extra_aspects = std::max(new_extra_aspects, rti->signal_extra_aspects); + } + for (const GRFFile *grf : _new_signals_grfs) { + new_extra_aspects = std::max(new_extra_aspects, grf->new_signal_extra_aspects); + } + } + + + if (new_extra_aspects != _extra_aspects) { + _extra_aspects = new_extra_aspects; + if (_extra_aspects > 0) UpdateAllSignalAspects(); + MarkWholeScreenDirty(); + } +} diff --git a/src/signal_func.h b/src/signal_func.h index 4655f8aa5f..368dd00ffc 100644 --- a/src/signal_func.h +++ b/src/signal_func.h @@ -19,6 +19,8 @@ #include "settings_type.h" #include "vehicle_type.h" +extern uint8 _extra_aspects; + /** * Maps a trackdir to the bit that stores its status in the map arrays, in the * direction along with the trackdir. @@ -85,6 +87,12 @@ static inline bool IsProgrammableSignal(SignalType type) return type == SIGTYPE_PROG; } +/** One-way signals can't be passed the 'wrong' way. */ +static inline bool IsOnewaySignal(SignalType type) +{ + return type != SIGTYPE_PBS; +} + /// Is this signal type unsuitable for realistic braking? static inline bool IsSignalTypeUnsuitableForRealisticBraking(SignalType type) { @@ -153,5 +161,17 @@ void AddTrackToSignalBuffer(TileIndex tile, Track track, Owner owner); void AddSideToSignalBuffer(TileIndex tile, DiagDirection side, Owner owner); void UpdateSignalsInBuffer(); void UpdateSignalsInBufferIfOwnerNotAddable(Owner owner); +uint8 GetForwardAspectFollowingTrack(TileIndex tile, Trackdir trackdir); +uint8 GetSignalAspectGeneric(TileIndex tile, Trackdir trackdir); +void PropagateAspectChange(TileIndex tile, Trackdir trackdir, uint8 aspect); +void UpdateAspectDeferred(TileIndex tile, Trackdir trackdir); +void FlushDeferredAspectUpdates(); +void UpdateAllSignalAspects(); +void UpdateExtraAspectsVariable(); + +inline uint8 GetForwardAspectFollowingTrackAndIncrement(TileIndex tile, Trackdir trackdir) +{ + return std::min(GetForwardAspectFollowingTrack(tile, trackdir) + 1, _extra_aspects + 1); +} #endif /* SIGNAL_FUNC_H */ diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index d27392adaa..cc5534113e 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -909,6 +909,20 @@ class NIHRailType : public NIHelper { if (secondary != INVALID_RAILTYPE) { writeRailType(secondary); } + + if (IsTileType(index, MP_RAILWAY) && HasSignals(index)) { + print("Signals:"); + for (Trackdir td = TRACKDIR_BEGIN; td < TRACKDIR_END; td = (Trackdir)(td + 1)) { + if (!IsValidTrackdir(td)) continue; + if (HasTrack(index, TrackdirToTrack(td)) && HasSignalOnTrackdir(index, td)) { + char *b = buffer; + const SignalState state = GetSignalStateByTrackdir(index, td); + b += seprintf(b, lastof(buffer), " trackdir: %d, state: %d", td, state); + if (_extra_aspects > 0 && state == SIGNAL_STATE_GREEN) seprintf(b, lastof(buffer), ", aspect: %d", GetSignalAspect(index, TrackdirToTrack(td))); + print(buffer); + } + } + } } }; diff --git a/src/table/railtypes.h b/src/table/railtypes.h index 7ff8776fb1..e558926cd6 100644 --- a/src/table/railtypes.h +++ b/src/table/railtypes.h @@ -83,6 +83,9 @@ static const RailtypeInfo _original_railtypes[] = { /* control flags */ 0, + /* signal extra aspects */ + 0, + /* cost multiplier */ 8, @@ -190,6 +193,9 @@ static const RailtypeInfo _original_railtypes[] = { /* control flags */ 0, + /* signal extra aspects */ + 0, + /* cost multiplier */ 12, @@ -293,6 +299,9 @@ static const RailtypeInfo _original_railtypes[] = { /* control flags */ 0, + /* signal extra aspects */ + 0, + /* cost multiplier */ 16, @@ -396,6 +405,9 @@ static const RailtypeInfo _original_railtypes[] = { /* control flags */ 0, + /* signal extra aspects */ + 0, + /* cost multiplier */ 24, diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 2f0eab374f..2d78f89ffc 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2903,6 +2903,9 @@ void ReverseTrainDirection(Train *v) if (IsTunnelBridgeWithSignalSimulation(v->tile) && IsTunnelBridgeSignalSimulationEntrance(v->tile)) { /* Flip signal on tunnel entrance tile red. */ SetTunnelBridgeEntranceSignalState(v->tile, SIGNAL_STATE_RED); + if (_extra_aspects > 0) { + PropagateAspectChange(v->tile, GetTunnelBridgeEntranceTrackdir(v->tile), 0); + } MarkTileDirtyByTile(v->tile, VMDF_NOT_MAP_MODE); update_check_tunnel_bridge_signal_counters(v); ClrBit(v->flags, VRF_TRAIN_STUCK); @@ -3346,11 +3349,46 @@ static int GetAndClearLastBridgeEntranceSetSignalIndex(TileIndex bridge_entrance return 0; } +static void UpdateTunnelBridgeEntranceSignalAspect(TileIndex tile) +{ + Trackdir trackdir = GetTunnelBridgeEntranceTrackdir(tile); + uint8 aspect = GetForwardAspectFollowingTrackAndIncrement(tile, trackdir); + uint8 old_aspect = GetTunnelBridgeEntranceSignalAspect(tile); + if (aspect != old_aspect) { + SetTunnelBridgeEntranceSignalAspect(tile, aspect); + MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + PropagateAspectChange(tile, trackdir, aspect); + } +} + static void SetTunnelBridgeEntranceSignalGreen(TileIndex tile) { if (GetTunnelBridgeEntranceSignalState(tile) == SIGNAL_STATE_RED) { SetTunnelBridgeEntranceSignalState(tile, SIGNAL_STATE_GREEN); MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + if (_extra_aspects > 0) { + SetTunnelBridgeEntranceSignalAspect(tile, 0); + UpdateAspectDeferred(tile, GetTunnelBridgeEntranceTrackdir(tile)); + } + } else if (_extra_aspects > 0) { + UpdateTunnelBridgeEntranceSignalAspect(tile); + } +} + +static void UpdateEntranceAspectFromMiddleSignalChange(TileIndex entrance, int signal_number) +{ + if (signal_number < _extra_aspects && GetTunnelBridgeEntranceSignalState(entrance) == SIGNAL_STATE_GREEN) { + UpdateTunnelBridgeEntranceSignalAspect(entrance); + } +} + +static void UpdateAspectFromBridgeMiddleSignalChange(TileIndex entrance, TileIndexDiff diff, int signal_number) +{ + UpdateEntranceAspectFromMiddleSignalChange(entrance, signal_number); + if (signal_number > 0) { + for (int i = std::max(0, signal_number - _extra_aspects); i < signal_number; i++) { + MarkTileDirtyByTile(entrance + (diff * (i + 1)), VMDF_NOT_MAP_MODE); + } } } @@ -3360,8 +3398,10 @@ static void HandleLastTunnelBridgeSignals(TileIndex tile, TileIndex end, DiagDir /* Clearing last bridge signal. */ int signal_offset = GetAndClearLastBridgeEntranceSetSignalIndex(end); if (signal_offset) { - TileIndex last_signal_tile = end + (TileOffsByDiagDir(dir) * GetTunnelBridgeSignalSimulationSpacing(tile) * signal_offset); + TileIndexDiff diff = TileOffsByDiagDir(dir) * GetTunnelBridgeSignalSimulationSpacing(tile); + TileIndex last_signal_tile = end + (diff * signal_offset); MarkTileDirtyByTile(last_signal_tile, VMDF_NOT_MAP_MODE); + if (_extra_aspects > 0) UpdateAspectFromBridgeMiddleSignalChange(end, diff, signal_offset - 1); } MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); } @@ -3380,6 +3420,9 @@ static void HandleLastTunnelBridgeSignals(TileIndex tile, TileIndex end, DiagDir if (IsTunnelBridgeSignalSimulationEntrance(end)) SetTunnelBridgeEntranceSignalGreen(end); if (IsTunnelBridgeSignalSimulationEntrance(tile)) SetTunnelBridgeEntranceSignalGreen(tile); + } else if (IsTunnel(end) && _extra_aspects > 0) { + uint signal_count = GetTunnelBridgeLength(tile, end) / GetTunnelBridgeSignalSimulationSpacing(end); + if (signal_count > 0) UpdateEntranceAspectFromMiddleSignalChange(end, signal_count - 1); } } @@ -3389,6 +3432,7 @@ static void UnreserveBridgeTunnelTile(TileIndex tile) if (IsTunnelBridgeSignalSimulationExit(tile) && IsTunnelBridgeEffectivelyPBS(tile)) { if (IsTunnelBridgePBS(tile)) { SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_RED); + if (_extra_aspects > 0) PropagateAspectChange(tile, GetTunnelBridgeExitTrackdir(tile), 0); } else { UpdateSignalsOnSegment(tile, INVALID_DIAGDIR, GetTileOwner(tile)); } @@ -3985,6 +4029,10 @@ static void TryLongReserveChooseTrainTrack(Train *v, TileIndex tile, Trackdir td SetTunnelReservation(exit_tile, true); } SetTunnelBridgeExitSignalState(exit_tile, SIGNAL_STATE_GREEN); + if (_extra_aspects > 0) { + SetTunnelBridgeExitSignalAspect(exit_tile, 0); + UpdateAspectDeferred(exit_tile, GetTunnelBridgeExitTrackdir(exit_tile)); + } ChooseTrainTrack(v, ft.m_new_tile, ft.m_exitdir, TrackdirBitsToTrackBits(ft.m_new_td_bits), CTTF_NO_LOOKAHEAD_VALIDATE | (force_res ? CTTF_FORCE_RES : CTTF_NONE), nullptr, lookahead_state); @@ -4087,6 +4135,10 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, do_track_reservation = true; changed_signal = TrackEnterdirToTrackdir(track, enterdir); SetSignalStateByTrackdir(tile, changed_signal, SIGNAL_STATE_GREEN); + if (_extra_aspects > 0) { + SetSignalAspect(tile, track, 0); + UpdateAspectDeferred(tile, changed_signal); + } } else if (!do_track_reservation) { return track; } @@ -4927,6 +4979,10 @@ static bool CheckTrainStayInWormHolePathReserve(Train *t, TileIndex tile) if (t->lookahead->reservation_end_tile == t->tile && t->lookahead->reservation_end_position - t->lookahead->current_position <= (int)TILE_SIZE && !HasBit(t->lookahead->flags, TRLF_TB_EXIT_FREE)) return false; SignalState exit_state = GetTunnelBridgeExitSignalState(tile); SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_GREEN); + if (_extra_aspects > 0) { + SetTunnelBridgeExitSignalAspect(tile, 0); + UpdateAspectDeferred(tile, GetTunnelBridgeExitTrackdir(tile)); + } TileIndex veh_orig_tile = t->tile; TrackBits veh_orig_track = t->track; Direction veh_orig_direction = t->direction; @@ -4978,6 +5034,10 @@ static bool CheckTrainStayInWormHolePathReserve(Train *t, TileIndex tile) t->direction = veh_orig_direction; if (ok && IsTunnelBridgeEffectivelyPBS(tile)) { SetTunnelBridgeExitSignalState(tile, SIGNAL_STATE_GREEN); + if (_extra_aspects > 0) { + SetTunnelBridgeExitSignalAspect(tile, 0); + UpdateAspectDeferred(tile, GetTunnelBridgeExitTrackdir(tile)); + } mark_dirty = true; } return ok; @@ -5033,6 +5093,9 @@ static void HandleSignalBehindTrain(Train *v, int signal_number) } else if (IsBridge(v->tile) && signal_number >= 0) { SetBridgeEntranceSimulatedSignalState(v->tile, signal_number, SIGNAL_STATE_GREEN); MarkTileDirtyByTile(tile, VMDF_NOT_MAP_MODE); + if (_extra_aspects > 0) UpdateAspectFromBridgeMiddleSignalChange(v->tile, TileOffsByDiagDir(GetTunnelBridgeDirection(v->tile)) * simulated_wormhole_signals, signal_number); + } else if (IsTunnel(v->tile) && signal_number >= 0 && _extra_aspects > 0) { + UpdateEntranceAspectFromMiddleSignalChange(v->tile, signal_number); } } @@ -5335,11 +5398,17 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) } /* Flip signal on tunnel entrance tile red. */ SetTunnelBridgeEntranceSignalState(gp.new_tile, SIGNAL_STATE_RED); + if (_extra_aspects > 0) { + PropagateAspectChange(gp.new_tile, GetTunnelBridgeEntranceTrackdir(gp.new_tile), 0); + } MarkTileDirtyByTile(gp.new_tile, VMDF_NOT_MAP_MODE); if (IsTunnelBridgeSignalSimulationBidirectional(gp.new_tile)) { /* Set incoming signal in other direction to red as well */ TileIndex other_end = GetOtherTunnelBridgeEnd(gp.new_tile); SetTunnelBridgeEntranceSignalState(other_end, SIGNAL_STATE_RED); + if (_extra_aspects > 0) { + PropagateAspectChange(other_end, GetTunnelBridgeEntranceTrackdir(other_end), 0); + } MarkTileDirtyByTile(other_end, VMDF_NOT_MAP_MODE); } } diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp index 93ed5ec684..49e92930b1 100644 --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -1719,7 +1719,15 @@ static void DrawTunnelBridgeRampSingleSignal(const TileInfo *ti, bool is_green, SignalVariant variant = IsTunnelBridgeSemaphore(ti->tile) ? SIG_SEMAPHORE : SIG_ELECTRIC; const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile)); - PalSpriteID sprite = GetCustomSignalSprite(rti, ti->tile, type, variant, is_green ? SIGNAL_STATE_GREEN : SIGNAL_STATE_RED).sprite; + uint8 aspect = 0; + if (is_green) { + if (_extra_aspects > 0) { + aspect = show_exit ? GetTunnelBridgeExitSignalAspect(ti->tile) : GetTunnelBridgeEntranceSignalAspect(ti->tile); + } else { + aspect = 1; + } + } + PalSpriteID sprite = GetCustomSignalSprite(rti, ti->tile, type, variant, aspect).sprite; bool is_custom_sprite = (sprite.sprite != 0); if (is_custom_sprite) { @@ -1764,9 +1772,8 @@ static void DrawTunnelBridgeRampSignal(const TileInfo *ti) } /* Draws a signal on tunnel / bridge entrance tile. */ -static void DrawBridgeSignalOnMiddlePart(const TileInfo *ti, TileIndex bridge_start_tile, uint z) +static void DrawBridgeSignalOnMiddlePart(const TileInfo *ti, TileIndex bridge_start_tile, TileIndex bridge_end_tile, uint z) { - uint bridge_signal_position = 0; int m2_position = 0; @@ -1802,9 +1809,27 @@ static void DrawBridgeSignalOnMiddlePart(const TileInfo *ti, TileIndex bridge_st SignalVariant variant = IsTunnelBridgeSemaphore(bridge_start_tile) ? SIG_SEMAPHORE : SIG_ELECTRIC; SignalState state = GetBridgeEntranceSimulatedSignalState(bridge_start_tile, m2_position); + uint8 aspect = 0; + if (state == SIGNAL_STATE_GREEN) { + aspect = 1; + if (_extra_aspects > 0) { + const uint bridge_length = GetTunnelBridgeLength(bridge_start_tile, bridge_end_tile) + 1; + while (true) { + bridge_signal_position += simulated_wormhole_signals; + if (bridge_signal_position >= bridge_length) { + if (GetTunnelBridgeExitSignalState(bridge_end_tile) == SIGNAL_STATE_GREEN) aspect += GetTunnelBridgeExitSignalAspect(bridge_end_tile); + break; + } + m2_position++; + if (GetBridgeEntranceSimulatedSignalState(bridge_start_tile, m2_position) != SIGNAL_STATE_GREEN) break; + aspect++; + if (aspect >= _extra_aspects + 1) break; + } + } + } const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(bridge_start_tile)); - PalSpriteID sprite = GetCustomSignalSprite(rti, bridge_start_tile, SIGTYPE_NORMAL, variant, state).sprite; + PalSpriteID sprite = GetCustomSignalSprite(rti, bridge_start_tile, SIGTYPE_NORMAL, variant, aspect).sprite; if (sprite.sprite != 0) { sprite.sprite += position; @@ -1820,6 +1845,7 @@ static void DrawBridgeSignalOnMiddlePart(const TileInfo *ti, TileIndex bridge_st } AddSortableSpriteToDraw(sprite.sprite, sprite.pal, x, y, 1, 1, TILE_HEIGHT, z, false, 0, 0, BB_Z_SEPARATOR); + break; } m2_position++; } @@ -1979,7 +2005,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti, DrawTileProcParams params) if (IsTunnelBridgeWithSignalSimulation(ti->tile)) { extern void DrawSingleSignal(TileIndex tile, const RailtypeInfo *rti, Track track, SignalState condition, - SignalOffsets image, uint pos, SignalType type, SignalVariant variant, bool show_restricted); + SignalOffsets image, uint pos, SignalType type, SignalVariant variant, bool show_restricted, bool exit_signal = false); DiagDirection dir = GetTunnelBridgeDirection(ti->tile); SignalVariant variant = IsTunnelBridgeSemaphore(ti->tile) ? SIG_SEMAPHORE : SIG_ELECTRIC; @@ -1992,14 +2018,14 @@ static void DrawTile_TunnelBridge(TileInfo *ti, DrawTileProcParams params) image = (SignalOffsets)(image ^ 1); } if (IsTunnelBridgeSignalSimulationEntrance(ti->tile)) { - DrawSingleSignal(ti->tile, rti, t, GetTunnelBridgeEntranceSignalState(ti->tile), image, position, SIGTYPE_NORMAL, variant, false); + DrawSingleSignal(ti->tile, rti, t, GetTunnelBridgeEntranceSignalState(ti->tile), image, position, SIGTYPE_NORMAL, variant, false, false); } if (IsTunnelBridgeSignalSimulationExit(ti->tile)) { SignalType type = SIGTYPE_NORMAL; if (IsTunnelBridgePBS(ti->tile)) { type = IsTunnelBridgeSignalSimulationEntrance(ti->tile) ? SIGTYPE_PBS : SIGTYPE_PBS_ONEWAY; } - DrawSingleSignal(ti->tile, rti, t, GetTunnelBridgeExitSignalState(ti->tile), (SignalOffsets)(image ^ 1), position ^ 1, type, variant, false); + DrawSingleSignal(ti->tile, rti, t, GetTunnelBridgeExitSignalState(ti->tile), (SignalOffsets)(image ^ 1), position ^ 1, type, variant, false, true); } }; switch (t) { @@ -2308,8 +2334,8 @@ void DrawBridgeMiddle(const TileInfo *ti) if (HasRailCatenaryDrawn(GetRailType(rampsouth))) { DrawRailCatenaryOnBridge(ti); } - if (IsTunnelBridgeSignalSimulationEntrance(rampsouth)) DrawBridgeSignalOnMiddlePart(ti, rampsouth, z); - if (IsTunnelBridgeSignalSimulationEntrance(rampnorth)) DrawBridgeSignalOnMiddlePart(ti, rampnorth, z); + if (IsTunnelBridgeSignalSimulationEntrance(rampsouth)) DrawBridgeSignalOnMiddlePart(ti, rampsouth, rampnorth, z); + if (IsTunnelBridgeSignalSimulationEntrance(rampnorth)) DrawBridgeSignalOnMiddlePart(ti, rampnorth, rampsouth, z); } /* draw roof, the component of the bridge which is logically between the vehicle and the camera */ diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h index f6e5782e8f..123c4a0dcf 100644 --- a/src/tunnelbridge_map.h +++ b/src/tunnelbridge_map.h @@ -506,6 +506,30 @@ static inline void SetTunnelBridgePBS(TileIndex t, bool is_pbs) SB(_me[t].m6, 6, 1, is_pbs ? 1 : 0); } +static inline uint8 GetTunnelBridgeEntranceSignalAspect(TileIndex t) +{ + assert_tile(IsTunnelBridgeWithSignalSimulation(t), t); + return GB(_m[t].m3, 0, 3); +} + +static inline void SetTunnelBridgeEntranceSignalAspect(TileIndex t, uint8 aspect) +{ + assert_tile(IsTunnelBridgeWithSignalSimulation(t), t); + SB(_m[t].m3, 0, 3, aspect); +} + +static inline uint8 GetTunnelBridgeExitSignalAspect(TileIndex t) +{ + assert_tile(IsTunnelBridgeWithSignalSimulation(t), t); + return GB(_m[t].m3, 3, 3); +} + +static inline void SetTunnelBridgeExitSignalAspect(TileIndex t, uint8 aspect) +{ + assert_tile(IsTunnelBridgeWithSignalSimulation(t), t); + SB(_m[t].m3, 3, 3, aspect); +} + static inline Trackdir GetTunnelBridgeExitTrackdir(TileIndex t, DiagDirection tunnel_bridge_dir) { return TrackEnterdirToTrackdir((Track)FIND_FIRST_BIT(GetAcrossTunnelBridgeTrackBits(t)), ReverseDiagDir(tunnel_bridge_dir));