diff --git a/source.list b/source.list index df35cdd26e..db128d271b 100644 --- a/source.list +++ b/source.list @@ -1193,3 +1193,8 @@ thread/thread.h #else thread/thread_none.cpp #end + +tracerestrict.h +tracerestrict.cpp +tracerestrict_gui.cpp +saveload/tracerestrict_sl.cpp diff --git a/src/command.cpp b/src/command.cpp index 959610cd28..96d88f3854 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -198,6 +198,8 @@ CommandProc CmdSetTimetableStart; CommandProc CmdOpenCloseAirport; +CommandProc CmdProgramSignalTraceRestrict; + #define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type} /** @@ -354,6 +356,8 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdSetTimetableStart, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SET_TIMETABLE_START DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT + + DEF_CMD(CmdProgramSignalTraceRestrict, 0, CMDT_OTHER_MANAGEMENT ), // CMD_PROGRAM_TRACERESTRICT_SIGNAL }; /*! diff --git a/src/command_type.h b/src/command_type.h index f318216acc..1f99797ada 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -329,6 +329,8 @@ enum Commands { CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft + CMD_PROGRAM_TRACERESTRICT_SIGNAL, ///< modify a signal tracerestrict program + CMD_END, ///< Must ALWAYS be on the end of this list!! (period) }; diff --git a/src/lang/english.txt b/src/lang/english.txt index ad29b3d596..8b690d96d9 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2370,6 +2370,54 @@ 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_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_UNDEFINED :undefined +STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_INTEGER :{STRING} {STRING} {STRING} {COMMA} 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_VALUE_CAPTION :{WHITE}Value +STR_TRACE_RESTRICT_CAPTION :{WHITE}Routefinding restriction +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_GOTO_SIGNAL_TOOLTIP :{BLACK}Go to signal +STR_TRACE_RESTRICT_INSERT :{BLACK}Insert +STR_TRACE_RESTRICT_REMOVE :{BLACK}Remove +STR_TRACE_RESTRICT_INSERT_TOOLTIP :{BLACK}Insert an instruction +STR_TRACE_RESTRICT_REMOVE_TOOLTIP :{BLACK}Remove the selected instruction +STR_TRACE_RESTRICT_SIGNAL_GUI_TOOLTIP :{BLACK}Routefinding restriction +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 + # Bridge selection window STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Select Rail Bridge STR_SELECT_ROAD_BRIDGE_CAPTION :{WHITE}Select Road Bridge 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/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..81c362ae31 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(), 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..29efc63de0 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) { 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..a42c84f784 100644 --- a/src/rail_map.h +++ b/src/rail_map.h @@ -479,6 +479,18 @@ static inline bool HasOnewaySignalBlockingTrackdir(TileIndex tile, Trackdir td) !HasSignalOnTrackdir(tile, td) && IsOnewaySignal(tile, TrackdirToTrack(td)); } +static inline bool IsRestrictedSignal(TileIndex t) +{ + assert(GetRailTileType(t) == RAIL_TILE_SIGNALS); + return (bool) GB(_m[t].m2, 12, 1); +} + +static inline void SetRestrictedSignal(TileIndex t, bool is_restricted) +{ + assert(GetRailTileType(t) == RAIL_TILE_SIGNALS); + SB(_m[t].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/saveload.cpp b/src/saveload/saveload.cpp index bd3c83d139..3c239def5b 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -262,8 +262,9 @@ * 192 26700 * 193 26802 * 194 26881 1.5.x + * 2000 Trace restrict patch */ -extern const uint16 SAVEGAME_VERSION = 194; ///< Current savegame version of OpenTTD. +extern const uint16 SAVEGAME_VERSION = 2000; ///< Current savegame version of OpenTTD. SavegameType _savegame_type; ///< type of savegame we are loading @@ -447,6 +448,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[] = { @@ -483,6 +485,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..a646c3a72a --- /dev/null +++ b/src/saveload/tracerestrict_sl.cpp @@ -0,0 +1,92 @@ +/* $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() +}; + +static void Load_TRRM() +{ + int index; + while ((index = SlIterateArray()) != -1) { + TraceRestrictMappingItem &item = _tracerestrictprogram_mapping[index]; + SlObject(&item, _trace_restrict_mapping_desc); + } +} + +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); + } +} + +struct TraceRestrictProgramStub { + uint32 length; +}; + +static const SaveLoad _trace_restrict_program_stub_desc[] = { + SLE_VAR(TraceRestrictProgramStub, length, SLE_UINT32), + SLE_END() +}; + +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()); + } +} + +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); +} + +static void Save_TRRP() +{ + TraceRestrictProgram *prog; + + FOR_ALL_TRACE_RESTRICT_PROGRAMS(prog) { + SlSetArrayIndex(prog->index); + SlAutolength((AutolengthProc*) RealSave_TRRP, prog); + } +} + +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}, + { 'TRRP', Save_TRRP, Load_TRRP, NULL, NULL, CH_ARRAY | CH_LAST}, +}; 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/tracerestrict.cpp b/src/tracerestrict.cpp new file mode 100644 index 0000000000..74b182b4f0 --- /dev/null +++ b/src/tracerestrict.cpp @@ -0,0 +1,510 @@ +/* + * 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 Restriction. */ + +#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 "pathfinder/yapf/yapf_cache.h" +#include + +/** Initialize theprogram pool */ +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(); +} + +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) + +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; + } +} + +/// 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; + } +} + +/// Execute program on train and store results in out +void TraceRestrictProgram::Execute(const Train* v, 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; + + 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()); +} + +void TraceRestrictProgram::DecrementRefCount() { + assert(this->refcount > 0); + this->refcount--; + if (this->refcount == 0) { + delete this; + } +} + +/// 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(); +} + +void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueType value_type) +{ + switch (value_type) { + case TRVT_NONE: + case TRVT_INT: + case TRVT_DENY: + SetTraceRestrictValue(item, 0); + break; + + default: + NOT_REACHED(); + break; + } +} + +/// Set the type field of a TraceRestrictItem, and +/// reset 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); + } +} + +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); +} + +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); +} + +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 trace restrict program for the tile/track ref ID identified by @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 items associated with it +void TraceRestrictNotifySignalRemoval(TileIndex tile, Track track) +{ + TraceRestrictRefId ref = MakeTraceRestrictRefId(tile, track); + TraceRestrictRemoveProgramMapping(ref); + DeleteWindowById(WC_TRACE_RESTRICT, ref); +} + +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)); +} + +/** + * The main command for editing a signal tracerestrict program. + * @param tile The tile which contains the signal. + * @param flags Internal command handler stuff. + * @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) +{ + Track track = static_cast(GB(p1, 0, 3)); + TraceRestrictDoCommandType type = static_cast(GB(p1, 3, 5)); + uint32 offset = GB(p1, 8, 16); + TraceRestrictItem item = static_cast(p2); + + // Check tile ownership + CommandCost ret = CheckTileOwnership(tile); + if (ret.Failed()) { + return ret; + } + + // 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); + } + + 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(); +} diff --git a/src/tracerestrict.h b/src/tracerestrict.h new file mode 100644 index 0000000000..1309220669 --- /dev/null +++ b/src/tracerestrict.h @@ -0,0 +1,284 @@ +/* + * 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 Restriction. */ + +#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; + +/** Unique identifiers for a trace restrict nodes. */ +typedef uint32 TraceRestrictProgramID; +struct TraceRestrictProgram; + +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) + +struct TraceRestrictMappingItem { + TraceRestrictProgramID program_id; + + TraceRestrictMappingItem() { } + + TraceRestrictMappingItem(TraceRestrictProgramID program_id_) + : program_id(program_id_) { } +}; + +typedef std::map TraceRestrictMapping; +extern TraceRestrictMapping _tracerestrictprogram_mapping; + +void ClearTraceRestrictMapping(); + +/// Of the fields below, the type and cond flags seem the most likely +/// to need future expansion, hence the reserved bits are placed +/// immediately after them +enum TraceRestrictItemFlagAllocation { + TRIFA_TYPE_COUNT = 5, + TRIFA_TYPE_OFFSET = 0, + + /* 3 bits reserved for future use */ + + TRIFA_COND_FLAGS_COUNT = 2, + TRIFA_COND_FLAGS_OFFSET = 8, + + /* 3 bits reserved for future use */ + + TRIFA_COND_OP_COUNT = 3, + TRIFA_COND_OP_OFFSET = 13, + + TRIFA_VALUE_COUNT = 16, + TRIFA_VALUE_OFFSET = 16, +}; + +enum TraceRestrictItemType { + TRIT_NULL = 0, + TRIT_PF_DENY = 1, + TRIT_PF_PENALTY = 2, + + TRIT_COND_BEGIN = 8, ///< Start of conditional item types + 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 + /* space up to 31 */ +}; + +/* no flags set indicates end if for TRIT_COND_ENDIF, if otherwise */ +enum TraceRestrictCondFlags { + TRCF_ELSE = 1 << 0, + TRCF_OR = 1 << 1, +}; +DECLARE_ENUM_AS_BIT_SET(TraceRestrictCondFlags) + +enum TraceRestictNullTypeSpecialValue { + TRNTSV_NULL = 0, + TRNTSV_START = 1, + TRNTSV_END = 2, +}; + +enum TraceRestrictCondOp { + TRCO_IS = 0, + TRCO_ISNOT = 1, + TRCO_LT = 2, + TRCO_LTE = 3, + TRCO_GT = 4, + TRCO_GTE = 5, + /* space up to 7 */ +}; + +enum TraceRestrictProgramResultFlags { + TRPRF_DENY = 1 << 0, +}; +DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramResultFlags) + +struct TraceRestrictProgramResult { + uint32 penalty; + TraceRestrictProgramResultFlags flags; + + TraceRestrictProgramResult() + : penalty(0), flags(static_cast(0)) { } +}; + +typedef uint32 TraceRestrictItem; + +struct TraceRestrictProgram : TraceRestrictProgramPool::PoolItem<&_tracerestrictprogram_pool> { + std::vector items; + uint32 refcount; + + TraceRestrictProgram() + : refcount(0) { } + + void Execute(const Train *v, TraceRestrictProgramResult &out) const; + + void IncrementRefCount() { refcount++; } + + void DecrementRefCount(); + + static CommandCost Validate(const std::vector &items); + + CommandCost Validate() const { return TraceRestrictProgram::Validate(items); } +}; + +static inline TraceRestrictItemType GetTraceRestrictType(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_TYPE_OFFSET, TRIFA_TYPE_COUNT)); +} + +static inline TraceRestrictCondFlags GetTraceRestrictCondFlags(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_COND_FLAGS_OFFSET, TRIFA_COND_FLAGS_COUNT)); +} + +static inline TraceRestrictCondOp GetTraceRestrictCondOp(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_COND_OP_OFFSET, TRIFA_COND_OP_COUNT)); +} + +static inline uint16 GetTraceRestrictValue(TraceRestrictItem item) +{ + return static_cast(GB(item, TRIFA_VALUE_OFFSET, TRIFA_VALUE_COUNT)); +} + +static inline void SetTraceRestrictType(TraceRestrictItem &item, TraceRestrictItemType type) +{ + SB(item, TRIFA_TYPE_OFFSET, TRIFA_TYPE_COUNT, type); +} + +static inline void SetTraceRestrictCondOp(TraceRestrictItem &item, TraceRestrictCondOp condop) +{ + SB(item, TRIFA_COND_OP_OFFSET, TRIFA_COND_OP_COUNT, condop); +} + +void SetTraceRestrictTypeAndNormalise(TraceRestrictItem &item, TraceRestrictItemType type); + +static inline void SetTraceRestrictValue(TraceRestrictItem &item, uint16 value) +{ + SB(item, TRIFA_VALUE_OFFSET, TRIFA_VALUE_COUNT, value); +} + +static inline bool IsTraceRestrictTypeConditional(TraceRestrictItemType type) +{ + return type >= TRIT_COND_BEGIN; +} + +static inline bool IsTraceRestrictConditional(TraceRestrictItem item) +{ + return IsTraceRestrictTypeConditional(GetTraceRestrictType(item)); +} + +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) +}; + +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 integer value + TRVT_DENY = 3, ///< takes a value 0 = deny, 1 = allow (cancel previous deny) +}; + +struct TraceRestrictTypePropertySet { + TraceRestrictConditionOpType cond_type; + TraceRestrictValueType value_type; +}; + +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; + out.value_type = TRVT_INT; + } 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; +} + +static inline TraceRestrictRefId MakeTraceRestrictRefId(TileIndex t, Track track) +{ + return (t << 3) | track; +} + +static inline TileIndex GetTraceRestrictRefIdTileIndex(TraceRestrictRefId ref) +{ + return static_cast(ref >> 3); +} + +static inline Track GetTraceRestrictRefIdTrack(TraceRestrictRefId ref) +{ + return static_cast(ref & 7); +} + +void TraceRestrictCreateProgramMapping(TraceRestrictRefId ref, TraceRestrictProgram *prog); +void TraceRestrictRemoveProgramMapping(TraceRestrictRefId ref); + +/// Gets the signal program for the tile identified by @p t and @p track. +/// An empty program will be constructed if none exists, and create_new is true +/// unless the pool is full +TraceRestrictProgram *GetTraceRestrictProgram(TraceRestrictRefId ref, bool create_new); + +/// Notify that a signal is being removed +/// Remove any trace restrict items associated with it +void TraceRestrictNotifySignalRemoval(TileIndex tile, Track track); + +/// Gets the 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; + } +} + +enum TraceRestrictDoCommandType { + TRDCT_INSERT_ITEM = 0, + TRDCT_MODIFY_ITEM = 1, + TRDCT_REMOVE_ITEM = 2, +}; + +void TraceRestrictDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, uint32 offset, uint32 value, StringID error_msg); + +CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text); + +void ShowTraceRestrictProgramWindow(TileIndex tile, Track track); + +#endif /* TRACERESTRICT_H */ diff --git a/src/tracerestrict_gui.cpp b/src/tracerestrict_gui.cpp new file mode 100644 index 0000000000..1cfd46ccc5 --- /dev/null +++ b/src/tracerestrict_gui.cpp @@ -0,0 +1,809 @@ +/* $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 related to signal tracerestrict */ + +#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 "widgets/dropdown_func.h" +#include "gui.h" +#include "gfx_func.h" +#include "rail_map.h" +#include "tile_cmd.h" +#include "error.h" +#include "table/sprites.h" + +enum TraceRestrictWindowWidgets { + TR_WIDGET_CAPTION, + TR_WIDGET_INSTRUCTION_LIST, + TR_WIDGET_SCROLLBAR, + + TR_WIDGET_SEL_TOP_LEFT, + TR_WIDGET_SEL_TOP_MIDDLE, + TR_WIDGET_SEL_TOP_RIGHT, + + TR_WIDGET_TYPE, + TR_WIDGET_COMPARATOR, + TR_WIDGET_VALUE_INT, + TR_WIDGET_VALUE_DROPDOWN, + + TR_WIDGET_BLANK_L, + TR_WIDGET_BLANK_M, + TR_WIDGET_BLANK_R, + + TR_WIDGET_GOTO_SIGNAL, + TR_WIDGET_INSERT, + TR_WIDGET_REMOVE, +}; + +enum PanelWidgets { + // Left + DPL_TYPE = 0, + DPL_BLANK, + + // Middle + DPM_COMPARATOR = 0, + DPM_BLANK, + + // Right + DPR_VALUE_INT = 0, + DPR_VALUE_DROPDOWN, + DPR_BLANK, +}; + +/// 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_PF_DENY, + STR_TRACE_RESTRICT_PF_PENALTY, + INVALID_STRING_ID +}; +static const uint _program_insert_val[] = { + TRIT_COND_UNDEFINED, + TRIT_PF_DENY, + TRIT_PF_PENALTY, +}; + +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, +}; + +static const TraceRestrictDropDownListSet _deny_value = { + _deny_value_str, _deny_value_val, +}; + +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; +} + +static StringID GetDropDownStringByValue(const TraceRestrictDropDownListSet *list_set, uint value) +{ + return list_set->string_array[GetDropDownListIndexByValue(list_set, value, false)]; +} + +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_UNDEFINED, + INVALID_STRING_ID, + }; + static const uint val_cond[] = { + TRIT_COND_TRAIN_LENGTH, + TRIT_COND_UNDEFINED, + }; + static const TraceRestrictDropDownListSet set_cond = { + str_cond, val_cond, + }; + + return IsTraceRestrictTypeConditional(type) ? &set_cond : &set_action; +} + +static StringID GetTypeString(TraceRestrictItemType type) +{ + return GetDropDownStringByValue(GetTypeDropDownListSet(type), type); +} + +static const TraceRestrictDropDownListSet *GetCondOpDropDownListSet(TraceRestrictConditionOpType type) +{ + 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, + }; + + switch (type) { + case TRCOT_NONE: + return NULL; + + case TRCOT_BINARY: + return &set_short; + + case TRCOT_ALL: + return &set_long; + } + NOT_REACHED(); + return NULL; +} + +static const StringID _program_cond_type[] = { + /* 0 */ STR_TRACE_RESTRICT_CONDITIONAL_IF, + /* TRCF_ELSE */ STR_TRACE_RESTRICT_CONDITIONAL_ELIF, + /* TRCF_OR */ STR_TRACE_RESTRICT_CONDITIONAL_ORIF, +}; + +/** + * 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 if (properties.value_type == TRVT_INT) { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_COMPARE_INTEGER; + + assert(GetTraceRestrictCondFlags(item) <= TRCF_OR); + SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]); + SetDParam(1, GetTypeString(GetTraceRestrictType(item))); + SetDParam(2, GetDropDownStringByValue(GetCondOpDropDownListSet(properties.cond_type), GetTraceRestrictCondOp(item))); + SetDParam(3, GetTraceRestrictValue(item)); + } else { + NOT_REACHED(); + } + } 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); +} + +class TraceRestrictWindow: public Window { + TileIndex tile; + Track track; + int selected_instruction; // NB: this is offset by one due to the display of the "start" item + Scrollbar *vscroll; + std::map drop_down_list_mapping; + TraceRestrictItem expecting_inserted_item; + +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->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) + { + switch (widget) { + case TR_WIDGET_INSTRUCTION_LIST: { + int sel = this->GetInstructionFromPt(pt.y); + + 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; + } + this->ShowDropDownListWithValue(&_program_insert, 0, true, TR_WIDGET_INSERT, 0, 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_TYPE: { + TraceRestrictItem item = this->GetSelected(); + TraceRestrictItemType type = GetTraceRestrictType(item); + + if (type != TRIT_NULL) { + this->ShowDropDownListWithValue(GetTypeDropDownListSet(type), type, false, TR_WIDGET_TYPE, 0, 0, 0); + } + break; + } + + case TR_WIDGET_COMPARATOR: { + TraceRestrictItem item = this->GetSelected(); + const TraceRestrictDropDownListSet *list_set = GetCondOpDropDownListSet(GetTraceRestrictTypeProperties(item).cond_type); + 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(); + if (GetTraceRestrictTypeProperties(item).value_type == TRVT_INT) { + SetDParam(0, GetTraceRestrictValue(item)); + ShowQueryString(STR_JUST_INT, STR_TRACE_RESTRICT_VALUE_CAPTION, 6, this, CS_NUMERAL, QSF_NONE); // 5 digit num, + terminating null + } + break; + } + + case TR_WIDGET_VALUE_DROPDOWN: { + TraceRestrictItem item = this->GetSelected(); + if (GetTraceRestrictTypeProperties(item).value_type == TRVT_DENY) { + this->ShowDropDownListWithValue(&_deny_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); + } + break; + } + + case TR_WIDGET_GOTO_SIGNAL: + ScrollMainWindowToTile(this->tile); + break; + } + } + + virtual void OnQueryTextFinished(char *str) + { + if (StrEmpty(str)) { + return; + } + + TraceRestrictItem item = GetSelected(); + if (GetTraceRestrictTypeProperties(item).value_type != TRVT_INT) { + return; + } + + uint value = atoi(str); + if (value >= (1 << TRIFA_VALUE_COUNT)) { + SetDParam(0, (1 << TRIFA_VALUE_COUNT) - 1); + 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) + { + 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; + SetTraceRestrictTypeAndNormalise(insert_item, static_cast(value)); + 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_TYPE: { + SetTraceRestrictTypeAndNormalise(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_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 UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + 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() + { + /* Update the scroll bar */ + this->vscroll->SetCapacityFromWidget(this, TR_WIDGET_INSTRUCTION_LIST); + } + + virtual void OnPaint() + { + this->DrawWidgets(); + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + 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) { + if (gui_scope) { + this->ReloadProgramme(); + } + } + + + virtual void SetStringParameters(int widget) const + { + switch (widget) { + case TR_WIDGET_VALUE_INT: { + SetDParam(0, 0); + + TraceRestrictItem item = this->GetSelected(); + if (GetTraceRestrictTypeProperties(item).value_type == TRVT_INT) { + SetDParam(0, GetTraceRestrictValue(item)); + } + } break; + } + } + +private: + TraceRestrictItem MakeSpecialItem(TraceRestictNullTypeSpecialValue value) const + { + TraceRestrictItem item = 0; + SetTraceRestrictType(item, TRIT_NULL); + SetTraceRestrictValue(item, value); + return item; + } + + int GetItemCount(const TraceRestrictProgram *prog) const + { + if (prog) { + return 2 + prog->items.size(); + } else { + return 2; + } + } + + /// This may return NULL if no program currently exists + const TraceRestrictProgram *GetProgram() const + { + return GetTraceRestrictProgram(MakeTraceRestrictRefId(tile, track), false); + } + + /// 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; + } + } + } + + TraceRestrictItem GetSelected() const + { + return this->GetItem(this->GetProgram(), this->selected_instruction); + } + + Owner GetOwner() + { + return GetTileOwner(tile); + } + + int GetInstructionFromPt(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; + } + + 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(); + } + + void UpdateButtonState() + { + this->RaiseWidget(TR_WIDGET_INSERT); + this->RaiseWidget(TR_WIDGET_REMOVE); + this->RaiseWidget(TR_WIDGET_TYPE); + this->RaiseWidget(TR_WIDGET_COMPARATOR); + this->RaiseWidget(TR_WIDGET_VALUE_INT); + this->RaiseWidget(TR_WIDGET_VALUE_DROPDOWN); + + 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); + + this->DisableWidget(TR_WIDGET_TYPE); + this->DisableWidget(TR_WIDGET_COMPARATOR); + this->DisableWidget(TR_WIDGET_VALUE_INT); + this->DisableWidget(TR_WIDGET_VALUE_DROPDOWN); + + this->DisableWidget(TR_WIDGET_INSERT); + this->DisableWidget(TR_WIDGET_REMOVE); + + this->DisableWidget(TR_WIDGET_BLANK_L); + this->DisableWidget(TR_WIDGET_BLANK_M); + this->DisableWidget(TR_WIDGET_BLANK_R); + + left_sel->SetDisplayedPlane(DPL_BLANK); + middle_sel->SetDisplayedPlane(DPM_BLANK); + right_sel->SetDisplayedPlane(DPR_BLANK); + + // Don't allow modifications if don't own, or have selected invalid instruction + if (this->GetOwner() != _local_company || this->selected_instruction < 1) { + this->SetDirty(); + return; + } + + TraceRestrictItem item = this->GetSelected(); + 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, enable removing + this->EnableWidget(TR_WIDGET_REMOVE); + } + } else { + TraceRestrictTypePropertySet properties = GetTraceRestrictTypeProperties(item); + + left_sel->SetDisplayedPlane(DPL_TYPE); + this->EnableWidget(TR_WIDGET_TYPE); + + this->GetWidget(TR_WIDGET_TYPE)->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.cond_type); + + if (list_set) { + this->GetWidget(TR_WIDGET_COMPARATOR)->widget_data = + GetDropDownStringByValue(list_set, GetTraceRestrictCondOp(item)); + } + } + + if (properties.value_type == TRVT_INT) { + right_sel->SetDisplayedPlane(DPR_VALUE_INT); + this->EnableWidget(TR_WIDGET_VALUE_INT); + } else if (properties.value_type == 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; + } + + this->EnableWidget(TR_WIDGET_INSERT); + this->EnableWidget(TR_WIDGET_REMOVE); + } + } + + this->SetDirty(); + } + + 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); + } +}; + +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_SIGNAL_GUI_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), + NWidget(WWT_DROPDOWN, COLOUR_GREY, TR_WIDGET_TYPE), 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_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(186, 12), SetFill(1, 0), + SetDataTip(STR_TRACE_RESTRICT_REMOVE, STR_TRACE_RESTRICT_REMOVE_TOOLTIP), SetResize(1, 0), + 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) +); + +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/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. };