diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index e69ac66eb2..2fac08703a 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 96d88f3854..f67a449452 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -199,6 +199,11 @@ CommandProc CmdSetTimetableStart; CommandProc CmdOpenCloseAirport; CommandProc CmdProgramSignalTraceRestrict; +CommandProc CmdCreateTraceRestrictSlot; +CommandProc CmdAlterTraceRestrictSlot; +CommandProc CmdDeleteTraceRestrictSlot; +CommandProc CmdAddVehicleTraceRestrictSlot; +CommandProc CmdRemoveVehicleTraceRestrictSlot; #define DEF_CMD(proc, flags, type) {proc, #proc, (CommandFlags)flags, type} @@ -358,6 +363,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 }; /*! diff --git a/src/command_type.h b/src/command_type.h index 1f99797ada..ec12411a7d 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -330,6 +330,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_END, ///< Must ALWAYS be on the end of this list!! (period) }; diff --git a/src/group_gui.cpp b/src/group_gui.cpp index ef877ab2be..e6266d3f0e 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -848,6 +848,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 92e77893cd..582ab8bc42 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2453,6 +2453,16 @@ STR_TRACE_RESTRICT_DIRECTION_NE :north-east STR_TRACE_RESTRICT_DIRECTION_SE :south-east STR_TRACE_RESTRICT_DIRECTION_SW :south-west STR_TRACE_RESTRICT_DIRECTION_NW :north-west +STR_TRACE_RESTRICT_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 :Release +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_ITEM :Release slot: {STRING1} +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 @@ -2463,6 +2473,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 @@ -2499,6 +2523,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... # Bridge selection window STR_SELECT_RAIL_BRIDGE_CAPTION :{WHITE}Select Rail Bridge 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/strings.cpp b/src/strings.cpp index 3a920811e8..2f46d4a69b 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" @@ -1677,6 +1678,15 @@ 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; diff --git a/src/table/control_codes.h b/src/table/control_codes.h index 14fd6647f8..7e1c36756b 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, diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h index 04c6b26b37..eb56aec7dd 100644 --- a/src/table/strgen_tables.h +++ b/src/table/strgen_tables.h @@ -119,6 +119,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}, diff --git a/src/tracerestrict.cpp b/src/tracerestrict.cpp index 58e24e51e2..7d2e81aec7 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 @@ -446,6 +450,32 @@ void TraceRestrictProgram::Execute(const Train* v, const TraceRestrictProgramInp } 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: + if (input.permitted_slot_operations & TRPISP_RELEASE) slot->Vacate(v->index); + break; + + default: + NOT_REACHED(); + break; + } + break; + } + default: NOT_REACHED(); } @@ -559,12 +589,32 @@ CommandCost TraceRestrictProgram::Validate(const std::vector 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: + actions_used_flags |= TRPAUF_SLOT_RELEASE; + 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(); @@ -650,6 +700,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; @@ -1298,3 +1353,304 @@ void TraceRestrictRemoveGroupID(GroupID index) // 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 && GetTraceRestrictValue(item) == index) { + SetTraceRestrictValueDefault(item, TRVT_SLOT_INDEX); // this updates the instruction in-place + } + if (IsTraceRestrictDoubleItem(item)) i++; + } + } +} + +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 520e183477..a18f41520d 100644 --- a/src/tracerestrict.h +++ b/src/tracerestrict.h @@ -14,12 +14,15 @@ #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 #include +#include struct Train; @@ -35,6 +38,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) @@ -101,6 +117,7 @@ enum TraceRestrictItemType { 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 @@ -203,6 +220,16 @@ 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 = 2, ///< release a slot + /* space up to 8 */ +}; + /** * TraceRestrictItem pathfinder penalty preset index * This may not be shortened, only lengthened, as preset indexes are stored in save games @@ -233,9 +260,20 @@ enum TraceRestrictProgramActionsUsedFlags { 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 = 1 << 5, ///< Slot release 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 = 1 << 1, ///< Slot release is permitted +}; +DECLARE_ENUM_AS_BIT_SET(TraceRestrictProgramInputSlotPermissions) + /** * Execution input of a TraceRestrictProgram */ @@ -246,9 +284,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)) { } }; /** @@ -444,6 +484,7 @@ enum TraceRestrictValueType { 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 }; /** @@ -562,6 +603,8 @@ static inline TraceRestrictTypePropertySet GetTraceRestrictTypeProperties(TraceR 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; } @@ -660,5 +703,54 @@ void ShowTraceRestrictProgramWindow(TileIndex tile, Track track); void TraceRestrictRemoveDestinationID(TraceRestrictOrderCondAuxField type, uint16 index); void TraceRestrictRemoveGroupID(GroupID index); +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 7106111013..9858292d5e 100644 --- a/src/tracerestrict_gui.cpp +++ b/src/tracerestrict_gui.cpp @@ -37,7 +37,12 @@ #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 "core/geometry_func.hpp" /** Widget IDs */ enum TraceRestrictWindowWidgets { @@ -58,6 +63,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, @@ -91,6 +97,7 @@ enum PanelWidgets { // Middle DPM_COMPARATOR = 0, + DPM_SLOT_OP, DPM_BLANK, // Right @@ -128,6 +135,7 @@ static const StringID _program_insert_str[] = { 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 @@ -143,6 +151,7 @@ static const uint _program_insert_val[] = { 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 */ @@ -288,6 +297,7 @@ static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictG 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[] = { @@ -296,6 +306,7 @@ static const TraceRestrictDropDownListSet *GetTypeDropDownListSet(TraceRestrictG TRIT_RESERVE_THROUGH, TRIT_LONG_RESERVE, TRIT_WAIT_AT_PBS, + TRIT_SLOT, }; static const TraceRestrictDropDownListSet set_action = { str_action, val_action, @@ -401,6 +412,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, @@ -415,6 +467,22 @@ 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, + INVALID_STRING_ID, +}; +static const uint _slot_op_cond_ops_val[] = { + TRSCOF_ACQUIRE_WAIT, + TRSCOF_ACQUIRE_TRY, + TRSCOF_RELEASE, +}; +/** 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 */ @@ -903,6 +971,33 @@ static void DrawInstructionString(const TraceRestrictProgram *prog, TraceRestric 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: + instruction_string = STR_TRACE_RESTRICT_SLOT_RELEASE_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; @@ -1101,6 +1196,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; @@ -1165,6 +1266,13 @@ public: 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; } @@ -1181,9 +1289,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); @@ -1252,10 +1362,13 @@ public: return; } - if (widget == TR_WIDGET_VALUE_DROPDOWN && GetTraceRestrictTypeProperties(item).value_type == TRVT_GROUP_INDEX) { - 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 (type.value_type == TRVT_GROUP_INDEX || type.value_type == TRVT_SLOT_INDEX) { + 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]; @@ -1305,7 +1418,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; @@ -1600,9 +1714,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 +1887,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 +1904,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); @@ -2013,6 +2131,32 @@ private: } break; + case TRVT_SLOT_INDEX: + middle_sel->SetDisplayedPlane(DPM_SLOT_OP); + right_sel->SetDisplayedPlane(DPR_VALUE_DROPDOWN); + 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; } @@ -2148,6 +2292,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(), @@ -2209,3 +2355,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 93c5cf3dc5..d46f050342 100644 --- a/src/train.h +++ b/src/train.h @@ -27,6 +27,7 @@ struct Train; 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 c43d4d2925..7409714073 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1321,6 +1321,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 @@ -2592,9 +2596,11 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, 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) { + if (prog && prog->actions_used_flags & (TRPAUF_WAIT_AT_PBS | TRPAUF_SLOT_ACQUIRE)) { TraceRestrictProgramResult out; - prog->Execute(v, TraceRestrictProgramInput(tile, TrackEnterdirToTrackdir(track, enterdir), NULL, NULL), 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; @@ -3317,6 +3323,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 (HasSignalOnTrackdir(gp.new_tile, dir)) { + const TraceRestrictProgram *prog = GetExistingTraceRestrictProgram(gp.new_tile, TrackdirToTrack(dir)); + if (prog && prog->actions_used_flags & TRPAUF_SLOT_ACQUIRE) { + TraceRestrictProgramResult out; + TraceRestrictProgramInput input(gp.new_tile, dir, NULL, NULL); + input.permitted_slot_operations = TRPISP_ACQUIRE; + prog->Execute(v, input, out); + } + } + } } } else { /* The wagon is active, simply follow the prev vehicle. */ @@ -3504,6 +3523,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) { + TraceRestrictProgramResult out; + TraceRestrictProgramInput input(gp.old_tile, ReverseTrackdir(rev_trackdir), NULL, NULL); + input.permitted_slot_operations = TRPISP_RELEASE; + prog->Execute(first, input, out); + } + } + } } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 4dbdf24dff..c1a3e40027 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -50,6 +50,7 @@ #include "tunnel_map.h" #include "depot_map.h" #include "gamelog.h" +#include "tracerestrict.h" #include "linkgraph/linkgraph.h" #include "linkgraph/refresh.h" @@ -797,6 +798,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()) { @@ -817,6 +823,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 45bea35e14..204c4aeb3d 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -38,6 +38,9 @@ #include "station_base.h" #include "tilehighlight_func.h" #include "zoom_func.h" +#include "tracerestrict.h" + +#include #include "safeguards.h" @@ -176,6 +179,9 @@ DropDownList *BaseVehicleListWindow::BuildActionDropdownList(bool show_autorepla *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); } + if (this->vli.vtype == VEH_TRAIN) { + *list->Append() = new DropDownListStringItem(STR_TRACE_RESTRICT_SLOT_MANAGE, ADI_TRACERESTRICT_SLOT_MGMT, false); + } return list; } @@ -1668,6 +1674,12 @@ public: DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : (DepotCommand)0), this->window_number, GetCmdSendToDepot(this->vli.vtype)); break; + case ADI_TRACERESTRICT_SLOT_MGMT: { + extern void ShowTraceRestrictSlotWindow(CompanyID company); + ShowTraceRestrictSlotWindow(this->owner); + break; + } + default: NOT_REACHED(); } break; @@ -1867,6 +1879,7 @@ struct VehicleDetailsWindow : Window { TrainDetailsWindowTabs tab; ///< For train vehicles: which tab is displayed. Scrollbar *vscroll; 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) @@ -1933,6 +1946,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) { @@ -1940,8 +1959,10 @@ struct VehicleDetailsWindow : Window { const Vehicle *v = Vehicle::Get(this->window_number); Dimension dim = { 0, 0 }; this->vehicle_weight_ratio_line_shown = ShouldShowWeightRatioLine(v); + this->vehicle_slots_line_shown = ShouldShowSlotsLine(v); int lines = 4; 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 < 4; i++) SetDParamMaxValue(i, INT16_MAX); @@ -2117,8 +2138,29 @@ struct VehicleDetailsWindow : Window { SetDParam(0, ToPercent16(v->reliability)); SetDParam(1, v->breakdowns_since_last_service); DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS); + y += FONT_HEIGHT_NORMAL; - if (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_slots_line_shown != should_show_slots) { const_cast(this)->ReInit(); } break; diff --git a/src/vehicle_gui_base.h b/src/vehicle_gui_base.h index 5755c7fa88..d4426afd67 100644 --- a/src/vehicle_gui_base.h +++ b/src/vehicle_gui_base.h @@ -32,6 +32,7 @@ struct BaseVehicleListWindow : public Window { ADI_DEPOT, ADI_ADD_SHARED, ADI_REMOVE_ALL, + ADI_TRACERESTRICT_SLOT_MGMT, }; static const StringID vehicle_depot_name[]; diff --git a/src/vehiclelist.cpp b/src/vehiclelist.cpp index f1f5d0424a..dcfbc9afe6 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 (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 (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 48491e6601..d1df7c844a 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -687,6 +687,12 @@ enum WindowClass { */ WC_TRACE_RESTRICT, + /** + * Trace restrict slot window; %Window numbers: + * - Packed value = #SlotListWidgets / #VehicleListWidgets + */ + WC_TRACE_RESTRICT_SLOTS, + WC_INVALID = 0xFFFF, ///< Invalid window. };