Initial implementation of trace restrict slots

pull/16/head^2
Jonathan G Rennison 7 years ago
parent 688ee9ac11
commit 6417fb16c3

@ -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 */

@ -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
};
/*!

@ -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)
};

@ -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;

@ -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

@ -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
};

@ -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;

@ -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,

@ -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},

@ -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 <vector>
#include <algorithm>
@ -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<TraceRestrictSlotCondOpField>(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<TraceRestrictItem>
actions_used_flags |= TRPAUF_WAIT_AT_PBS;
break;
case TRIT_SLOT:
switch (static_cast<TraceRestrictSlotCondOpField>(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<VehicleID, TraceRestrictSlotID> 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<TraceRestrictSlotID> 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<TraceRestrictSlotID> &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();
}

@ -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 <map>
#include <vector>
#include <unordered_map>
struct Train;
@ -35,6 +38,19 @@ typedef Pool<TraceRestrictProgram, TraceRestrictProgramID, 16, 256000> 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<TraceRestrictSlot, TraceRestrictSlotID, 16, 0xFFF0> 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<TraceRestrictProgramInputSlotPermissions>(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<TraceRestrictSlotID> &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<VehicleID> 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 */

@ -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<const TraceRestrictSlot*> 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<TraceRestrictSlotCondOpField>(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<TraceRestrictCondOp>(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<NWidgetCore>(TR_WIDGET_SLOT_OP)->widget_data =
GetDropDownStringByValue(&_slot_op_cond_ops, GetTraceRestrictCondOp(item));
switch (GetTraceRestrictValue(item)) {
case INVALID_TRACE_RESTRICT_SLOT_ID:
this->GetWidget<NWidgetCore>(TR_WIDGET_VALUE_DROPDOWN)->widget_data = STR_TRACE_RESTRICT_VARIABLE_UNDEFINED;
break;
default:
this->GetWidget<NWidgetCore>(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<const TraceRestrictSlot*> 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<NWidgetCore>(WID_TRSL_CREATE_SLOT)->widget_data).height;
max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_TRSL_DELETE_SLOT)->widget_data).height);
max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_TRSL_RENAME_SLOT)->widget_data).height);
max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(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<NWidgetCore>(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<NWidgetCore>(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<TraceRestrictSlotWindow>(&_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);
}

@ -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.

@ -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);
}
}
}
}
}

@ -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);
}

@ -38,6 +38,9 @@
#include "station_base.h"
#include "tilehighlight_func.h"
#include "zoom_func.h"
#include "tracerestrict.h"
#include <vector>
#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;
bool should_show_slots = this->ShouldShowSlotsLine(v);
if (should_show_slots) {
std::vector<TraceRestrictSlotID> 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) {
if (this->vehicle_weight_ratio_line_shown != should_show_weight_ratio || this->vehicle_slots_line_shown != should_show_slots) {
const_cast<VehicleDetailsWindow *>(this)->ReInit();
}
break;

@ -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[];

@ -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;
}

@ -24,6 +24,7 @@ enum VehicleListType {
VL_STATION_LIST,
VL_DEPOT_LIST,
VL_GROUP_LIST,
VL_SLOT_LIST,
VLT_END
};

@ -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.
};

Loading…
Cancel
Save