diff --git a/src/lang/english.txt b/src/lang/english.txt index 57a4dd6fc2..a7153ca872 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2689,6 +2689,7 @@ STR_TRACE_RESTRICT_DIRECTION_NW :north-west STR_TRACE_RESTRICT_COMPANY :Company STR_TRACE_RESTRICT_UNDEFINED_COMPANY :Undefined company STR_TRACE_RESTRICT_SLOT_OP :Slot operation +STR_TRACE_RESTRICT_REVERSE :Reverse STR_TRACE_RESTRICT_SLOT_ACQUIRE_WAIT :Acquire or wait STR_TRACE_RESTRICT_SLOT_TRY_ACQUIRE :Try to acquire STR_TRACE_RESTRICT_SLOT_RELEASE_FRONT :Release (front) @@ -2717,6 +2718,8 @@ STR_TRACE_RESTRICT_TRAIN_STATUS_LOADING :loading STR_TRACE_RESTRICT_TRAIN_STATUS_WAITING :waiting STR_TRACE_RESTRICT_TRAIN_STATUS_LOST :lost STR_TRACE_RESTRICT_TRAIN_STATUS_REQUIRES_SERVICE :requires service +STR_TRACE_RESTRICT_REVERSE_SIG :Reverse behind signal +STR_TRACE_RESTRICT_REVERSE_SIG_CANCEL :Cancel reverse behind signal STR_TRACE_RESTRICT_VALUE_CAPTION :{WHITE}Value STR_TRACE_RESTRICT_CAPTION :{WHITE}Routefinding restriction STR_TRACE_RESTRICT_CAPTION_SHARED :{WHITE}Routefinding restriction - shared by {COMMA} signals diff --git a/src/pathfinder/yapf/yapf_base.hpp b/src/pathfinder/yapf/yapf_base.hpp index 6360567bb2..e72b9d0c83 100644 --- a/src/pathfinder/yapf/yapf_base.hpp +++ b/src/pathfinder/yapf/yapf_base.hpp @@ -208,17 +208,25 @@ public: } /** add multiple nodes - direct children of the given node */ - inline void AddMultipleNodes(Node *parent, const TrackFollower &tf) + template + inline void AddMultipleNodes(Node *parent, const TrackFollower &tf, TNodeFunc node_func) { bool is_choice = (KillFirstBit(tf.m_new_td_bits) != TRACKDIR_BIT_NONE); for (TrackdirBits rtds = tf.m_new_td_bits; rtds != TRACKDIR_BIT_NONE; rtds = KillFirstBit(rtds)) { Trackdir td = (Trackdir)FindFirstBit2x64(rtds); Node &n = Yapf().CreateNewNode(); n.Set(parent, tf.m_new_tile, td, is_choice); + node_func(n); Yapf().AddNewNode(n, tf); } } + /** add multiple nodes - direct children of the given node */ + inline void AddMultipleNodes(Node *parent, const TrackFollower &tf) + { + AddMultipleNodes(parent, tf, [&](Node &n) {}); + } + /** * In some cases an intermediate node branch should be pruned. * The most prominent case is when a red EOL signal is encountered, but diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index 064cd65dcd..9f5c04e66a 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -273,6 +273,9 @@ private: *is_res_through = false; flags_to_check |= TRPAUF_RESERVE_THROUGH; } + if (GetSignalType(tile, TrackdirToTrack(trackdir)) == SIGTYPE_PBS && !HasSignalOnTrackdir(tile, trackdir)) { + flags_to_check |= TRPAUF_REVERSE; + } if (prog && prog->actions_used_flags & flags_to_check) { prog->Execute(Yapf().GetVehicle(), TraceRestrictProgramInput(tile, trackdir, &TraceRestrictPreviousSignalCallback, &n), out); if (out.flags & TRPRF_RESERVE_THROUGH && is_res_through != NULL) { @@ -282,6 +285,10 @@ private: n.m_segment->m_end_segment_reason |= ESRB_DEAD_END; return true; } + if (out.flags & TRPRF_REVERSE && flags_to_check & TRPAUF_REVERSE && !n.flags_u.flags_s.m_reverse_pending) { + n.flags_u.flags_s.m_reverse_pending = true; + n.m_segment->m_end_segment_reason |= ESRB_REVERSE; + } cost += out.penalty; } return false; @@ -478,6 +485,17 @@ public: assert(!is_cached_segment); /* Skip the first transition cost calculation. */ goto no_entry_cost; + } else if (n.flags_u.flags_s.m_teleport) { + int x1 = 2 * TileX(prev.tile); + int y1 = 2 * TileY(prev.tile); + int x2 = 2 * TileX(cur.tile); + int y2 = 2 * TileY(cur.tile); + int dx = abs(x1 - x2); + int dy = abs(y1 - y2); + int dmin = min(dx, dy); + int dxy = abs(dx - dy); + segment_entry_cost += dmin * YAPF_TILE_CORNER_LENGTH + (dxy - 1) * (YAPF_TILE_LENGTH / 2); + goto no_entry_cost; } for (;;) { @@ -723,8 +741,10 @@ no_entry_cost: // jump here at the beginning if the node has no parent (it is th /* Do we have an excuse why not to continue pathfinding in this direction? */ if (!target_seen && (end_segment_reason & ESRB_ABORT_PF_MASK) != ESRB_NONE) { - /* Reason to not continue. Stop this PF branch. */ - return false; + if (likely(!n.flags_u.flags_s.m_reverse_pending || (end_segment_reason & ESRB_ABORT_PF_MASK_PENDING_REVERSE) != ESRB_NONE)) { + /* Reason to not continue. Stop this PF branch. */ + return false; + } } /* Special costs for the case we have reached our target. */ diff --git a/src/pathfinder/yapf/yapf_node_rail.hpp b/src/pathfinder/yapf/yapf_node_rail.hpp index 94c8d6c2f4..d8fb0cc9f9 100644 --- a/src/pathfinder/yapf/yapf_node_rail.hpp +++ b/src/pathfinder/yapf/yapf_node_rail.hpp @@ -137,6 +137,8 @@ struct CYapfRailNodeT bool m_targed_seen : 1; bool m_choice_seen : 1; bool m_last_signal_was_red : 1; + bool m_reverse_pending : 1; + bool m_teleport : 1; } flags_s; } flags_u; SignalType m_last_red_signal_type; @@ -174,6 +176,7 @@ struct CYapfRailNodeT m_last_signal_type = parent->m_last_signal_type; } flags_u.flags_s.m_choice_seen |= is_choice; + flags_u.flags_s.m_teleport = false; } inline TileIndex GetLastTile() const diff --git a/src/pathfinder/yapf/yapf_rail.cpp b/src/pathfinder/yapf/yapf_rail.cpp index 645065f6cc..a7a937ae27 100644 --- a/src/pathfinder/yapf/yapf_rail.cpp +++ b/src/pathfinder/yapf/yapf_rail.cpp @@ -293,6 +293,21 @@ public: inline void PfFollowNode(Node &old_node) { TrackFollower F(Yapf().GetVehicle()); + if (old_node.flags_u.flags_s.m_reverse_pending && old_node.m_segment->m_end_segment_reason & (ESRB_SAFE_TILE | ESRB_DEPOT | ESRB_DEAD_END)) { + Node *rev_node = &old_node; + while (rev_node && !(rev_node->m_segment->m_end_segment_reason & ESRB_REVERSE)) { + rev_node = rev_node->m_parent; + } + if (rev_node) { + if (F.Follow(rev_node->GetLastTile(), ReverseTrackdir(rev_node->GetLastTrackdir()))) { + Yapf().AddMultipleNodes(&old_node, F, [&](Node &n) { + n.flags_u.flags_s.m_reverse_pending = false; + n.flags_u.flags_s.m_teleport = true; + }); + } + return; + } + } if (F.Follow(old_node.GetLastTile(), old_node.GetLastTrackdir())) { Yapf().AddMultipleNodes(&old_node, F); } @@ -471,6 +486,21 @@ public: inline void PfFollowNode(Node &old_node) { TrackFollower F(Yapf().GetVehicle()); + if (old_node.flags_u.flags_s.m_reverse_pending && old_node.m_segment->m_end_segment_reason & (ESRB_SAFE_TILE | ESRB_DEPOT | ESRB_DEAD_END)) { + Node *rev_node = &old_node; + while (rev_node && !(rev_node->m_segment->m_end_segment_reason & ESRB_REVERSE)) { + rev_node = rev_node->m_parent; + } + if (rev_node) { + if (F.Follow(rev_node->GetLastTile(), ReverseTrackdir(rev_node->GetLastTrackdir()))) { + Yapf().AddMultipleNodes(&old_node, F, [&](Node &n) { + n.flags_u.flags_s.m_reverse_pending = false; + n.flags_u.flags_s.m_teleport = true; + }); + } + return; + } + } if (F.Follow(old_node.GetLastTile(), old_node.GetLastTrackdir())) { Yapf().AddMultipleNodes(&old_node, F); } diff --git a/src/pathfinder/yapf/yapf_type.hpp b/src/pathfinder/yapf/yapf_type.hpp index 8d24eee5e2..d56d5e926a 100644 --- a/src/pathfinder/yapf/yapf_type.hpp +++ b/src/pathfinder/yapf/yapf_type.hpp @@ -31,6 +31,7 @@ enum EndSegmentReason { ESR_FIRST_TWO_WAY_RED, ///< first signal was 2-way and it was red ESR_LOOK_AHEAD_END, ///< we have just passed the last look-ahead signal ESR_TARGET_REACHED, ///< we have just reached the destination + ESR_REVERSE, ///< we should reverse after this point /* Special values */ ESR_NONE = 0xFF, ///< no reason to end the segment here @@ -53,6 +54,7 @@ enum EndSegmentReasonBits { ESRB_FIRST_TWO_WAY_RED = 1 << ESR_FIRST_TWO_WAY_RED, ESRB_LOOK_AHEAD_END = 1 << ESR_LOOK_AHEAD_END, ESRB_TARGET_REACHED = 1 << ESR_TARGET_REACHED, + ESRB_REVERSE = 1 << ESR_REVERSE, /* Additional (composite) values. */ @@ -60,10 +62,13 @@ enum EndSegmentReasonBits { ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, /* What reasons can be stored back into cached segment. */ - ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE, + ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE | ESRB_REVERSE, /* Reasons to abort pathfinding in this direction. */ ESRB_ABORT_PF_MASK = ESRB_DEAD_END | ESRB_PATH_TOO_LONG | ESRB_INFINITE_LOOP | ESRB_FIRST_TWO_WAY_RED, + + /* Reasons to abort pathfinding in this direction, when reversing is pending. */ + ESRB_ABORT_PF_MASK_PENDING_REVERSE = ESRB_ABORT_PF_MASK & ~ESRB_DEAD_END, }; DECLARE_ENUM_AS_BIT_SET(EndSegmentReasonBits) diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index ea78c252fd..ff062b89f7 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -51,6 +51,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_TRACE_RESTRICT_OWNER, XSCF_NULL, 1, 1, "tracerestrict_owner", NULL, NULL, NULL }, { XSLFI_TRACE_RESTRICT_ORDRCND, XSCF_NULL, 2, 2, "tracerestrict_order_cond", NULL, NULL, NULL }, { XSLFI_TRACE_RESTRICT_STATUSCND,XSCF_NULL, 1, 1, "tracerestrict_status_cond", NULL, NULL, NULL }, + { XSLFI_TRACE_RESTRICT_REVERSE, XSCF_NULL, 1, 1, "tracerestrict_reverse", NULL, NULL, NULL }, { XSLFI_PROG_SIGS, XSCF_NULL, 1, 1, "programmable_signals", NULL, NULL, "SPRG" }, { XSLFI_ADJACENT_CROSSINGS, XSCF_NULL, 1, 1, "adjacent_crossings", NULL, NULL, NULL }, { XSLFI_SAFER_CROSSINGS, XSCF_NULL, 1, 1, "safer_crossings", NULL, NULL, NULL }, diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index 02e91e7133..12c6ddf7f3 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -25,6 +25,7 @@ enum SlXvFeatureIndex { XSLFI_TRACE_RESTRICT_OWNER, ///< Trace restrict: train owner test XSLFI_TRACE_RESTRICT_ORDRCND, ///< Trace restrict: slot conditional order XSLFI_TRACE_RESTRICT_STATUSCND, ///< Trace restrict: train status condition + XSLFI_TRACE_RESTRICT_REVERSE, ///< Trace restrict: reverse XSLFI_PROG_SIGS, ///< programmable signals patch XSLFI_ADJACENT_CROSSINGS, ///< Adjacent level crossings closure patch XSLFI_SAFER_CROSSINGS, ///< Safer level crossings diff --git a/src/tracerestrict.cpp b/src/tracerestrict.cpp index 18c986905b..0a53c0e0ab 100644 --- a/src/tracerestrict.cpp +++ b/src/tracerestrict.cpp @@ -612,6 +612,22 @@ void TraceRestrictProgram::Execute(const Train* v, const TraceRestrictProgramInp break; } + case TRIT_REVERSE: + switch (static_cast(GetTraceRestrictValue(item))) { + case TRRVF_REVERSE: + out.flags |= TRPRF_REVERSE; + break; + + case TRRVF_CANCEL_REVERSE: + out.flags &= ~TRPRF_REVERSE; + break; + + default: + NOT_REACHED(); + break; + } + break; + default: NOT_REACHED(); } @@ -776,6 +792,10 @@ CommandCost TraceRestrictProgram::Validate(const std::vector } break; + case TRIT_REVERSE: + actions_used_flags |= TRPAUF_REVERSE; + break; + default: return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION); } @@ -836,6 +856,7 @@ void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueTyp case TRVT_FORCE_WEIGHT_RATIO: case TRVT_WAIT_AT_PBS: case TRVT_TRAIN_STATUS: + case TRVT_REVERSE: SetTraceRestrictValue(item, 0); if (!IsTraceRestrictTypeAuxSubtype(GetTraceRestrictType(item))) { SetTraceRestrictAuxField(item, 0); diff --git a/src/tracerestrict.h b/src/tracerestrict.h index 8f7f03d796..fa6cc9f251 100644 --- a/src/tracerestrict.h +++ b/src/tracerestrict.h @@ -140,7 +140,7 @@ enum TraceRestrictItemType { TRIT_COND_TRAIN_STATUS = 25, ///< Test train status TRIT_COND_END = 48, ///< End (exclusive) of conditional item types, note that this has the same value as TRIT_REVERSE - //TRIT_REVERSE = 48, ///< Reverse: reserved for future use + TRIT_REVERSE = 48, ///< Reverse behind signal /* space up to 63 */ }; @@ -238,6 +238,14 @@ enum TraceRestrictWaitAtPbsValueField { TRWAPVF_CANCEL_PBS_RES_END_WAIT = 3, ///< Cancel PBS reservations ending at this signal wait }; +/** + * TraceRestrictItem value field, for TRIT_REVERSE + */ +enum TraceRestrictReverseValueField { + TRRVF_REVERSE = 0, ///< Reverse + TRRVF_CANCEL_REVERSE = 1, ///< Cancel reverse +}; + /** * TraceRestrictItem value field, for TRIT_COND_TRAIN_STATUS */ @@ -298,6 +306,7 @@ enum TraceRestrictProgramResultFlags { TRPRF_LONG_RESERVE = 1 << 2, ///< Long reserve is set TRPRF_WAIT_AT_PBS = 1 << 3, ///< Wait at PBS signal is set TRPRF_PBS_RES_END_WAIT = 1 << 4, ///< PBS reservations ending at this signal wait is set + TRPRF_REVERSE = 1 << 5, ///< Reverse behind signal }; DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramResultFlags) @@ -314,6 +323,7 @@ enum TraceRestrictProgramActionsUsedFlags { TRPAUF_SLOT_RELEASE_FRONT = 1 << 6, ///< Slot release (front) action is present TRPAUF_PBS_RES_END_WAIT = 1 << 7, ///< PBS reservations ending at this signal wait action is present TRPAUF_PBS_RES_END_SLOT = 1 << 8, ///< PBS reservations ending at this signal slot action is present + TRPAUF_REVERSE = 1 << 9, ///< Reverse behind signal }; DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramActionsUsedFlags) @@ -545,6 +555,7 @@ enum TraceRestrictValueType { TRVT_SLOT_INDEX_INT = 20,///< takes a TraceRestrictSlotID, and an integer in the next item slot TRVT_OWNER = 40,///< takes a CompanyID TRVT_TRAIN_STATUS = 41,///< takes a TraceRestrictTrainStatusValueField + TRVT_REVERSE = 42,///< takes a TraceRestrictReverseValueField }; /** @@ -684,6 +695,8 @@ static inline TraceRestrictTypePropertySet GetTraceRestrictTypeProperties(TraceR out.value_type = TRVT_WAIT_AT_PBS; } else if (GetTraceRestrictType(item) == TRIT_SLOT) { out.value_type = TRVT_SLOT_INDEX; + } else if (GetTraceRestrictType(item) == TRIT_REVERSE) { + out.value_type = TRVT_REVERSE; } else { out.value_type = TRVT_NONE; } diff --git a/src/tracerestrict_gui.cpp b/src/tracerestrict_gui.cpp index 1bb9927057..49ab530f99 100644 --- a/src/tracerestrict_gui.cpp +++ b/src/tracerestrict_gui.cpp @@ -152,6 +152,7 @@ static const StringID _program_insert_str[] = { STR_TRACE_RESTRICT_LONG_RESERVE, STR_TRACE_RESTRICT_WAIT_AT_PBS, STR_TRACE_RESTRICT_SLOT_OP, + STR_TRACE_RESTRICT_REVERSE, INVALID_STRING_ID }; static const uint32 _program_insert_else_hide_mask = 8; ///< disable bitmask for else @@ -170,6 +171,7 @@ static const uint _program_insert_val[] = { TRIT_LONG_RESERVE, // long reserve TRIT_WAIT_AT_PBS, // wait at PBS signal TRIT_SLOT, // slot operation + TRIT_REVERSE, // reverse }; /** insert drop down list strings and values */ @@ -297,6 +299,21 @@ static const TraceRestrictDropDownListSet _train_status_value = { _train_status_value_str, _train_status_value_val, }; +static const StringID _reverse_value_str[] = { + STR_TRACE_RESTRICT_REVERSE_SIG, + STR_TRACE_RESTRICT_REVERSE_SIG_CANCEL, + INVALID_STRING_ID +}; +static const uint _reverse_value_val[] = { + TRRVF_REVERSE, + TRRVF_CANCEL_REVERSE, +}; + +/** value drop down list for wait at PBS types strings and values */ +static const TraceRestrictDropDownListSet _reverse_value = { + _reverse_value_str, _reverse_value_val, +}; + /** * Get index of @p value in @p list_set * if @p value is not present, assert if @p missing_ok is false, otherwise return -1 @@ -353,6 +370,7 @@ static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictG STR_TRACE_RESTRICT_LONG_RESERVE, STR_TRACE_RESTRICT_WAIT_AT_PBS, STR_TRACE_RESTRICT_SLOT_OP, + STR_TRACE_RESTRICT_REVERSE, INVALID_STRING_ID, }; static const uint val_action[] = { @@ -362,6 +380,7 @@ static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictG TRIT_LONG_RESERVE, TRIT_WAIT_AT_PBS, TRIT_SLOT, + TRIT_REVERSE, }; static const TraceRestrictDropDownListSet set_action = { str_action, val_action, @@ -1188,6 +1207,22 @@ static void DrawInstructionString(const TraceRestrictProgram *prog, TraceRestric SetDParam(2, selected ? STR_TRACE_RESTRICT_WHITE : STR_EMPTY); break; + case TRIT_REVERSE: + switch (static_cast(GetTraceRestrictValue(item))) { + case TRRVF_REVERSE: + instruction_string = STR_TRACE_RESTRICT_REVERSE_SIG; + break; + + case TRRVF_CANCEL_REVERSE: + instruction_string = STR_TRACE_RESTRICT_REVERSE_SIG_CANCEL; + break; + + default: + NOT_REACHED(); + break; + } + break; + default: NOT_REACHED(); break; @@ -1482,6 +1517,10 @@ public: this->ShowDropDownListWithValue(&_train_status_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); break; + case TRVT_REVERSE: + this->ShowDropDownListWithValue(&_reverse_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); + break; + default: break; } @@ -2465,6 +2504,13 @@ private: GetDropDownStringByValue(&_train_status_value, GetTraceRestrictValue(item)); break; + case TRVT_REVERSE: + right_sel->SetDisplayedPlane(DPR_VALUE_DROPDOWN); + this->EnableWidget(TR_WIDGET_VALUE_DROPDOWN); + this->GetWidget(TR_WIDGET_VALUE_DROPDOWN)->widget_data = + GetDropDownStringByValue(&_reverse_value, GetTraceRestrictValue(item)); + break; + default: break; } diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 34edfc6eb5..5ec8eb8d24 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -4007,11 +4007,16 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) const Trackdir dir = FindFirstTrackdir(trackdirbits); if (HasSignalOnTrack(gp.new_tile, TrackdirToTrack(dir))) { const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(gp.new_tile, TrackdirToTrack(dir)); - if (prog && prog->actions_used_flags & (TRPAUF_SLOT_ACQUIRE | TRPAUF_SLOT_RELEASE_FRONT)) { + if (prog && prog->actions_used_flags & (TRPAUF_SLOT_ACQUIRE | TRPAUF_SLOT_RELEASE_FRONT | TRPAUF_REVERSE)) { TraceRestrictProgramResult out; TraceRestrictProgramInput input(gp.new_tile, dir, NULL, NULL); input.permitted_slot_operations = TRPISP_ACQUIRE | TRPISP_RELEASE_FRONT; prog->Execute(v, input, out); + if (out.flags & TRPRF_REVERSE && GetSignalType(gp.new_tile, TrackdirToTrack(dir)) == SIGTYPE_PBS && + !HasSignalOnTrackdir(gp.new_tile, dir)) { + v->reverse_distance = v->gcache.cached_total_length + (IsDiagonalTrack(TrackdirToTrack(dir)) ? 16 : 8); + SetWindowDirty(WC_VEHICLE_VIEW, v->index); + } } } }