diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 06ef881b0b..2904dbc7b7 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -18,6 +18,7 @@ #include "autoreplace_func.h" #include "autoreplace_gui.h" #include "articulated_vehicles.h" +#include "tracerestrict.h" #include "core/random_func.hpp" #include "table/strings.h" @@ -567,6 +568,11 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon /* Success ! */ if ((flags & DC_EXEC) != 0 && new_head != old_head) { *chain = new_head; + if (HasBit(Train::From(old_head)->flags, VRF_HAVE_SLOT)) { + TraceRestrictTransferVehicleOccupantInAllSlots(old_head->index, new_head->index); + ClrBit(Train::From(old_head)->flags, VRF_HAVE_SLOT); + SetBit(Train::From(new_head)->flags, VRF_HAVE_SLOT); + } } /* Transfer cargo of old vehicles and sell them */ diff --git a/src/command.cpp b/src/command.cpp index 6dd49ef6dc..fcd8d09a68 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -228,6 +228,11 @@ CommandProc CmdSetTimetableStart; CommandProc CmdOpenCloseAirport; CommandProc CmdProgramSignalTraceRestrict; +CommandProc CmdCreateTraceRestrictSlot; +CommandProc CmdAlterTraceRestrictSlot; +CommandProc CmdDeleteTraceRestrictSlot; +CommandProc CmdAddVehicleTraceRestrictSlot; +CommandProc CmdRemoveVehicleTraceRestrictSlot; CommandProc CmdInsertSignalInstruction; CommandProc CmdModifySignalInstruction; @@ -427,6 +432,11 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_OPEN_CLOSE_AIRPORT DEF_CMD(CmdProgramSignalTraceRestrict, 0, CMDT_OTHER_MANAGEMENT ), // CMD_PROGRAM_TRACERESTRICT_SIGNAL + DEF_CMD(CmdCreateTraceRestrictSlot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_TRACERESTRICT_SLOT + DEF_CMD(CmdAlterTraceRestrictSlot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_ALTER_TRACERESTRICT_SLOT + DEF_CMD(CmdDeleteTraceRestrictSlot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_DELETE_TRACERESTRICT_SLOT + DEF_CMD(CmdAddVehicleTraceRestrictSlot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_ADD_VEHICLE_TRACERESTRICT_SLOT + DEF_CMD(CmdRemoveVehicleTraceRestrictSlot, 0, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_VEHICLE_TRACERESTRICT_SLOT DEF_CMD(CmdInsertSignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_INSERT_SIGNAL_INSTRUCTION DEF_CMD(CmdModifySignalInstruction, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_MODIFY_SIGNAL_INSTRUCTION diff --git a/src/command_type.h b/src/command_type.h index ccb924b1f6..f657741f83 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -371,6 +371,11 @@ enum Commands { CMD_OPEN_CLOSE_AIRPORT, ///< open/close an airport to incoming aircraft CMD_PROGRAM_TRACERESTRICT_SIGNAL, ///< modify a signal tracerestrict program + CMD_CREATE_TRACERESTRICT_SLOT, ///< create a tracerestrict slot + CMD_ALTER_TRACERESTRICT_SLOT, ///< alter a tracerestrict slot + CMD_DELETE_TRACERESTRICT_SLOT, ///< delete a tracerestrict slot + CMD_ADD_VEHICLE_TRACERESTRICT_SLOT, ///< add a vehicle to a tracerestrict slot + CMD_REMOVE_VEHICLE_TRACERESTRICT_SLOT, ///< remove a vehicle from a tracerestrict slot CMD_INSERT_SIGNAL_INSTRUCTION, ///< insert a signal instruction CMD_MODIFY_SIGNAL_INSTRUCTION, ///< modifies a signal instruction diff --git a/src/group_gui.cpp b/src/group_gui.cpp index 382ea8b52b..f7ede02fb9 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -531,16 +531,16 @@ public: this->vscroll->SetCount(this->vehicles.Length()); /* The drop down menu is out, *but* it may not be used, retract it. */ - if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_GL_MANAGE_VEHICLES_DROPDOWN)) { + if (!this->ShouldShowActionDropdownList() && this->IsWidgetLowered(WID_GL_MANAGE_VEHICLES_DROPDOWN)) { this->RaiseWidget(WID_GL_MANAGE_VEHICLES_DROPDOWN); HideDropDownMenu(this); } /* Disable all lists management button when the list is empty */ + this->SetWidgetDisabledState(WID_GL_MANAGE_VEHICLES_DROPDOWN, !this->ShouldShowActionDropdownList() || _local_company != this->vli.company); this->SetWidgetsDisabledState(this->vehicles.Length() == 0 || _local_company != this->vli.company, WID_GL_STOP_ALL, WID_GL_START_ALL, - WID_GL_MANAGE_VEHICLES_DROPDOWN, WIDGET_LIST_END); /* Disable the group specific function when we select the default group or all vehicles */ @@ -802,7 +802,7 @@ public: case WID_GL_MANAGE_VEHICLES_DROPDOWN: { DropDownList *list = this->BuildActionDropdownList(true, Group::IsValidID(this->vli.index), this->vli.vtype == VEH_TRAIN); - ShowDropDownList(this, list, 0, WID_GL_MANAGE_VEHICLES_DROPDOWN); + ShowDropDownList(this, list, -1, WID_GL_MANAGE_VEHICLES_DROPDOWN); break; } @@ -928,7 +928,7 @@ public: break; case WID_GL_MANAGE_VEHICLES_DROPDOWN: - assert(this->vehicles.Length() != 0); + assert(this->ShouldShowActionDropdownList()); switch (index) { case ADI_TEMPLATE_REPLACE: // TemplateReplace Window @@ -955,6 +955,13 @@ public: DoCommandP(0, this->vli.index, 0, CMD_REMOVE_ALL_VEHICLES_GROUP | CMD_MSG(STR_ERROR_GROUP_CAN_T_REMOVE_ALL_VEHICLES)); break; + + case ADI_TRACERESTRICT_SLOT_MGMT: { + extern void ShowTraceRestrictSlotWindow(CompanyID company); + ShowTraceRestrictSlotWindow(this->owner); + break; + } + default: NOT_REACHED(); } break; diff --git a/src/lang/english.txt b/src/lang/english.txt index fbcbafb8ba..d5442b4fba 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1314,6 +1314,8 @@ STR_CONFIG_SETTING_SHOW_TRAIN_WEIGHT_RATIOS_IN_DETAILS :Show train weig STR_CONFIG_SETTING_SHOW_TRAIN_WEIGHT_RATIOS_IN_DETAILS_HELPTEXT :Show train weight ratios in the vehicle details window STR_CONFIG_SETTING_SHOW_RESTRICTED_SIG_DEF :Show restricted electric signals using default graphics: {STRING2} STR_CONFIG_SETTING_SHOW_RESTRICTED_SIG_DEF_HELPTEXT :Show electric signals with routing restriction programs using the default signal graphics with a blue signal post, instead of using any NewGRF signal graphics. This is to make it easier to visually distinguish restricted signals. +STR_CONFIG_SETTING_SHOW_ADV_TRACE_RESTRICT_FEATURES :Show advanced routing restriction features: {STRING2} +STR_CONFIG_SETTING_SHOW_ADV_TRACE_RESTRICT_FEATURES_HELPTEXT :Show advanced routing restriction features. When disabled, some advanced features are not shown in the UI, but are still available to all players. STR_CONFIG_SETTING_LANDSCAPE :Landscape: {STRING2} STR_CONFIG_SETTING_LANDSCAPE_HELPTEXT :Landscapes define basic gameplay scenarios with different cargos and town growth requirements. NewGRF and Game Scripts allow finer control though @@ -2554,6 +2556,7 @@ STR_TRACE_RESTRICT_VARIABLE_ENTRY_DIRECTION :entry direction STR_TRACE_RESTRICT_VARIABLE_PBS_ENTRY_SIGNAL :PBS entry signal STR_TRACE_RESTRICT_VARIABLE_PBS_ENTRY_SIGNAL_LONG :entered signal of PBS block STR_TRACE_RESTRICT_VARIABLE_TRAIN_GROUP :train group +STR_TRACE_RESTRICT_VARIABLE_TRAIN_SLOT :slot STR_TRACE_RESTRICT_VARIABLE_TRAIN_WEIGHT :weight STR_TRACE_RESTRICT_VARIABLE_TRAIN_POWER :power STR_TRACE_RESTRICT_VARIABLE_TRAIN_MAX_TE :max T.E. @@ -2579,6 +2582,8 @@ STR_TRACE_RESTRICT_CONDITIONAL_TILE_INDEX :{STRING} {STRIN STR_TRACE_RESTRICT_CONDITIONAL_GROUP :{STRING} train {STRING} in group: {GROUP} then STR_TRACE_RESTRICT_CONDITIONAL_GROUP_STR :{STRING} train {STRING} in group: {STRING} {BLACK}{STRING}then STR_TRACE_RESTRICT_CONDITIONAL_OWNER :{STRING} {STRING} {STRING} {COMPANY} {COMPANY_NUM} then +STR_TRACE_RESTRICT_CONDITIONAL_SLOT :{STRING} train {STRING} in slot: {TRSLOT} then +STR_TRACE_RESTRICT_CONDITIONAL_SLOT_STR :{STRING} train {STRING} in slot: {STRING} {BLACK}{STRING}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} @@ -2593,6 +2598,8 @@ STR_TRACE_RESTRICT_RESERVE_THROUGH :Reserve through STR_TRACE_RESTRICT_RESERVE_THROUGH_CANCEL :Cancel reserve through STR_TRACE_RESTRICT_LONG_RESERVE :Long reserve STR_TRACE_RESTRICT_LONG_RESERVE_CANCEL :Cancel long reserve +STR_TRACE_RESTRICT_WAIT_AT_PBS :Wait at PBS signal +STR_TRACE_RESTRICT_WAIT_AT_PBS_CANCEL :Cancel wait at PBS signal STR_TRACE_RESTRICT_PF_PENALTY :Penalty STR_TRACE_RESTRICT_PF_VALUE_SMALL :small STR_TRACE_RESTRICT_PF_VALUE_MEDIUM :medium @@ -2606,6 +2613,18 @@ STR_TRACE_RESTRICT_DIRECTION_SW :south-west STR_TRACE_RESTRICT_DIRECTION_NW :north-west STR_TRACE_RESTRICT_COMPANY :Company STR_TRACE_RESTRICT_UNDEFINED_COMPANY :Undefined company +STR_TRACE_RESTRICT_SLOT_OP :Slot operation +STR_TRACE_RESTRICT_SLOT_ACQUIRE_WAIT :Acquire or wait +STR_TRACE_RESTRICT_SLOT_TRY_ACQUIRE :Try to acquire +STR_TRACE_RESTRICT_SLOT_RELEASE_FRONT :Release (front) +STR_TRACE_RESTRICT_SLOT_RELEASE_BACK :Release (back) +STR_TRACE_RESTRICT_SLOT_ACQUIRE_WAIT_ITEM :Acquire slot: {STRING1}{BLACK}{STRING}, or wait at PBS signal +STR_TRACE_RESTRICT_SLOT_TRY_ACQUIRE_ITEM :Try to acquire slot: {STRING1}{BLACK}{STRING}, or continue anyway +STR_TRACE_RESTRICT_SLOT_RELEASE_FRONT_ITEM :Release slot: {STRING1}{BLACK}{STRING} (front of train) +STR_TRACE_RESTRICT_SLOT_RELEASE_BACK_ITEM :Release slot: {STRING1}{BLACK}{STRING} (back of train) +STR_TRACE_RESTRICT_SLOT_NAME :{TRSLOT} +STR_TRACE_RESTRICT_SLOT_LIST_HEADER :{BLACK}Slot{CONSUME_ARG}{P "" s}: {LTBLUE} +STR_TRACE_RESTRICT_SLOT_LIST_SEPARATOR :{BLACK}, {LTBLUE} 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 @@ -2616,6 +2635,20 @@ STR_TRACE_RESTRICT_COND_COMPARATOR_TOOLTIP :{BLACK}Comparis 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_SLOT_OP_TOOLTIP :{BLACK}Slot operation type +STR_TRACE_RESTRICT_SLOT_GUI_LIST_TOOLTIP :{BLACK}Slots - click on a slot to list all vehicles of this slot. +STR_TRACE_RESTRICT_SLOT_CREATE_TOOLTIP :{BLACK}Click to create a slot +STR_TRACE_RESTRICT_SLOT_DELETE_TOOLTIP :{BLACK}Delete the selected slot +STR_TRACE_RESTRICT_SLOT_RENAME_TOOLTIP :{BLACK}Rename the selected slot +STR_TRACE_RESTRICT_SLOT_SET_MAX_OCCUPANCY_TOOLTIP :{BLACK}Set the maximum occupancy of the selected slot +STR_TRACE_RESTRICT_SLOT_CAPTION :{WHITE}Routing Restrictions - Slot Management +STR_TRACE_RESTRICT_SLOT_MANAGE :Manage slots +STR_TRACE_RESTRICT_SLOT_MAX_OCCUPANCY :{TINY_FONT}{COMMA} / {COMMA} +STR_TRACE_RESTRICT_SLOT_RENAME_CAPTION :{BLACK}Rename a slot +STR_TRACE_RESTRICT_SLOT_CREATE_CAPTION :{BLACK}Create a slot +STR_TRACE_RESTRICT_SLOT_SET_MAX_OCCUPANCY_CAPTION :{BLACK}Set maximum occupancy of a slot +STR_TRACE_RESTRICT_SLOT_QUERY_DELETE_CAPTION :{WHITE}Delete Slot +STR_TRACE_RESTRICT_SLOT_DELETE_QUERY_TEXT :{WHITE}Are you sure you want to delete this slot? STR_TRACE_RESTRICT_INSERT :{BLACK}Insert STR_TRACE_RESTRICT_REMOVE :{BLACK}Remove STR_TRACE_RESTRICT_RESET :{BLACK}Reset @@ -2652,6 +2685,12 @@ STR_TRACE_RESTRICT_ERROR_CAN_T_RESET_SIGNAL :{WHITE}Can't re 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 +STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_CREATE :{WHITE}Can't create slot... +STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_DELETE :{WHITE}Can't delete this slot... +STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_RENAME :{WHITE}Can't rename slot... +STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_ADD_VEHICLE :{WHITE}Can't add the vehicle to this slot... +STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_REMOVE_VEHICLE :{WHITE}Can't remove the vehicle from this slot... +STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_SET_MAX_OCCUPANCY :{WHITE}Can't set maximum occupancy of this slot... # Programmable Signals STR_PROGRAM_SIGNAL_TOOLTIP :{BLACK}Program signal @@ -4161,6 +4200,7 @@ STR_VEHICLE_STATUS_BROKEN_DOWN_VEL_SHORT :{RED}Broken dow STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL :{RED}Stopping, {VELOCITY} STR_VEHICLE_STATUS_TRAIN_NO_POWER :{RED}No power STR_VEHICLE_STATUS_TRAIN_STUCK :{ORANGE}Waiting for free path +STR_VEHICLE_STATUS_TRAIN_STUCK_WAIT_RESTRICTION :{ORANGE}Waiting due to routing restriction STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR :{ORANGE}Too far to next destination STR_BREAKDOWN_TYPE_CRITICAL :Mechanical failure diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 5b11a856f4..f5c0264126 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -47,7 +47,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, 6, 6, "tracerestrict", NULL, NULL, "TRRM,TRRP" }, + { XSLFI_TRACE_RESTRICT, XSCF_NULL, 7, 7, "tracerestrict", NULL, NULL, "TRRM,TRRP,TRRS" }, { XSLFI_TRACE_RESTRICT_OWNER, XSCF_NULL, 1, 1, "tracerestrict_owner", NULL, NULL, NULL }, { XSLFI_PROG_SIGS, XSCF_NULL, 1, 1, "programmable_signals", NULL, NULL, "SPRG" }, { XSLFI_ADJACENT_CROSSINGS, XSCF_NULL, 1, 1, "adjacent_crossings", NULL, NULL, NULL }, @@ -178,6 +178,12 @@ void SlXvCheckSpecialSavegameVersions() _sl_is_faked_ext = true; _sl_xv_feature_versions[XSLFI_TRACE_RESTRICT] = 6; } + if (_sl_version == 2002) { + DEBUG(sl, 1, "Loading a trace restrict patch savegame version %d as version 196", _sl_version); + _sl_version = 196; + _sl_is_faked_ext = true; + _sl_xv_feature_versions[XSLFI_TRACE_RESTRICT] = 6; + } if (_sl_version == 220) { /* SL_SPRING_2013_v2_0_102 */ DEBUG(sl, 1, "Loading a SpringPP 2013 v2.0.102 savegame version %d as version 187", _sl_version); diff --git a/src/saveload/tracerestrict_sl.cpp b/src/saveload/tracerestrict_sl.cpp index 8e983a78b3..f29aacb468 100644 --- a/src/saveload/tracerestrict_sl.cpp +++ b/src/saveload/tracerestrict_sl.cpp @@ -108,6 +108,65 @@ static void Save_TRRP() } } +/** program length save header struct */ +struct TraceRestrictSlotStub { + uint32 length; +}; + +static const SaveLoad _trace_restrict_slot_stub_desc[] = { + SLE_VAR(TraceRestrictSlotStub, length, SLE_UINT32), + SLE_END() +}; + +static const SaveLoad _trace_restrict_slot_desc[] = { + SLE_VAR(TraceRestrictSlot, max_occupancy, SLE_UINT32), + SLE_STDSTR(TraceRestrictSlot, name, SLF_ALLOW_CONTROL), + SLE_VAR(TraceRestrictSlot, owner, SLE_UINT8), + SLE_END() +}; + +/** + * Load slot pool + */ +static void Load_TRRS() +{ + int index; + TraceRestrictSlotStub stub; + while ((index = SlIterateArray()) != -1) { + TraceRestrictSlot *slot = new (index) TraceRestrictSlot(); + SlObject(slot, _trace_restrict_slot_desc); + SlObject(&stub, _trace_restrict_slot_stub_desc); + slot->occupants.resize(stub.length); + if (stub.length) SlArray(&(slot->occupants[0]), stub.length, SLE_UINT32); + } + TraceRestrictSlot::RebuildVehicleIndex(); +} + +/** + * Save a slot, used by SlAutolength + */ +static void RealSave_TRRS(TraceRestrictSlot *slot) +{ + SlObject(slot, _trace_restrict_slot_desc); + TraceRestrictSlotStub stub; + stub.length = slot->occupants.size(); + SlObject(&stub, _trace_restrict_slot_stub_desc); + if (stub.length) SlArray(&(slot->occupants[0]), stub.length, SLE_UINT32); +} + +/** + * Save slot pool + */ +static void Save_TRRS() +{ + TraceRestrictSlot *slot; + + FOR_ALL_TRACE_RESTRICT_SLOTS(slot) { + SlSetArrayIndex(slot->index); + SlAutolength((AutolengthProc*) RealSave_TRRS, slot); + } +} + /** * Update program reference counts from just-loaded mapping */ @@ -121,5 +180,6 @@ void AfterLoadTraceRestrict() 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 + { 'TRRP', Save_TRRP, Load_TRRP, NULL, NULL, CH_ARRAY}, // Trace Restrict Mapping Program Pool chunk + { 'TRRS', Save_TRRS, Load_TRRS, NULL, NULL, CH_ARRAY | CH_LAST}, // Trace Restrict Slot Pool chunk }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index b402260b8f..7fcfa6addf 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1598,6 +1598,7 @@ static SettingsContainer &GetSettingsTree() interface->Add(new SettingEntry("gui.show_train_weight_ratios_in_details")); interface->Add(new SettingEntry("gui.show_vehicle_group_in_details")); interface->Add(new SettingEntry("gui.show_vehicle_list_company_colour")); + interface->Add(new SettingEntry("gui.show_adv_tracerestrict_features")); } SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS)); diff --git a/src/settings_type.h b/src/settings_type.h index 6185c00446..8138aa83b5 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -172,6 +172,7 @@ struct GUISettings { bool show_train_weight_ratios_in_details; ///< show train weight ratios in vehicle details window top widget bool show_vehicle_group_in_details; ///< show vehicle group in vehicle details window top widget bool show_restricted_signal_default; ///< Show restricted electric signals using the default sprite + bool show_adv_tracerestrict_features; ///< Show advanced trace restrict features in UI uint8 osk_activation; ///< Mouse gesture to trigger the OSK. bool show_vehicle_route_steps; ///< when a window related to a specific vehicle is focused, show route steps bool show_vehicle_list_company_colour; ///< show the company colour of vehicles which have an owner different to the owner of the vehicle list diff --git a/src/strings.cpp b/src/strings.cpp index 46b5fd8e8c..9cf8c83334 100644 --- a/src/strings.cpp +++ b/src/strings.cpp @@ -35,6 +35,7 @@ #include "window_func.h" #include "debug.h" #include "unit_conversion.h" +#include "tracerestrict.h" #include "game/game_text.hpp" #ifdef ENABLE_NETWORK # include "network/network_content_gui.h" @@ -1742,11 +1743,24 @@ static char *FormatString(char *buff, const char *str_arg, StringParameters *arg break; } + case SCC_TR_SLOT_NAME: { // {TRSLOT} + const TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(args->GetInt32(SCC_TR_SLOT_NAME)); + if (slot == NULL) break; + int64 args_array[] = {(int64)(size_t)slot->name.c_str()}; + StringParameters tmp_params(args_array); + buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last); + break; + } + case SCC_STATION_FEATURES: { // {STATIONFEATURES} buff = StationGetSpecialString(buff, args->GetInt32(SCC_STATION_FEATURES), last); break; } + case SCC_CONSUME_ARG: + // do nothing + break; + default: if (buff + Utf8CharLen(b) < last) buff += Utf8Encode(buff, b); break; diff --git a/src/table/control_codes.h b/src/table/control_codes.h index 0cb3f370b8..646a260ada 100644 --- a/src/table/control_codes.h +++ b/src/table/control_codes.h @@ -45,6 +45,7 @@ enum StringControlCode { SCC_COMPANY_NAME, SCC_PRESIDENT_NAME, SCC_ENGINE_NAME, + SCC_TR_SLOT_NAME, SCC_CURRENCY_SHORT, SCC_CURRENCY_LONG, @@ -121,6 +122,8 @@ enum StringControlCode { SCC_BLACK, SCC_PREVIOUS_COLOUR, + SCC_CONSUME_ARG, + /** * The next variables are part of a NewGRF subsystem for creating text strings. * It uses a "stack" of bytes and reads from there. diff --git a/src/table/settings.ini b/src/table/settings.ini index 8a2285178d..3a7f8ca67d 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -4029,6 +4029,15 @@ str = STR_CONFIG_SETTING_SHOW_RESTRICTED_SIG_DEF strhelp = STR_CONFIG_SETTING_SHOW_RESTRICTED_SIG_DEF_HELPTEXT proc = RedrawScreen +[SDTC_BOOL] +var = gui.show_adv_tracerestrict_features +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = false +str = STR_CONFIG_SETTING_SHOW_ADV_TRACE_RESTRICT_FEATURES +strhelp = STR_CONFIG_SETTING_SHOW_ADV_TRACE_RESTRICT_FEATURES_HELPTEXT +proc = RedrawScreen +cat = SC_EXPERT + ; For the dedicated build we'll enable dates in logs by default. [SDTC_BOOL] ifdef = DEDICATED diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h index 63532de1d7..bb5d8d17e0 100644 --- a/src/table/strgen_tables.h +++ b/src/table/strgen_tables.h @@ -124,6 +124,7 @@ static const CmdStruct _cmd_structs[] = { {"COMPANY", EmitSingleChar, SCC_COMPANY_NAME, 1, -1, C_NONE | C_GENDER}, {"COMPANY_NUM", EmitSingleChar, SCC_COMPANY_NUM, 1, -1, C_NONE}, {"PRESIDENT_NAME", EmitSingleChar, SCC_PRESIDENT_NAME, 1, -1, C_NONE | C_GENDER}, + {"TRSLOT", EmitSingleChar, SCC_TR_SLOT_NAME, 1, -1, C_NONE | C_GENDER}, {"", EmitSingleChar, '\n', 0, -1, C_DONTCOUNT}, {"{", EmitSingleChar, '{', 0, -1, C_DONTCOUNT}, @@ -144,6 +145,8 @@ static const CmdStruct _cmd_structs[] = { {"SMALL_LEFT_ARROW", EmitSingleChar, SCC_LESS_THAN, 0, -1, C_DONTCOUNT}, {"SMALL_RIGHT_ARROW", EmitSingleChar, SCC_GREATER_THAN, 0, -1, C_DONTCOUNT}, + {"CONSUME_ARG", EmitSingleChar, SCC_CONSUME_ARG, 1, 0, C_NONE}, + /* The following are directional formatting codes used to get the RTL strings right: * http://www.unicode.org/unicode/reports/tr9/#Directional_Formatting_Codes */ {"LRM", EmitSingleChar, CHAR_TD_LRM, 0, -1, C_DONTCOUNT}, diff --git a/src/tracerestrict.cpp b/src/tracerestrict.cpp index 17c0ff3694..aed18cf1f2 100644 --- a/src/tracerestrict.cpp +++ b/src/tracerestrict.cpp @@ -19,6 +19,7 @@ #include "order_base.h" #include "cargotype.h" #include "group.h" +#include "string_func.h" #include "pathfinder/yapf/yapf_cache.h" #include #include @@ -60,6 +61,9 @@ TraceRestrictProgramPool _tracerestrictprogram_pool("TraceRestrictProgram"); INSTANTIATE_POOL_METHODS(TraceRestrictProgram) +TraceRestrictSlotPool _tracerestrictslot_pool("TraceRestrictSlot"); +INSTANTIATE_POOL_METHODS(TraceRestrictSlot) + /** * TraceRestrictRefId --> TraceRestrictProgramID (Pool ID) mapping * The indirection is mainly to enable shared programs @@ -350,6 +354,12 @@ void TraceRestrictProgram::Execute(const Train* v, const TraceRestrictProgramInp break; } + case TRIT_COND_SLOT: { + const TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item)); + result = TestBinaryConditionCommon(item, slot != NULL && slot->IsOccupant(v->index)); + break; + } + case TRIT_COND_PHYS_PROP: { switch (static_cast(GetTraceRestrictAuxField(item))) { case TRPPCAF_WEIGHT: @@ -443,6 +453,44 @@ void TraceRestrictProgram::Execute(const Train* v, const TraceRestrictProgramInp } break; + case TRIT_WAIT_AT_PBS: + if (GetTraceRestrictValue(item)) { + out.flags &= ~TRPRF_WAIT_AT_PBS; + } else { + out.flags |= TRPRF_WAIT_AT_PBS; + } + break; + + case TRIT_SLOT: { + if (!input.permitted_slot_operations) break; + TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item)); + if (slot == NULL) break; + switch (static_cast(GetTraceRestrictCondOp(item))) { + case TRSCOF_ACQUIRE_WAIT: + if (input.permitted_slot_operations & TRPISP_ACQUIRE) { + if (!slot->Occupy(v->index)) out.flags |= TRPRF_WAIT_AT_PBS; + } + break; + + case TRSCOF_ACQUIRE_TRY: + if (input.permitted_slot_operations & TRPISP_ACQUIRE) slot->Occupy(v->index); + break; + + case TRSCOF_RELEASE_BACK: + if (input.permitted_slot_operations & TRPISP_RELEASE_BACK) slot->Vacate(v->index); + break; + + case TRSCOF_RELEASE_FRONT: + if (input.permitted_slot_operations & TRPISP_RELEASE_FRONT) slot->Vacate(v->index); + break; + + default: + NOT_REACHED(); + break; + } + break; + } + default: NOT_REACHED(); } @@ -530,6 +578,7 @@ CommandCost TraceRestrictProgram::Validate(const std::vector case TRIT_COND_ENTRY_DIRECTION: case TRIT_COND_PBS_ENTRY_SIGNAL: case TRIT_COND_TRAIN_GROUP: + case TRIT_COND_SLOT: case TRIT_COND_PHYS_PROP: case TRIT_COND_PHYS_RATIO: case TRIT_COND_TRAIN_OWNER: @@ -553,12 +602,40 @@ CommandCost TraceRestrictProgram::Validate(const std::vector actions_used_flags |= TRPAUF_LONG_RESERVE; break; + case TRIT_WAIT_AT_PBS: + actions_used_flags |= TRPAUF_WAIT_AT_PBS; + break; + + case TRIT_SLOT: + switch (static_cast(GetTraceRestrictCondOp(item))) { + case TRSCOF_ACQUIRE_WAIT: + actions_used_flags |= TRPAUF_SLOT_ACQUIRE | TRPAUF_WAIT_AT_PBS; + break; + + case TRSCOF_ACQUIRE_TRY: + actions_used_flags |= TRPAUF_SLOT_ACQUIRE; + break; + + case TRSCOF_RELEASE_BACK: + actions_used_flags |= TRPAUF_SLOT_RELEASE_BACK; + break; + + case TRSCOF_RELEASE_FRONT: + actions_used_flags |= TRPAUF_SLOT_RELEASE_FRONT; + break; + + default: + NOT_REACHED(); + break; + } + break; + default: return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION); } } } - if(!condstack.empty()) { + if (!condstack.empty()) { return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK); } return CommandCost(); @@ -611,6 +688,7 @@ void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueTyp case TRVT_FORCE: case TRVT_POWER_WEIGHT_RATIO: case TRVT_FORCE_WEIGHT_RATIO: + case TRVT_WAIT_AT_PBS: SetTraceRestrictValue(item, 0); if (!IsTraceRestrictTypeAuxSubtype(GetTraceRestrictType(item))) { SetTraceRestrictAuxField(item, 0); @@ -648,6 +726,11 @@ void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueTyp SetTraceRestrictAuxField(item, 0); break; + case TRVT_SLOT_INDEX: + SetTraceRestrictValue(item, INVALID_TRACE_RESTRICT_SLOT_ID); + SetTraceRestrictAuxField(item, 0); + break; + default: NOT_REACHED(); break; @@ -1320,3 +1403,307 @@ void TraceRestrictUpdateCompanyID(CompanyID old_company, CompanyID new_company) // update windows InvalidateWindowClassesData(WC_TRACE_RESTRICT); } + +static std::unordered_multimap slot_vehicle_index; + +/** + * Add vehicle ID to occupants if possible and not already an occupant + * @param id Vehicle ID + * @param force Add the vehicle even if the slot is at/over capacity + * @return whether vehicle ID is now an occupant + */ +bool TraceRestrictSlot::Occupy(VehicleID id, bool force) +{ + if (this->IsOccupant(id)) return true; + if (this->occupants.size() >= this->max_occupancy && !force) return false; + this->occupants.push_back(id); + slot_vehicle_index.emplace(id, this->index); + SetBit(Train::Get(id)->flags, VRF_HAVE_SLOT); + SetWindowDirty(WC_VEHICLE_DETAILS, id); + InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS); + return true; +} + +/** + * Remove vehicle ID from occupants + * @param id Vehicle ID + */ +void TraceRestrictSlot::Vacate(VehicleID id) +{ + if (container_unordered_remove(this->occupants, id)) { + this->DeIndex(id); + } +} + +/** Remove all occupants */ +void TraceRestrictSlot::Clear() +{ + for (VehicleID id : this->occupants) { + this->DeIndex(id); + } + this->occupants.clear(); +} + +void TraceRestrictSlot::DeIndex(VehicleID id) +{ + auto range = slot_vehicle_index.equal_range(id); + for (auto it = range.first; it != range.second; ++it) { + if (it->second == this->index) { + auto next = slot_vehicle_index.erase(it); + if (it == range.first && next == range.second) { + /* Only one item, which we've just erased, clear the vehicle flag */ + ClrBit(Train::Get(id)->flags, VRF_HAVE_SLOT); + } + break; + } + } + SetWindowDirty(WC_VEHICLE_DETAILS, id); + InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS); +} + +/** Rebuild slot vehicle index after loading */ +void TraceRestrictSlot::RebuildVehicleIndex() +{ + slot_vehicle_index.clear(); + const TraceRestrictSlot *slot; + FOR_ALL_TRACE_RESTRICT_SLOTS(slot) { + for (VehicleID id : slot->occupants) { + slot_vehicle_index.emplace(id, slot->index); + } + } +} + +/** Slot pool is about to be cleared */ +void TraceRestrictSlot::PreCleanPool() +{ + slot_vehicle_index.clear(); +} + +/** Remove vehicle ID from all slot occupants */ +void TraceRestrictRemoveVehicleFromAllSlots(VehicleID id) +{ + auto range = slot_vehicle_index.equal_range(id); + for (auto it = range.first; it != range.second; ++it) { + TraceRestrictSlot *slot = TraceRestrictSlot::Get(it->second); + container_unordered_remove(slot->occupants, id); + } + slot_vehicle_index.erase(range.first, range.second); + if (range.first != range.second) InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS); +} + +/** Replace all instance of a vehicle ID with another, in all slot occupants */ +void TraceRestrictTransferVehicleOccupantInAllSlots(VehicleID from, VehicleID to) +{ + auto range = slot_vehicle_index.equal_range(from); + std::vector slots; + for (auto it = range.first; it != range.second; ++it) { + slots.push_back(it->second); + } + slot_vehicle_index.erase(range.first, range.second); + for (TraceRestrictSlotID slot_id : slots) { + TraceRestrictSlot *slot = TraceRestrictSlot::Get(slot_id); + for (VehicleID &id : slot->occupants) { + if (id == from) { + id = to; + slot_vehicle_index.emplace(to, slot_id); + } + } + } + if (!slots.empty()) InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS); +} + +/** Get list of slots occupied by a vehicle ID */ +void TraceRestrictGetVehicleSlots(VehicleID id, std::vector &out) +{ + auto range = slot_vehicle_index.equal_range(id); + for (auto it = range.first; it != range.second; ++it) { + out.push_back(it->second); + } +} + +/** + * This is called when a slot is about to be deleted + * Scan program pool and change any references to it to the invalid group ID, to avoid dangling references + */ +void TraceRestrictRemoveSlotID(TraceRestrictSlotID 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_SLOT || GetTraceRestrictType(item) == TRIT_COND_SLOT) && GetTraceRestrictValue(item) == index) { + SetTraceRestrictValueDefault(item, TRVT_SLOT_INDEX); // this updates the instruction in-place + } + if (IsTraceRestrictDoubleItem(item)) i++; + } + } + + // update windows + InvalidateWindowClassesData(WC_TRACE_RESTRICT); +} + +static bool IsUniqueSlotName(const char *name) +{ + const TraceRestrictSlot *slot; + FOR_ALL_TRACE_RESTRICT_SLOTS(slot) { + if (slot->name == name) return false; + } + return true; +} + +/** + * Create a new slot. + * @param tile unused + * @param flags type of operation + * @param p1 unused + * @param p2 unused + * @param text new slot name + * @return the cost of this operation or an error + */ +CommandCost CmdCreateTraceRestrictSlot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (!TraceRestrictSlot::CanAllocateItem()) return CMD_ERROR; + if (StrEmpty(text)) return CMD_ERROR; + + size_t length = Utf8StringLength(text); + if (length <= 0) return CMD_ERROR; + if (length >= MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS) return CMD_ERROR; + if (!IsUniqueSlotName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); + + if (flags & DC_EXEC) { + TraceRestrictSlot *slot = new TraceRestrictSlot(_current_company); + slot->name = text; + + // update windows + InvalidateWindowClassesData(WC_TRACE_RESTRICT); + InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS); + } + + return CommandCost(); +} + + +/** + * Deletes a slot. + * @param tile unused + * @param flags type of operation + * @param p1 index of array group + * - p1 bit 0-15 : Slot ID + * @param p2 unused + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdDeleteTraceRestrictSlot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(p1); + if (slot == NULL || slot->owner != _current_company) return CMD_ERROR; + + if (flags & DC_EXEC) { + /* notify tracerestrict that group is about to be deleted */ + TraceRestrictRemoveSlotID(slot->index); + + delete slot; + + InvalidateWindowClassesData(WC_TRACE_RESTRICT); + InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS); + } + + return CommandCost(); +} + +/** + * Alter a slot + * @param tile unused + * @param flags type of operation + * @param p1 index of array group + * - p1 bit 0-15 : GroupID + * - p1 bit 16: 0 - Rename grouop + * 1 - Change max occupancy + * @param p2 new max occupancy + * @param text the new name + * @return the cost of this operation or an error + */ +CommandCost CmdAlterTraceRestrictSlot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(GB(p1, 0, 16)); + if (slot == NULL || slot->owner != _current_company) return CMD_ERROR; + + if (!HasBit(p1, 16)) { + /* Rename slot */ + + if (StrEmpty(text)) return CMD_ERROR; + size_t length = Utf8StringLength(text); + if (length <= 0) return CMD_ERROR; + if (length >= MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS) return CMD_ERROR; + if (!IsUniqueSlotName(text)) return_cmd_error(STR_ERROR_NAME_MUST_BE_UNIQUE); + + if (flags & DC_EXEC) { + slot->name = text; + } + } else { + /* Change max occupancy */ + + if (flags & DC_EXEC) { + slot->max_occupancy = p2; + } + } + + if (flags & DC_EXEC) { + // update windows + InvalidateWindowClassesData(WC_TRACE_RESTRICT); + InvalidateWindowClassesData(WC_TRACE_RESTRICT_SLOTS); + } + + return CommandCost(); +} + +/** + * Add a vehicle to a slot + * @param tile unused + * @param flags type of operation + * @param p1 index of array group + * - p1 bit 0-15 : GroupID + * @param p2 index of vehicle + * - p2 bit 0-19 : VehicleID + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdAddVehicleTraceRestrictSlot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(p1); + Vehicle *v = Vehicle::GetIfValid(p2); + if (slot == NULL || slot->owner != _current_company) return CMD_ERROR; + if (v == NULL || v->owner != _current_company) return CMD_ERROR; + + if (flags & DC_EXEC) { + slot->Occupy(v->index, true); + } + + return CommandCost(); +} + +/** + * Remove a vehicle from a slot + * @param tile unused + * @param flags type of operation + * @param p1 index of array group + * - p1 bit 0-15 : GroupID + * @param p2 index of vehicle + * - p2 bit 0-19 : VehicleID + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdRemoveVehicleTraceRestrictSlot(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(p1); + Vehicle *v = Vehicle::GetIfValid(p2); + if (slot == NULL || slot->owner != _current_company) return CMD_ERROR; + if (v == NULL) return CMD_ERROR; // permit removing vehicles of other owners from your own slot + + if (flags & DC_EXEC) { + slot->Vacate(v->index); + } + + return CommandCost(); +} diff --git a/src/tracerestrict.h b/src/tracerestrict.h index 395ac644d4..7db959d0bf 100644 --- a/src/tracerestrict.h +++ b/src/tracerestrict.h @@ -14,12 +14,16 @@ #include "core/bitmath_func.hpp" #include "core/enum_type.hpp" #include "core/pool_type.hpp" +#include "core/container_func.hpp" #include "command_func.h" #include "rail_map.h" #include "tile_type.h" #include "group_type.h" +#include "vehicle_type.h" #include "3rdparty/cpp-btree/btree_map.h" +#include #include +#include struct Train; @@ -35,6 +39,19 @@ typedef Pool TraceRest /** The actual pool for trace restrict nodes. */ extern TraceRestrictProgramPool _tracerestrictprogram_pool; +/** Slot pool ID type. */ +typedef uint16 TraceRestrictSlotID; +struct TraceRestrictSlot; + +/** Type of the pool for trace restrict slots. */ +typedef Pool TraceRestrictSlotPool; +/** The actual pool for trace restrict nodes. */ +extern TraceRestrictSlotPool _tracerestrictslot_pool; + +static const TraceRestrictSlotID NEW_TRACE_RESTRICT_SLOT_ID = 0xFFFD; // for GUI use only +static const TraceRestrictSlotID ALL_TRAINS_TRACE_RESTRICT_SLOT_ID = 0xFFFE; // for GUI use only +static const TraceRestrictSlotID INVALID_TRACE_RESTRICT_SLOT_ID = 0xFFFF; + extern const uint16 _tracerestrict_pathfinder_penalty_preset_values[]; #define FOR_ALL_TRACE_RESTRICT_PROGRAMS_FROM(var, start) FOR_ALL_ITEMS_FROM(TraceRestrictProgram, tr_index, var, start) @@ -100,6 +117,8 @@ enum TraceRestrictItemType { TRIT_PF_PENALTY = 2, ///< Add to pathfinder penalty TRIT_RESERVE_THROUGH = 3, ///< Reserve through PBS signal TRIT_LONG_RESERVE = 4, ///< Long reserve PBS signal + TRIT_WAIT_AT_PBS = 5, ///< Wait at PBS signal + TRIT_SLOT = 6, ///< Slot operation TRIT_COND_BEGIN = 8, ///< Start of conditional item types, note that this has the same value as TRIT_COND_ENDIF TRIT_COND_ENDIF = 8, ///< This is an endif block or an else block @@ -115,6 +134,7 @@ enum TraceRestrictItemType { TRIT_COND_TRAIN_GROUP = 18, ///< Test train group membership TRIT_COND_PHYS_PROP = 19, ///< Test train physical property TRIT_COND_PHYS_RATIO = 20, ///< Test train physical property ratio + TRIT_COND_SLOT = 21, ///< Test train slot membership TRIT_COND_TRAIN_OWNER = 24, ///< Test train owner /* space up to 31 */ }; @@ -202,6 +222,17 @@ enum TraceRestrictPathfinderPenaltyAuxField { /* space up to 3 */ }; +/** + * TraceRestrictItem repurposed condition operator field, for slot operation type actions + */ +enum TraceRestrictSlotCondOpField { + TRSCOF_ACQUIRE_WAIT = 0, ///< acquire a slot, or wait at the current signal + TRSCOF_ACQUIRE_TRY = 1, ///< try to acquire a slot, or carry on otherwise + TRSCOF_RELEASE_BACK = 2, ///< release a slot (back of train) + TRSCOF_RELEASE_FRONT = 3, ///< release a slot (front of train) + /* space up to 8 */ +}; + /** * TraceRestrictItem pathfinder penalty preset index * This may not be shortened, only lengthened, as preset indexes are stored in save games @@ -220,6 +251,7 @@ enum TraceRestrictProgramResultFlags { TRPRF_DENY = 1 << 0, ///< Pathfinder deny is set TRPRF_RESERVE_THROUGH = 1 << 1, ///< Reserve through is set TRPRF_LONG_RESERVE = 1 << 2, ///< Long reserve is set + TRPRF_WAIT_AT_PBS = 1 << 3, ///< Wait at PBS signal is set }; DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramResultFlags) @@ -230,9 +262,23 @@ enum TraceRestrictProgramActionsUsedFlags { TRPAUF_PF = 1 << 0, ///< Pathfinder deny or penalty are present TRPAUF_RESERVE_THROUGH = 1 << 1, ///< Reserve through action is present TRPAUF_LONG_RESERVE = 1 << 2, ///< Long reserve action is present + TRPAUF_WAIT_AT_PBS = 1 << 3, ///< Wait at PBS signal action is present + TRPAUF_SLOT_ACQUIRE = 1 << 4, ///< Slot acquire action is present + TRPAUF_SLOT_RELEASE_BACK = 1 << 5, ///< Slot release (back) action is present + TRPAUF_SLOT_RELEASE_FRONT = 1 << 6, ///< Slot release (front) action is present }; DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramActionsUsedFlags) +/** + * Enumeration for TraceRestrictProgram::actions_used_flags + */ +enum TraceRestrictProgramInputSlotPermissions { + TRPISP_ACQUIRE = 1 << 0, ///< Slot acquire is permitted + TRPISP_RELEASE_BACK = 1 << 1, ///< Slot release (back) is permitted + TRPISP_RELEASE_FRONT = 1 << 2, ///< Slot release (front) is permitted +}; +DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramInputSlotPermissions) + /** * Execution input of a TraceRestrictProgram */ @@ -243,9 +289,11 @@ struct TraceRestrictProgramInput { Trackdir trackdir; ///< Track direction on tile of restrict signal, for direction testing PreviousSignalProc *previous_signal_callback; ///< Callback to retrieve tile and direction of previous signal, may be NULL const void *previous_signal_ptr; ///< Opaque pointer suitable to be passed to previous_signal_callback + TraceRestrictProgramInputSlotPermissions permitted_slot_operations; ///< Permitted slot operations TraceRestrictProgramInput(TileIndex tile_, Trackdir trackdir_, PreviousSignalProc *previous_signal_callback_, const void *previous_signal_ptr_) - : tile(tile_), trackdir(trackdir_), previous_signal_callback(previous_signal_callback_), previous_signal_ptr(previous_signal_ptr_) { } + : tile(tile_), trackdir(trackdir_), previous_signal_callback(previous_signal_callback_), previous_signal_ptr(previous_signal_ptr_), + permitted_slot_operations(static_cast(0)) { } }; /** @@ -440,6 +488,8 @@ enum TraceRestrictValueType { TRVT_FORCE = 15,///< takes a force TRVT_POWER_WEIGHT_RATIO = 16,///< takes a power / weight ratio, * 100 TRVT_FORCE_WEIGHT_RATIO = 17,///< takes a force / weight ratio, * 100 + TRVT_WAIT_AT_PBS = 18,///< takes a value 0 = wait at PBS signal, 1 = cancel wait at PBS signal + TRVT_SLOT_INDEX = 19,///< takes a TraceRestrictSlotID TRVT_OWNER = 40,///< takes a CompanyID }; @@ -507,6 +557,11 @@ static inline TraceRestrictTypePropertySet GetTraceRestrictTypeProperties(TraceR out.cond_type = TRCOT_BINARY; break; + case TRIT_COND_SLOT: + out.value_type = TRVT_SLOT_INDEX; + out.cond_type = TRCOT_BINARY; + break; + case TRIT_COND_PHYS_PROP: switch (static_cast(GetTraceRestrictAuxField(item))) { case TRPPCAF_WEIGHT: @@ -562,6 +617,10 @@ static inline TraceRestrictTypePropertySet GetTraceRestrictTypeProperties(TraceR out.value_type = TRVT_RESERVE_THROUGH; } else if (GetTraceRestrictType(item) == TRIT_LONG_RESERVE) { out.value_type = TRVT_LONG_RESERVE; + } else if (GetTraceRestrictType(item) == TRIT_WAIT_AT_PBS) { + out.value_type = TRVT_WAIT_AT_PBS; + } else if (GetTraceRestrictType(item) == TRIT_SLOT) { + out.value_type = TRVT_SLOT_INDEX; } else { out.value_type = TRVT_NONE; } @@ -662,5 +721,54 @@ void ShowTraceRestrictProgramWindow(TileIndex tile, Track track); void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type, uint16 index); void TraceRestrictRemoveGroupID(GroupID index); void TraceRestrictUpdateCompanyID(CompanyID old_company, CompanyID new_company); +void TraceRestrictRemoveSlotID(TraceRestrictSlotID index); + +void TraceRestrictRemoveVehicleFromAllSlots(VehicleID id); +void TraceRestrictTransferVehicleOccupantInAllSlots(VehicleID from, VehicleID to); +void TraceRestrictGetVehicleSlots(VehicleID id, std::vector &out); + +static const uint MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS = 128; ///< The maximum length of a slot name in characters including '\0' + +/** + * Slot type, used for slot operations + */ +struct TraceRestrictSlot : TraceRestrictSlotPool::PoolItem<&_tracerestrictslot_pool> { + std::vector occupants; + uint32 max_occupancy = 1; + std::string name; + OwnerByte owner; + + static void RebuildVehicleIndex(); + static void PreCleanPool(); + + TraceRestrictSlot(CompanyID owner = INVALID_COMPANY) + { + this->owner = owner; + } + + ~TraceRestrictSlot() + { + if (!CleaningPool()) this->Clear(); + } + + /** Test whether vehicle ID is already an occupant */ + bool IsOccupant(VehicleID id) const { + for (size_t i = 0; i < occupants.size(); i++) { + if (occupants[i] == id) return true; + } + return false; + } + + bool Occupy(VehicleID id, bool force = false); + void Vacate(VehicleID id); + void Clear(); + + private: + void DeIndex(VehicleID id); +}; + + +#define FOR_ALL_TRACE_RESTRICT_SLOTS_FROM(var, start) FOR_ALL_ITEMS_FROM(TraceRestrictSlot, slot_index, var, start) +#define FOR_ALL_TRACE_RESTRICT_SLOTS(var) FOR_ALL_TRACE_RESTRICT_SLOTS_FROM(var, 0) #endif /* TRACERESTRICT_H */ diff --git a/src/tracerestrict_gui.cpp b/src/tracerestrict_gui.cpp index 117f2a9879..ea90f1f8b1 100644 --- a/src/tracerestrict_gui.cpp +++ b/src/tracerestrict_gui.cpp @@ -38,8 +38,13 @@ #include "sortlist_type.h" #include "group.h" #include "unit_conversion.h" +#include "company_base.h" +#include "vehicle_base.h" +#include "vehicle_gui.h" +#include "vehicle_gui_base.h" #include "table/sprites.h" #include "toolbar_gui.h" +#include "core/geometry_func.hpp" /** Widget IDs */ enum TraceRestrictWindowWidgets { @@ -60,6 +65,7 @@ enum TraceRestrictWindowWidgets { TR_WIDGET_TYPE_NONCOND, TR_WIDGET_CONDFLAGS, TR_WIDGET_COMPARATOR, + TR_WIDGET_SLOT_OP, TR_WIDGET_VALUE_INT, TR_WIDGET_VALUE_DECIMAL, TR_WIDGET_VALUE_DROPDOWN, @@ -93,6 +99,7 @@ enum PanelWidgets { // Middle DPM_COMPARATOR = 0, + DPM_SLOT_OP, DPM_BLANK, // Right @@ -129,11 +136,15 @@ static const StringID _program_insert_str[] = { STR_TRACE_RESTRICT_PF_PENALTY, STR_TRACE_RESTRICT_RESERVE_THROUGH, STR_TRACE_RESTRICT_LONG_RESERVE, + STR_TRACE_RESTRICT_WAIT_AT_PBS, + STR_TRACE_RESTRICT_SLOT_OP, INVALID_STRING_ID }; static const uint32 _program_insert_else_hide_mask = 8; ///< disable bitmask for else static const uint32 _program_insert_or_if_hide_mask = 4; ///< disable bitmask for orif static const uint32 _program_insert_else_if_hide_mask = 2; ///< disable bitmask for elif +static const uint32 _program_wait_pbs_hide_mask = 0x100; ///< disable bitmask for wait at PBS +static const uint32 _program_slot_hide_mask = 0x200; ///< disable bitmask for slot static const uint _program_insert_val[] = { TRIT_COND_UNDEFINED, // if block TRIT_COND_UNDEFINED | (TRCF_ELSE << 16), // elif block @@ -143,6 +154,8 @@ static const uint _program_insert_val[] = { TRIT_PF_PENALTY, // penalty TRIT_RESERVE_THROUGH, // reserve through TRIT_LONG_RESERVE, // long reserve + TRIT_WAIT_AT_PBS, // wait at PBS signal + TRIT_SLOT, // slot operation }; /** insert drop down list strings and values */ @@ -195,6 +208,21 @@ static const TraceRestrictDropDownListSet _long_reserve_value = { _long_reserve_value_str, _long_reserve_value_val, }; +static const StringID _wait_at_pbs_value_str[] = { + STR_TRACE_RESTRICT_WAIT_AT_PBS, + STR_TRACE_RESTRICT_WAIT_AT_PBS_CANCEL, + INVALID_STRING_ID +}; +static const uint _wait_at_pbs_value_val[] = { + 0, + 1, +}; + +/** value drop down list for wait at PBS types strings and values */ +static const TraceRestrictDropDownListSet _wait_at_pbs_value = { + _wait_at_pbs_value_str, _wait_at_pbs_value_val, +}; + static const StringID _direction_value_str[] = { STR_TRACE_RESTRICT_DIRECTION_FRONT, STR_TRACE_RESTRICT_DIRECTION_BACK, @@ -265,13 +293,15 @@ static TraceRestrictItemType ItemTypeFromGuiType(TraceRestrictGuiItemType type) /** * Return the appropriate type dropdown TraceRestrictDropDownListSet for the given item type @p type */ -static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictGuiItemType type) +static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictGuiItemType type, uint32 *hide_mask = nullptr) { static const StringID str_action[] = { STR_TRACE_RESTRICT_PF_DENY, STR_TRACE_RESTRICT_PF_PENALTY, STR_TRACE_RESTRICT_RESERVE_THROUGH, STR_TRACE_RESTRICT_LONG_RESERVE, + STR_TRACE_RESTRICT_WAIT_AT_PBS, + STR_TRACE_RESTRICT_SLOT_OP, INVALID_STRING_ID, }; static const uint val_action[] = { @@ -279,6 +309,8 @@ static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictG TRIT_PF_PENALTY, TRIT_RESERVE_THROUGH, TRIT_LONG_RESERVE, + TRIT_WAIT_AT_PBS, + TRIT_SLOT, }; static const TraceRestrictDropDownListSet set_action = { str_action, val_action, @@ -300,6 +332,7 @@ static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictG STR_TRACE_RESTRICT_VARIABLE_TRAIN_MAX_TE, STR_TRACE_RESTRICT_VARIABLE_TRAIN_POWER_WEIGHT_RATIO, STR_TRACE_RESTRICT_VARIABLE_TRAIN_MAX_TE_WEIGHT_RATIO, + STR_TRACE_RESTRICT_VARIABLE_TRAIN_SLOT, STR_TRACE_RESTRICT_VARIABLE_UNDEFINED, INVALID_STRING_ID, }; @@ -319,13 +352,22 @@ static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictG TRIT_COND_PHYS_PROP | (TRPPCAF_MAX_TE << 16), TRIT_COND_PHYS_RATIO | (TRPPRCAF_POWER_WEIGHT << 16), TRIT_COND_PHYS_RATIO | (TRPPRCAF_MAX_TE_WEIGHT << 16), + TRIT_COND_SLOT, TRIT_COND_UNDEFINED, }; static const TraceRestrictDropDownListSet set_cond = { str_cond, val_cond, }; - return IsTraceRestrictTypeConditional(ItemTypeFromGuiType(type)) ? &set_cond : &set_action; + bool is_conditional = IsTraceRestrictTypeConditional(ItemTypeFromGuiType(type)); + if (hide_mask) { + if (_settings_client.gui.show_adv_tracerestrict_features) { + *hide_mask = 0; + } else { + *hide_mask = is_conditional ? 0x8000 : 0x30; + } + } + return is_conditional ? &set_cond : &set_action; } /** @@ -386,6 +428,47 @@ static DropDownList *GetGroupDropDownList(Owner owner, GroupID group_id, int &se return dlist; } +/** Sort slots by their name */ +static int CDECL SlotNameSorter(const TraceRestrictSlot * const *a, const TraceRestrictSlot * const *b) +{ + int r = strnatcmp((*a)->name.c_str(), (*b)->name.c_str()); // Sort by name (natural sorting). + if (r == 0) return (*a)->index - (*b)->index; + return r; +} + +/** + * Get a DropDownList of the group list + */ +static DropDownList *GetSlotDropDownList(Owner owner, TraceRestrictSlotID slot_id, int &selected) +{ + GUIList list; + + const TraceRestrictSlot *slot; + FOR_ALL_TRACE_RESTRICT_SLOTS(slot) { + if (slot->owner == owner) { + *list.Append() = slot; + } + } + + if (list.Length() == 0) return NULL; + + list.ForceResort(); + list.Sort(&SlotNameSorter); + + DropDownList *dlist = new DropDownList(); + selected = -1; + + for (size_t i = 0; i < list.Length(); ++i) { + const TraceRestrictSlot *s = list[i]; + if (slot_id == s->index) selected = slot_id; + DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_TRACE_RESTRICT_SLOT_NAME, s->index, false); + item->SetParam(0, s->index); + *dlist->Append() = item; + } + + return dlist; +} + static const StringID _cargo_cond_ops_str[] = { STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_EQUALS, STR_TRACE_RESTRICT_CONDITIONAL_COMPARATOR_CARGO_NOT_EQUALS, @@ -400,6 +483,24 @@ static const TraceRestrictDropDownListSet _cargo_cond_ops = { _cargo_cond_ops_str, _cargo_cond_ops_val, }; +static const StringID _slot_op_cond_ops_str[] = { + STR_TRACE_RESTRICT_SLOT_ACQUIRE_WAIT, + STR_TRACE_RESTRICT_SLOT_TRY_ACQUIRE, + STR_TRACE_RESTRICT_SLOT_RELEASE_FRONT, + STR_TRACE_RESTRICT_SLOT_RELEASE_BACK, + INVALID_STRING_ID, +}; +static const uint _slot_op_cond_ops_val[] = { + TRSCOF_ACQUIRE_WAIT, + TRSCOF_ACQUIRE_TRY, + TRSCOF_RELEASE_FRONT, + TRSCOF_RELEASE_BACK, +}; +/** cargo conditional operators dropdown list set */ +static const TraceRestrictDropDownListSet _slot_op_cond_ops = { + _slot_op_cond_ops_str, _slot_op_cond_ops_val, +}; + /** * Get the StringID for a given CargoID @p cargo, or STR_NEWGRF_INVALID_CARGO */ @@ -845,6 +946,19 @@ static void DrawInstructionString(const TraceRestrictProgram *prog, TraceRestric DrawInstructionStringConditionalIntegerCommon(item, properties); break; + case TRVT_SLOT_INDEX: + SetDParam(0, _program_cond_type[GetTraceRestrictCondFlags(item)]); + SetDParam(1, GetDropDownStringByValue(GetCondOpDropDownListSet(properties), GetTraceRestrictCondOp(item))); + if (GetTraceRestrictValue(item) == INVALID_TRACE_RESTRICT_SLOT_ID) { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_SLOT_STR; + SetDParam(2, STR_TRACE_RESTRICT_VARIABLE_UNDEFINED_RED); + SetDParam(3, selected ? STR_TRACE_RESTRICT_WHITE : STR_EMPTY); + } else { + instruction_string = STR_TRACE_RESTRICT_CONDITIONAL_SLOT; + SetDParam(2, GetTraceRestrictValue(item)); + } + break; + default: NOT_REACHED(); break; @@ -900,6 +1014,41 @@ static void DrawInstructionString(const TraceRestrictProgram *prog, TraceRestric instruction_string = GetTraceRestrictValue(item) ? STR_TRACE_RESTRICT_LONG_RESERVE_CANCEL : STR_TRACE_RESTRICT_LONG_RESERVE; break; + case TRIT_WAIT_AT_PBS: + instruction_string = GetTraceRestrictValue(item) ? STR_TRACE_RESTRICT_WAIT_AT_PBS_CANCEL : STR_TRACE_RESTRICT_WAIT_AT_PBS; + break; + + case TRIT_SLOT: + switch (static_cast(GetTraceRestrictCondOp(item))) { + case TRSCOF_ACQUIRE_WAIT: + instruction_string = STR_TRACE_RESTRICT_SLOT_ACQUIRE_WAIT_ITEM; + break; + + case TRSCOF_ACQUIRE_TRY: + instruction_string = STR_TRACE_RESTRICT_SLOT_TRY_ACQUIRE_ITEM; + break; + + case TRSCOF_RELEASE_BACK: + instruction_string = STR_TRACE_RESTRICT_SLOT_RELEASE_BACK_ITEM; + break; + + case TRSCOF_RELEASE_FRONT: + instruction_string = STR_TRACE_RESTRICT_SLOT_RELEASE_FRONT_ITEM; + break; + + default: + NOT_REACHED(); + break; + } + if (GetTraceRestrictValue(item) == INVALID_TRACE_RESTRICT_SLOT_ID) { + SetDParam(0, STR_TRACE_RESTRICT_VARIABLE_UNDEFINED_RED); + } else { + SetDParam(0, STR_TRACE_RESTRICT_SLOT_NAME); + SetDParam(1, GetTraceRestrictValue(item)); + } + SetDParam(2, selected ? STR_TRACE_RESTRICT_WHITE : STR_EMPTY); + break; + default: NOT_REACHED(); break; @@ -999,6 +1148,7 @@ public: } uint32 disabled = _program_insert_or_if_hide_mask; + uint32 hidden = 0; TraceRestrictItem item = this->GetSelected(); if (GetTraceRestrictType(item) == TRIT_COND_ENDIF || (IsTraceRestrictConditional(item) && GetTraceRestrictCondFlags(item) != 0)) { @@ -1019,8 +1169,9 @@ public: if (ElseIfInsertionDryRun(false)) disabled &= ~_program_insert_or_if_hide_mask; } } + if (!_settings_client.gui.show_adv_tracerestrict_features) hidden |= _program_slot_hide_mask | _program_wait_pbs_hide_mask; - this->ShowDropDownListWithValue(&_program_insert, 0, true, TR_WIDGET_INSERT, disabled, 0, 0); + this->ShowDropDownListWithValue(&_program_insert, 0, true, TR_WIDGET_INSERT, disabled, hidden, 0); break; } @@ -1085,7 +1236,9 @@ public: TraceRestrictGuiItemType type = GetItemGuiType(item); if (type != TRIT_NULL) { - this->ShowDropDownListWithValue(GetTypeDropDownListSet(type), type, false, widget, 0, 0, 0); + uint32 hide_mask = 0; + const TraceRestrictDropDownListSet *set = GetTypeDropDownListSet(type, &hide_mask); + this->ShowDropDownListWithValue(set, type, false, widget, 0, hide_mask, 0); } break; } @@ -1099,6 +1252,12 @@ public: break; } + case TR_WIDGET_SLOT_OP: { + TraceRestrictItem item = this->GetSelected(); + this->ShowDropDownListWithValue(&_slot_op_cond_ops, GetTraceRestrictCondOp(item), false, TR_WIDGET_SLOT_OP, 0, 0, 0); + break; + } + case TR_WIDGET_VALUE_INT: { TraceRestrictItem item = this->GetSelected(); TraceRestrictValueType type = GetTraceRestrictTypeProperties(item).value_type; @@ -1152,6 +1311,10 @@ public: this->ShowDropDownListWithValue(&_long_reserve_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); break; + case TRVT_WAIT_AT_PBS: + this->ShowDropDownListWithValue(&_wait_at_pbs_value, GetTraceRestrictValue(item), false, TR_WIDGET_VALUE_DROPDOWN, 0, 0, 0); + break; + case TRVT_GROUP_INDEX: { int selected; DropDownList *dlist = GetGroupDropDownList(this->GetOwner(), GetTraceRestrictValue(item), selected); @@ -1163,6 +1326,13 @@ public: this->ShowCompanyDropDownListWithValue(static_cast(GetTraceRestrictValue(item)), false, TR_WIDGET_VALUE_DROPDOWN); break; + case TRVT_SLOT_INDEX: { + int selected; + DropDownList *dlist = GetSlotDropDownList(this->GetOwner(), GetTraceRestrictValue(item), selected); + if (dlist != NULL) ShowDropDownList(this, dlist, selected, TR_WIDGET_VALUE_DROPDOWN); + break; + } + default: break; } @@ -1179,9 +1349,11 @@ public: break; } - case TR_WIDGET_GOTO_SIGNAL: + case TR_WIDGET_GOTO_SIGNAL: { ScrollMainWindowToTile(this->tile); + this->UpdateButtonState(); break; + } case TR_WIDGET_RESET: { TraceRestrictProgMgmtDoCommandP(tile, track, TRDCT_PROG_RESET, STR_TRACE_RESTRICT_ERROR_CAN_T_RESET_SIGNAL); @@ -1250,11 +1422,14 @@ public: return; } - if (widget == TR_WIDGET_VALUE_DROPDOWN && (this->value_drop_down_is_company || GetTraceRestrictTypeProperties(item).value_type == TRVT_GROUP_INDEX)) { - // this is a special company drop-down or group-index drop-down - SetTraceRestrictValue(item, index); - TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); - return; + if (widget == TR_WIDGET_VALUE_DROPDOWN) { + TraceRestrictTypePropertySet type = GetTraceRestrictTypeProperties(item); + if (this->value_drop_down_is_company || type.value_type == TRVT_GROUP_INDEX || type.value_type == TRVT_SLOT_INDEX) { + // this is a special company drop-down or group/slot-index drop-down + SetTraceRestrictValue(item, index); + TraceRestrictDoCommandP(this->tile, this->track, TRDCT_MODIFY_ITEM, this->selected_instruction - 1, item, STR_TRACE_RESTRICT_ERROR_CAN_T_MODIFY_ITEM); + return; + } } const TraceRestrictDropDownListSet *list_set = this->drop_down_list_mapping[widget]; @@ -1304,7 +1479,8 @@ public: break; } - case TR_WIDGET_COMPARATOR: { + case TR_WIDGET_COMPARATOR: + case TR_WIDGET_SLOT_OP: { 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; @@ -1599,9 +1775,11 @@ public: case TR_WIDGET_VALUE_DROPDOWN: { TraceRestrictItem item = this->GetSelected(); - if ((GetTraceRestrictTypeProperties(item).value_type == TRVT_PF_PENALTY && + TraceRestrictTypePropertySet type = GetTraceRestrictTypeProperties(item); + if ((type.value_type == TRVT_PF_PENALTY && GetTraceRestrictAuxField(item) == TRPPAF_VALUE) - || GetTraceRestrictTypeProperties(item).value_type == TRVT_GROUP_INDEX) { + || type.value_type == TRVT_GROUP_INDEX + || type.value_type == TRVT_SLOT_INDEX) { SetDParam(0, GetTraceRestrictValue(item)); } break; @@ -1771,6 +1949,7 @@ private: this->RaiseWidget(TR_WIDGET_TYPE_NONCOND); this->RaiseWidget(TR_WIDGET_CONDFLAGS); this->RaiseWidget(TR_WIDGET_COMPARATOR); + this->RaiseWidget(TR_WIDGET_SLOT_OP); this->RaiseWidget(TR_WIDGET_VALUE_INT); this->RaiseWidget(TR_WIDGET_VALUE_DECIMAL); this->RaiseWidget(TR_WIDGET_VALUE_DROPDOWN); @@ -1787,6 +1966,7 @@ private: this->DisableWidget(TR_WIDGET_TYPE_NONCOND); this->DisableWidget(TR_WIDGET_CONDFLAGS); this->DisableWidget(TR_WIDGET_COMPARATOR); + this->DisableWidget(TR_WIDGET_SLOT_OP); this->DisableWidget(TR_WIDGET_VALUE_INT); this->DisableWidget(TR_WIDGET_VALUE_DECIMAL); this->DisableWidget(TR_WIDGET_VALUE_DROPDOWN); @@ -1988,6 +2168,13 @@ private: GetTraceRestrictValue(item) ? STR_TRACE_RESTRICT_LONG_RESERVE_CANCEL : STR_TRACE_RESTRICT_LONG_RESERVE; break; + case TRVT_WAIT_AT_PBS: + 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_WAIT_AT_PBS_CANCEL : STR_TRACE_RESTRICT_WAIT_AT_PBS; + break; + case TRVT_GROUP_INDEX: right_sel->SetDisplayedPlane(DPR_VALUE_DROPDOWN); this->EnableWidget(TR_WIDGET_VALUE_DROPDOWN); @@ -2012,6 +2199,34 @@ private: this->GetWidget(TR_WIDGET_VALUE_DROPDOWN)->widget_data = STR_TRACE_RESTRICT_COMPANY; break; + case TRVT_SLOT_INDEX: + right_sel->SetDisplayedPlane(DPR_VALUE_DROPDOWN); + if (!IsTraceRestrictConditional(item)) { + middle_sel->SetDisplayedPlane(DPM_SLOT_OP); + this->EnableWidget(TR_WIDGET_SLOT_OP); + } + + const TraceRestrictSlot *slot; + FOR_ALL_TRACE_RESTRICT_SLOTS(slot) { + if (slot->owner == this->GetOwner()) { + this->EnableWidget(TR_WIDGET_VALUE_DROPDOWN); + break; + } + } + + this->GetWidget(TR_WIDGET_SLOT_OP)->widget_data = + GetDropDownStringByValue(&_slot_op_cond_ops, GetTraceRestrictCondOp(item)); + switch (GetTraceRestrictValue(item)) { + case INVALID_TRACE_RESTRICT_SLOT_ID: + this->GetWidget(TR_WIDGET_VALUE_DROPDOWN)->widget_data = STR_TRACE_RESTRICT_VARIABLE_UNDEFINED; + break; + + default: + this->GetWidget(TR_WIDGET_VALUE_DROPDOWN)->widget_data = STR_TRACE_RESTRICT_SLOT_NAME; + break; + } + break; + default: break; } @@ -2171,6 +2386,8 @@ static const NWidgetPart _nested_program_widgets[] = { 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_DROPDOWN, COLOUR_GREY, TR_WIDGET_SLOT_OP), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_NULL, STR_TRACE_RESTRICT_SLOT_OP_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(), @@ -2232,3 +2449,658 @@ void ShowTraceRestrictProgramWindow(TileIndex tile, Track track) new TraceRestrictWindow(&_program_desc, tile, track); } + +/** Slot GUI widget IDs */ +enum TraceRestrictSlotWindowWidgets { + WID_TRSL_CAPTION, + WID_TRSL_ALL_VEHICLES, + WID_TRSL_LIST_SLOTS, + WID_TRSL_LIST_SLOTS_SCROLLBAR, + WID_TRSL_CREATE_SLOT, + WID_TRSL_DELETE_SLOT, + WID_TRSL_RENAME_SLOT, + WID_TRSL_SET_SLOT_MAX_OCCUPANCY, + WID_TRSL_SORT_BY_ORDER, + WID_TRSL_SORT_BY_DROPDOWN, + WID_TRSL_LIST_VEHICLE, + WID_TRSL_LIST_VEHICLE_SCROLLBAR, +}; + + +static const NWidgetPart _nested_slot_widgets[] = { + NWidget(NWID_HORIZONTAL), // Window header + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_TRSL_CAPTION), SetDataTip(STR_TRACE_RESTRICT_SLOT_CAPTION, STR_NULL), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + NWidget(NWID_HORIZONTAL), + /* left part */ + NWidget(NWID_VERTICAL), + NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalTextLines(1, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM), SetFill(1, 0), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_TRSL_ALL_VEHICLES), SetFill(1, 0), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_TRSL_LIST_SLOTS), SetMatrixDataTip(1, 0, STR_TRACE_RESTRICT_SLOT_GUI_LIST_TOOLTIP), + SetFill(1, 0), SetResize(0, 1), SetScrollbar(WID_TRSL_LIST_SLOTS_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_TRSL_LIST_SLOTS_SCROLLBAR), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_TRSL_CREATE_SLOT), SetFill(0, 1), + SetDataTip(SPR_GROUP_CREATE_TRAIN, STR_TRACE_RESTRICT_SLOT_CREATE_TOOLTIP), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_TRSL_DELETE_SLOT), SetFill(0, 1), + SetDataTip(SPR_GROUP_DELETE_TRAIN, STR_TRACE_RESTRICT_SLOT_DELETE_TOOLTIP), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_TRSL_RENAME_SLOT), SetFill(0, 1), + SetDataTip(SPR_GROUP_RENAME_TRAIN, STR_TRACE_RESTRICT_SLOT_RENAME_TOOLTIP), + NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_TRSL_SET_SLOT_MAX_OCCUPANCY), SetFill(0, 1), + SetDataTip(SPR_IMG_SETTINGS, STR_TRACE_RESTRICT_SLOT_SET_MAX_OCCUPANCY_TOOLTIP), + EndContainer(), + EndContainer(), + /* right part */ + NWidget(NWID_VERTICAL), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_TRSL_SORT_BY_ORDER), SetMinimalSize(81, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_TRSL_SORT_BY_DROPDOWN), SetMinimalSize(167, 12), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA), + NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetResize(1, 0), EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_TRSL_LIST_VEHICLE), SetMinimalSize(248, 0), SetMatrixDataTip(1, 0, STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP), SetResize(1, 1), SetFill(1, 0), SetScrollbar(WID_TRSL_LIST_VEHICLE_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_TRSL_LIST_VEHICLE_SCROLLBAR), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(1, 0), SetFill(1, 1), SetResize(1, 0), EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), EndContainer(), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), + EndContainer(), + EndContainer(), +}; + +class TraceRestrictSlotWindow : public BaseVehicleListWindow { +private: + /* Columns in the group list */ + enum ListColumns { + VGC_NAME, ///< Group name. + VGC_NUMBER, ///< Number of vehicles in the group. + + VGC_END + }; + + VehicleID vehicle_sel; ///< Selected vehicle + TraceRestrictSlotID slot_sel; ///< Selected slot (for drag/drop) + bool slot_set_max_occupancy; ///< True if slot max occupancy is being changed, instead of renaming + TraceRestrictSlotID slot_rename; ///< Slot being renamed or max occupancy changed, INVALID_TRACE_RESTRICT_SLOT_ID if none + TraceRestrictSlotID slot_over; ///< Slot over which a vehicle is dragged, INVALID_TRACE_RESTRICT_SLOT_ID if none + TraceRestrictSlotID slot_confirm; ///< Slot awaiting delete confirmation + GUIList slots; ///< List of slots + uint tiny_step_height; ///< Step height for the slot list + Scrollbar *slot_sb; + + Dimension column_size[VGC_END]; ///< Size of the columns in the group list. + + /** + * (Re)Build the slot list. + * + * @param owner The owner of the window + */ + void BuildSlotList(Owner owner) + { + if (!this->slots.NeedRebuild()) return; + + this->slots.Clear(); + + const TraceRestrictSlot *slot; + FOR_ALL_TRACE_RESTRICT_SLOTS(slot) { + if (slot->owner == owner) { + *(this->slots.Append()) = slot; + } + } + + this->slots.ForceResort(); + this->slots.Sort(&SlotNameSorter); + this->slots.Compact(); + this->slots.RebuildDone(); + } + + /** + * Compute tiny_step_height and column_size + * @return Total width required for the group list. + */ + uint ComputeSlotInfoSize() + { + this->column_size[VGC_NAME] = GetStringBoundingBox(STR_GROUP_ALL_TRAINS); + this->column_size[VGC_NAME].width = max(170u, this->column_size[VGC_NAME].width); + this->tiny_step_height = this->column_size[VGC_NAME].height; + + SetDParamMaxValue(0, 9999, 3, FS_SMALL); + SetDParamMaxValue(1, 9999, 3, FS_SMALL); + this->column_size[VGC_NUMBER] = GetStringBoundingBox(STR_TRACE_RESTRICT_SLOT_MAX_OCCUPANCY); + this->tiny_step_height = max(this->tiny_step_height, this->column_size[VGC_NUMBER].height); + + this->tiny_step_height += WD_MATRIX_TOP; + + return WD_FRAMERECT_LEFT + 8 + + this->column_size[VGC_NAME].width + 8 + + this->column_size[VGC_NUMBER].width + 2 + + WD_FRAMERECT_RIGHT; + } + + /** + * Draw a row in the slot list. + * @param y Top of the row. + * @param left Left of the row. + * @param right Right of the row. + * @param g_id Group to list. + */ + void DrawSlotInfo(int y, int left, int right, TraceRestrictSlotID slot_id) const + { + /* Highlight the group if a vehicle is dragged over it */ + if (slot_id == this->slot_over) { + GfxFillRect(left + WD_FRAMERECT_LEFT, y + WD_FRAMERECT_TOP, right - WD_FRAMERECT_RIGHT, y + this->tiny_step_height - WD_FRAMERECT_BOTTOM - WD_MATRIX_TOP, _colour_gradient[COLOUR_GREY][7]); + } + + /* draw the selected group in white, else we draw it in black */ + TextColour colour = slot_id == this->vli.index ? TC_WHITE : TC_BLACK; + bool rtl = _current_text_dir == TD_RTL; + + /* draw group name */ + StringID str; + if (slot_id == ALL_TRAINS_TRACE_RESTRICT_SLOT_ID) { + str = STR_GROUP_ALL_TRAINS; + } else { + SetDParam(0, slot_id); + str = STR_TRACE_RESTRICT_SLOT_NAME; + } + int x = rtl ? right - WD_FRAMERECT_RIGHT - 8 - this->column_size[VGC_NAME].width + 1 : left + WD_FRAMERECT_LEFT + 8; + DrawString(x, x + this->column_size[VGC_NAME].width - 1, y + (this->tiny_step_height - this->column_size[VGC_NAME].height) / 2, str, colour); + + if (slot_id == ALL_TRAINS_TRACE_RESTRICT_SLOT_ID) return; + + const TraceRestrictSlot *slot = TraceRestrictSlot::Get(slot_id); + + /* draw the number of vehicles of the group */ + x = rtl ? x - 2 - this->column_size[VGC_NUMBER].width : x + 2 + this->column_size[VGC_NAME].width; + SetDParam(0, slot->occupants.size()); + SetDParam(1, slot->max_occupancy); + DrawString(x, x + this->column_size[VGC_NUMBER].width - 1, y + (this->tiny_step_height - this->column_size[VGC_NUMBER].height) / 2, STR_TRACE_RESTRICT_SLOT_MAX_OCCUPANCY, colour, SA_RIGHT | SA_FORCE); + } + + /** + * Mark the widget containing the currently highlighted slot as dirty. + */ + void DirtyHighlightedSlotWidget() + { + if (this->slot_over == INVALID_TRACE_RESTRICT_SLOT_ID) return; + + if (this->slot_over == ALL_TRAINS_TRACE_RESTRICT_SLOT_ID) { + this->SetWidgetDirty(WID_TRSL_ALL_VEHICLES); + } else { + this->SetWidgetDirty(WID_TRSL_LIST_SLOTS); + } + } + +public: + TraceRestrictSlotWindow(WindowDesc *desc, WindowNumber window_number) : BaseVehicleListWindow(desc, window_number) + { + this->CreateNestedTree(); + + this->vscroll = this->GetScrollbar(WID_TRSL_LIST_VEHICLE_SCROLLBAR); + this->slot_sb = this->GetScrollbar(WID_TRSL_LIST_SLOTS_SCROLLBAR); + this->sorting = &_sorting.train; + + this->vli.index = ALL_TRAINS_TRACE_RESTRICT_SLOT_ID; + this->vehicle_sel = INVALID_VEHICLE; + this->slot_sel = INVALID_TRACE_RESTRICT_SLOT_ID; + this->slot_rename = INVALID_TRACE_RESTRICT_SLOT_ID; + this->slot_set_max_occupancy = false; + this->slot_over = INVALID_TRACE_RESTRICT_SLOT_ID; + + this->vehicles.SetListing(*this->sorting); + this->vehicles.ForceRebuild(); + this->vehicles.NeedResort(); + + this->BuildVehicleList(); + this->SortVehicleList(); + + this->slots.ForceRebuild(); + this->slots.NeedResort(); + this->BuildSlotList(vli.company); + + this->FinishInitNested(window_number); + this->owner = vli.company; + } + + ~TraceRestrictSlotWindow() + { + *this->sorting = this->vehicles.GetListing(); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case WID_TRSL_LIST_SLOTS: { + size->width = this->ComputeSlotInfoSize(); + resize->height = this->tiny_step_height; + + /* Minimum height is the height of the list widget minus all vehicles... */ + size->height = 4 * GetVehicleListHeight(this->vli.vtype, this->tiny_step_height) - this->tiny_step_height; + + /* ... minus the buttons at the bottom ... */ + uint max_icon_height = GetSpriteSize(this->GetWidget(WID_TRSL_CREATE_SLOT)->widget_data).height; + max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget(WID_TRSL_DELETE_SLOT)->widget_data).height); + max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget(WID_TRSL_RENAME_SLOT)->widget_data).height); + max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget(WID_TRSL_SET_SLOT_MAX_OCCUPANCY)->widget_data).height); + + /* Get a multiple of tiny_step_height of that amount */ + size->height = Ceil(size->height - max_icon_height, tiny_step_height); + break; + } + + case WID_TRSL_ALL_VEHICLES: + size->width = this->ComputeSlotInfoSize(); + size->height = this->tiny_step_height; + break; + + case WID_TRSL_SORT_BY_ORDER: { + Dimension d = GetStringBoundingBox(this->GetWidget(widget)->widget_data); + d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better. + d.height += padding.height; + *size = maxdim(*size, d); + break; + } + + case WID_TRSL_LIST_VEHICLE: + this->ComputeSlotInfoSize(); + resize->height = GetVehicleListHeight(this->vli.vtype, this->tiny_step_height); + size->height = 4 * resize->height; + break; + } + } + + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. + */ + virtual void OnInvalidateData(int data = 0, bool gui_scope = true) + { + if (data == 0) { + /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */ + this->vehicles.ForceRebuild(); + this->slots.ForceRebuild(); + } else { + this->vehicles.ForceResort(); + this->slots.ForceResort(); + } + + /* Process ID-invalidation in command-scope as well */ + if (this->slot_rename != INVALID_TRACE_RESTRICT_SLOT_ID && this->slot_rename != NEW_TRACE_RESTRICT_SLOT_ID && + !TraceRestrictSlot::IsValidID(this->slot_rename)) { + DeleteWindowByClass(WC_QUERY_STRING); + this->slot_rename = INVALID_TRACE_RESTRICT_SLOT_ID; + } + + if (this->vli.index != ALL_TRAINS_TRACE_RESTRICT_SLOT_ID && !TraceRestrictSlot::IsValidID(this->vli.index)) { + this->vli.index = ALL_TRAINS_TRACE_RESTRICT_SLOT_ID; + } + this->SetDirty(); + } + + virtual void OnPaint() + { + /* If we select the all vehicles, this->list will contain all vehicles of the owner + * else this->list will contain all vehicles which belong to the selected group */ + this->BuildVehicleList(); + this->SortVehicleList(); + + this->BuildSlotList(this->owner); + + this->slot_sb->SetCount(this->slots.Length()); + this->vscroll->SetCount(this->vehicles.Length()); + + /* Disable the slot specific function when we select all vehicles */ + this->SetWidgetsDisabledState(this->vli.index == ALL_TRAINS_TRACE_RESTRICT_SLOT_ID || _local_company != this->vli.company, + WID_TRSL_DELETE_SLOT, + WID_TRSL_RENAME_SLOT, + WID_TRSL_SET_SLOT_MAX_OCCUPANCY, + WIDGET_LIST_END); + + /* Disable remaining buttons for non-local companies + * Needed while changing _local_company, eg. by cheats + * All procedures (eg. move vehicle to a slot) + * verify, whether you are the owner of the vehicle, + * so it doesn't have to be disabled + */ + this->SetWidgetsDisabledState(_local_company != this->vli.company, + WID_TRSL_CREATE_SLOT, + WIDGET_LIST_END); + + /* Set text of sort by dropdown */ + this->GetWidget(WID_TRSL_SORT_BY_DROPDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()]; + + this->DrawWidgets(); + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (widget) { + case WID_TRSL_ALL_VEHICLES: + DrawSlotInfo(r.top + WD_FRAMERECT_TOP, r.left, r.right, ALL_TRAINS_TRACE_RESTRICT_SLOT_ID); + break; + + case WID_TRSL_LIST_SLOTS: { + int y1 = r.top + WD_FRAMERECT_TOP; + int max = min(this->slot_sb->GetPosition() + this->slot_sb->GetCapacity(), this->slots.Length()); + for (int i = this->slot_sb->GetPosition(); i < max; ++i) { + const TraceRestrictSlot *slot = this->slots[i]; + + assert(slot->owner == this->owner); + + DrawSlotInfo(y1, r.left, r.right, slot->index); + + y1 += this->tiny_step_height; + } + break; + } + + case WID_TRSL_SORT_BY_ORDER: + this->DrawSortButtonState(WID_TRSL_SORT_BY_ORDER, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP); + break; + + case WID_TRSL_LIST_VEHICLE: + this->DrawVehicleListItems(this->vehicle_sel, this->resize.step_height, r); + break; + } + } + + static void DeleteSlotCallback(Window *win, bool confirmed) + { + if (confirmed) { + TraceRestrictSlotWindow *w = (TraceRestrictSlotWindow*)win; + w->vli.index = ALL_TRAINS_TRACE_RESTRICT_SLOT_ID; + DoCommandP(0, w->slot_confirm, 0, CMD_DELETE_TRACERESTRICT_SLOT | CMD_MSG(STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_DELETE)); + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_TRSL_SORT_BY_ORDER: // Flip sorting method ascending/descending + this->vehicles.ToggleSortOrder(); + this->SetDirty(); + break; + + case WID_TRSL_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu + ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), WID_TRSL_SORT_BY_DROPDOWN, 0, 0); + return; + + case WID_TRSL_ALL_VEHICLES: // All vehicles button + if (this->vli.index != ALL_TRAINS_TRACE_RESTRICT_SLOT_ID) { + this->vli.index = ALL_TRAINS_TRACE_RESTRICT_SLOT_ID; + this->vehicles.ForceRebuild(); + this->SetDirty(); + } + break; + + case WID_TRSL_LIST_SLOTS: { // Matrix Slot + uint id_s = this->slot_sb->GetScrolledRowFromWidget(pt.y, this, WID_TRSL_LIST_SLOTS, 0, this->tiny_step_height); + if (id_s >= this->slots.Length()) return; + + this->slot_sel = this->vli.index = this->slots[id_s]->index; + + this->vehicles.ForceRebuild(); + this->SetDirty(); + break; + } + + case WID_TRSL_LIST_VEHICLE: { // Matrix Vehicle + uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_TRSL_LIST_VEHICLE); + if (id_v >= this->vehicles.Length()) return; // click out of list bound + + const Vehicle *v = this->vehicles[id_v]; + if (VehicleClicked(v)) break; + + this->vehicle_sel = v->index; + + SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this); + SetMouseCursorVehicle(v, EIT_IN_LIST); + _cursor.vehchain = true; + + this->SetDirty(); + break; + } + + case WID_TRSL_CREATE_SLOT: { // Create a new slot + this->ShowCreateSlotWindow(); + break; + } + + case WID_TRSL_DELETE_SLOT: { // Delete the selected slot + this->slot_confirm = this->vli.index; + ShowQuery(STR_TRACE_RESTRICT_SLOT_QUERY_DELETE_CAPTION, STR_TRACE_RESTRICT_SLOT_DELETE_QUERY_TEXT, this, DeleteSlotCallback); + break; + } + + case WID_TRSL_RENAME_SLOT: // Rename the selected slot + this->ShowRenameSlotWindow(this->vli.index); + break; + + case WID_TRSL_SET_SLOT_MAX_OCCUPANCY: // Set max occupancy of the selected slot + this->ShowSetSlotMaxOccupancyWindow(this->vli.index); + break; + } + } + + void OnDragDrop_Vehicle(Point pt, int widget) + { + switch (widget) { + case WID_TRSL_ALL_VEHICLES: // All vehicles + DoCommandP(0, this->slot_sel, this->vehicle_sel, CMD_REMOVE_VEHICLE_TRACERESTRICT_SLOT | CMD_MSG(STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_REMOVE_VEHICLE)); + + this->vehicle_sel = INVALID_VEHICLE; + this->slot_over = INVALID_GROUP; + + this->SetDirty(); + break; + + case WID_TRSL_LIST_SLOTS: { // Matrix slot + const VehicleID vindex = this->vehicle_sel; + this->vehicle_sel = INVALID_VEHICLE; + this->slot_over = INVALID_GROUP; + this->SetDirty(); + + uint id_s = this->slot_sb->GetScrolledRowFromWidget(pt.y, this, WID_TRSL_LIST_SLOTS, 0, this->tiny_step_height); + if (id_s >= this->slots.Length()) return; // click out of list bound + + if (_ctrl_pressed) { + // remove from old group + DoCommandP(0, this->slot_sel, vindex, CMD_REMOVE_VEHICLE_TRACERESTRICT_SLOT | CMD_MSG(STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_REMOVE_VEHICLE)); + } + DoCommandP(0, this->slots[id_s]->index, vindex, CMD_ADD_VEHICLE_TRACERESTRICT_SLOT | CMD_MSG(STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_ADD_VEHICLE)); + break; + } + + case WID_TRSL_LIST_VEHICLE: { // Matrix vehicle + const VehicleID vindex = this->vehicle_sel; + this->vehicle_sel = INVALID_VEHICLE; + this->slot_over = INVALID_GROUP; + this->SetDirty(); + + uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_TRSL_LIST_VEHICLE); + if (id_v >= this->vehicles.Length()) return; // click out of list bound + + const Vehicle *v = this->vehicles[id_v]; + if (!VehicleClicked(v) && vindex == v->index) { + ShowVehicleViewWindow(v); + } + break; + } + } + } + + virtual void OnDragDrop(Point pt, int widget) + { + if (this->vehicle_sel != INVALID_VEHICLE) OnDragDrop_Vehicle(pt, widget); + + _cursor.vehchain = false; + } + + virtual void OnQueryTextFinished(char *str) + { + if (str != NULL) { + if (this->slot_set_max_occupancy) { + if (!StrEmpty(str)) DoCommandP(0, this->slot_rename | (1 << 16), atoi(str), CMD_ALTER_TRACERESTRICT_SLOT | CMD_MSG(STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_SET_MAX_OCCUPANCY)); + } else if (this->slot_rename == NEW_TRACE_RESTRICT_SLOT_ID) { + DoCommandP(0, 0, 0, CMD_CREATE_TRACERESTRICT_SLOT | CMD_MSG(STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_CREATE), NULL, str); + } else { + DoCommandP(0, this->slot_rename, 0, CMD_ALTER_TRACERESTRICT_SLOT | CMD_MSG(STR_TRACE_RESTRICT_ERROR_SLOT_CAN_T_RENAME), NULL, str); + } + } + this->slot_rename = INVALID_TRACE_RESTRICT_SLOT_ID; + } + + virtual void OnResize() + { + this->slot_sb->SetCapacityFromWidget(this, WID_TRSL_LIST_SLOTS); + this->vscroll->SetCapacityFromWidget(this, WID_TRSL_LIST_VEHICLE); + } + + virtual void OnDropdownSelect(int widget, int index) + { + switch (widget) { + case WID_TRSL_SORT_BY_DROPDOWN: + this->vehicles.SetSortType(index); + break; + + default: NOT_REACHED(); + } + + this->SetDirty(); + } + + virtual void OnTick() + { + if (_pause_mode != PM_UNPAUSED) return; + if (this->slots.NeedResort() || this->vehicles.NeedResort()) { + this->SetDirty(); + } + } + + virtual void OnPlaceObjectAbort() + { + /* abort drag & drop */ + this->vehicle_sel = INVALID_VEHICLE; + this->DirtyHighlightedSlotWidget(); + this->slot_over = INVALID_GROUP; + this->SetWidgetDirty(WID_TRSL_LIST_VEHICLE); + } + + virtual void OnMouseDrag(Point pt, int widget) + { + if (this->vehicle_sel == INVALID_VEHICLE) return; + + /* A vehicle is dragged over... */ + TraceRestrictSlotID new_slot_over = INVALID_TRACE_RESTRICT_SLOT_ID; + switch (widget) { + case WID_TRSL_ALL_VEHICLES: // ... all trains. + new_slot_over = ALL_TRAINS_TRACE_RESTRICT_SLOT_ID; + break; + + case WID_TRSL_LIST_SLOTS: { // ... the list of slots. + uint id_s = this->slot_sb->GetScrolledRowFromWidget(pt.y, this, WID_TRSL_LIST_SLOTS, 0, this->tiny_step_height); + if (id_s < this->slots.Length()) new_slot_over = this->slots[id_s]->index; + break; + } + } + + /* Do not highlight when dragging over the current group */ + if (this->slot_sel == new_slot_over) new_slot_over = INVALID_TRACE_RESTRICT_SLOT_ID; + + /* Mark widgets as dirty if the group changed. */ + if (new_slot_over != this->slot_over) { + this->DirtyHighlightedSlotWidget(); + this->slot_over = new_slot_over; + this->DirtyHighlightedSlotWidget(); + } + } + + void ShowRenameSlotWindow(TraceRestrictSlotID slot_id) + { + assert(TraceRestrictSlot::IsValidID(slot_id)); + this->slot_set_max_occupancy = false; + this->slot_rename = slot_id; + SetDParam(0, slot_id); + ShowQueryString(STR_TRACE_RESTRICT_SLOT_NAME, STR_TRACE_RESTRICT_SLOT_RENAME_CAPTION, MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS); + } + + void ShowSetSlotMaxOccupancyWindow(TraceRestrictSlotID slot_id) + { + this->slot_set_max_occupancy = true; + this->slot_rename = slot_id; + SetDParam(0, TraceRestrictSlot::Get(slot_id)->max_occupancy); + ShowQueryString(STR_JUST_INT, STR_TRACE_RESTRICT_SLOT_SET_MAX_OCCUPANCY_CAPTION, 5, this, CS_NUMERAL, QSF_ENABLE_DEFAULT); + } + + void ShowCreateSlotWindow() + { + this->slot_set_max_occupancy = false; + this->slot_rename = NEW_TRACE_RESTRICT_SLOT_ID; + ShowQueryString(STR_EMPTY, STR_TRACE_RESTRICT_SLOT_CREATE_CAPTION, MAX_LENGTH_TRACE_RESTRICT_SLOT_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS); + } + + /** + * Tests whether a given vehicle is selected in the window, and unselects it if necessary. + * Called when the vehicle is deleted. + * @param vehicle Vehicle that is going to be deleted + */ + void UnselectVehicle(VehicleID vehicle) + { + if (this->vehicle_sel == vehicle) ResetObjectToPlace(); + } +}; + +static WindowDesc _slot_window_desc( + WDP_AUTO, "list_groups_train", 525, 246, + WC_TRACE_RESTRICT_SLOTS, WC_NONE, + 0, + _nested_slot_widgets, lengthof(_nested_slot_widgets) +); + +/** + * Show the trace restrict slot window for the given company. + * @param company The company to show the window for. + */ +void ShowTraceRestrictSlotWindow(CompanyID company) +{ + if (!Company::IsValidID(company)) return; + + WindowNumber num = VehicleListIdentifier(VL_SLOT_LIST, VEH_TRAIN, company).Pack(); + AllocateWindowDescFront(&_slot_window_desc, num); +} + +/** + * Finds a group list window determined by vehicle type and owner + * @param vt vehicle type + * @param owner owner of groups + * @return pointer to VehicleGroupWindow, NULL if not found + */ +static inline TraceRestrictSlotWindow *FindTraceRestrictSlotWindow(Owner owner) +{ + return (TraceRestrictSlotWindow *)FindWindowById(GetWindowClassForVehicleType(VEH_TRAIN), VehicleListIdentifier(VL_SLOT_LIST, VEH_TRAIN, owner).Pack()); +} + +/** + * Removes the highlight of a vehicle in a group window + * @param *v Vehicle to remove all highlights from + */ +void DeleteTraceRestrictSlotHighlightOfVehicle(const Vehicle *v) +{ + /* If we haven't got any vehicles on the mouse pointer, we haven't got any highlighted in any group windows either + * If that is the case, we can skip looping though the windows and save time + */ + if (_special_mouse_mode != WSM_DRAGDROP) return; + + TraceRestrictSlotWindow *w = FindTraceRestrictSlotWindow(v->owner); + if (w != NULL) w->UnselectVehicle(v->index); +} diff --git a/src/train.h b/src/train.h index 536083b401..cc4ef053c1 100644 --- a/src/train.h +++ b/src/train.h @@ -26,6 +26,8 @@ struct Train; /** Rail vehicle flags. */ enum VehicleRailFlags { VRF_REVERSING = 0, + VRF_WAITING_RESTRICTION = 1, ///< Train is waiting due to a routing restriction, only valid when VRF_TRAIN_STUCK is also set. + VRF_HAVE_SLOT = 2, ///< Train has 1 or more slots VRF_POWEREDWAGON = 3, ///< Wagon is powered. VRF_REVERSE_DIRECTION = 4, ///< Reverse the visible direction of the vehicle. diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index ab0a4ce693..d047622500 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1397,6 +1397,10 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u DeleteVehicleOrders(src); RemoveVehicleFromGroup(src); src->unitnumber = 0; + if (HasBit(src->flags, VRF_HAVE_SLOT)) { + TraceRestrictRemoveVehicleFromAllSlots(src->index); + ClrBit(src->flags, VRF_HAVE_SLOT); + } } /* We weren't a front engine but are becoming one. So @@ -1603,11 +1607,12 @@ void Train::UpdateDeltaXY(Direction direction) * Mark a train as stuck and stop it if it isn't stopped right now. * @param v %Train to mark as being stuck. */ -static void MarkTrainAsStuck(Train *v) +static void MarkTrainAsStuck(Train *v, bool waiting_restriction = false) { if (!HasBit(v->flags, VRF_TRAIN_STUCK)) { /* It is the first time the problem occurred, set the "train stuck" flag. */ SetBit(v->flags, VRF_TRAIN_STUCK); + SB(v->flags, VRF_WAITING_RESTRICTION, 1, waiting_restriction ? 1 : 0); v->wait_counter = 0; @@ -1616,6 +1621,9 @@ static void MarkTrainAsStuck(Train *v) v->subspeed = 0; v->SetLastSpeed(); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); + } else if (waiting_restriction != HasBit(v->flags, VRF_WAITING_RESTRICTION)) { + ToggleBit(v->flags, VRF_WAITING_RESTRICTION); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } } @@ -2823,6 +2831,21 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, Track track = FindFirstTrack(tracks); /* We need to check for signals only here, as a junction tile can't have signals. */ if (track != INVALID_TRACK && HasPbsSignalOnTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir))) { + if (IsRestrictedSignal(tile) && v->force_proceed != TFP_SIGNAL) { + const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(tile, track); + if (prog && prog->actions_used_flags & (TRPAUF_WAIT_AT_PBS | TRPAUF_SLOT_ACQUIRE)) { + TraceRestrictProgramResult out; + TraceRestrictProgramInput input(tile, TrackEnterdirToTrackdir(track, enterdir), NULL, NULL); + input.permitted_slot_operations = TRPISP_ACQUIRE; + prog->Execute(v, input, out); + if (out.flags & TRPRF_WAIT_AT_PBS) { + if (mark_stuck) MarkTrainAsStuck(v, true); + return track; + } + } + } + ClrBit(v->flags, VRF_WAITING_RESTRICTION); + do_track_reservation = true; changed_signal = true; SetSignalStateByTrackdir(tile, TrackEnterdirToTrackdir(track, enterdir), SIGNAL_STATE_GREEN); @@ -3717,6 +3740,19 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) goto reverse_train_direction; } else { TryReserveRailTrack(gp.new_tile, TrackBitsToTrack(chosen_track), false); + + if (IsPlainRailTile(gp.new_tile) && HasSignals(gp.new_tile) && IsRestrictedSignal(gp.new_tile)) { + const Trackdir dir = FindFirstTrackdir(trackdirbits); + if (HasSignalOnTrack(gp.new_tile, TrackdirToTrack(dir))) { + const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(gp.new_tile, TrackdirToTrack(dir)); + if (prog && prog->actions_used_flags & (TRPAUF_SLOT_ACQUIRE | TRPAUF_SLOT_RELEASE_FRONT)) { + TraceRestrictProgramResult out; + TraceRestrictProgramInput input(gp.new_tile, dir, NULL, NULL); + input.permitted_slot_operations = TRPISP_ACQUIRE | TRPISP_RELEASE_FRONT; + prog->Execute(v, input, out); + } + } + } } } else { /* The wagon is active, simply follow the prev vehicle. */ @@ -3994,6 +4030,21 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) if (v->Next() == NULL) { TrainMovedChangeSignal(v, gp.old_tile, ReverseDiagDir(enterdir)); if (IsLevelCrossingTile(gp.old_tile)) UpdateLevelCrossing(gp.old_tile); + + if (IsTileType(gp.old_tile, MP_RAILWAY) && HasSignals(gp.old_tile) && IsRestrictedSignal(gp.old_tile)) { + const TrackdirBits rev_tracks = TrackBitsToTrackdirBits(GetTrackBits(gp.old_tile)) & DiagdirReachesTrackdirs(ReverseDiagDir(enterdir)); + const Trackdir rev_trackdir = FindFirstTrackdir(rev_tracks); + const Track track = TrackdirToTrack(rev_trackdir); + if (HasSignalOnTrack(gp.old_tile, track)) { + const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(gp.old_tile, track); + if (prog && prog->actions_used_flags & TRPAUF_SLOT_RELEASE_BACK) { + TraceRestrictProgramResult out; + TraceRestrictProgramInput input(gp.old_tile, ReverseTrackdir(rev_trackdir), NULL, NULL); + input.permitted_slot_operations = TRPISP_RELEASE_BACK; + prog->Execute(first, input, out); + } + } + } } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index e6900e034e..edd7087492 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -51,6 +51,7 @@ #include "tunnel_map.h" #include "depot_map.h" #include "gamelog.h" +#include "tracerestrict.h" #include "linkgraph/linkgraph.h" #include "linkgraph/refresh.h" #include "blitter/factory.hpp" @@ -856,6 +857,11 @@ void Vehicle::PreDestructor() if (this->owner == _local_company) InvalidateAutoreplaceWindow(this->engine_type, this->group_id); DeleteGroupHighlightOfVehicle(this); + if (this->type == VEH_TRAIN) { + extern void DeleteTraceRestrictSlotHighlightOfVehicle(const Vehicle *v); + + DeleteTraceRestrictSlotHighlightOfVehicle(this); + } } if (this->type == VEH_AIRCRAFT && this->IsPrimaryVehicle()) { @@ -876,6 +882,11 @@ void Vehicle::PreDestructor() } } + if (this->type == VEH_TRAIN && HasBit(Train::From(this)->flags, VRF_HAVE_SLOT)) { + TraceRestrictRemoveVehicleFromAllSlots(this->index); + ClrBit(Train::From(this)->flags, VRF_HAVE_SLOT); + } + if (this->Previous() == NULL) { InvalidateWindowData(WC_VEHICLE_DEPOT, this->tile); } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index dd8ebccb1b..50b735e7c4 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -41,6 +41,9 @@ #include "train.h" #include "tbtr_template_gui_main.h" #include "zoom_func.h" +#include "tracerestrict.h" + +#include #include "safeguards.h" @@ -176,6 +179,15 @@ Dimension BaseVehicleListWindow::GetActionDropdownSize(bool show_autoreplace, bo return d; } +/** + * Whether the Action dropdown window should be shown/available. + * @return Whether available + */ +bool BaseVehicleListWindow::ShouldShowActionDropdownList() const +{ + return this->vehicles.Length() != 0 || (this->vli.vtype == VEH_TRAIN && _settings_client.gui.show_adv_tracerestrict_features); +} + /** * Display the Action dropdown window. * @param show_autoreplace If true include the autoreplace item. @@ -186,17 +198,21 @@ DropDownList *BaseVehicleListWindow::BuildActionDropdownList(bool show_autorepla StringID change_order_str, bool show_create_group) { DropDownList *list = new DropDownList(); + bool disable = this->vehicles.Length() == 0; - if (show_autoreplace) *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_REPLACE_VEHICLES, ADI_REPLACE, false); + if (show_autoreplace) *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_REPLACE_VEHICLES, ADI_REPLACE, disable); if (show_autoreplace && show_template_replace) { - *list->Append() = new DropDownListStringItem(STR_TMPL_TEMPLATE_REPLACEMENT, ADI_TEMPLATE_REPLACE, false); + *list->Append() = new DropDownListStringItem(STR_TMPL_TEMPLATE_REPLACEMENT, ADI_TEMPLATE_REPLACE, disable); } - *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_SEND_FOR_SERVICING, ADI_SERVICE, false); - *list->Append() = new DropDownListStringItem(this->vehicle_depot_name[this->vli.vtype], ADI_DEPOT, false); + *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_SEND_FOR_SERVICING, ADI_SERVICE, disable); + *list->Append() = new DropDownListStringItem(this->vehicle_depot_name[this->vli.vtype], ADI_DEPOT, disable); if (show_group) { - *list->Append() = new DropDownListStringItem(STR_GROUP_ADD_SHARED_VEHICLE, ADI_ADD_SHARED, false); - *list->Append() = new DropDownListStringItem(STR_GROUP_REMOVE_ALL_VEHICLES, ADI_REMOVE_ALL, false); + *list->Append() = new DropDownListStringItem(STR_GROUP_ADD_SHARED_VEHICLE, ADI_ADD_SHARED, disable); + *list->Append() = new DropDownListStringItem(STR_GROUP_REMOVE_ALL_VEHICLES, ADI_REMOVE_ALL, disable); + } + if (this->vli.vtype == VEH_TRAIN && _settings_client.gui.show_adv_tracerestrict_features) { + *list->Append() = new DropDownListStringItem(STR_TRACE_RESTRICT_SLOT_MANAGE, ADI_TRACERESTRICT_SLOT_MGMT, false); } if (change_order_str != 0) { *list->Append() = new DropDownListStringItem(change_order_str, ADI_CHANGE_ORDER, false); @@ -1713,7 +1729,7 @@ public: this->BuildVehicleList(); this->SortVehicleList(); - if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_VL_MANAGE_VEHICLES_DROPDOWN)) { + if (!this->ShouldShowActionDropdownList() && this->IsWidgetLowered(WID_VL_MANAGE_VEHICLES_DROPDOWN)) { HideDropDownMenu(this); } @@ -1727,8 +1743,8 @@ public: } if (this->owner == _local_company) { this->SetWidgetDisabledState(WID_VL_AVAILABLE_VEHICLES, this->vli.type != VL_STANDARD); + this->SetWidgetDisabledState(WID_VL_MANAGE_VEHICLES_DROPDOWN, !this->ShouldShowActionDropdownList()); this->SetWidgetsDisabledState(this->vehicles.Length() == 0, - WID_VL_MANAGE_VEHICLES_DROPDOWN, WID_VL_STOP_ALL, WID_VL_START_ALL, WIDGET_LIST_END); @@ -1769,7 +1785,7 @@ public: case WID_VL_MANAGE_VEHICLES_DROPDOWN: { DropDownList *list = this->BuildActionDropdownList(VehicleListIdentifier::UnPack(this->window_number).type == VL_STANDARD, false, this->vli.vtype == VEH_TRAIN, this->GetChangeOrderStringID(), true); - ShowDropDownList(this, list, 0, WID_VL_MANAGE_VEHICLES_DROPDOWN); + ShowDropDownList(this, list, -1, WID_VL_MANAGE_VEHICLES_DROPDOWN); break; } @@ -1787,7 +1803,7 @@ public: this->vehicles.SetSortType(index); break; case WID_VL_MANAGE_VEHICLES_DROPDOWN: - assert(this->vehicles.Length() != 0); + assert(this->ShouldShowActionDropdownList()); switch (index) { case ADI_REPLACE: // Replace window @@ -1811,6 +1827,12 @@ public: ShowQueryString(STR_EMPTY, STR_GROUP_RENAME_CAPTION, MAX_LENGTH_GROUP_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS); break; + case ADI_TRACERESTRICT_SLOT_MGMT: { + extern void ShowTraceRestrictSlotWindow(CompanyID company); + ShowTraceRestrictSlotWindow(this->owner); + break; + } + default: NOT_REACHED(); } break; @@ -2059,6 +2081,7 @@ struct VehicleDetailsWindow : Window { Scrollbar *vscroll; bool vehicle_group_line_shown; bool vehicle_weight_ratio_line_shown; + bool vehicle_slots_line_shown; /** Initialize a newly created vehicle details window */ VehicleDetailsWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) @@ -2148,6 +2171,12 @@ struct VehicleDetailsWindow : Window { return (v->type == VEH_TRAIN && _settings_client.gui.show_train_weight_ratios_in_details); } + bool ShouldShowSlotsLine(const Vehicle *v) const + { + if (v->type != VEH_TRAIN) return false; + return HasBit(Train::From(v)->flags, VRF_HAVE_SLOT); + } + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) { switch (widget) { @@ -2156,9 +2185,11 @@ struct VehicleDetailsWindow : Window { Dimension dim = { 0, 0 }; this->vehicle_group_line_shown = ShouldShowGroupLine(v); this->vehicle_weight_ratio_line_shown = ShouldShowWeightRatioLine(v); + this->vehicle_slots_line_shown = ShouldShowSlotsLine(v); int lines = 4; if (this->vehicle_group_line_shown) lines++; if (this->vehicle_weight_ratio_line_shown) lines++; + if (this->vehicle_slots_line_shown) lines++; size->height = WD_FRAMERECT_TOP + lines * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM; for (uint i = 0; i < 5; i++) SetDParamMaxValue(i, INT16_MAX); @@ -2386,8 +2417,32 @@ struct VehicleDetailsWindow : Window { if (should_show_group) { SetDParam(0, v->group_id); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_GROUP); + y += FONT_HEIGHT_NORMAL; } - if (this->vehicle_group_line_shown != should_show_group || this->vehicle_weight_ratio_line_shown != should_show_weight_ratio) { + + bool should_show_slots = this->ShouldShowSlotsLine(v); + if (should_show_slots) { + std::vector slots; + TraceRestrictGetVehicleSlots(v->index, slots); + + char text_buffer[512]; + char *buffer = text_buffer; + const char * const last = lastof(text_buffer); + SetDParam(0, slots.size()); + buffer = GetString(buffer, STR_TRACE_RESTRICT_SLOT_LIST_HEADER, last); + + for (size_t i = 0; i < slots.size(); i++) { + if (i != 0) buffer = GetString(buffer, STR_TRACE_RESTRICT_SLOT_LIST_SEPARATOR, last); + buffer = strecpy(buffer, TraceRestrictSlot::Get(slots[i])->name.c_str(), last); + } + SetDParamStr(0, text_buffer); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_JUST_RAW_STRING); + y += FONT_HEIGHT_NORMAL; + } + + if (this->vehicle_weight_ratio_line_shown != should_show_weight_ratio || + this->vehicle_weight_ratio_line_shown != should_show_weight_ratio || + this->vehicle_slots_line_shown != should_show_slots) { const_cast(this)->ReInit(); } break; @@ -2975,7 +3030,7 @@ public: str = STR_VEHICLE_STATUS_STOPPED; } } else if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_TRAIN_STUCK) && !v->current_order.IsType(OT_LOADING)) { - str = STR_VEHICLE_STATUS_TRAIN_STUCK; + str = HasBit(Train::From(v)->flags, VRF_WAITING_RESTRICTION) ? STR_VEHICLE_STATUS_TRAIN_STUCK_WAIT_RESTRICTION : STR_VEHICLE_STATUS_TRAIN_STUCK; } else if (v->type == VEH_AIRCRAFT && HasBit(Aircraft::From(v)->flags, VAF_DEST_TOO_FAR) && !v->current_order.IsType(OT_LOADING)) { str = STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR; } else { // vehicle is in a "normal" state, show current order diff --git a/src/vehicle_gui_base.h b/src/vehicle_gui_base.h index 08e1c5ff58..458887d336 100644 --- a/src/vehicle_gui_base.h +++ b/src/vehicle_gui_base.h @@ -35,6 +35,7 @@ struct BaseVehicleListWindow : public Window { ADI_REMOVE_ALL, ADI_CHANGE_ORDER, ADI_CREATE_GROUP, + ADI_TRACERESTRICT_SLOT_MGMT, }; static const StringID vehicle_depot_name[]; @@ -53,6 +54,7 @@ struct BaseVehicleListWindow : public Window { Dimension GetActionDropdownSize(bool show_autoreplace, bool show_group, bool show_template_replace, StringID change_order_str = 0); DropDownList *BuildActionDropdownList(bool show_autoreplace, bool show_group, bool show_template_replace, StringID change_order_str = 0, bool show_create_group = false); + bool ShouldShowActionDropdownList() const; }; uint GetVehicleListHeight(VehicleType type, uint divisor = 1); diff --git a/src/vehiclelist.cpp b/src/vehiclelist.cpp index 1880463013..354d776435 100644 --- a/src/vehiclelist.cpp +++ b/src/vehiclelist.cpp @@ -13,6 +13,7 @@ #include "train.h" #include "vehiclelist.h" #include "group.h" +#include "tracerestrict.h" #include "safeguards.h" @@ -119,6 +120,14 @@ bool GenerateVehicleSortList(VehicleList *list, const VehicleListIdentifier &vli const Vehicle *v; + auto fill_all_vehicles = [&]() { + FOR_ALL_VEHICLES(v) { + if (!HasBit(v->subtype, GVSF_VIRTUAL) && v->type == vli.vtype && v->owner == vli.company && v->IsPrimaryVehicle()) { + *list->Append() = v; + } + } + }; + switch (vli.type) { case VL_STATION_LIST: FOR_ALL_VEHICLES(v) { @@ -156,14 +165,11 @@ bool GenerateVehicleSortList(VehicleList *list, const VehicleListIdentifier &vli } break; } - /* FALL THROUGH */ + fill_all_vehicles(); + break; case VL_STANDARD: - FOR_ALL_VEHICLES(v) { - if (!HasBit(v->subtype, GVSF_VIRTUAL) && v->type == vli.vtype && v->owner == vli.company && v->IsPrimaryVehicle()) { - *list->Append() = v; - } - } + fill_all_vehicles(); break; case VL_DEPOT_LIST: @@ -181,6 +187,19 @@ bool GenerateVehicleSortList(VehicleList *list, const VehicleListIdentifier &vli } break; + case VL_SLOT_LIST: { + if (vli.index == ALL_TRAINS_TRACE_RESTRICT_SLOT_ID) { + fill_all_vehicles(); + } else { + const TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(vli.index); + if (slot == NULL) return false; + for (VehicleID id : slot->occupants) { + *list->Append() = Vehicle::Get(id); + } + } + break; + } + default: return false; } diff --git a/src/vehiclelist.h b/src/vehiclelist.h index 996c8c007f..2e586fcea3 100644 --- a/src/vehiclelist.h +++ b/src/vehiclelist.h @@ -24,6 +24,7 @@ enum VehicleListType { VL_STATION_LIST, VL_DEPOT_LIST, VL_GROUP_LIST, + VL_SLOT_LIST, VLT_END }; diff --git a/src/window_type.h b/src/window_type.h index 4625741b5c..f17f2cc69b 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -712,6 +712,12 @@ enum WindowClass { */ WC_TRACE_RESTRICT, + /** + * Trace restrict slot window; %Window numbers: + * - Packed value = #SlotListWidgets / #VehicleListWidgets + */ + WC_TRACE_RESTRICT_SLOTS, + /** * Programmable signals window */