diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index 0571ef4862..8d1c9058fe 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -1273,6 +1273,10 @@ + + + + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index 0b6dc573b4..37c6b1559c 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -3048,6 +3048,18 @@ Threading + + Threading + + + Threading + + + Threading + + + Threading + diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index f6b938ed7d..5c874a0801 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -4490,6 +4490,22 @@ RelativePath=".\..\src\thread\thread_win32.cpp" > + + + + + + + + + + + + + + + + index); + TraceRestrictRemoveDestinationID(TROCAF_DEPOT, this->index); + /* Delete the depot-window */ DeleteWindowById(WC_VEHICLE_DEPOT, this->xy); diff --git a/src/lang/english.txt b/src/lang/english.txt index ad29b3d596..e077670977 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2370,6 +2370,93 @@ STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP :{BLACK}Dragging STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_DECREASE_TOOLTIP :{BLACK}Decrease dragging signal density STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP :{BLACK}Increase dragging signal density +# Tracerestrict GUI +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_EQUALS :is +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_NOT_EQUALS :is not +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_THAN :< +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_EQUALS :<= +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_THAN :> +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_EQUALS :>= +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_EQUALS :can carry +STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_NOT_EQUALS :can't carry +STR_TRACE_RESTRICT_CONDITIONAL_IF :If +STR_TRACE_RESTRICT_CONDITIONAL_ELIF :Else if +STR_TRACE_RESTRICT_CONDITIONAL_ORIF :Or if +STR_TRACE_RESTRICT_CONDITIONAL_ELSE :Else +STR_TRACE_RESTRICT_CONDITIONAL_ENDIF :End if +STR_TRACE_RESTRICT_VARIABLE_TRAIN_LENGTH :train length +STR_TRACE_RESTRICT_VARIABLE_MAX_SPEED :max speed +STR_TRACE_RESTRICT_VARIABLE_CURRENT_ORDER :current order +STR_TRACE_RESTRICT_VARIABLE_NEXT_ORDER :next order +STR_TRACE_RESTRICT_VARIABLE_LAST_VISITED_STATION :last visited station +STR_TRACE_RESTRICT_VARIABLE_CARGO :cargo +STR_TRACE_RESTRICT_VARIABLE_ENTRY_DIRECTION :entry direction +STR_TRACE_RESTRICT_VARIABLE_UNDEFINED :undefined +STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_INTEGER :{STRING} {STRING} {STRING} {COMMA} then +STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_SPEED :{STRING} {STRING} {STRING} {VELOCITY} then +STR_TRACE_RESTRICT_CONDITIONAL_ORDER_STATION :{STRING} {STRING} {STRING} {STATION} then +STR_TRACE_RESTRICT_CONDITIONAL_ORDER_WAYPOINT :{STRING} {STRING} {STRING} {WAYPOINT} then +STR_TRACE_RESTRICT_CONDITIONAL_ORDER_DEPOT :{STRING} {STRING} {STRING} {DEPOT} then +STR_TRACE_RESTRICT_CONDITIONAL_CARGO :{STRING} train {STRING} cargo: {STRING} then +STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_DIRECTION :{STRING} train {STRING} entering from {STRING} tile edge then +STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_SIGNAL_FACE :{STRING} train {STRING} entering from {STRING} of signal then +STR_TRACE_RESTRICT_CONDITIONAL_UNDEFINED :{STRING} {STRING} {STRING} {RED}undefined {BLACK}{STRING}then +STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_UNDEFINED :{STRING} {RED}undefined {BLACK}{STRING}then +STR_TRACE_RESTRICT_PF_PENALTY_ITEM :Add pathfinder penalty: {COMMA} +STR_TRACE_RESTRICT_WHITE :{WHITE} +STR_TRACE_RESTRICT_START :Start +STR_TRACE_RESTRICT_END :End +STR_TRACE_RESTRICT_PF_DENY :Deny +STR_TRACE_RESTRICT_PF_ALLOW :Allow +STR_TRACE_RESTRICT_PF_ALLOW_LONG :Allow (cancel previous Deny) +STR_TRACE_RESTRICT_PF_PENALTY :Penalty +STR_TRACE_RESTRICT_DIRECTION_FRONT :front +STR_TRACE_RESTRICT_DIRECTION_BACK :back +STR_TRACE_RESTRICT_DIRECTION_NE :north-east +STR_TRACE_RESTRICT_DIRECTION_SE :south-east +STR_TRACE_RESTRICT_DIRECTION_SW :south-west +STR_TRACE_RESTRICT_DIRECTION_NW :north-west +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 +STR_TRACE_RESTRICT_TYPE_TOOLTIP :{BLACK}Type +STR_TRACE_RESTRICT_COND_COMPARATOR_TOOLTIP :{BLACK}Comparison operator +STR_TRACE_RESTRICT_COND_VALUE_TOOLTIP :{BLACK}Value +STR_TRACE_RESTRICT_CONDFLAGS_TOOLTIP :{BLACK}Condition type +STR_TRACE_RESTRICT_GOTO_SIGNAL_TOOLTIP :{BLACK}Go to signal +STR_TRACE_RESTRICT_INSERT :{BLACK}Insert +STR_TRACE_RESTRICT_REMOVE :{BLACK}Remove +STR_TRACE_RESTRICT_RESET :{BLACK}Reset +STR_TRACE_RESTRICT_COPY :{BLACK}Copy +STR_TRACE_RESTRICT_SHARE :{BLACK}Share +STR_TRACE_RESTRICT_UNSHARE :{BLACK}Unshare +STR_TRACE_RESTRICT_SELECT_TARGET :{BLACK}Select Target +STR_TRACE_RESTRICT_INSERT_TOOLTIP :{BLACK}Insert an instruction +STR_TRACE_RESTRICT_REMOVE_TOOLTIP :{BLACK}Remove the selected instruction +STR_TRACE_RESTRICT_RESET_TOOLTIP :{BLACK}Reset the current signal (without affecting shared programs) +STR_TRACE_RESTRICT_COPY_TOOLTIP :{BLACK}Copy program from another signal +STR_TRACE_RESTRICT_SHARE_TOOLTIP :{BLACK}Share program with another signal +STR_TRACE_RESTRICT_UNSHARE_TOOLTIP :{BLACK}Stop sharing program with other signals, create a copy of the program +STR_TRACE_RESTRICT_SIGNAL_GUI_TOOLTIP :{BLACK}Routefinding restriction +STR_TRACE_RESTRICT_INSTRUCTION_LIST_TOOLTIP :{BLACK}Click an instruction to select it{}Ctrl+Click to scroll to the instruction's target (if any) +STR_TRACE_RESTRICT_ERROR_CAN_T_INSERT_ITEM :{WHITE}Can't insert instruction +STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM :{WHITE}Can't modify instruction +STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ITEM :{WHITE}Can't remove instruction +STR_TRACE_RESTRICT_ERROR_VALUE_TOO_LARGE :{WHITE}Value too large, maximum is {COMMA} +STR_TRACE_RESTRICT_ERROR_NO_PROGRAM :No trace restrict program exists +STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE :Offset too large +STR_TRACE_RESTRICT_ERROR_CAN_T_CHANGE_CONDITIONALITY :Can't change conditionality +STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ENDIF :Can't remove an 'end if' +STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK :Validation failed: condstack non-empty at exit +STR_TRACE_RESTRICT_ERROR_VALIDATE_NO_IF :Validation failed: else/endif without opening if +STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE :Validation failed: duplicate else +STR_TRACE_RESTRICT_ERROR_VALIDATE_ELIF_NO_IF :Validation failed: else if without opening if +STR_TRACE_RESTRICT_ERROR_SOURCE_SAME_AS_TARGET :Source and target signals are the same +STR_TRACE_RESTRICT_ERROR_CAN_T_RESET_SIGNAL :{WHITE}Can't reset signal +STR_TRACE_RESTRICT_ERROR_CAN_T_COPY_PROGRAM :{WHITE}Can't copy program +STR_TRACE_RESTRICT_ERROR_CAN_T_SHARE_PROGRAM :{WHITE}Can't share program +STR_TRACE_RESTRICT_ERROR_CAN_T_UNSHARE_PROGRAM :{WHITE}Can't unshare program + # Bridge selection window STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Select Rail Bridge STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Select Road Bridge @@ -2625,6 +2712,8 @@ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS :{STRING} track STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS :{STRING} track with path and one-way path signals STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :{STRING} train depot +STR_LAI_RAIL_DESCRIPTION_RESTRICTED_SIGNAL :{STRING1} (restricted) + STR_LAI_ROAD_DESCRIPTION_ROAD :Road STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_STREETLIGHTS :Road with street lights STR_LAI_ROAD_DESCRIPTION_TREE_LINED_ROAD :Tree-lined road diff --git a/src/misc.cpp b/src/misc.cpp index d9d506993f..939338af15 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -28,6 +28,7 @@ #include "core/pool_type.hpp" #include "game/game.hpp" #include "linkgraph/linkgraphschedule.h" +#include "tracerestrict.h" #include "safeguards.h" @@ -72,6 +73,7 @@ void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settin } LinkGraphSchedule::Clear(); + ClearTraceRestrictMapping(); PoolBase::Clean(PT_NORMAL); ResetPersistentNewGRFData(); diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index 62da50b966..8794292b11 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -175,6 +175,7 @@ public: /* Tiletype */ SetDParam(0, td.dparam[0]); + SetDParam(1, td.dparam[1]); GetString(this->landinfo_data[line_nr], td.str, lastof(this->landinfo_data[line_nr])); line_nr++; diff --git a/src/openttd.cpp b/src/openttd.cpp index c149ebbd4d..1e4450da9e 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -65,6 +65,7 @@ #include "viewport_sprite_sorter.h" #include "linkgraph/linkgraphschedule.h" +#include "tracerestrict.h" #include @@ -302,6 +303,7 @@ static void ShutdownGame() #endif LinkGraphSchedule::Clear(); + ClearTraceRestrictMapping(); PoolBase::Clean(PT_ALL); /* No NewGRFs were loaded when it was still bootstrapping. */ diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp index c6080f2a15..5a4b6531c3 100644 --- a/src/pathfinder/yapf/yapf_costrail.hpp +++ b/src/pathfinder/yapf/yapf_costrail.hpp @@ -13,6 +13,7 @@ #define YAPF_COSTRAIL_HPP #include "../../pbs.h" +#include "../../tracerestrict.h" template class CYapfCostRailT @@ -180,6 +181,30 @@ public: return 0; } +private: + // returns true if ExecuteTraceRestrict should be called + inline bool ShouldCheckTraceRestrict(Node& n, TileIndex tile) + { + return n.m_num_signals_passed < m_sig_look_ahead_costs.Size() && + IsRestrictedSignal(tile); + } + + // returns true if dead end bit has been set + inline bool ExecuteTraceRestrict(Node& n, TileIndex tile, Trackdir trackdir, int& cost, TraceRestrictProgramResult &out) + { + const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(tile, TrackdirToTrack(trackdir)); + if (prog) { + prog->Execute(Yapf().GetVehicle(), TraceRestrictProgramInput(tile, trackdir), out); + if (out.flags & TRPRF_DENY) { + n.m_segment->m_end_segment_reason |= ESRB_DEAD_END; + return true; + } + cost += out.penalty; + } + return false; + } + +public: int SignalCost(Node& n, TileIndex tile, Trackdir trackdir) { int cost = 0; @@ -239,6 +264,13 @@ public: } } + if (ShouldCheckTraceRestrict(n, tile)) { + TraceRestrictProgramResult out; + if (ExecuteTraceRestrict(n, tile, trackdir, cost, out)) { + return -1; + } + } + n.m_num_signals_passed++; n.m_segment->m_last_signal_tile = tile; n.m_segment->m_last_signal_td = trackdir; @@ -246,6 +278,13 @@ public: if (has_signal_against && IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) { cost += n.m_num_signals_passed < Yapf().PfGetSettings().rail_look_ahead_max_signals ? Yapf().PfGetSettings().rail_pbs_signal_back_penalty : 0; + + if (ShouldCheckTraceRestrict(n, tile)) { + TraceRestrictProgramResult out; + if (ExecuteTraceRestrict(n, tile, trackdir, cost, out)) { + return -1; + } + } } } } diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index d7a25d8bb1..35b7f1e3c3 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -33,6 +33,7 @@ #include "strings_func.h" #include "company_gui.h" #include "object_map.h" +#include "tracerestrict.h" #include "table/strings.h" #include "table/railtypes.h" @@ -1452,6 +1453,7 @@ CommandCost CmdRemoveSingleSignal(TileIndex tile, DoCommandFlag flags, uint32 p1 SetPresentSignals(tile, GetPresentSignals(tile) & ~SignalOnTrack(track)); Company::Get(GetTileOwner(tile))->infrastructure.signal += CountBits(GetPresentSignals(tile)); DirtyCompanyInfrastructureWindows(GetTileOwner(tile)); + TraceRestrictNotifySignalRemoval(tile, track); /* removed last signal from tile? */ if (GetPresentSignals(tile) == 0) { @@ -2768,6 +2770,12 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td) } td->str = signal_type[secondary_signal][primary_signal]; + + if (IsRestrictedSignal(tile)) { + SetDParamX(td->dparam, 0, td->str); + SetDParamX(td->dparam, 1, rti->strings.name); + td->str = STR_LAI_RAIL_DESCRIPTION_RESTRICTED_SIGNAL; + } break; } diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index a8c2fc6b33..1776a0e118 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -34,6 +34,7 @@ #include "vehicle_func.h" #include "zoom_func.h" #include "rail_gui.h" +#include "tracerestrict.h" #include "station_map.h" #include "tunnelbridge_map.h" @@ -49,6 +50,7 @@ static DiagDirection _build_depot_direction; ///< Currently selected depot direc static byte _waypoint_count = 1; ///< Number of waypoint types static byte _cur_waypoint_type; ///< Currently selected waypoint type static bool _convert_signal_button; ///< convert signal button in the signal GUI pressed +static bool _trace_restrict_button; ///< trace restrict button in the signal GUI pressed static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI) static SignalType _cur_signal_type; ///< set the signal type (for signal GUI) @@ -224,6 +226,10 @@ static void GenericPlaceSignals(TileIndex tile) if (_remove_button_clicked) { DoCommandP(tile, track, 0, CMD_REMOVE_SIGNALS | CMD_MSG(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM), CcPlaySound1E); + } else if (_trace_restrict_button) { + if (IsPlainRailTile(tile) && HasTrack(tile, track) && HasSignalOnTrack(tile, track)) { + ShowTraceRestrictProgramWindow(tile, track); + } } else { const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0); @@ -1518,6 +1524,7 @@ public: ~BuildSignalWindow() { _convert_signal_button = false; + _trace_restrict_button = false; } virtual void OnInit() @@ -1602,6 +1609,12 @@ public: case WID_BS_CONVERT: _convert_signal_button = !_convert_signal_button; + if (_convert_signal_button) _trace_restrict_button = false; + break; + + case WID_BS_TRACE_RESTRICT: + _trace_restrict_button = !_trace_restrict_button; + if (_trace_restrict_button) _convert_signal_button = false; break; case WID_BS_DRAG_SIGNALS_DENSITY_DECREASE: @@ -1635,6 +1648,7 @@ public: this->LowerWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type); this->SetWidgetLoweredState(WID_BS_CONVERT, _convert_signal_button); + this->SetWidgetLoweredState(WID_BS_TRACE_RESTRICT, _trace_restrict_button); this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, _settings_client.gui.drag_signals_density == 1); this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, _settings_client.gui.drag_signals_density == 20); @@ -1656,6 +1670,7 @@ static const NWidgetPart _nested_signal_builder_widgets[] = { NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP), EndContainer(), SetFill(1, 1), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(1, 1), NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_CONVERT), SetDataTip(SPR_IMG_SIGNAL_CONVERT, STR_BUILD_SIGNAL_CONVERT_TOOLTIP), SetFill(1, 1), + NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_TRACE_RESTRICT), SetDataTip(SPR_IMG_SETTINGS, STR_TRACE_RESTRICT_SIGNAL_GUI_TOOLTIP), SetFill(1, 1), EndContainer(), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP), EndContainer(), SetFill(1, 1), @@ -1674,6 +1689,7 @@ static const NWidgetPart _nested_signal_builder_widgets[] = { EndContainer(), NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetFill(1, 0), EndContainer(), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), EndContainer(), SetFill(1, 1), EndContainer(), EndContainer(), }; @@ -1974,6 +1990,7 @@ void InitializeRailGUI() SetDefaultRailGui(); _convert_signal_button = false; + _trace_restrict_button = false; _cur_signal_type = _default_signal_type[_settings_client.gui.default_signal_type]; ResetSignalVariant(); } diff --git a/src/rail_map.h b/src/rail_map.h index 2431a79202..e6f4d7e406 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -479,6 +479,26 @@ static inline bool HasOnewaySignalBlockingTrackdir(TileIndex tile, Trackdir td) !HasSignalOnTrackdir(tile, td) && IsOnewaySignal(tile, TrackdirToTrack(td)); } +/** + * Does signal tile have "one or more trace restrict mappings present" bit set + * @param tile the tile to check + */ +static inline bool IsRestrictedSignal(TileIndex tile) +{ + assert(GetRailTileType(tile) == RAIL_TILE_SIGNALS); + return (bool) GB(_m[tile].m2, 12, 1); +} + +/** + * Set signal tile "one or more trace restrict mappings present" bit + * @param tile the tile to set + */ +static inline void SetRestrictedSignal(TileIndex tile, bool is_restricted) +{ + assert(GetRailTileType(tile) == RAIL_TILE_SIGNALS); + SB(_m[tile].m2, 12, 1, is_restricted); +} + RailType GetTileRailType(TileIndex tile); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 9690481154..132ab32bde 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -2988,6 +2988,8 @@ bool AfterLoadGame() ResetSignalHandlers(); AfterLoadLinkGraphs(); + + AfterLoadTraceRestrict(); return true; } diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 7127faf7b6..7053a9bd96 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -45,6 +45,7 @@ std::vector _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_TRACE_RESTRICT, XSCF_NULL, 1, 1, "tracerestrict", NULL, NULL, "TRRM,TRRP" }, { XSLFI_NULL, XSCF_NULL, 0, 0, NULL, NULL, NULL, NULL },// This is the end marker }; @@ -121,7 +122,12 @@ void SlXvCheckSpecialSavegameVersions() { extern uint16 _sl_version; - // TODO: check for savegame versions + if (_sl_version == 2000) { + DEBUG(sl, 1, "Loading a trace restrict patch savegame version %d as version 194", _sl_version); + _sl_version = 194; + _sl_is_faked_ext = true; + _sl_xv_feature_versions[XSLFI_TRACE_RESTRICT] = 1; + } } /** diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index a7e9496944..5532f1b6fd 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -21,6 +21,7 @@ */ enum SlXvFeatureIndex { XSLFI_NULL = 0, ///< Unused value, to indicate that no extended feature test is in use + XSLFI_TRACE_RESTRICT, ///< Trace restrict XSLFI_SIZE, ///< Total count of features, including null feature }; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index 55a37f8ee8..2e2d7e1465 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -454,6 +454,7 @@ extern const ChunkHandler _linkgraph_chunk_handlers[]; extern const ChunkHandler _airport_chunk_handlers[]; extern const ChunkHandler _object_chunk_handlers[]; extern const ChunkHandler _persistent_storage_chunk_handlers[]; +extern const ChunkHandler _trace_restrict_chunk_handlers[]; /** Array of all chunks in a savegame, \c NULL terminated. */ static const ChunkHandler * const _chunk_handlers[] = { @@ -491,6 +492,7 @@ static const ChunkHandler * const _chunk_handlers[] = { _airport_chunk_handlers, _object_chunk_handlers, _persistent_storage_chunk_handlers, + _trace_restrict_chunk_handlers, NULL, }; diff --git a/src/saveload/saveload_internal.h b/src/saveload/saveload_internal.h index 74e5b9936d..2d1ae11bf7 100644 --- a/src/saveload/saveload_internal.h +++ b/src/saveload/saveload_internal.h @@ -34,6 +34,7 @@ void AfterLoadLabelMaps(); void AfterLoadStoryBook(); void AfterLoadLinkGraphs(); void AfterLoadCompanyStats(); +void AfterLoadTraceRestrict(); void UpdateHousesAndTowns(); void UpdateOldAircraft(); diff --git a/src/saveload/tracerestrict_sl.cpp b/src/saveload/tracerestrict_sl.cpp new file mode 100644 index 0000000000..93d43cf3ea --- /dev/null +++ b/src/saveload/tracerestrict_sl.cpp @@ -0,0 +1,111 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file tracerestrict_sl.cpp Code handling saving and loading of trace restrict programs */ + +#include "../stdafx.h" +#include "../tracerestrict.h" +#include "saveload.h" +#include +#include "saveload.h" + +static const SaveLoad _trace_restrict_mapping_desc[] = { + SLE_VAR(TraceRestrictMappingItem, program_id, SLE_UINT32), + SLE_END() +}; + +/** + * Load mappings + */ +static void Load_TRRM() +{ + int index; + while ((index = SlIterateArray()) != -1) { + TraceRestrictMappingItem &item = _tracerestrictprogram_mapping[index]; + SlObject(&item, _trace_restrict_mapping_desc); + } +} + +/** + * Save mappings + */ +static void Save_TRRM() +{ + for (TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.begin(); + iter != _tracerestrictprogram_mapping.end(); ++iter) { + SlSetArrayIndex(iter->first); + SlObject(&(iter->second), _trace_restrict_mapping_desc); + } +} + +/** program length save header struct */ +struct TraceRestrictProgramStub { + uint32 length; +}; + +static const SaveLoad _trace_restrict_program_stub_desc[] = { + SLE_VAR(TraceRestrictProgramStub, length, SLE_UINT32), + SLE_END() +}; + +/** + * Load program pool + */ +static void Load_TRRP() +{ + int index; + TraceRestrictProgramStub stub; + while ((index = SlIterateArray()) != -1) { + TraceRestrictProgram *prog = new (index) TraceRestrictProgram(); + SlObject(&stub, _trace_restrict_program_stub_desc); + prog->items.resize(stub.length); + SlArray(&(prog->items[0]), stub.length, SLE_UINT32); + assert(prog->Validate().Succeeded()); + } +} + +/** + * Save a program, used by SlAutolength + */ +static void RealSave_TRRP(TraceRestrictProgram *prog) +{ + TraceRestrictProgramStub stub; + stub.length = prog->items.size(); + SlObject(&stub, _trace_restrict_program_stub_desc); + SlArray(&(prog->items[0]), stub.length, SLE_UINT32); +} + +/** + * Save program pool + */ +static void Save_TRRP() +{ + TraceRestrictProgram *prog; + + FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog) { + SlSetArrayIndex(prog->index); + SlAutolength((AutolengthProc*) RealSave_TRRP, prog); + } +} + +/** + * Update program reference counts from just-loaded mapping + */ +void AfterLoadTraceRestrict() +{ + for (TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.begin(); + iter != _tracerestrictprogram_mapping.end(); ++iter) { + _tracerestrictprogram_pool.Get(iter->second.program_id)->IncrementRefCount(); + } +} + +extern const ChunkHandler _trace_restrict_chunk_handlers[] = { + { 'TRRM', Save_TRRM, Load_TRRM, NULL, NULL, CH_SPARSE_ARRAY}, // Trace Restrict Mapping chunk + { 'TRRP', Save_TRRP, Load_TRRP, NULL, NULL, CH_ARRAY | CH_LAST}, // Trace Restrict Mapping Program Pool chunk +}; diff --git a/src/script/api/script_window.hpp b/src/script/api/script_window.hpp index 58e114734e..4405f69451 100644 --- a/src/script/api/script_window.hpp +++ b/src/script/api/script_window.hpp @@ -2054,6 +2054,7 @@ public: WID_BS_ELECTRIC_PBS = ::WID_BS_ELECTRIC_PBS, ///< Build an electric path signal. WID_BS_ELECTRIC_PBS_OWAY = ::WID_BS_ELECTRIC_PBS_OWAY, ///< Build an electric one way path signal. WID_BS_CONVERT = ::WID_BS_CONVERT, ///< Convert the signal. + WID_BS_TRACE_RESTRICT = ::WID_BS_TRACE_RESTRICT, ///< Open trace restrict window. WID_BS_DRAG_SIGNALS_DENSITY_LABEL = ::WID_BS_DRAG_SIGNALS_DENSITY_LABEL, ///< The current signal density. WID_BS_DRAG_SIGNALS_DENSITY_DECREASE = ::WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, ///< Decrease the signal density. WID_BS_DRAG_SIGNALS_DENSITY_INCREASE = ::WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, ///< Increase the signal density. diff --git a/src/station.cpp b/src/station.cpp index 456262dea4..9b5dcfb4ca 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -26,6 +26,7 @@ #include "core/random_func.hpp" #include "linkgraph/linkgraph.h" #include "linkgraph/linkgraphschedule.h" +#include "tracerestrict.h" #include "table/strings.h" @@ -137,6 +138,8 @@ Station::~Station() /* Now delete all orders that go to the station */ RemoveOrderFromAllVehicles(OT_GOTO_STATION, this->index); + TraceRestrictRemoveDestinationID(TROCAF_STATION, this->index); + /* Remove all news items */ DeleteStationNews(this->index); diff --git a/src/stdafx.h b/src/stdafx.h index d68605c196..13bc9685d2 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -514,4 +514,15 @@ static inline void free(const void *ptr) #define IGNORE_UNINITIALIZED_WARNING_STOP #endif +/* + * Conditional define for the override keyword. + * Use of the override keyword can prevent various types of problems when the base method signature is changed, but derived overriding methods are not + * This is conditional to maintain compatibility with legacy compilers + */ +#if __cplusplus >= 201103L || defined(__STDCXX_VERSION__) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define OVERRIDE override +#else + #define OVERRIDE +#endif + #endif /* STDAFX_H */ diff --git a/src/tracerestrict.cpp b/src/tracerestrict.cpp new file mode 100644 index 0000000000..968ab49eb0 --- /dev/null +++ b/src/tracerestrict.cpp @@ -0,0 +1,894 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file tracerestrict.cpp Main file for Trace Restrict */ + +#include "stdafx.h" +#include "tracerestrict.h" +#include "train.h" +#include "core/bitmath_func.hpp" +#include "core/pool_func.hpp" +#include "command_func.h" +#include "company_func.h" +#include "viewport_func.h" +#include "window_func.h" +#include "order_base.h" +#include "cargotype.h" +#include "pathfinder/yapf/yapf_cache.h" +#include + +/** @file + * + * Trace Restrict Data Storage Model Notes: + * + * Signals may have 0, 1 or 2 trace restrict programs attached to them, + * up to one for each track. Two-way signals share the same program. + * + * The mapping between signals and programs is defined in terms of + * TraceRestrictRefId to TraceRestrictProgramID, + * where TraceRestrictRefId is formed of the tile index and track, + * and TraceRestrictProgramID is an index into the program pool. + * + * If one or more mappings exist for a given signal tile, bit 12 of M3 will be set to 1. + * This is updated whenever mappings are added/removed for that tile. This is to avoid + * needing to do a mapping lookup for the common case where there is no trace restrict + * program mapping for the given tile. + * + * Programs in the program pool are refcounted based on the number of mappings which exist. + * When this falls to 0, the program is deleted from the pool. + * If a program has a refcount greater than 1, it is a shared program. + * + * In all cases, an empty program is evaluated the same as the absence of a program. + * Therefore it is not necessary to store mappings to empty unshared programs. + * Any editing action which would otherwise result in a mapping to an empty program + * which has no other references, instead removes the mapping. + * This is not done for shared programs as this would delete the shared aspect whenever + * the program became empty. + * + * Empty programs with a refcount of 1 may still exist due to the edge case where: + * 1: There is an empty program with refcount 2 + * 2: One of the two mappings is deleted + * Finding the other mapping would entail a linear search of the mappings, and there is little + * to be gained by doing so. + */ + +TraceRestrictProgramPool _tracerestrictprogram_pool("TraceRestrictProgram"); +INSTANTIATE_POOL_METHODS(TraceRestrictProgram) + +/** + * TraceRestrictRefId --> TraceRestrictProgramID (Pool ID) mapping + * The indirection is mainly to enable shared programs + * TODO: use a more efficient container/indirection mechanism + */ +TraceRestrictMapping _tracerestrictprogram_mapping; + +/** + * This should be used when all pools have been or are immediately about to be also cleared + * Calling this at other times will leave dangling refcounts + */ +void ClearTraceRestrictMapping() { + _tracerestrictprogram_mapping.clear(); +} + +/** + * Flags used for the program execution condition stack + * Each 'if' pushes onto the stack + * Each 'end if' pops from the stack + * Elif/orif/else may modify the stack top + */ +enum TraceRestrictCondStackFlags { + TRCSF_DONE_IF = 1<<0, ///< The if/elif/else is "done", future elif/else branches will not be executed + TRCSF_SEEN_ELSE = 1<<1, ///< An else branch has been seen already, error if another is seen afterwards + TRCSF_ACTIVE = 1<<2, ///< The condition is currently active + TRCSF_PARENT_INACTIVE = 1<<3, ///< The parent condition is not active, thus this condition is also not active +}; +DECLARE_ENUM_AS_BIT_SET(TraceRestrictCondStackFlags) + +/** + * Helper function to handle condition stack manipulatoin + */ +static void HandleCondition(std::vector &condstack, TraceRestrictCondFlags condflags, bool value) +{ + if (condflags & TRCF_OR) { + assert(!condstack.empty()); + if (condstack.back() & TRCSF_ACTIVE) { + // leave TRCSF_ACTIVE set + return; + } + } + + if (condflags & (TRCF_OR | TRCF_ELSE)) { + assert(!condstack.empty()); + if (condstack.back() & (TRCSF_DONE_IF | TRCSF_PARENT_INACTIVE)) { + condstack.back() &= ~TRCSF_ACTIVE; + return; + } + } else { + if (!condstack.empty() && !(condstack.back() & TRCSF_ACTIVE)) { + //this is a 'nested if', the 'parent if' is not active + condstack.push_back(TRCSF_PARENT_INACTIVE); + return; + } + condstack.push_back(static_cast(0)); + } + + if (value) { + condstack.back() |= TRCSF_DONE_IF | TRCSF_ACTIVE; + } else { + condstack.back() &= ~TRCSF_ACTIVE; + } +} + +/** + * Integer condition testing + * Test value op condvalue + */ +static bool TestCondition(uint16 value, TraceRestrictCondOp condop, uint16 condvalue) +{ + switch (condop) { + case TRCO_IS: + return value == condvalue; + case TRCO_ISNOT: + return value != condvalue; + case TRCO_LT: + return value < condvalue; + case TRCO_LTE: + return value <= condvalue; + case TRCO_GT: + return value > condvalue; + case TRCO_GTE: + return value >= condvalue; + default: + NOT_REACHED(); + return false; + } +} + +/** + * Binary condition testing helper function + */ +static bool TestBinaryConditionCommon(TraceRestrictItem item, bool input) +{ + switch (GetTraceRestrictCondOp(item)) { + case TRCO_IS: + return input; + + case TRCO_ISNOT: + return !input; + + default: + NOT_REACHED(); + return false; + } +} + +/** + * Test order condition + * @p order may be NULL + */ +static bool TestOrderCondition(const Order *order, TraceRestrictItem item) +{ + bool result = false; + + if (order) { + DestinationID condvalue = GetTraceRestrictValue(item); + switch (static_cast(GetTraceRestrictAuxField(item))) { + case TROCAF_STATION: + result = order->IsType(OT_GOTO_STATION) && order->GetDestination() == condvalue; + break; + + case TROCAF_WAYPOINT: + result = order->IsType(OT_GOTO_WAYPOINT) && order->GetDestination() == condvalue; + break; + + case OT_GOTO_DEPOT: + result = order->IsType(OT_GOTO_DEPOT) && order->GetDestination() == condvalue; + break; + + default: + NOT_REACHED(); + } + } + return TestBinaryConditionCommon(item, result); +} + +/** + * Test station condition + */ +static bool TestStationCondition(StationID station, TraceRestrictItem item) +{ + bool result = (GetTraceRestrictAuxField(item) == TROCAF_STATION) && (station == GetTraceRestrictValue(item)); + return TestBinaryConditionCommon(item, result); + +} + +/** + * Execute program on train and store results in out + * @p v may not be NULL + * @p out should be zero-initialised + */ +void TraceRestrictProgram::Execute(const Train* v, const TraceRestrictProgramInput &input, TraceRestrictProgramResult& out) const +{ + // static to avoid needing to re-alloc/resize on each execution + static std::vector condstack; + condstack.clear(); + + size_t size = this->items.size(); + for (size_t i = 0; i < size; i++) { + TraceRestrictItem item = this->items[i]; + TraceRestrictItemType type = GetTraceRestrictType(item); + + if (IsTraceRestrictConditional(item)) { + TraceRestrictCondFlags condflags = GetTraceRestrictCondFlags(item); + TraceRestrictCondOp condop = GetTraceRestrictCondOp(item); + + if (type == TRIT_COND_ENDIF) { + assert(!condstack.empty()); + if (condflags & TRCF_ELSE) { + // else + assert(!(condstack.back() & TRCSF_SEEN_ELSE)); + HandleCondition(condstack, condflags, true); + condstack.back() |= TRCSF_SEEN_ELSE; + } else { + // end if + condstack.pop_back(); + } + } else { + uint16 condvalue = GetTraceRestrictValue(item); + bool result = false; + switch(type) { + case TRIT_COND_UNDEFINED: + result = false; + break; + + case TRIT_COND_TRAIN_LENGTH: + result = TestCondition(CeilDiv(v->gcache.cached_total_length, TILE_SIZE), condop, condvalue); + break; + + case TRIT_COND_MAX_SPEED: + result = TestCondition(v->GetDisplayMaxSpeed(), condop, condvalue); + break; + + case TRIT_COND_CURRENT_ORDER: + result = TestOrderCondition(&(v->current_order), item); + break; + + case TRIT_COND_NEXT_ORDER: { + if (v->orders.list == NULL) break; + if (v->orders.list->GetNumOrders() == 0) break; + + const Order *current_order = v->GetOrder(v->cur_real_order_index); + for (const Order *order = v->orders.list->GetNext(current_order); order != current_order; order = v->orders.list->GetNext(order)) { + if (order->IsGotoOrder()) { + result = TestOrderCondition(order, item); + break; + } + } + break; + } + + case TRIT_COND_LAST_STATION: + result = TestStationCondition(v->last_station_visited, item); + break; + + case TRIT_COND_CARGO: { + bool have_cargo = false; + for (const Vehicle *v_iter = v; v_iter != NULL; v_iter = v_iter->Next()) { + if (v_iter->cargo_type == GetTraceRestrictValue(item) && v_iter->cargo_cap > 0) { + have_cargo = true; + break; + } + } + result = TestBinaryConditionCommon(item, have_cargo); + break; + } + + case TRIT_COND_ENTRY_DIRECTION: { + bool direction_match; + switch (GetTraceRestrictValue(item)) { + case TRNTSV_NE: + case TRNTSV_SE: + case TRNTSV_SW: + case TRNTSV_NW: + direction_match = (static_cast(GetTraceRestrictValue(item)) == TrackdirToExitdir(ReverseTrackdir(input.trackdir))); + break; + + case TRDTSV_FRONT: + direction_match = IsTileType(input.tile, MP_RAILWAY) && HasSignalOnTrackdir(input.tile, input.trackdir); + break; + + case TRDTSV_BACK: + direction_match = IsTileType(input.tile, MP_RAILWAY) && !HasSignalOnTrackdir(input.tile, input.trackdir); + break; + + default: + NOT_REACHED(); + break; + } + result = TestBinaryConditionCommon(item, direction_match); + break; + } + + default: + NOT_REACHED(); + } + HandleCondition(condstack, condflags, result); + } + } else { + if (condstack.empty() || condstack.back() & TRCSF_ACTIVE) { + switch(type) { + case TRIT_PF_DENY: + if (GetTraceRestrictValue(item)) { + out.flags &= ~TRPRF_DENY; + } else { + out.flags |= TRPRF_DENY; + } + break; + case TRIT_PF_PENALTY: + out.penalty += GetTraceRestrictValue(item); + break; + default: + NOT_REACHED(); + } + } + } + } + assert(condstack.empty()); +} + +/** + * Decrement ref count, only use when removing a mapping + */ +void TraceRestrictProgram::DecrementRefCount() { + assert(this->refcount > 0); + this->refcount--; + if (this->refcount == 0) { + delete this; + } +} + +/** + * Validate a instruction list + * Returns successful result if program seems OK + * This only validates that conditional nesting is correct, at present + */ +CommandCost TraceRestrictProgram::Validate(const std::vector &items) { + // static to avoid needing to re-alloc/resize on each execution + static std::vector condstack; + condstack.clear(); + + size_t size = items.size(); + for (size_t i = 0; i < size; i++) { + TraceRestrictItem item = items[i]; + TraceRestrictItemType type = GetTraceRestrictType(item); + + if (IsTraceRestrictConditional(item)) { + TraceRestrictCondFlags condflags = GetTraceRestrictCondFlags(item); + + if (type == TRIT_COND_ENDIF) { + if (condstack.empty()) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_NO_IF); // else/endif with no starting if + } + if (condflags & TRCF_ELSE) { + // else + if (condstack.back() & TRCSF_SEEN_ELSE) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE); // Two else clauses + } + HandleCondition(condstack, condflags, true); + condstack.back() |= TRCSF_SEEN_ELSE; + } else { + // end if + condstack.pop_back(); + } + } else { + if (condflags & (TRCF_OR | TRCF_ELSE)) { // elif/orif + if (condstack.empty()) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_ELIF_NO_IF); // Pre-empt assertions in HandleCondition + } + if (condstack.back() & TRCSF_SEEN_ELSE) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE); // else clause followed by elif/orif + } + } + HandleCondition(condstack, condflags, true); + } + } + } + if(!condstack.empty()) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK); + } + return CommandCost(); +} + +/** + * Set the value and aux field of @p item, as per the value type in @p value_type + */ +void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueType value_type) +{ + switch (value_type) { + case TRVT_NONE: + case TRVT_INT: + case TRVT_DENY: + case TRVT_SPEED: + SetTraceRestrictValue(item, 0); + SetTraceRestrictAuxField(item, 0); + break; + + case TRVT_ORDER: + SetTraceRestrictValue(item, INVALID_STATION); + SetTraceRestrictAuxField(item, TROCAF_STATION); + break; + + case TRVT_CARGO_ID: + assert(_sorted_standard_cargo_specs_size > 0); + SetTraceRestrictValue(item, _sorted_cargo_specs[0]->Index()); + SetTraceRestrictAuxField(item, 0); + break; + + case TRVT_DIRECTION: + SetTraceRestrictValue(item, TRDTSV_FRONT); + SetTraceRestrictAuxField(item, 0); + break; + + default: + NOT_REACHED(); + break; + } +} + +/** + * Set the type field of a TraceRestrictItem, and resets any other fields which are no longer valid/meaningful to sensible defaults + */ +void SetTraceRestrictTypeAndNormalise(TraceRestrictItem &item, TraceRestrictItemType type) +{ + if (item != 0) { + assert(GetTraceRestrictType(item) != TRIT_NULL); + assert(IsTraceRestrictConditional(item) == IsTraceRestrictTypeConditional(type)); + } + assert(type != TRIT_NULL); + + TraceRestrictTypePropertySet old_properties = GetTraceRestrictTypeProperties(item); + SetTraceRestrictType(item, type); + TraceRestrictTypePropertySet new_properties = GetTraceRestrictTypeProperties(item); + + if (old_properties.cond_type != new_properties.cond_type || + old_properties.value_type != new_properties.value_type) { + SetTraceRestrictCondOp(item, TRCO_IS); + SetTraceRestrictValueDefault(item, new_properties.value_type); + } +} + +/** + * Sets the "signal has a trace restrict mapping" bit + * This looks for mappings with that tile index + */ +void SetIsSignalRestrictedBit(TileIndex t) +{ + // First mapping for this tile, or later + TraceRestrictMapping::iterator lower_bound = _tracerestrictprogram_mapping.lower_bound(MakeTraceRestrictRefId(t, static_cast(0))); + + // First mapping for next tile, or later + TraceRestrictMapping::iterator upper_bound = _tracerestrictprogram_mapping.lower_bound(MakeTraceRestrictRefId(t + 1, static_cast(0))); + + // If iterators are the same, there are no mappings for this tile + SetRestrictedSignal(t, lower_bound != upper_bound); +} + +/** + * Create a new program mapping to an existing program + * If a mapping already exists, it is removed + */ +void TraceRestrictCreateProgramMapping(TraceRestrictRefId ref, TraceRestrictProgram *prog) +{ + std::pair insert_result = + _tracerestrictprogram_mapping.insert(std::make_pair(ref, TraceRestrictMappingItem(prog->index))); + + if (!insert_result.second) { + // value was not inserted, there is an existing mapping + // unref the existing mapping before updating it + _tracerestrictprogram_pool.Get(insert_result.first->second.program_id)->DecrementRefCount(); + insert_result.first->second = prog->index; + } + prog->IncrementRefCount(); + + TileIndex tile = GetTraceRestrictRefIdTileIndex(ref); + Track track = GetTraceRestrictRefIdTrack(ref); + SetIsSignalRestrictedBit(tile); + MarkTileDirtyByTile(tile); + YapfNotifyTrackLayoutChange(tile, track); +} + +/** + * Remove a program mapping + */ +void TraceRestrictRemoveProgramMapping(TraceRestrictRefId ref) +{ + TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.find(ref); + if (iter != _tracerestrictprogram_mapping.end()) { + // Found + _tracerestrictprogram_pool.Get(iter->second.program_id)->DecrementRefCount(); + _tracerestrictprogram_mapping.erase(iter); + + TileIndex tile = GetTraceRestrictRefIdTileIndex(ref); + Track track = GetTraceRestrictRefIdTrack(ref); + SetIsSignalRestrictedBit(tile); + MarkTileDirtyByTile(tile); + YapfNotifyTrackLayoutChange(tile, track); + } +} + +/** + * Gets the signal program for the tile ref @p ref + * An empty program will be constructed if none exists, and @p create_new is true, unless the pool is full + */ +TraceRestrictProgram *GetTraceRestrictProgram(TraceRestrictRefId ref, bool create_new) +{ + // Optimise for lookup, creating doesn't have to be that fast + + TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.find(ref); + if (iter != _tracerestrictprogram_mapping.end()) { + // Found + return _tracerestrictprogram_pool.Get(iter->second.program_id); + } else if (create_new) { + // Not found + + // Create new pool item + if (!TraceRestrictProgram::CanAllocateItem()) { + return NULL; + } + TraceRestrictProgram *prog = new TraceRestrictProgram(); + + // Create new mapping to pool item + TraceRestrictCreateProgramMapping(ref, prog); + return prog; + } else { + return NULL; + } +} + +/** + * Notify that a signal is being removed + * Remove any trace restrict mappings associated with it + */ +void TraceRestrictNotifySignalRemoval(TileIndex tile, Track track) +{ + TraceRestrictRefId ref = MakeTraceRestrictRefId(tile, track); + TraceRestrictRemoveProgramMapping(ref); + DeleteWindowById(WC_TRACE_RESTRICT, ref); +} + +/** + * Helper function to perform parameter bit-packing and call DoCommandP, for instruction modification actions + */ +void TraceRestrictDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, uint32 offset, uint32 value, StringID error_msg) +{ + uint32 p1 = 0; + SB(p1, 0, 3, track); + SB(p1, 3, 5, type); + assert(offset < (1 << 16)); + SB(p1, 8, 16, offset); + DoCommandP(tile, p1, value, CMD_PROGRAM_TRACERESTRICT_SIGNAL | CMD_MSG(error_msg)); +} + +/** + * Check whether a tile/tracl pair contains a usable signal + */ +static CommandCost TraceRestrictCheckTileIsUsable(TileIndex tile, Track track) +{ + // Check that there actually is a signal here + if (!IsPlainRailTile(tile) || !HasTrack(tile, track)) { + return_cmd_error(STR_ERROR_THERE_IS_NO_RAILROAD_TRACK); + } + if (!HasSignalOnTrack(tile, track)) { + return_cmd_error(STR_ERROR_THERE_ARE_NO_SIGNALS); + } + + // Check tile ownership, do this afterwards to avoid tripping up on house/industry tiles + CommandCost ret = CheckTileOwnership(tile); + if (ret.Failed()) { + return ret; + } + + return CommandCost(); +} + +/** + * The main command for editing a signal tracerestrict program. + * @param tile The tile which contains the signal. + * @param flags Internal command handler stuff. + * Below apply for instruction modification actions only + * @param p1 Bitstuffed items + * @param p2 Item, for insert and modify operations + * @return the cost of this operation (which is free), or an error + */ +CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TraceRestrictDoCommandType type = static_cast(GB(p1, 3, 5)); + + if (type >= TRDCT_PROG_COPY) { + return CmdProgramSignalTraceRestrictProgMgmt(tile, flags, p1, p2, text); + } + + Track track = static_cast(GB(p1, 0, 3)); + uint32 offset = GB(p1, 8, 16); + TraceRestrictItem item = static_cast(p2); + + CommandCost ret = TraceRestrictCheckTileIsUsable(tile, track); + if (ret.Failed()) { + return ret; + } + + bool can_make_new = (type == TRDCT_INSERT_ITEM) && (flags & DC_EXEC); + bool need_existing = (type != TRDCT_INSERT_ITEM); + TraceRestrictProgram *prog = GetTraceRestrictProgram(MakeTraceRestrictRefId(tile, track), can_make_new); + if (need_existing && !prog) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_NO_PROGRAM); + } + + uint32 offset_limit_exclusive = ((type == TRDCT_INSERT_ITEM) ? 1 : 0); + if (prog) offset_limit_exclusive += prog->items.size(); + + if (offset >= offset_limit_exclusive) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE); + } + + // copy program + std::vector items; + if (prog) items = prog->items; + + switch (type) { + case TRDCT_INSERT_ITEM: + items.insert(items.begin() + offset, item); + if (IsTraceRestrictConditional(item) && + GetTraceRestrictCondFlags(item) == 0 && + GetTraceRestrictType(item) != TRIT_COND_ENDIF) { + // this is an opening if block, insert a corresponding end if + TraceRestrictItem endif_item = 0; + SetTraceRestrictType(endif_item, TRIT_COND_ENDIF); + items.insert(items.begin() + offset + 1, endif_item); + } + break; + + case TRDCT_MODIFY_ITEM: { + TraceRestrictItem old_item = items[offset]; + if (IsTraceRestrictConditional(old_item) != IsTraceRestrictConditional(item)) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_CHANGE_CONDITIONALITY); + } + items[offset] = item; + break; + } + + case TRDCT_REMOVE_ITEM: { + TraceRestrictItem old_item = items[offset]; + if (IsTraceRestrictConditional(old_item)) { + bool remove_whole_block = false; + if (GetTraceRestrictCondFlags(old_item) == 0) { + if (GetTraceRestrictType(old_item) == TRIT_COND_ENDIF) { + // this is an end if, can't remove these + return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ENDIF); + } else { + // this is an opening if + remove_whole_block = true; + } + } + + uint32 recursion_depth = 1; + std::vector::iterator remove_start = items.begin() + offset; + std::vector::iterator remove_end = remove_start + 1; + + // iterate until matching end block found + for (; remove_end != items.end(); ++remove_end) { + TraceRestrictItem current_item = *remove_end; + if (IsTraceRestrictConditional(current_item)) { + if (GetTraceRestrictCondFlags(current_item) == 0) { + if (GetTraceRestrictType(current_item) == TRIT_COND_ENDIF) { + // this is an end if + recursion_depth--; + if (recursion_depth == 0) { + if (remove_whole_block) { + // inclusively remove up to here + ++remove_end; + break; + } else { + // exclusively remove up to here + break; + } + } + } else { + // this is an opening if + recursion_depth++; + } + } else { + // this is an else/or type block + if (recursion_depth == 1 && !remove_whole_block) { + // exclusively remove up to here + recursion_depth = 0; + break; + } + } + } + } + if (recursion_depth != 0) return CMD_ERROR; // ran off the end + items.erase(remove_start, remove_end); + } else { + items.erase(items.begin() + offset); + } + break; + } + + default: + NOT_REACHED(); + break; + } + + CommandCost validation_result = TraceRestrictProgram::Validate(items); + if (validation_result.Failed()) { + return validation_result; + } + + if (flags & DC_EXEC) { + assert(prog); + + // move in modified program + prog->items.swap(items); + + if (prog->items.size() == 0 && prog->refcount == 1) { + // program is empty, and this tile is the only reference to it + // so delete it, as it's redundant + TraceRestrictRemoveProgramMapping(MakeTraceRestrictRefId(tile, track)); + } + + // update windows + InvalidateWindowClassesData(WC_TRACE_RESTRICT); + } + + return CommandCost(); +} + +/** + * Helper function to perform parameter bit-packing and call DoCommandP, for program management actions + */ +void TraceRestrictProgMgmtWithSourceDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, + TileIndex source_tile, Track source_track, StringID error_msg) +{ + uint32 p1 = 0; + SB(p1, 0, 3, track); + SB(p1, 3, 5, type); + SB(p1, 8, 3, source_track); + DoCommandP(tile, p1, source_tile, CMD_PROGRAM_TRACERESTRICT_SIGNAL | CMD_MSG(error_msg)); +} + +/** + * Sub command for copy/share/unshare operations on signal tracerestrict programs. + * @param tile The tile which contains the signal. + * @param flags Internal command handler stuff. + * @param p1 Bitstuffed items + * @param p2 Source tile, for share/copy operations + * @return the cost of this operation (which is free), or an error + */ +CommandCost CmdProgramSignalTraceRestrictProgMgmt(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TraceRestrictDoCommandType type = static_cast(GB(p1, 3, 5)); + Track track = static_cast(GB(p1, 0, 3)); + Track source_track = static_cast(GB(p1, 8, 3)); + TileIndex source_tile = static_cast(p2); + + TraceRestrictRefId self = MakeTraceRestrictRefId(tile, track); + TraceRestrictRefId source = MakeTraceRestrictRefId(source_tile, source_track); + + assert(type >= TRDCT_PROG_COPY); + + CommandCost ret = TraceRestrictCheckTileIsUsable(tile, track); + if (ret.Failed()) { + return ret; + } + + if (type == TRDCT_PROG_SHARE || type == TRDCT_PROG_COPY) { + if (self == source) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_SOURCE_SAME_AS_TARGET); + } + + ret = TraceRestrictCheckTileIsUsable(source_tile, source_track); + if (ret.Failed()) { + return ret; + } + } + + if (!(flags & DC_EXEC)) { + return CommandCost(); + } + + switch (type) { + case TRDCT_PROG_COPY: { + TraceRestrictRemoveProgramMapping(self); + TraceRestrictProgram *prog = GetTraceRestrictProgram(self, true); + if (!prog) { + // allocation failed + return CMD_ERROR; + } + + TraceRestrictProgram *source_prog = GetTraceRestrictProgram(source, false); + if (source_prog) { + prog->items = source_prog->items; // copy + } + break; + } + + case TRDCT_PROG_SHARE: { + TraceRestrictRemoveProgramMapping(self); + TraceRestrictProgram *source_prog = GetTraceRestrictProgram(source, true); + if (!source_prog) { + // allocation failed + return CMD_ERROR; + } + + TraceRestrictCreateProgramMapping(self, source_prog); + break; + } + + case TRDCT_PROG_UNSHARE: { + std::vector items; + TraceRestrictProgram *prog = GetTraceRestrictProgram(self, false); + if (prog) { + // copy program into temporary + items = prog->items; + } + // remove old program + TraceRestrictRemoveProgramMapping(self); + + if (items.size()) { + // if prog is non-empty, create new program and move temporary in + TraceRestrictProgram *new_prog = GetTraceRestrictProgram(self, true); + if (!new_prog) { + // allocation failed + return CMD_ERROR; + } + + new_prog->items.swap(items); + } + break; + } + + case TRDCT_PROG_RESET: { + TraceRestrictRemoveProgramMapping(self); + break; + } + + default: + NOT_REACHED(); + break; + } + + // update windows + InvalidateWindowClassesData(WC_TRACE_RESTRICT); + + return CommandCost(); +} + +/** + * This is called when a station, waypoint or depot is about to be deleted + * Scan program pool and change any references to it to the invalid station ID, to avoid dangling references + */ +void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type, uint16 index) +{ + TraceRestrictProgram *prog; + + FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog) { + for (size_t i = 0; i < prog->items.size(); i++) { + TraceRestrictItem &item = prog->items[i]; // note this is a reference, + if (GetTraceRestrictType(item) == TRIT_COND_CURRENT_ORDER || + GetTraceRestrictType(item) == TRIT_COND_NEXT_ORDER || + GetTraceRestrictType(item) == TRIT_COND_LAST_STATION) { + if (GetTraceRestrictAuxField(item) == type && GetTraceRestrictValue(item) == index) { + SetTraceRestrictValueDefault(item, TRVT_ORDER); // this updates the instruction in-place + } + } + } + } + + // update windows + InvalidateWindowClassesData(WC_TRACE_RESTRICT); +} diff --git a/src/tracerestrict.h b/src/tracerestrict.h new file mode 100644 index 0000000000..4e0f0f52bb --- /dev/null +++ b/src/tracerestrict.h @@ -0,0 +1,465 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file tracerestrict.h Header file for Trace Restrict */ + +#ifndef TRACERESTRICT_H +#define TRACERESTRICT_H + +#include "stdafx.h" +#include "core/bitmath_func.hpp" +#include "core/enum_type.hpp" +#include "core/pool_type.hpp" +#include "command_func.h" +#include "rail_map.h" +#include "tile_type.h" +#include +#include + +struct Train; + +/** Program pool ID type. */ +typedef uint32 TraceRestrictProgramID; +struct TraceRestrictProgram; + +/** Tile/track mapping type. */ +typedef uint32 TraceRestrictRefId; + +/** Type of the pool for trace restrict programs. */ +typedef Pool TraceRestrictProgramPool; +/** The actual pool for trace restrict nodes. */ +extern TraceRestrictProgramPool _tracerestrictprogram_pool; + +#define FOR_ALL_TRACE_RESTRICT_PROGRAMS_FROM(var, start) FOR_ALL_ITEMS_FROM(TraceRestrictProgram, tr_index, var, start) +#define FOR_ALL_TRACE_RESTRICT_PROGRAMS(var) FOR_ALL_TRACE_RESTRICT_PROGRAMS_FROM(var, 0) + +/** Type used for the TraceRestrictRefId -> TraceRestrictProgramID mapping */ +struct TraceRestrictMappingItem { + TraceRestrictProgramID program_id; + + TraceRestrictMappingItem() { } + + TraceRestrictMappingItem(TraceRestrictProgramID program_id_) + : program_id(program_id_) { } +}; + +typedef std::map TraceRestrictMapping; + +/** The actual mapping from TraceRestrictRefId to TraceRestrictProgramID. */ +extern TraceRestrictMapping _tracerestrictprogram_mapping; + +void ClearTraceRestrictMapping(); + +/** Type of a single instruction, this is bit-packed as per TraceRestrictItemFlagAllocation */ +typedef uint32 TraceRestrictItem; + +/** + * Describes the allocation of bits to fields in TraceRestrictItem + * Of the fields below, the type seem the most likely + * to need future expansion, hence the reserved bits are placed + * immediately after them + * + * COUNT values describe the field bit width + * OFFSET values describe the field bit offset + */ +enum TraceRestrictItemFlagAllocation { + TRIFA_TYPE_COUNT = 5, + TRIFA_TYPE_OFFSET = 0, + + /* 3 bits reserved for future use */ + + TRIFA_COND_FLAGS_COUNT = 3, + TRIFA_COND_FLAGS_OFFSET = 8, + + TRIFA_AUX_FIELD_COUNT = 2, + TRIFA_AUX_FIELD_OFFSET = 11, + + TRIFA_COND_OP_COUNT = 3, + TRIFA_COND_OP_OFFSET = 13, + + TRIFA_VALUE_COUNT = 16, + TRIFA_VALUE_OFFSET = 16, +}; + +/** + * Enumeration of TraceRestrictItem type field + * This is split into two halves: + * * non-conditionals < TRIT_COND_BEGIN + * * conditionals, >= TRIT_COND_BEGIN + */ +enum TraceRestrictItemType { + TRIT_NULL = 0, ///< Null-type, not in programs and not valid for execution, mainly used with TraceRestrictNullTypeSpecialValue for start/end + TRIT_PF_DENY = 1, ///< Pathfinder deny/allow + TRIT_PF_PENALTY = 2, ///< Add to pathfinder penalty + + TRIT_COND_BEGIN = 8, ///< Start of conditional item types, note that this has the save value as TRIT_COND_ENDIF + TRIT_COND_ENDIF = 8, ///< This is an endif block or an else block + TRIT_COND_UNDEFINED = 9, ///< This condition has no type defined (evaluate as false) + TRIT_COND_TRAIN_LENGTH = 10, ///< Test train length + TRIT_COND_MAX_SPEED = 11, ///< Test train max speed + TRIT_COND_CURRENT_ORDER = 12, ///< Test train current order (station, waypoint or depot) + TRIT_COND_NEXT_ORDER = 13, ///< Test train next order (station, waypoint or depot) + TRIT_COND_LAST_STATION = 14, ///< Test train last visited station + TRIT_COND_CARGO = 15, ///< Test if train can carry cargo type + TRIT_COND_ENTRY_DIRECTION = 16, ///< Test which side of signal/signal tile is being entered from + /* space up to 31 */ +}; + +/** + * TraceRestrictItem condition flags field, only valid with conditional types (IsTraceRestrictTypeConditional() is true) + */ +enum TraceRestrictCondFlags { + TRCF_DEFAULT = 0, ///< indicates end if for type: TRIT_COND_ENDIF, if otherwise + TRCF_ELSE = 1 << 0, ///< indicates an else block for type: TRIT_COND_ENDIF, elif otherwise + TRCF_OR = 1 << 1, ///< indicates an orif block, not valid with type: TRIT_COND_ENDIF + /* 1 bit spare */ +}; +DECLARE_ENUM_AS_BIT_SET(TraceRestrictCondFlags) + +/** + * Enumeration of TraceRestrictItemvalue type field when type is TRIT_NULL + */ +enum TraceRestrictNullTypeSpecialValue { + TRNTSV_NULL = 0, ///< null, what you get when you zero-init a TraceRestrictItemvalue + TRNTSV_START = 1, ///< start tag, generated within GUI + TRNTSV_END = 2, ///< end tag, generated within GUI +}; + +/** + * Enumeration of TraceRestrictItemvalue type field when value type is TRVT_DIRECTION + */ +enum TraceRestrictDirectionTypeSpecialValue { + TRNTSV_NE = 0, ///< DIAGDIR_NE: entering at NE tile edge + TRNTSV_SE = 1, ///< DIAGDIR_SE: entering at SE tile edge + TRNTSV_SW = 2, ///< DIAGDIR_SW: entering at SW tile edge + TRNTSV_NW = 3, ///< DIAGDIR_NW: entering at NW tile edge + TRDTSV_FRONT = 4, ///< entering at front face of signal + TRDTSV_BACK = 5, ///< entering at rear face of signal +}; + +/** + * TraceRestrictItem condition operator field, only valid with conditional types (IsTraceRestrictTypeConditional() is true) + */ +enum TraceRestrictCondOp { + TRCO_IS = 0, ///< equality test, or can carry test for cargo + TRCO_ISNOT = 1, ///< inequality test, or can't carry test for cargo + TRCO_LT = 2, ///< less than test + TRCO_LTE = 3, ///< less than or equal test + TRCO_GT = 4, ///< greater than test + TRCO_GTE = 5, ///< greater than or equal test + /* space up to 7 */ +}; + +/** + * TraceRestrictItem auxiliary type field, for order type conditionals + */ +enum TraceRestrictOrderCondAuxField { + TROCAF_STATION = 0, ///< value field is a station StationID + TROCAF_WAYPOINT = 1, ///< value field is a waypoint StationID + TROCAF_DEPOT = 2, ///< value field is a depot DepotID + /* space up to 3 */ +}; + +/** + * Enumeration for TraceRestrictProgramResult::flags + */ +enum TraceRestrictProgramResultFlags { + TRPRF_DENY = 1 << 0, ///< Pathfinder deny is set +}; +DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramResultFlags) + +/** + * Execution input of a TraceRestrictProgram + */ +struct TraceRestrictProgramInput { + TileIndex tile; ///< Tile of restrict signal, for direction testing + Trackdir trackdir; ///< Track direction on tile of restrict signal, for direction testing + + TraceRestrictProgramInput(TileIndex tile_, Trackdir trackdir_) + : tile(tile_), trackdir(trackdir_) { } +}; + +/** + * Execution result of a TraceRestrictProgram + */ +struct TraceRestrictProgramResult { + uint32 penalty; ///< Total additional pathfinder penalty + TraceRestrictProgramResultFlags flags; ///< Flags of other actions to take + + TraceRestrictProgramResult() + : penalty(0), flags(static_cast(0)) { } +}; + +/** + * Program type, this stores the instruction list + * This is refcounted, see info at top of tracerestrict.cpp + */ +struct TraceRestrictProgram : TraceRestrictProgramPool::PoolItem<&_tracerestrictprogram_pool> { + std::vector items; + uint32 refcount; + + TraceRestrictProgram() + : refcount(0) { } + + void Execute(const Train *v, const TraceRestrictProgramInput &input, TraceRestrictProgramResult &out) const; + + /** + * Increment ref count, only use when creating a mapping + */ + void IncrementRefCount() { refcount++; } + + void DecrementRefCount(); + + static CommandCost Validate(const std::vector &items); + + /** + * Call validation function on current program instruction list + */ + CommandCost Validate() const { return TraceRestrictProgram::Validate(items); } +}; + +/** Get TraceRestrictItem type field */ +static inline TraceRestrictItemType GetTraceRestrictType(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_TYPE_OFFSET, TRIFA_TYPE_COUNT)); +} + +/** Get TraceRestrictItem condition flags field */ +static inline TraceRestrictCondFlags GetTraceRestrictCondFlags(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_COND_FLAGS_OFFSET, TRIFA_COND_FLAGS_COUNT)); +} + +/** Get TraceRestrictItem condition operator field */ +static inline TraceRestrictCondOp GetTraceRestrictCondOp(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_COND_OP_OFFSET, TRIFA_COND_OP_COUNT)); +} + +/** Get TraceRestrictItem auxiliary field */ +static inline uint8 GetTraceRestrictAuxField(TraceRestrictItem item) +{ + return GB(item, TRIFA_AUX_FIELD_OFFSET, TRIFA_AUX_FIELD_COUNT); +} + +/** Get TraceRestrictItem value field */ +static inline uint16 GetTraceRestrictValue(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_VALUE_OFFSET, TRIFA_VALUE_COUNT)); +} + +/** Set TraceRestrictItem type field */ +static inline void SetTraceRestrictType(TraceRestrictItem &item, TraceRestrictItemType type) +{ + SB(item, TRIFA_TYPE_OFFSET, TRIFA_TYPE_COUNT, type); +} + +/** Set TraceRestrictItem condition operator field */ +static inline void SetTraceRestrictCondOp(TraceRestrictItem &item, TraceRestrictCondOp condop) +{ + SB(item, TRIFA_COND_OP_OFFSET, TRIFA_COND_OP_COUNT, condop); +} + +/** Set TraceRestrictItem condition flags field */ +static inline void SetTraceRestrictCondFlags(TraceRestrictItem &item, TraceRestrictCondFlags condflags) +{ + SB(item, TRIFA_COND_FLAGS_OFFSET, TRIFA_COND_FLAGS_COUNT, condflags); +} + +/** Set TraceRestrictItem auxiliary field */ +static inline void SetTraceRestrictAuxField(TraceRestrictItem &item, uint8 data) +{ + SB(item, TRIFA_AUX_FIELD_OFFSET, TRIFA_AUX_FIELD_COUNT, data); +} + +/** Set TraceRestrictItem value field */ +static inline void SetTraceRestrictValue(TraceRestrictItem &item, uint16 value) +{ + SB(item, TRIFA_VALUE_OFFSET, TRIFA_VALUE_COUNT, value); +} + +/** Is TraceRestrictItemType a conditional type? */ +static inline bool IsTraceRestrictTypeConditional(TraceRestrictItemType type) +{ + return type >= TRIT_COND_BEGIN; +} + +/** Is TraceRestrictItem type field a conditional type? */ +static inline bool IsTraceRestrictConditional(TraceRestrictItem item) +{ + return IsTraceRestrictTypeConditional(GetTraceRestrictType(item)); +} + +/** + * Categorisation of what is allowed in the TraceRestrictItem condition op field + * see TraceRestrictTypePropertySet + */ +enum TraceRestrictConditionOpType { + TRCOT_NONE = 0, ///< takes no condition op + TRCOT_BINARY = 1, ///< takes "is" and "is not" condition ops + TRCOT_ALL = 2, ///< takes all condition ops (i.e. all relational ops) +}; + +/** + * Categorisation of what is in the TraceRestrictItem value field + * see TraceRestrictTypePropertySet + */ +enum TraceRestrictValueType { + TRVT_NONE = 0, ///< value field not used (set to 0) + TRVT_SPECIAL = 1, ///< special handling of value field + TRVT_INT = 2, ///< takes an unsigned integer value + TRVT_DENY = 3, ///< takes a value 0 = deny, 1 = allow (cancel previous deny) + TRVT_SPEED = 4, ///< takes an integer speed value + TRVT_ORDER = 5, ///< takes an order target ID, as per the auxiliary field as type: TraceRestrictOrderCondAuxField + TRVT_CARGO_ID = 6, ///< takes a CargoID + TRVT_DIRECTION = 7, ///< takes a TraceRestrictDirectionTypeSpecialValue +}; + +/** + * Describes formats of TraceRestrictItem condition op and value fields + */ +struct TraceRestrictTypePropertySet { + TraceRestrictConditionOpType cond_type; + TraceRestrictValueType value_type; +}; + +void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueType value_type); +void SetTraceRestrictTypeAndNormalise(TraceRestrictItem &item, TraceRestrictItemType type); + +/** + * Get TraceRestrictTypePropertySet for a given instruction, only looks at value field + */ +static inline TraceRestrictTypePropertySet GetTraceRestrictTypeProperties(TraceRestrictItem item) +{ + TraceRestrictTypePropertySet out; + + if (GetTraceRestrictType(item) == TRIT_NULL) { + out.cond_type = TRCOT_NONE; + out.value_type = TRVT_SPECIAL; + } else if (GetTraceRestrictType(item) == TRIT_COND_ENDIF || + GetTraceRestrictType(item) == TRIT_COND_UNDEFINED) { + out.cond_type = TRCOT_NONE; + out.value_type = TRVT_NONE; + } else if (IsTraceRestrictConditional(item)) { + out.cond_type = TRCOT_ALL; + + switch (GetTraceRestrictType(item)) { + case TRIT_COND_TRAIN_LENGTH: + out.value_type = TRVT_INT; + break; + + case TRIT_COND_MAX_SPEED: + out.value_type = TRVT_SPEED; + break; + + case TRIT_COND_CURRENT_ORDER: + case TRIT_COND_NEXT_ORDER: + case TRIT_COND_LAST_STATION: + out.value_type = TRVT_ORDER; + out.cond_type = TRCOT_BINARY; + break; + + case TRIT_COND_CARGO: + out.value_type = TRVT_CARGO_ID; + out.cond_type = TRCOT_BINARY; + break; + + case TRIT_COND_ENTRY_DIRECTION: + out.value_type = TRVT_DIRECTION; + out.cond_type = TRCOT_BINARY; + break; + + default: + NOT_REACHED(); + break; + } + } else { + out.cond_type = TRCOT_NONE; + if (GetTraceRestrictType(item) == TRIT_PF_PENALTY) { + out.value_type = TRVT_INT; + } else if (GetTraceRestrictType(item) == TRIT_PF_DENY) { + out.value_type = TRVT_DENY; + } else { + out.value_type = TRVT_NONE; + } + } + + return out; +} + +/** Get mapping ref ID from tile and track */ +static inline TraceRestrictRefId MakeTraceRestrictRefId(TileIndex t, Track track) +{ + return (t << 3) | track; +} + +/** Get tile from mapping ref ID */ +static inline TileIndex GetTraceRestrictRefIdTileIndex(TraceRestrictRefId ref) +{ + return static_cast(ref >> 3); +} + +/** Get track from mapping ref ID */ +static inline Track GetTraceRestrictRefIdTrack(TraceRestrictRefId ref) +{ + return static_cast(ref & 7); +} + +void TraceRestrictCreateProgramMapping(TraceRestrictRefId ref, TraceRestrictProgram *prog); +void TraceRestrictRemoveProgramMapping(TraceRestrictRefId ref); + +TraceRestrictProgram *GetTraceRestrictProgram(TraceRestrictRefId ref, bool create_new); + +void TraceRestrictNotifySignalRemoval(TileIndex tile, Track track); + +/** + * Gets the existing signal program for the tile identified by @p t and @p track, or NULL + */ +static inline const TraceRestrictProgram *GetExistingTraceRestrictProgram(TileIndex t, Track track) +{ + if (IsRestrictedSignal(t)) { + return GetTraceRestrictProgram(MakeTraceRestrictRefId(t, track), false); + } else { + return NULL; + } +} + +/** + * Enumeration for command action type field, indicates what command to do + */ +enum TraceRestrictDoCommandType { + TRDCT_INSERT_ITEM = 0, ///< insert new instruction before offset field as given value + TRDCT_MODIFY_ITEM = 1, ///< modify instruction at offset field to given value + TRDCT_REMOVE_ITEM = 2, ///< remove instruction at offset field + + TRDCT_PROG_COPY = 3, ///< copy program operation. Do not re-order this with respect to other values + TRDCT_PROG_SHARE = 4, ///< share program operation + TRDCT_PROG_UNSHARE = 5, ///< unshare program (copy as a new program) + TRDCT_PROG_RESET = 6, ///< reset program state of signal +}; + +void TraceRestrictDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, uint32 offset, uint32 value, StringID error_msg); + +void TraceRestrictProgMgmtWithSourceDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, + TileIndex source_tile, Track source_track, StringID error_msg); + +/** + * Short-hand to call TraceRestrictProgMgmtWithSourceDoCommandP with 0 for source tile/track + */ +inline void TraceRestrictProgMgmtDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, StringID error_msg) +{ + TraceRestrictProgMgmtWithSourceDoCommandP(tile, track, type, static_cast(0), static_cast(0), error_msg); +} + +CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text); +CommandCost CmdProgramSignalTraceRestrictProgMgmt(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text); + +void ShowTraceRestrictProgramWindow(TileIndex tile, Track track); + +void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type, uint16 index); + +#endif /* TRACERESTRICT_H */ diff --git a/src/tracerestrict_gui.cpp b/src/tracerestrict_gui.cpp new file mode 100644 index 0000000000..cfd9a5eafe --- /dev/null +++ b/src/tracerestrict_gui.cpp @@ -0,0 +1,1588 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file tracerestrict_gui.cpp GUI code for Trace Restrict + * + * This is largely based on the programmable signals patch's GUI + * */ + +#include "stdafx.h" +#include "tracerestrict.h" +#include "command_func.h" +#include "window_func.h" +#include "strings_func.h" +#include "string_func.h" +#include "viewport_func.h" +#include "textbuf_gui.h" +#include "company_func.h" +#include "tilehighlight_func.h" +#include "widgets/dropdown_func.h" +#include "gui.h" +#include "gfx_func.h" +#include "rail_map.h" +#include "depot_map.h" +#include "tile_cmd.h" +#include "station_base.h" +#include "waypoint_base.h" +#include "depot_base.h" +#include "error.h" +#include "cargotype.h" +#include "table/sprites.h" + +extern uint ConvertSpeedToDisplaySpeed(uint speed); +extern uint ConvertDisplaySpeedToSpeed(uint speed); + +/** Widget IDs */ +enum TraceRestrictWindowWidgets { + TR_WIDGET_CAPTION, + TR_WIDGET_INSTRUCTION_LIST, + TR_WIDGET_SCROLLBAR, + + TR_WIDGET_SEL_TOP_LEFT_2, + TR_WIDGET_SEL_TOP_LEFT, + TR_WIDGET_SEL_TOP_MIDDLE, + TR_WIDGET_SEL_TOP_RIGHT, + TR_WIDGET_SEL_SHARE, + + TR_WIDGET_TYPE_COND, + TR_WIDGET_TYPE_NONCOND, + TR_WIDGET_CONDFLAGS, + TR_WIDGET_COMPARATOR, + TR_WIDGET_VALUE_INT, + TR_WIDGET_VALUE_DROPDOWN, + TR_WIDGET_VALUE_DEST, + + TR_WIDGET_BLANK_L2, + TR_WIDGET_BLANK_L, + TR_WIDGET_BLANK_M, + TR_WIDGET_BLANK_R, + + TR_WIDGET_GOTO_SIGNAL, + TR_WIDGET_INSERT, + TR_WIDGET_REMOVE, + TR_WIDGET_RESET, + TR_WIDGET_COPY, + TR_WIDGET_SHARE, + TR_WIDGET_UNSHARE, +}; + +/** Selection mappings for NWID_SELECTION selectors */ +enum PanelWidgets { + // Left 2 + DPL2_TYPE = 0, + DPL2_CONDFLAGS, + DPL2_BLANK, + + // Left + DPL_TYPE = 0, + DPL_BLANK, + + // Middle + DPM_COMPARATOR = 0, + DPM_BLANK, + + // Right + DPR_VALUE_INT = 0, + DPR_VALUE_DROPDOWN, + DPR_VALUE_DEST, + DPR_BLANK, + + // Share + DPS_SHARE = 0, + DPS_UNSHARE, +}; + +/** + * drop down list string array, and corresponding integer values + * + * value_array *must* be at least as long as string_array, + * where the length of string_array is defined as the offset + * of the first INVALID_STRING_ID + */ +struct TraceRestrictDropDownListSet { + const StringID *string_array; + const uint *value_array; +}; + +static const StringID _program_insert_str[] = { + STR_TRACE_RESTRICT_CONDITIONAL_IF, + STR_TRACE_RESTRICT_CONDITIONAL_ELIF, + STR_TRACE_RESTRICT_CONDITIONAL_ELSE, + STR_TRACE_RESTRICT_PF_DENY, + STR_TRACE_RESTRICT_PF_PENALTY, + INVALID_STRING_ID +}; +static const uint _program_insert_else_flag = 0x100; ///< flag to indicate that TRCF_ELSE should be set +static const uint32 _program_insert_else_hide_mask = 4; ///< disable bitmask for else +static const uint32 _program_insert_else_if_hide_mask = 2; ///< disable bitmask for elif +static const uint _program_insert_val[] = { + TRIT_COND_UNDEFINED, // if block + TRIT_COND_UNDEFINED | _program_insert_else_flag, // elif block + TRIT_COND_ENDIF | _program_insert_else_flag, // else block + TRIT_PF_DENY, // deny + TRIT_PF_PENALTY, // penalty +}; + +/** insert drop down list strings and values */ +static const TraceRestrictDropDownListSet _program_insert = { + _program_insert_str, _program_insert_val, +}; + +static const StringID _deny_value_str[] = { + STR_TRACE_RESTRICT_PF_DENY, + STR_TRACE_RESTRICT_PF_ALLOW, + INVALID_STRING_ID +}; +static const uint _deny_value_val[] = { + 0, + 1, +}; + +/** value drop down list for deny types strings and values */ +static const TraceRestrictDropDownListSet _deny_value = { + _deny_value_str, _deny_value_val, +}; + +static const StringID _direction_value_str[] = { + STR_TRACE_RESTRICT_DIRECTION_FRONT, + STR_TRACE_RESTRICT_DIRECTION_BACK, + STR_TRACE_RESTRICT_DIRECTION_NE, + STR_TRACE_RESTRICT_DIRECTION_SE, + STR_TRACE_RESTRICT_DIRECTION_SW, + STR_TRACE_RESTRICT_DIRECTION_NW, + INVALID_STRING_ID +}; +static const uint _direction_value_val[] = { + TRDTSV_FRONT, + TRDTSV_BACK, + TRNTSV_NE, + TRNTSV_SE, + TRNTSV_SW, + TRNTSV_NW, +}; + +/** value drop down list for direction type strings and values */ +static const TraceRestrictDropDownListSet _direction_value = { + _direction_value_str, _direction_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 + */ +static int GetDropDownListIndexByValue(const TraceRestrictDropDownListSet *list_set, uint value, bool missing_ok) +{ + const StringID *string_array = list_set->string_array; + const uint *value_array = list_set->value_array; + + for (; *string_array != INVALID_STRING_ID; string_array++, value_array++) { + if (*value_array == value) { + return value_array - list_set->value_array; + } + } + assert(missing_ok == true); + return -1; +} + +/** + * Get StringID correspoding to @p value, in @list_set + * @p value must be present + */ +static StringID GetDropDownStringByValue(const TraceRestrictDropDownListSet *list_set, uint value) +{ + return list_set->string_array[GetDropDownListIndexByValue(list_set, value, false)]; +} + +/** + * Return the appropriate type dropdown TraceRestrictDropDownListSet for the given item type @p type + */ +static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictItemType type) +{ + static const StringID str_action[] = { + STR_TRACE_RESTRICT_PF_DENY, + STR_TRACE_RESTRICT_PF_PENALTY, + INVALID_STRING_ID, + }; + static const uint val_action[] = { + TRIT_PF_DENY, + TRIT_PF_PENALTY, + }; + static const TraceRestrictDropDownListSet set_action = { + str_action, val_action, + }; + + static const StringID str_cond[] = { + STR_TRACE_RESTRICT_VARIABLE_TRAIN_LENGTH, + STR_TRACE_RESTRICT_VARIABLE_MAX_SPEED, + STR_TRACE_RESTRICT_VARIABLE_CURRENT_ORDER, + STR_TRACE_RESTRICT_VARIABLE_NEXT_ORDER, + STR_TRACE_RESTRICT_VARIABLE_LAST_VISITED_STATION, + STR_TRACE_RESTRICT_VARIABLE_CARGO, + STR_TRACE_RESTRICT_VARIABLE_ENTRY_DIRECTION, + STR_TRACE_RESTRICT_VARIABLE_UNDEFINED, + INVALID_STRING_ID, + }; + static const uint val_cond[] = { + TRIT_COND_TRAIN_LENGTH, + TRIT_COND_MAX_SPEED, + TRIT_COND_CURRENT_ORDER, + TRIT_COND_NEXT_ORDER, + TRIT_COND_LAST_STATION, + TRIT_COND_CARGO, + TRIT_COND_ENTRY_DIRECTION, + TRIT_COND_UNDEFINED, + }; + static const TraceRestrictDropDownListSet set_cond = { + str_cond, val_cond, + }; + + return IsTraceRestrictTypeConditional(type) ? &set_cond : &set_action; +} + +/** + * Get a TraceRestrictDropDownListSet of the sorted cargo list + */ +static const TraceRestrictDropDownListSet *GetSortedCargoTypeDropDownListSet() +{ + static StringID cargo_list_str[NUM_CARGO + 1]; + static uint cargo_list_id[NUM_CARGO]; + static const TraceRestrictDropDownListSet cargo_list = { + cargo_list_str, cargo_list_id, + }; + + for (size_t i = 0; i < _sorted_standard_cargo_specs_size; ++i) { + const CargoSpec *cs = _sorted_cargo_specs[i]; + cargo_list_str[i] = cs->name; + cargo_list_id[i] = cs->Index(); + } + cargo_list_str[_sorted_standard_cargo_specs_size] = INVALID_STRING_ID; + + return &cargo_list; +} + +static const StringID _cargo_cond_ops_str[] = { + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_EQUALS, + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_NOT_EQUALS, + INVALID_STRING_ID, +}; +static const uint _cargo_cond_ops_val[] = { + TRCO_IS, + TRCO_ISNOT, +}; +/** cargo conditional operators dropdown list set */ +static const TraceRestrictDropDownListSet _cargo_cond_ops = { + _cargo_cond_ops_str, _cargo_cond_ops_val, +}; + +/** + * Get the StringID for a given CargoID @p cargo, or STR_NEWGRF_INVALID_CARGO + */ +static StringID GetCargoStringByID(CargoID cargo) +{ + const CargoSpec *cs = CargoSpec::Get(cargo); + return cs->IsValid() ? cs->name : STR_NEWGRF_INVALID_CARGO; +} + +/** + * Get the StringID for a given item type @p type + */ +static StringID GetTypeString(TraceRestrictItemType type) +{ + return GetDropDownStringByValue(GetTypeDropDownListSet(type), type); +} + +/** + * Get the conditional operator field drop down list set for a given type property set @p properties + */ +static const TraceRestrictDropDownListSet *GetCondOpDropDownListSet(TraceRestrictTypePropertySet properties) +{ + static const StringID str_long[] = { + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_EQUALS, + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_NOT_EQUALS, + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_THAN, + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_LESS_EQUALS, + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_THAN, + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_MORE_EQUALS, + INVALID_STRING_ID, + }; + static const uint val_long[] = { + TRCO_IS, + TRCO_ISNOT, + TRCO_LT, + TRCO_LTE, + TRCO_GT, + TRCO_GTE, + }; + static const TraceRestrictDropDownListSet set_long = { + str_long, val_long, + }; + + static const StringID str_short[] = { + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_EQUALS, + STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_NOT_EQUALS, + INVALID_STRING_ID, + }; + static const uint val_short[] = { + TRCO_IS, + TRCO_ISNOT, + }; + static const TraceRestrictDropDownListSet set_short = { + str_short, val_short, + }; + + if (properties.value_type == TRVT_CARGO_ID) return &_cargo_cond_ops; + + switch (properties.cond_type) { + case TRCOT_NONE: + return NULL; + + case TRCOT_BINARY: + return &set_short; + + case TRCOT_ALL: + return &set_long; + } + NOT_REACHED(); + return NULL; +} + +/** + * Return true if item type field @p type is an integer value type + */ +static bool IsIntegerValueType(TraceRestrictValueType type) +{ + switch (type) { + case TRVT_INT: + case TRVT_SPEED: + return true; + + default: + return false; + } +} + +/** + * Convert integer values between internal units and display units + */ +static uint ConvertIntegerValue(TraceRestrictValueType type, uint in, bool to_display) +{ + switch (type) { + case TRVT_INT: + return in; + + case TRVT_SPEED: + return to_display + ? ConvertSpeedToDisplaySpeed(in) * 10 / 16 + : ConvertDisplaySpeedToSpeed(in) * 16 / 10; + + default: + NOT_REACHED(); + return 0; + } +} + +/** String values for TraceRestrictCondFlags, value gives offset into array */ +static const StringID _program_cond_type[] = { + STR_TRACE_RESTRICT_CONDITIONAL_IF, // TRCF_DEFAULT + STR_TRACE_RESTRICT_CONDITIONAL_ELIF, // TRCF_ELSE + STR_TRACE_RESTRICT_CONDITIONAL_ORIF, // TRCF_OR +}; + +/** condition flags field drop down value types */ +enum CondFlagsDropDownType { + CFDDT_ELSE = 0, ///< This is an else block + CFDDT_ELIF = TRCF_ELSE, ///< This is an else-if block + CFDDT_ORIF = TRCF_OR, ///< This is an or-if block +}; + +static const uint32 _condflags_dropdown_else_hide_mask = 1; ///< disable bitmask for CFDDT_ELSE +static const uint32 _condflags_dropdown_else_if_hide_mask = 6; ///< disable bitmask for CFDDT_ELIF and CFDDT_ORIF + +static const StringID _condflags_dropdown_str[] = { + STR_TRACE_RESTRICT_CONDITIONAL_ELSE, + STR_TRACE_RESTRICT_CONDITIONAL_ELIF, + STR_TRACE_RESTRICT_CONDITIONAL_ORIF, + INVALID_STRING_ID, +}; +static const uint _condflags_dropdown_val[] = { + CFDDT_ELSE, + CFDDT_ELIF, + CFDDT_ORIF, +}; +/** condition flags dropdown list set */ +static const TraceRestrictDropDownListSet _condflags_dropdown = { + _condflags_dropdown_str, _condflags_dropdown_val, +}; + +/** Common function for drawing an ordinary conditional instruction */ +static void DrawInstructionStringConditionalCommon(TraceRestrictItem item, const TraceRestrictTypePropertySet &properties) +{ + assert(GetTraceRestrictCondFlags(item) <= TRCF_OR); + SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]); + SetDParam(1, GetTypeString(GetTraceRestrictType(item))); + SetDParam(2, GetDropDownStringByValue(GetCondOpDropDownListSet(properties), GetTraceRestrictCondOp(item))); +} + +/** Common function for drawing an integer conditional instruction */ +static void DrawInstructionStringConditionalIntegerCommon(TraceRestrictItem item, const TraceRestrictTypePropertySet &properties) +{ + DrawInstructionStringConditionalCommon(item, properties); + SetDParam(3, GetTraceRestrictValue(item)); +} + +/** + * Draws an instruction in the programming GUI + * @param instruction The instruction to draw + * @param y Y position for drawing + * @param selected True, if the order is selected + * @param indent How many levels the instruction is indented + * @param left Left border for text drawing + * @param right Right border for text drawing + */ +static void DrawInstructionString(TraceRestrictItem item, int y, bool selected, int indent, int left, int right) +{ + StringID instruction_string = INVALID_STRING_ID; + + TraceRestrictTypePropertySet properties = GetTraceRestrictTypeProperties(item); + + if (IsTraceRestrictConditional(item)) { + if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) { + if (GetTraceRestrictCondFlags(item) & TRCF_ELSE) { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ELSE; + } else { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ENDIF; + } + } else if (GetTraceRestrictType(item) == TRIT_COND_UNDEFINED) { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_UNDEFINED; + SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]); + SetDParam(1, selected ? STR_TRACE_RESTRICT_WHITE : STR_EMPTY); + } else { + switch (properties.value_type) { + case TRVT_INT: + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_INTEGER; + DrawInstructionStringConditionalIntegerCommon(item, properties); + break; + + case TRVT_SPEED: + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_SPEED; + DrawInstructionStringConditionalIntegerCommon(item, properties); + break; + + case TRVT_ORDER: { + switch (static_cast(GetTraceRestrictAuxField(item))) { + case TROCAF_STATION: + if (GetTraceRestrictValue(item) != INVALID_STATION) { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ORDER_STATION; + DrawInstructionStringConditionalIntegerCommon(item, properties); + } else { + // this is an invalid station, use a seperate string + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_UNDEFINED; + DrawInstructionStringConditionalCommon(item, properties); + SetDParam(3, selected ? STR_TRACE_RESTRICT_WHITE : STR_EMPTY); + } + break; + + case TROCAF_WAYPOINT: + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ORDER_WAYPOINT; + DrawInstructionStringConditionalIntegerCommon(item, properties); + break; + + case TROCAF_DEPOT: + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ORDER_DEPOT; + DrawInstructionStringConditionalCommon(item, properties); + SetDParam(3, VEH_TRAIN); + SetDParam(4, GetTraceRestrictValue(item)); + break; + + default: + NOT_REACHED(); + break; + } + break; + } + + case TRVT_CARGO_ID: + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_CARGO; + assert(GetTraceRestrictCondFlags(item) <= TRCF_OR); + SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]); + SetDParam(1, GetDropDownStringByValue(&_cargo_cond_ops, GetTraceRestrictCondOp(item))); + SetDParam(2, GetCargoStringByID(GetTraceRestrictValue(item))); + break; + + case TRVT_DIRECTION: + if (GetTraceRestrictValue(item) >= TRDTSV_FRONT) { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_SIGNAL_FACE; + } else { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_ENTRY_DIRECTION; + } + SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]); + SetDParam(1, GetDropDownStringByValue(GetCondOpDropDownListSet(properties), GetTraceRestrictCondOp(item))); + SetDParam(2, GetDropDownStringByValue(&_direction_value, GetTraceRestrictValue(item))); + break; + + default: + NOT_REACHED(); + break; + } + } + } else { + switch (GetTraceRestrictType(item)) { + case TRIT_NULL: + switch (GetTraceRestrictValue(item)) { + case TRNTSV_START: + instruction_string = STR_TRACE_RESTRICT_START; + break; + + case TRNTSV_END: + instruction_string = STR_TRACE_RESTRICT_END; + break; + + default: + NOT_REACHED(); + break; + } + break; + + case TRIT_PF_DENY: + instruction_string = GetTraceRestrictValue(item) ? STR_TRACE_RESTRICT_PF_ALLOW_LONG : STR_TRACE_RESTRICT_PF_DENY; + break; + + case TRIT_PF_PENALTY: + instruction_string = STR_TRACE_RESTRICT_PF_PENALTY_ITEM; + SetDParam(0, GetTraceRestrictValue(item)); + break; + + default: + NOT_REACHED(); + break; + } + } + + DrawString(left + indent * 16, right, y, instruction_string, selected ? TC_WHITE : TC_BLACK); +} + +/** Main GUI window class */ +class TraceRestrictWindow: public Window { + TileIndex tile; ///< tile this window is for + Track track; ///< track this window is for + int selected_instruction; ///< selected instruction index, this is offset by one due to the display of the "start" item + Scrollbar *vscroll; ///< scrollbar widget + std::map drop_down_list_mapping; ///< mapping of widget IDs to drop down list sets + TraceRestrictItem expecting_inserted_item; ///< set to instruction when performing an instruction insertion, used to handle selection update on insertion + int current_placement_widget; ///< which widget has a SetObjectToPlaceWnd, if any + +public: + TraceRestrictWindow(WindowDesc *desc, TileIndex tile, Track track) + : Window(desc) + { + this->tile = tile; + this->track = track; + this->selected_instruction = -1; + this->expecting_inserted_item = static_cast(0); + this->current_placement_widget = -1; + + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(TR_WIDGET_SCROLLBAR); + this->FinishInitNested(MakeTraceRestrictRefId(tile, track)); + + this->ReloadProgramme(); + } + + virtual void OnClick(Point pt, int widget, int click_count) OVERRIDE + { + switch (widget) { + case TR_WIDGET_INSTRUCTION_LIST: { + int sel = this->GetItemIndexFromPt(pt.y); + + if (_ctrl_pressed) { + // scroll to target (for stations, waypoints, depots) + + if (sel == -1) return; + + TraceRestrictItem item = this->GetItem(this->GetProgram(), sel); + if (GetTraceRestrictTypeProperties(item).value_type == TRVT_ORDER) { + switch (static_cast(GetTraceRestrictAuxField(item))) { + case TROCAF_STATION: + case TROCAF_WAYPOINT: { + BaseStation *st = BaseStation::GetIfValid(GetTraceRestrictValue(item)); + if (st) { + ScrollMainWindowToTile(st->xy); + } + break; + } + + case TROCAF_DEPOT: { + Depot *depot = Depot::GetIfValid(GetTraceRestrictValue(item)); + if (depot) { + ScrollMainWindowToTile(depot->xy); + } + break; + } + } + } + return; + } + + this->DeleteChildWindows(); + HideDropDownMenu(this); + + if (sel == -1 || this->GetOwner() != _local_company) { + // Deselect + this->selected_instruction = -1; + } else { + this->selected_instruction = sel; + } + + this->expecting_inserted_item = static_cast(0); + + this->UpdateButtonState(); + break; + } + + case TR_WIDGET_INSERT: { + if (this->GetOwner() != _local_company || this->selected_instruction < 1) { + return; + } + + uint32 disabled = 0; + TraceRestrictItem item = this->GetSelected(); + if (GetTraceRestrictType(item) == TRIT_COND_ENDIF || + (IsTraceRestrictConditional(item) && GetTraceRestrictCondFlags(item) != 0)) { + // this is either: an else/or if, an else, or an end if + // try to include else if, else in insertion list + if (!ElseInsertionDryRun(false)) disabled |= _program_insert_else_hide_mask; + if (!ElseIfInsertionDryRun(false)) disabled |= _program_insert_else_if_hide_mask; + } else { + // can't insert else/end if here + disabled |= _program_insert_else_hide_mask | _program_insert_else_if_hide_mask; + } + + this->ShowDropDownListWithValue(&_program_insert, 0, true, TR_WIDGET_INSERT, disabled, 0, 0); + break; + } + + case TR_WIDGET_REMOVE: { + TraceRestrictItem item = this->GetSelected(); + if (this->GetOwner() != _local_company || item == 0) { + return; + } + + TraceRestrictDoCommandP(tile, track, TRDCT_REMOVE_ITEM, this->selected_instruction - 1, 0, STR_TRACE_RESTRICT_ERROR_CAN_T_REMOVE_ITEM); + break; + } + + case TR_WIDGET_CONDFLAGS: { + TraceRestrictItem item = this->GetSelected(); + if (this->GetOwner() != _local_company || item == 0) { + return; + } + + CondFlagsDropDownType type; + if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) { + if (GetTraceRestrictCondFlags(item) == 0) return; // end if + type = CFDDT_ELSE; + } else if (IsTraceRestrictConditional(item) && GetTraceRestrictCondFlags(item) != 0) { + type = static_cast(GetTraceRestrictCondFlags(item)); + } else { + return; + } + + uint32 disabled = 0; + if (!ElseInsertionDryRun(true)) disabled |= _condflags_dropdown_else_hide_mask; + if (!ElseIfInsertionDryRun(true)) disabled |= _condflags_dropdown_else_if_hide_mask; + + this->ShowDropDownListWithValue(&_condflags_dropdown, type, false, TR_WIDGET_CONDFLAGS, disabled, 0, 0); + break; + } + + case TR_WIDGET_TYPE_COND: + case TR_WIDGET_TYPE_NONCOND: { + TraceRestrictItem item = this->GetSelected(); + TraceRestrictItemType type = GetTraceRestrictType(item); + + if (type != TRIT_NULL) { + this->ShowDropDownListWithValue(GetTypeDropDownListSet(type), type, false, widget, 0, 0, 0); + } + break; + } + + case TR_WIDGET_COMPARATOR: { + TraceRestrictItem item = this->GetSelected(); + const TraceRestrictDropDownListSet *list_set = GetCondOpDropDownListSet(GetTraceRestrictTypeProperties(item)); + if (list_set) { + this->ShowDropDownListWithValue(list_set, GetTraceRestrictCondOp(item), false, TR_WIDGET_COMPARATOR, 0, 0, 0); + } + break; + } + + case TR_WIDGET_VALUE_INT: { + TraceRestrictItem item = this->GetSelected(); + TraceRestrictValueType type = GetTraceRestrictTypeProperties(item).value_type; + if (IsIntegerValueType(type)) { + SetDParam(0, ConvertIntegerValue(type, GetTraceRestrictValue(item), true)); + ShowQueryString(STR_JUST_INT, STR_TRACE_RESTRICT_VALUE_CAPTION, 10, this, CS_NUMERAL, QSF_NONE); + } + break; + } + + case TR_WIDGET_VALUE_DROPDOWN: { + TraceRestrictItem item = this->GetSelected(); + switch (GetTraceRestrictTypeProperties(item).value_type) { + case TRVT_DENY: + this->ShowDropDownListWithValue(&_deny_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); + break; + + case TRVT_CARGO_ID: + this->ShowDropDownListWithValue(GetSortedCargoTypeDropDownListSet(), GetTraceRestrictValue(item), true, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); // current cargo is permitted to not be in list + break; + + case TRVT_DIRECTION: + this->ShowDropDownListWithValue(&_direction_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); + break; + + default: + break; + } + break; + } + + case TR_WIDGET_VALUE_DEST: { + SetObjectToPlaceAction(widget, ANIMCURSOR_PICKSTATION); + break; + } + + case TR_WIDGET_GOTO_SIGNAL: + ScrollMainWindowToTile(this->tile); + break; + + case TR_WIDGET_RESET: { + TraceRestrictProgMgmtDoCommandP(tile, track, TRDCT_PROG_RESET, STR_TRACE_RESTRICT_ERROR_CAN_T_RESET_SIGNAL); + break; + } + + case TR_WIDGET_COPY: + case TR_WIDGET_SHARE: + SetObjectToPlaceAction(widget, ANIMCURSOR_BUILDSIGNALS); + break; + + case TR_WIDGET_UNSHARE: { + TraceRestrictProgMgmtDoCommandP(tile, track, TRDCT_PROG_UNSHARE, STR_TRACE_RESTRICT_ERROR_CAN_T_UNSHARE_PROGRAM); + break; + } + } + } + + virtual void OnQueryTextFinished(char *str) OVERRIDE + { + if (StrEmpty(str)) { + return; + } + + TraceRestrictItem item = GetSelected(); + TraceRestrictValueType type = GetTraceRestrictTypeProperties(item).value_type; + if (!IsIntegerValueType(type)) { + return; + } + + uint value = ConvertIntegerValue(type, atoi(str), false); + if (value >= (1 << TRIFA_VALUE_COUNT)) { + SetDParam(0, ConvertIntegerValue(type, (1 << TRIFA_VALUE_COUNT) - 1, true)); + ShowErrorMessage(STR_TRACE_RESTRICT_ERROR_VALUE_TOO_LARGE, STR_EMPTY, WL_INFO); + return; + } + + SetTraceRestrictValue(item, value); + TraceRestrictDoCommandP(tile, track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + } + + virtual void OnDropdownSelect(int widget, int index) OVERRIDE + { + TraceRestrictItem item = GetSelected(); + if (item == 0 || index < 0 || this->selected_instruction < 1) { + return; + } + + const TraceRestrictDropDownListSet *list_set = this->drop_down_list_mapping[widget]; + if (!list_set) { + return; + } + + uint value = list_set->value_array[index]; + + switch (widget) { + case TR_WIDGET_INSERT: { + TraceRestrictItem insert_item = 0; + + bool have_else = false; + if (value & _program_insert_else_flag) { + value &= ~_program_insert_else_flag; + have_else = true; + } + SetTraceRestrictTypeAndNormalise(insert_item, static_cast(value)); + if (have_else) SetTraceRestrictCondFlags(insert_item, TRCF_ELSE); // this needs to happen after calling SetTraceRestrictTypeAndNormalise + + this->expecting_inserted_item = insert_item; + TraceRestrictDoCommandP(this->tile, this->track, TRDCT_INSERT_ITEM, this->selected_instruction - 1, insert_item, STR_TRACE_RESTRICT_ERROR_CAN_T_INSERT_ITEM); + break; + } + + case TR_WIDGET_CONDFLAGS: { + CondFlagsDropDownType cond_type = static_cast(value); + if (cond_type == CFDDT_ELSE) { + SetTraceRestrictTypeAndNormalise(item, TRIT_COND_ENDIF); + SetTraceRestrictCondFlags(item, TRCF_ELSE); + } else { + if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) { + // item is currently an else, convert to else/or if + SetTraceRestrictTypeAndNormalise(item, TRIT_COND_UNDEFINED); + } + + SetTraceRestrictCondFlags(item, static_cast(cond_type)); + } + + TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + break; + } + + case TR_WIDGET_TYPE_COND: + case TR_WIDGET_TYPE_NONCOND: { + SetTraceRestrictTypeAndNormalise(item, static_cast(value)); + if (GetTraceRestrictType(item) == TRIT_COND_LAST_STATION && GetTraceRestrictAuxField(item) != TROCAF_STATION) { + // if changing type from another order type to last visited station, reset value if not currently a station + SetTraceRestrictValueDefault(item, TRVT_ORDER); + } + TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + break; + } + + case TR_WIDGET_COMPARATOR: { + SetTraceRestrictCondOp(item, static_cast(value)); + TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + break; + } + + case TR_WIDGET_VALUE_DROPDOWN: { + SetTraceRestrictValue(item, value); + TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + break; + } + } + } + + virtual void OnPlaceObject(Point pt, TileIndex tile) OVERRIDE + { + int widget = this->current_placement_widget; + this->current_placement_widget = -1; + + this->RaiseButtons(); + ResetObjectToPlace(); + + if (widget < 0) { + return; + } + + switch (widget) { + case TR_WIDGET_COPY: + OnPlaceObjectSignal(pt, tile, widget, STR_TRACE_RESTRICT_ERROR_CAN_T_COPY_PROGRAM); + break; + + case TR_WIDGET_SHARE: + OnPlaceObjectSignal(pt, tile, widget, STR_TRACE_RESTRICT_ERROR_CAN_T_SHARE_PROGRAM); + break; + + case TR_WIDGET_VALUE_DEST: + OnPlaceObjectDestination(pt, tile, widget, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + break; + + default: + NOT_REACHED(); + break; + } + } + + /** + * Common OnPlaceObject handler for program management actions which involve clicking on a signal + */ + void OnPlaceObjectSignal(Point pt, TileIndex source_tile, int widget, int error_message) + { + if (!IsPlainRailTile(source_tile)) { + ShowErrorMessage(error_message, STR_ERROR_THERE_IS_NO_RAILROAD_TRACK, WL_INFO); + return; + } + + TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(source_tile, TRANSPORT_RAIL, 0)); + if (trackbits & TRACK_BIT_VERT) { // N-S direction + trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT; + } + + if (trackbits & TRACK_BIT_HORZ) { // E-W direction + trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER; + } + Track source_track = FindFirstTrack(trackbits); + if(source_track == INVALID_TRACK) { + ShowErrorMessage(error_message, STR_ERROR_THERE_IS_NO_RAILROAD_TRACK, WL_INFO); + return; + } + + if (!HasTrack(source_tile, source_track)) { + ShowErrorMessage(error_message, STR_ERROR_THERE_IS_NO_RAILROAD_TRACK, WL_INFO); + return; + } + + if (!HasSignalOnTrack(source_tile, source_track)) { + ShowErrorMessage(error_message, STR_ERROR_THERE_ARE_NO_SIGNALS, WL_INFO); + return; + } + + switch (widget) { + case TR_WIDGET_COPY: + TraceRestrictProgMgmtWithSourceDoCommandP(this->tile, this->track, TRDCT_PROG_COPY, + source_tile, source_track, STR_TRACE_RESTRICT_ERROR_CAN_T_COPY_PROGRAM); + break; + + case TR_WIDGET_SHARE: + TraceRestrictProgMgmtWithSourceDoCommandP(this->tile, this->track, TRDCT_PROG_SHARE, + source_tile, source_track, STR_TRACE_RESTRICT_ERROR_CAN_T_SHARE_PROGRAM); + break; + + default: + NOT_REACHED(); + break; + } + } + + /** + * Common OnPlaceObject handler for instruction value modification actions which involve selecting an order target + */ + void OnPlaceObjectDestination(Point pt, TileIndex tile, int widget, int error_message) + { + TraceRestrictItem item = GetSelected(); + if (GetTraceRestrictTypeProperties(item).value_type != TRVT_ORDER) return; + + bool stations_only = (GetTraceRestrictType(item) == TRIT_COND_LAST_STATION); + + if (IsDepotTypeTile(tile, TRANSPORT_RAIL)) { + if (stations_only) return; + SetTraceRestrictValue(item, GetDepotIndex(tile)); + SetTraceRestrictAuxField(item, TROCAF_DEPOT); + } else if (IsRailWaypointTile(tile)) { + if (stations_only) return; + SetTraceRestrictValue(item, GetStationIndex(tile)); + SetTraceRestrictAuxField(item, TROCAF_WAYPOINT); + } else if (IsTileType(tile, MP_STATION)) { + StationID st_index = GetStationIndex(tile); + const Station *st = Station::Get(st_index); + if (st->facilities & FACIL_TRAIN) { + SetTraceRestrictValue(item, st_index); + SetTraceRestrictAuxField(item, TROCAF_STATION); + } else { + return; + } + } else { + return; + } + + if (!IsTileOwner(tile, _local_company)) { + ShowErrorMessage(error_message, STR_ERROR_AREA_IS_OWNED_BY_ANOTHER, WL_INFO); + return; + } + + TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + } + + virtual void OnPlaceObjectAbort() OVERRIDE + { + this->RaiseButtons(); + this->current_placement_widget = -1; + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) OVERRIDE + { + switch (widget) { + case TR_WIDGET_INSTRUCTION_LIST: + resize->height = FONT_HEIGHT_NORMAL; + size->height = 6 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; + break; + } + } + + virtual void OnResize() OVERRIDE + { + /* Update the scroll bar */ + this->vscroll->SetCapacityFromWidget(this, TR_WIDGET_INSTRUCTION_LIST); + } + + virtual void OnPaint() OVERRIDE + { + this->DrawWidgets(); + } + + virtual void DrawWidget(const Rect &r, int widget) const OVERRIDE + { + if (widget != TR_WIDGET_INSTRUCTION_LIST) return; + + int y = r.top + WD_FRAMERECT_TOP; + int line_height = this->GetWidget(TR_WIDGET_INSTRUCTION_LIST)->resize_y; + int scroll_position = this->vscroll->GetPosition(); + + // prog may be NULL + const TraceRestrictProgram *prog = this->GetProgram(); + + int count = this->GetItemCount(prog); + uint indent = 1; + for(int i = 0; i < count; i++) { + TraceRestrictItem item = this->GetItem(prog, i); + uint this_indent = indent; + if (IsTraceRestrictConditional(item)) { + if (GetTraceRestrictCondFlags(item) & (TRCF_ELSE | TRCF_OR)) { + this_indent--; + } else if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) { + indent--; + this_indent--; + } else { + indent++; + } + } else if (GetTraceRestrictType(item) == TRIT_NULL) { + this_indent = 0; + } + + if (i >= scroll_position && this->vscroll->IsVisible(i)) { + DrawInstructionString(item, y, i == this->selected_instruction, this_indent, r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT); + y += line_height; + } + } + } + + virtual void OnInvalidateData(int data, bool gui_scope) OVERRIDE + { + if (gui_scope) { + this->ReloadProgramme(); + } + } + + virtual void SetStringParameters(int widget) const OVERRIDE + { + switch (widget) { + case TR_WIDGET_VALUE_INT: { + SetDParam(0, 0); + TraceRestrictItem item = this->GetSelected(); + TraceRestrictValueType type = GetTraceRestrictTypeProperties(item).value_type; + if (IsIntegerValueType(type)) { + SetDParam(0, ConvertIntegerValue(type, GetTraceRestrictValue(item), true)); + } + break; + } + + case TR_WIDGET_CAPTION: { + const TraceRestrictProgram *prog = this->GetProgram(); + if (prog) { + SetDParam(0, prog->refcount); + } else { + SetDParam(0, 1); + } + break; + } + } + } + +private: + /** + * Helper function to make start and end instructions (these are not stored in the actual program) + */ + TraceRestrictItem MakeSpecialItem(TraceRestrictNullTypeSpecialValue value) const + { + TraceRestrictItem item = 0; + SetTraceRestrictType(item, TRIT_NULL); + SetTraceRestrictValue(item, value); + return item; + } + + /** + * Get item count of program, including start and end markers + */ + int GetItemCount(const TraceRestrictProgram *prog) const + { + if (prog) { + return 2 + prog->items.size(); + } else { + return 2; + } + } + + /** + * Get current program + * This may return NULL if no program currently exists + */ + const TraceRestrictProgram *GetProgram() const + { + return GetTraceRestrictProgram(MakeTraceRestrictRefId(tile, track), false); + } + + /** + * Get instruction at @p index in program @p prog + * This correctly handles start/end markers, offsets, etc. + * This returns a 0 instruction if out of bounds + * @p prog may be NULL + */ + TraceRestrictItem GetItem(const TraceRestrictProgram *prog, int index) const + { + if (index < 0) { + return 0; + } + + if (index == 0) { + return MakeSpecialItem(TRNTSV_START); + } + + if (prog) { + const std::vector &items = prog->items; + + if (static_cast(index) == items.size() + 1) { + return MakeSpecialItem(TRNTSV_END); + } + + if (static_cast(index) > items.size() + 1) { + return 0; + } + + return items[index - 1]; + } else { + // No program defined, this is equivalent to an empty program + if (index == 1) { + return MakeSpecialItem(TRNTSV_END); + } else { + return 0; + } + } + } + + /** + * Get selected instruction, or a zero instruction + */ + TraceRestrictItem GetSelected() const + { + return this->GetItem(this->GetProgram(), this->selected_instruction); + } + + /** + * Get owner of the signal tile this window is pointing at + */ + Owner GetOwner() + { + return GetTileOwner(this->tile); + } + + /** + * Return item index from point in instruction list widget + */ + int GetItemIndexFromPt(int y) + { + NWidgetBase *nwid = this->GetWidget(TR_WIDGET_INSTRUCTION_LIST); + int sel = (y - nwid->pos_y - WD_FRAMERECT_TOP) / nwid->resize_y; // Selected line + + if ((uint)sel >= this->vscroll->GetCapacity()) return -1; + + sel += this->vscroll->GetPosition(); + + return (sel < this->GetItemCount(this->GetProgram()) && sel >= 0) ? sel : -1; + } + + /** + * Reload details of program, and adjust length/selection position as necessary + */ + void ReloadProgramme() + { + const TraceRestrictProgram *prog = this->GetProgram(); + + if (this->vscroll->GetCount() != this->GetItemCount(prog)) { + // program length has changed + + if (this->GetItemCount(prog) < this->vscroll->GetCount() || + this->GetItem(prog, this->selected_instruction) != this->expecting_inserted_item) { + // length has shrunk or if we weren't expecting an insertion, deselect + this->selected_instruction = -1; + } + this->expecting_inserted_item = static_cast(0); + + // update scrollbar size + this->vscroll->SetCount(this->GetItemCount(prog)); + } + this->UpdateButtonState(); + } + + /** + * Update button states, text values, etc. + */ + void UpdateButtonState() + { + this->RaiseWidget(TR_WIDGET_INSERT); + this->RaiseWidget(TR_WIDGET_REMOVE); + this->RaiseWidget(TR_WIDGET_TYPE_COND); + this->RaiseWidget(TR_WIDGET_TYPE_NONCOND); + this->RaiseWidget(TR_WIDGET_CONDFLAGS); + this->RaiseWidget(TR_WIDGET_COMPARATOR); + this->RaiseWidget(TR_WIDGET_VALUE_INT); + this->RaiseWidget(TR_WIDGET_VALUE_DROPDOWN); + this->RaiseWidget(TR_WIDGET_VALUE_DEST); + + NWidgetStacked *left_2_sel = this->GetWidget(TR_WIDGET_SEL_TOP_LEFT_2); + NWidgetStacked *left_sel = this->GetWidget(TR_WIDGET_SEL_TOP_LEFT); + NWidgetStacked *middle_sel = this->GetWidget(TR_WIDGET_SEL_TOP_MIDDLE); + NWidgetStacked *right_sel = this->GetWidget(TR_WIDGET_SEL_TOP_RIGHT); + NWidgetStacked *share_sel = this->GetWidget(TR_WIDGET_SEL_SHARE); + + this->DisableWidget(TR_WIDGET_TYPE_COND); + this->DisableWidget(TR_WIDGET_TYPE_NONCOND); + this->DisableWidget(TR_WIDGET_CONDFLAGS); + this->DisableWidget(TR_WIDGET_COMPARATOR); + this->DisableWidget(TR_WIDGET_VALUE_INT); + this->DisableWidget(TR_WIDGET_VALUE_DROPDOWN); + this->DisableWidget(TR_WIDGET_VALUE_DEST); + + this->DisableWidget(TR_WIDGET_INSERT); + this->DisableWidget(TR_WIDGET_REMOVE); + this->DisableWidget(TR_WIDGET_RESET); + this->DisableWidget(TR_WIDGET_COPY); + this->DisableWidget(TR_WIDGET_SHARE); + this->DisableWidget(TR_WIDGET_UNSHARE); + + this->DisableWidget(TR_WIDGET_BLANK_L2); + this->DisableWidget(TR_WIDGET_BLANK_L); + this->DisableWidget(TR_WIDGET_BLANK_M); + this->DisableWidget(TR_WIDGET_BLANK_R); + + left_2_sel->SetDisplayedPlane(DPL2_BLANK); + left_sel->SetDisplayedPlane(DPL_BLANK); + middle_sel->SetDisplayedPlane(DPM_BLANK); + right_sel->SetDisplayedPlane(DPR_BLANK); + share_sel->SetDisplayedPlane(DPS_SHARE); + + const TraceRestrictProgram *prog = this->GetProgram(); + + this->GetWidget(TR_WIDGET_CAPTION)->widget_data = + (prog && prog->refcount > 1) ? STR_TRACE_RESTRICT_CAPTION_SHARED : STR_TRACE_RESTRICT_CAPTION; + + // Don't allow modifications if don't own + if (this->GetOwner() != _local_company) { + this->SetDirty(); + return; + } + + if (prog && prog->refcount > 1) { + // program is shared, show and enable unshare button, and reset button + share_sel->SetDisplayedPlane(DPS_UNSHARE); + this->EnableWidget(TR_WIDGET_UNSHARE); + this->EnableWidget(TR_WIDGET_RESET); + } else if (this->GetItemCount(prog) > 2) { + // program is non-empty and not shared, enable reset button + this->EnableWidget(TR_WIDGET_RESET); + } else { + // program is empty and not shared, show copy and share buttons + this->EnableWidget(TR_WIDGET_COPY); + this->EnableWidget(TR_WIDGET_SHARE); + } + + // haven't selected instruction + if (this->selected_instruction < 1) { + this->SetDirty(); + return; + } + + TraceRestrictItem item = this->GetItem(prog, this->selected_instruction); + if (item != 0) { + if (GetTraceRestrictType(item) == TRIT_NULL) { + switch (GetTraceRestrictValue(item)) { + case TRNTSV_START: + break; + + case TRNTSV_END: + this->EnableWidget(TR_WIDGET_INSERT); + break; + + default: + NOT_REACHED(); + break; + } + } else if (GetTraceRestrictType(item) == TRIT_COND_ENDIF) { + this->EnableWidget(TR_WIDGET_INSERT); + if (GetTraceRestrictCondFlags(item) != 0) { + // this is not an end if, it must be an else, enable removing + this->EnableWidget(TR_WIDGET_REMOVE); + + // setup condflags dropdown to show else + left_2_sel->SetDisplayedPlane(DPL2_CONDFLAGS); + this->EnableWidget(TR_WIDGET_CONDFLAGS); + this->GetWidget(TR_WIDGET_CONDFLAGS)->widget_data = STR_TRACE_RESTRICT_CONDITIONAL_ELSE; + } + } else { + TraceRestrictTypePropertySet properties = GetTraceRestrictTypeProperties(item); + + int type_widget; + if (IsTraceRestrictConditional(item)) { + // note that else and end if items are not handled here, they are handled above + + left_2_sel->SetDisplayedPlane(DPL2_CONDFLAGS); + left_sel->SetDisplayedPlane(DPL_TYPE); + type_widget = TR_WIDGET_TYPE_COND; + + // setup condflags dropdown box + left_2_sel->SetDisplayedPlane(DPL2_CONDFLAGS); + switch (GetTraceRestrictCondFlags(item)) { + case TRCF_DEFAULT: // opening if, leave disabled + this->GetWidget(TR_WIDGET_CONDFLAGS)->widget_data = STR_TRACE_RESTRICT_CONDITIONAL_IF; + break; + + case TRCF_ELSE: // else-if + this->GetWidget(TR_WIDGET_CONDFLAGS)->widget_data = STR_TRACE_RESTRICT_CONDITIONAL_ELIF; + this->EnableWidget(TR_WIDGET_CONDFLAGS); + break; + + case TRCF_OR: // or-if + this->GetWidget(TR_WIDGET_CONDFLAGS)->widget_data = STR_TRACE_RESTRICT_CONDITIONAL_ORIF; + this->EnableWidget(TR_WIDGET_CONDFLAGS); + break; + + default: + NOT_REACHED(); + break; + } + } else { + left_2_sel->SetDisplayedPlane(DPL2_TYPE); + type_widget = TR_WIDGET_TYPE_NONCOND; + } + this->EnableWidget(type_widget); + + this->GetWidget(type_widget)->widget_data = + GetTypeString(GetTraceRestrictType(item)); + + if (properties.cond_type == TRCOT_BINARY || properties.cond_type == TRCOT_ALL) { + middle_sel->SetDisplayedPlane(DPM_COMPARATOR); + this->EnableWidget(TR_WIDGET_COMPARATOR); + + const TraceRestrictDropDownListSet *list_set = GetCondOpDropDownListSet(properties); + + if (list_set) { + this->GetWidget(TR_WIDGET_COMPARATOR)->widget_data = + GetDropDownStringByValue(list_set, GetTraceRestrictCondOp(item)); + } + } + + if (IsIntegerValueType(properties.value_type)) { + right_sel->SetDisplayedPlane(DPR_VALUE_INT); + this->EnableWidget(TR_WIDGET_VALUE_INT); + } else { + switch (properties.value_type) { + case TRVT_DENY: + right_sel->SetDisplayedPlane(DPR_VALUE_DROPDOWN); + this->EnableWidget(TR_WIDGET_VALUE_DROPDOWN); + this->GetWidget(TR_WIDGET_VALUE_DROPDOWN)->widget_data = + GetTraceRestrictValue(item) ? STR_TRACE_RESTRICT_PF_ALLOW : STR_TRACE_RESTRICT_PF_DENY; + break; + + case TRVT_ORDER: + right_sel->SetDisplayedPlane(DPR_VALUE_DEST); + this->EnableWidget(TR_WIDGET_VALUE_DEST); + break; + + case TRVT_CARGO_ID: + right_sel->SetDisplayedPlane(DPR_VALUE_DROPDOWN); + this->EnableWidget(TR_WIDGET_VALUE_DROPDOWN); + this->GetWidget(TR_WIDGET_VALUE_DROPDOWN)->widget_data = + GetCargoStringByID(GetTraceRestrictValue(item)); + break; + + case TRVT_DIRECTION: + right_sel->SetDisplayedPlane(DPR_VALUE_DROPDOWN); + this->EnableWidget(TR_WIDGET_VALUE_DROPDOWN); + this->GetWidget(TR_WIDGET_VALUE_DROPDOWN)->widget_data = + GetDropDownStringByValue(&_direction_value, GetTraceRestrictValue(item)); + break; + + default: + break; + } + } + + this->EnableWidget(TR_WIDGET_INSERT); + this->EnableWidget(TR_WIDGET_REMOVE); + } + } + + this->SetDirty(); + } + + /** + * Show a drop down list using @p list_set, setting the pre-selected item to the one corresponding to @p value + * This asserts if @p value is not in @p list_set, and @p missing_ok is false + */ + void ShowDropDownListWithValue(const TraceRestrictDropDownListSet *list_set, uint value, bool missing_ok, + int button, uint32 disabled_mask, uint32 hidden_mask, uint width) + { + drop_down_list_mapping[button] = list_set; + int selected = GetDropDownListIndexByValue(list_set, value, missing_ok); + ShowDropDownMenu(this, list_set->string_array, selected, button, disabled_mask, hidden_mask, width); + } + + /** + * Helper function to set or unset a SetObjectToPlaceWnd, for the given widget and cursor type + */ + void SetObjectToPlaceAction(int widget, CursorID cursor) + { + this->ToggleWidgetLoweredState(widget); + this->SetWidgetDirty(widget); + if (this->IsWidgetLowered(widget)) { + SetObjectToPlaceWnd(cursor, PAL_NONE, HT_RECT, this); + this->current_placement_widget = widget; + } else { + ResetObjectToPlace(); + this->current_placement_widget = -1; + } + } + + /** + * This used for testing whether else or else-if blocks could be inserted, or replace the selection + * If @p replace is true, replace selection with @p item, else insert @p item before selection + * Returns true if resulting instruction list passes validation + */ + bool GenericElseInsertionDryRun(TraceRestrictItem item, bool replace) + { + if (this->selected_instruction < 1) return false; + uint offset = this->selected_instruction - 1; + + const TraceRestrictProgram *prog = this->GetProgram(); + if (!prog) return false; + + std::vector items = prog->items; // copy + + if (offset >= (items.size() + (replace ? 0 : 1))) return false; // off the end of the program + + if (replace) { + items[offset] = item; + } else { + items.insert(items.begin() + offset, item); + } + + return TraceRestrictProgram::Validate(items).Succeeded(); + } + + /** + * Run GenericElseInsertionDryRun with an else instruction + */ + bool ElseInsertionDryRun(bool replace) + { + TraceRestrictItem item = 0; + SetTraceRestrictType(item, TRIT_COND_ENDIF); + SetTraceRestrictCondFlags(item, TRCF_ELSE); + return GenericElseInsertionDryRun(item, replace); + } + + /** + * Run GenericElseInsertionDryRun with an elif instruction + */ + bool ElseIfInsertionDryRun(bool replace) + { + TraceRestrictItem item = 0; + SetTraceRestrictType(item, TRIT_COND_UNDEFINED); + SetTraceRestrictCondFlags(item, TRCF_ELSE); + return GenericElseInsertionDryRun(item, replace); + } +}; + +static const NWidgetPart _nested_program_widgets[] = { + // Title bar + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, TR_WIDGET_CAPTION), SetDataTip(STR_TRACE_RESTRICT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + + // Program display + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, TR_WIDGET_INSTRUCTION_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_TRACE_RESTRICT_INSTRUCTION_LIST_TOOLTIP), SetResize(1, 1), EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, TR_WIDGET_SCROLLBAR), + EndContainer(), + + // Button Bar + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(NWID_SELECTION, INVALID_COLOUR, TR_WIDGET_SEL_TOP_LEFT_2), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TR_WIDGET_TYPE_NONCOND), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_TRACE_RESTRICT_TYPE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TR_WIDGET_CONDFLAGS), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_TRACE_RESTRICT_CONDFLAGS_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_BLANK_L2), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_EMPTY, STR_NULL), SetResize(1, 0), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, TR_WIDGET_SEL_TOP_LEFT), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TR_WIDGET_TYPE_COND), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_TRACE_RESTRICT_TYPE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_BLANK_L), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_EMPTY, STR_NULL), SetResize(1, 0), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, TR_WIDGET_SEL_TOP_MIDDLE), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TR_WIDGET_COMPARATOR), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_TRACE_RESTRICT_COND_COMPARATOR_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_BLANK_M), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_EMPTY, STR_NULL), SetResize(1, 0), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, TR_WIDGET_SEL_TOP_RIGHT), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_VALUE_INT), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_BLACK_COMMA, STR_TRACE_RESTRICT_COND_VALUE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TR_WIDGET_VALUE_DROPDOWN), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_TRACE_RESTRICT_COND_VALUE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_VALUE_DEST), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_SELECT_TARGET, STR_TRACE_RESTRICT_SELECT_TARGET), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_BLANK_R), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_EMPTY, STR_NULL), SetResize(1, 0), + EndContainer(), + EndContainer(), + NWidget(WWT_IMGBTN, COLOUR_GREY, TR_WIDGET_GOTO_SIGNAL), SetMinimalSize(12, 12), SetDataTip(SPR_ARROW_RIGHT, STR_TRACE_RESTRICT_GOTO_SIGNAL_TOOLTIP), + EndContainer(), + + /* Second button row. */ + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TR_WIDGET_INSERT), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_INSERT, STR_TRACE_RESTRICT_INSERT_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_REMOVE), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_REMOVE, STR_TRACE_RESTRICT_REMOVE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_RESET), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_RESET, STR_TRACE_RESTRICT_RESET_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_COPY), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_COPY, STR_TRACE_RESTRICT_COPY_TOOLTIP), SetResize(1, 0), + NWidget(NWID_SELECTION, INVALID_COLOUR, TR_WIDGET_SEL_SHARE), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_SHARE), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_SHARE, STR_TRACE_RESTRICT_SHARE_TOOLTIP), SetResize(1, 0), + NWidget(WWT_TEXTBTN, COLOUR_GREY, TR_WIDGET_UNSHARE), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_UNSHARE, STR_TRACE_RESTRICT_UNSHARE_TOOLTIP), SetResize(1, 0), + EndContainer(), + EndContainer(), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), +}; + +static WindowDesc _program_desc( + WDP_AUTO, "trace_restrict_gui", 384, 100, + WC_TRACE_RESTRICT, WC_BUILD_SIGNAL, + WDF_CONSTRUCTION, + _nested_program_widgets, lengthof(_nested_program_widgets) +); + +/** + * Show or create program window for given @p tile and @p track + */ +void ShowTraceRestrictProgramWindow(TileIndex tile, Track track) +{ + if (BringWindowToFrontById(WC_TRACE_RESTRICT, MakeTraceRestrictRefId(tile, track)) != NULL) { + return; + } + + new TraceRestrictWindow(&_program_desc, tile, track); +} diff --git a/src/waypoint.cpp b/src/waypoint.cpp index 857f8ba874..3e5f02f3c7 100644 --- a/src/waypoint.cpp +++ b/src/waypoint.cpp @@ -15,6 +15,7 @@ #include "window_func.h" #include "newgrf_station.h" #include "waypoint_base.h" +#include "tracerestrict.h" #include "safeguards.h" @@ -54,4 +55,5 @@ Waypoint::~Waypoint() if (CleaningPool()) return; DeleteWindowById(WC_WAYPOINT_VIEW, this->index); RemoveOrderFromAllVehicles(OT_GOTO_WAYPOINT, this->index); + TraceRestrictRemoveDestinationID(TROCAF_WAYPOINT, this->index); } diff --git a/src/widgets/rail_widget.h b/src/widgets/rail_widget.h index f977f44803..fa271b0d95 100644 --- a/src/widgets/rail_widget.h +++ b/src/widgets/rail_widget.h @@ -91,6 +91,7 @@ enum BuildSignalWidgets { WID_BS_ELECTRIC_PBS, ///< Build an electric path signal. WID_BS_ELECTRIC_PBS_OWAY, ///< Build an electric one way path signal. WID_BS_CONVERT, ///< Convert the signal. + WID_BS_TRACE_RESTRICT, ///< Open trace restrict window. WID_BS_DRAG_SIGNALS_DENSITY_LABEL, ///< The current signal density. WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, ///< Decrease the signal density. WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, ///< Increase the signal density. diff --git a/src/window_type.h b/src/window_type.h index 809e81d485..48491e6601 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -681,6 +681,12 @@ enum WindowClass { */ WC_SAVE_PRESET, + /** + * Trace restrict programme window; %Window numbers: + * - #TileIndex << 3 | #Track = #TraceRestrictWindow + */ + WC_TRACE_RESTRICT, + WC_INVALID = 0xFFFF, ///< Invalid window. };