From 6c329871f1dde73863d84600117c3bd3b9d39c68 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sun, 28 Jan 2024 01:48:54 +0000 Subject: [PATCH] Scheduled dispatch: Add per dispatch slot flags field Add flag for slot re-use --- src/command.cpp | 2 + src/command_type.h | 1 + src/departures.cpp | 14 ++-- src/lang/extra/english.txt | 14 ++-- src/order_base.h | 24 ++++++- src/order_cmd.cpp | 4 +- src/schdispatch_cmd.cpp | 68 ++++++++++++++++--- src/schdispatch_gui.cpp | 136 +++++++++++++++++++++++++++++++------ src/sl/extended_ver_sl.cpp | 2 +- src/sl/order_sl.cpp | 34 +++++++++- src/timetable_cmd.cpp | 10 ++- 11 files changed, 260 insertions(+), 49 deletions(-) diff --git a/src/command.cpp b/src/command.cpp index 9034153ea1..bcee576496 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -305,6 +305,7 @@ CommandProc CmdScheduledDispatchDuplicateSchedule; CommandProc CmdScheduledDispatchAppendVehicleSchedules; CommandProc CmdScheduledDispatchAdjust; CommandProc CmdScheduledDispatchSwapSchedules; +CommandProcEx CmdScheduledDispatchSetSlotFlags; CommandProc CmdAddPlan; CommandProcEx CmdAddPlanLine; @@ -569,6 +570,7 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdScheduledDispatchAppendVehicleSchedules, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SCHEDULED_DISPATCH_APPEND_VEHICLE_SCHEDULE DEF_CMD(CmdScheduledDispatchAdjust, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SCHEDULED_DISPATCH_ADJUST DEF_CMD(CmdScheduledDispatchSwapSchedules, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SCHEDULED_DISPATCH_SWAP_SCHEDULES + DEF_CMD(CmdScheduledDispatchSetSlotFlags, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SCHEDULED_DISPATCH_SET_SLOT_FLAGS DEF_CMD(CmdAddPlan, CMD_NO_TEST, CMDT_OTHER_MANAGEMENT ), // CMD_ADD_PLAN DEF_CMD(CmdAddPlanLine, CMD_NO_TEST, CMDT_OTHER_MANAGEMENT ), // CMD_ADD_PLAN_LINE diff --git a/src/command_type.h b/src/command_type.h index 95ee9d986e..32c155b856 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -521,6 +521,7 @@ enum Commands { CMD_SCHEDULED_DISPATCH_APPEND_VEHICLE_SCHEDULE, ///< scheduled dispatch append schedules from another vehicle CMD_SCHEDULED_DISPATCH_ADJUST, ///< scheduled dispatch adjust time offsets in schedule CMD_SCHEDULED_DISPATCH_SWAP_SCHEDULES, ///< scheduled dispatch swap schedules in order + CMD_SCHEDULED_DISPATCH_SET_SLOT_FLAGS, ///< scheduled dispatch set flags of dispatch slot CMD_ADD_PLAN, CMD_ADD_PLAN_LINE, diff --git a/src/departures.cpp b/src/departures.cpp index af3f754714..e15263015b 100644 --- a/src/departures.cpp +++ b/src/departures.cpp @@ -132,15 +132,19 @@ static bool VehicleSetNextDepartureTime(Ticks *previous_departure, Ticks *waitin btree::btree_set &slot_cache = dept_schedule_last[&ds]; /* Find next available slots */ - for (auto current_offset : ds.GetScheduledDispatch()) { - if (current_offset >= dispatch_duration) continue; - DateTicksScaled current_departure = begin_time + current_offset; + for (const DispatchSlot &slot : ds.GetScheduledDispatch()) { + if (slot.offset >= dispatch_duration) continue; + DateTicksScaled current_departure = begin_time + slot.offset; while (current_departure <= earliest_departure) { current_departure += dispatch_duration; } /* Make sure the slots has not already been used previously in this departure board calculation */ while (slot_cache.count(current_departure) > 0) { + if (HasBit(slot.flags, DispatchSlot::SDSF_REUSE_SLOT)) { + /* Allow re-use of this slot if it's the last seen */ + if (*slot_cache.rbegin() == current_departure) break; + } current_departure += dispatch_duration; } @@ -151,7 +155,9 @@ static bool VehicleSetNextDepartureTime(Ticks *previous_departure, Ticks *waitin *waiting_time = (actual_departure - date_ticks_base).AsTicks() - *previous_departure - order->GetTravelTime(); *previous_departure = (actual_departure - date_ticks_base).AsTicks(); - slot_cache.insert(actual_departure); + if (!ds.GetScheduledDispatchReuseSlots()) { + slot_cache.insert(actual_departure); + } /* Return true means that vehicle lateness should be clear from this point onward */ return true; diff --git a/src/lang/extra/english.txt b/src/lang/extra/english.txt index 7b23f4e847..d55e70883e 100644 --- a/src/lang/extra/english.txt +++ b/src/lang/extra/english.txt @@ -2054,7 +2054,7 @@ STR_TMPL_CANT_RENAME :{WHITE}Can't re STR_SCHDISPATCH_CAPTION :{WHITE}{VEHICLE} (Scheduled Dispatch) STR_SCHDISPATCH_ENABLED :{BLACK}Enable STR_SCHDISPATCH_ENABLED_TOOLTIP :{BLACK}Enable scheduled dispatching for this order. Requires automatic separation to be off. -STR_SCHDISPATCH_ADD :{BLACK}Add Departure Slot +STR_SCHDISPATCH_ADD :{BLACK}Add Slot STR_SCHDISPATCH_ADD_TOOLTIP :{BLACK}Add new departure slot for this schedule. STR_SCHDISPATCH_ADD_TOOLTIP_EXTRA :{BLACK}{STRING} Ctrl+Click to add multiple departure slots at once. STR_SCHDISPATCH_ADD_CAPTION :{BLACK}Departure slot @@ -2074,9 +2074,9 @@ STR_SCHDISPATCH_DELAY_TOOLTIP :{BLACK}Set maxi STR_SCHDISPATCH_DELAY_CAPTION_MINUTE :{BLACK}Delay (minutes) STR_SCHDISPATCH_DELAY_CAPTION_DAY :{BLACK}Delay (days) STR_SCHDISPATCH_DELAY_CAPTION_TICKS :{BLACK}Delay (ticks) -STR_SCHDISPATCH_ADJUST :{BLACK}Adjust Departure Slots +STR_SCHDISPATCH_ADJUST :{BLACK}Adjust Slots STR_SCHDISPATCH_ADJUST_TOOLTIP :{BLACK}Adjust all departure slots in this schedule. -STR_SCHDISPATCH_REMOVE :{BLACK}Remove Departure Slots +STR_SCHDISPATCH_REMOVE :{BLACK}Remove Slots STR_SCHDISPATCH_REMOVE_TOOLTIP :{BLACK}Enable removing departure slots from this schedule. STR_SCHDISPATCH_ADJUST_CAPTION_MINUTE :{BLACK}Adjustment to add (minutes) STR_SCHDISPATCH_ADJUST_CAPTION_DAY :{BLACK}Adjustment to add (days) @@ -2099,8 +2099,11 @@ STR_SCHDISPATCH_DUPLICATE_SCHEDULE :{BLACK}Duplicat STR_SCHDISPATCH_DUPLICATE_SCHEDULE_TOOLTIP :{BLACK}Create a copy of this dispatch schedule. STR_SCHDISPATCH_APPEND_VEHICLE_SCHEDULES :{BLACK}Append Schedules From Vehicle STR_SCHDISPATCH_APPEND_VEHICLE_SCHEDULES_TOOLTIP :{BLACK}Append the dispatch schedules from another vehicle. -STR_SCHDISPATCH_APPEND_REUSE_DEPARTURE_SLOTS :{BLACK}Re-use departure slots -STR_SCHDISPATCH_APPEND_REUSE_DEPARTURE_SLOTS_TOOLTIP :{BLACK}Set whether departure slots may be used more than once. +STR_SCHDISPATCH_REUSE_DEPARTURE_SLOTS :Re-use departure slots +STR_SCHDISPATCH_REUSE_DEPARTURE_SLOTS_TOOLTIP :{BLACK}Set whether departure slots may be used more than once. +STR_SCHDISPATCH_MANAGE_SLOT :{BLACK}Manage Slot +STR_SCHDISPATCH_REUSE_THIS_DEPARTURE_SLOT :Re-use departure slot +STR_SCHDISPATCH_REUSE_THIS_DEPARTURE_SLOT_TOOLTIP :{BLACK}Set whether the selected departure slot may be used more than once. STR_SCHDISPATCH_NO_SCHEDULES :{BLACK}No Schedules STR_SCHDISPATCH_SCHEDULE_ID :{BLACK}Schedule {NUM} of {NUM} STR_SCHDISPATCH_NAMED_SCHEDULE_ID :{BLACK}{RAW_STRING} ({NUM} of {NUM}) @@ -2108,6 +2111,7 @@ STR_SCHDISPATCH_RENAME_SCHEDULE_TOOLTIP :{BLACK}Name sch STR_SCHDISPATCH_RENAME_SCHEDULE_CAPTION :{WHITE}Name schedule STR_ERROR_CAN_T_RENAME_SCHEDULE :{WHITE}Can't name schedule... STR_SCHDISPATCH_MOVE_SCHEDULE :{BLACK}Change position of current schedule in schedule list +STR_SCHDISPATCH_DATE_WALLCLOCK_TINY_FLAGGED :{DATE_WALLCLOCK_TINY}* STR_SCHDISPATCH_SUMMARY_NO_LAST_DEPARTURE :{BLACK}No previous departures. STR_SCHDISPATCH_SUMMARY_LAST_DEPARTURE_PAST :{BLACK}Last departure at {DATE_WALLCLOCK_TINY}. diff --git a/src/order_base.h b/src/order_base.h index ec55331535..7bd6da4b9c 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -716,11 +716,28 @@ template T CargoMaskValueFilter(CargoTypes &cargo_mask, return value; } +struct DispatchSlot { + uint32_t offset; + uint16_t flags; + + bool operator<(const DispatchSlot &other) const + { + return this->offset < other.offset; + } + + /** + * Flag bit numbers for scheduled_dispatch_flags + */ + enum ScheduledDispatchSlotFlags { + SDSF_REUSE_SLOT = 0, ///< Allow this slot to be used more than once + }; +}; + struct DispatchSchedule { private: friend SaveLoadTable GetDispatchScheduleDescription(); ///< Saving and loading of dispatch schedules - std::vector scheduled_dispatch; ///< Scheduled dispatch time + std::vector scheduled_dispatch; ///< Scheduled dispatch slots DateTicksScaled scheduled_dispatch_start_tick = -1; ///< Scheduled dispatch start tick uint32_t scheduled_dispatch_duration = 0; ///< Scheduled dispatch duration int32_t scheduled_dispatch_last_dispatch = INVALID_SCHEDULED_DISPATCH_OFFSET; ///< Last vehicle dispatched offset @@ -750,9 +767,10 @@ public: * Get the vector of all scheduled dispatch slot * @return first scheduled dispatch */ - inline const std::vector &GetScheduledDispatch() const { return this->scheduled_dispatch; } + inline const std::vector &GetScheduledDispatch() const { return this->scheduled_dispatch; } + inline std::vector &GetScheduledDispatchMutable() { return this->scheduled_dispatch; } - void SetScheduledDispatch(std::vector dispatch_list); + void SetScheduledDispatch(std::vector dispatch_list); void AddScheduledDispatch(uint32_t offset); void RemoveScheduledDispatch(uint32_t offset); void AdjustScheduledDispatch(int32_t adjust); diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 6f7fd633f5..0cc122cd95 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -3068,9 +3068,9 @@ bool EvaluateDispatchSlotConditionalOrder(const Order *order, const Vehicle *v, bool value; if (order->GetConditionValue() & 1) { - value = (offset == (int)sched.GetScheduledDispatch().back()); + value = (offset == (int32_t)sched.GetScheduledDispatch().back().offset); } else { - value = (offset == (int)sched.GetScheduledDispatch().front()); + value = (offset == (int32_t)sched.GetScheduledDispatch().front().offset); } return OrderConditionCompare(order->GetConditionComparator(), value ? 1 : 0, 0); diff --git a/src/schdispatch_cmd.cpp b/src/schdispatch_cmd.cpp index 10263c10be..a31b7f931a 100644 --- a/src/schdispatch_cmd.cpp +++ b/src/schdispatch_cmd.cpp @@ -647,11 +647,61 @@ CommandCost CmdScheduledDispatchSwapSchedules(TileIndex tile, DoCommandFlag flag return CommandCost(); } +/** + * Add scheduled dispatch time offset + * @param tile Not used. + * @param flags Operation to perform. + * @param p1 Vehicle index. + * @param p2 Slot offset. + * @param p3 various bitstuffed elements + * - p3 = (bit 0 - 15) - flag values + * - p3 = (bit 16 - 31) - flag mask + * @param text unused + * @return the cost of this operation or an error + */ +CommandCost CmdScheduledDispatchSetSlotFlags(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, uint64_t p3, const char *text, const CommandAuxiliaryBase *aux_data) +{ + VehicleID veh = GB(p1, 0, 20); + uint schedule_index = GB(p1, 20, 12); + uint32_t offset = p2; + uint16_t values = (uint16_t)GB(p3, 0, 16); + uint16_t mask = (uint16_t)GB(p3, 16, 16); + + const uint16_t permitted_mask = (1 << DispatchSlot::SDSF_REUSE_SLOT); + if ((mask & permitted_mask) != mask) return CMD_ERROR; + if ((values & (~mask)) != 0) return CMD_ERROR; + + Vehicle *v = Vehicle::GetIfValid(veh); + if (v == nullptr || !v->IsPrimaryVehicle()) return CMD_ERROR; + + CommandCost ret = CheckOwnership(v->owner); + if (ret.Failed()) return ret; + + if (v->orders == nullptr) return CMD_ERROR; + + if (schedule_index >= v->orders->GetScheduledDispatchScheduleCount()) return CMD_ERROR; + + DispatchSchedule &ds = v->orders->GetDispatchScheduleByIndex(schedule_index); + for (DispatchSlot &slot : ds.GetScheduledDispatchMutable()) { + if (slot.offset == offset) { + if (flags & DC_EXEC) { + slot.flags &= ~mask; + slot.flags |= values; + SchdispatchInvalidateWindows(v); + SetTimetableWindowsDirty(v, STWDF_SCHEDULED_DISPATCH); + } + return CommandCost(); + } + } + + return CMD_ERROR; +} + /** * Set scheduled dispatch slot list. * @param dispatch_list The offset time list, must be correctly sorted. */ -void DispatchSchedule::SetScheduledDispatch(std::vector dispatch_list) +void DispatchSchedule::SetScheduledDispatch(std::vector dispatch_list) { this->scheduled_dispatch = std::move(dispatch_list); assert(std::is_sorted(this->scheduled_dispatch.begin(), this->scheduled_dispatch.end())); @@ -665,11 +715,11 @@ void DispatchSchedule::SetScheduledDispatch(std::vector dispatch_list) void DispatchSchedule::AddScheduledDispatch(uint32_t offset) { /* Maintain sorted list status */ - auto insert_position = std::lower_bound(this->scheduled_dispatch.begin(), this->scheduled_dispatch.end(), offset); - if (insert_position != this->scheduled_dispatch.end() && *insert_position == offset) { + auto insert_position = std::lower_bound(this->scheduled_dispatch.begin(), this->scheduled_dispatch.end(), DispatchSlot{ offset, 0 }); + if (insert_position != this->scheduled_dispatch.end() && insert_position->offset == offset) { return; } - this->scheduled_dispatch.insert(insert_position, offset); + this->scheduled_dispatch.insert(insert_position, { offset, 0 }); this->UpdateScheduledDispatch(nullptr); } @@ -680,8 +730,8 @@ void DispatchSchedule::AddScheduledDispatch(uint32_t offset) void DispatchSchedule::RemoveScheduledDispatch(uint32_t offset) { /* Maintain sorted list status */ - auto erase_position = std::lower_bound(this->scheduled_dispatch.begin(), this->scheduled_dispatch.end(), offset); - if (erase_position == this->scheduled_dispatch.end() || *erase_position != offset) { + auto erase_position = std::lower_bound(this->scheduled_dispatch.begin(), this->scheduled_dispatch.end(), DispatchSlot{ offset, 0 }); + if (erase_position == this->scheduled_dispatch.end() || erase_position->offset != offset) { return; } this->scheduled_dispatch.erase(erase_position); @@ -693,11 +743,11 @@ void DispatchSchedule::RemoveScheduledDispatch(uint32_t offset) */ void DispatchSchedule::AdjustScheduledDispatch(int32_t adjust) { - for (uint32_t &time : this->scheduled_dispatch) { - int32_t t = (int32_t)time + adjust; + for (DispatchSlot &slot : this->scheduled_dispatch) { + int32_t t = (int32_t)slot.offset + adjust; if (t < 0) t += GetScheduledDispatchDuration(); if (t >= (int32_t)GetScheduledDispatchDuration()) t -= (int32_t)GetScheduledDispatchDuration(); - time = (uint32_t)t; + slot.offset = (uint32_t)t; } std::sort(this->scheduled_dispatch.begin(), this->scheduled_dispatch.end()); } diff --git a/src/schdispatch_gui.cpp b/src/schdispatch_gui.cpp index 0d9a4abad2..bcd2bf783f 100644 --- a/src/schdispatch_gui.cpp +++ b/src/schdispatch_gui.cpp @@ -60,6 +60,7 @@ enum SchdispatchWidgets { WID_SCHDISPATCH_MANAGEMENT, ///< Management button WID_SCHDISPATCH_ADJUST, ///< Adjust departure times WID_SCHDISPATCH_REMOVE, ///< Remove departure times + WID_SCHDISPATCH_MANAGE_SLOT, ///< Manage slot button }; /** @@ -129,10 +130,10 @@ static void ScheduleAddCallback(const Window *w, DateTicksScaled date) * @param offsets list of all dispatch offsets in the schedule * @return maxinum number of vehicle required */ -static int CalculateMaxRequiredVehicle(Ticks timetable_duration, uint32_t schedule_duration, std::vector offsets) +static int CalculateMaxRequiredVehicle(Ticks timetable_duration, uint32_t schedule_duration, const std::vector &slots) { if (timetable_duration == INVALID_TICKS) return -1; - if (offsets.size() == 0) return -1; + if (slots.size() == 0) return -1; /* Number of time required to ensure all vehicle are counted */ int required_loop = CeilDiv(timetable_duration, schedule_duration) + 1; @@ -140,10 +141,10 @@ static int CalculateMaxRequiredVehicle(Ticks timetable_duration, uint32_t schedu /* Create indice array to count maximum overlapping range */ std::vector> indices; for (int i = 0; i < required_loop; i++) { - for (uint32_t offset : offsets) { - if (offset >= schedule_duration) continue; - indices.push_back(std::make_pair(i * schedule_duration + offset, 1)); - indices.push_back(std::make_pair(i * schedule_duration + offset + timetable_duration, -1)); + for (const DispatchSlot &slot : slots) { + if (slot.offset >= schedule_duration) continue; + indices.push_back(std::make_pair(i * schedule_duration + slot.offset, 1)); + indices.push_back(std::make_pair(i * schedule_duration + slot.offset + timetable_duration, -1)); } } if (indices.empty()) return -1; @@ -204,6 +205,7 @@ struct SchdispatchWindow : GeneralVehicleWindow { int arrow_flag_height = 0; bool remove_slot_mode = false; + uint32_t selected_slot = UINT32_MAX; enum ManagementDropdown { SCH_MD_RESET_LAST_DISPATCHED, @@ -214,6 +216,10 @@ struct SchdispatchWindow : GeneralVehicleWindow { SCH_MD_REUSE_DEPARTURE_SLOTS, }; + enum SlotManagementDropdown { + SCH_SMD_REUSE_DEPARTURE_SLOT, + }; + SchdispatchWindow(WindowDesc *desc, WindowNumber window_number) : GeneralVehicleWindow(desc, Vehicle::Get(window_number)) @@ -248,6 +254,7 @@ struct SchdispatchWindow : GeneralVehicleWindow { } else { this->schedule_index = -1; } + this->selected_slot = UINT32_MAX; } } @@ -256,6 +263,21 @@ struct SchdispatchWindow : GeneralVehicleWindow { return this->vehicle->orders->GetDispatchScheduleByIndex(this->schedule_index); } + const DispatchSlot *GetSelectedDispatchSlot() const + { + if (!this->IsScheduleSelected()) return nullptr; + + const DispatchSchedule &ds = this->GetSelectedSchedule(); + if (this->selected_slot != UINT32_MAX) { + for (const DispatchSlot &slot : ds.GetScheduledDispatch()) { + if (slot.offset == this->selected_slot) { + return &slot; + } + } + } + return nullptr; + } + virtual void UpdateWidgetSize(WidgetID widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override { switch (widget) { @@ -263,7 +285,7 @@ struct SchdispatchWindow : GeneralVehicleWindow { uint min_height = 0; SetDParamMaxValue(0, _settings_time.time_in_minutes ? 0 : MAX_YEAR * DAYS_IN_YEAR); - Dimension unumber = GetStringBoundingBox(STR_JUST_DATE_WALLCLOCK_TINY); + Dimension unumber = GetStringBoundingBox(STR_SCHDISPATCH_DATE_WALLCLOCK_TINY_FLAGGED); const Sprite *spr = GetSprite(SPR_FLAG_VEH_STOPPED, SpriteType::Normal, ZoomMask(ZOOM_LVL_GUI)); this->delete_flag_width = UnScaleGUI(spr->width); @@ -351,6 +373,11 @@ struct SchdispatchWindow : GeneralVehicleWindow { this->SetWidgetDisabledState(WID_SCHDISPATCH_MANAGEMENT, disabled); this->SetWidgetDisabledState(WID_SCHDISPATCH_ADJUST, no_editable_slots); + if (no_editable_slots || this->GetSelectedDispatchSlot() == nullptr) { + this->selected_slot = UINT32_MAX; + } + this->SetWidgetDisabledState(WID_SCHDISPATCH_MANAGE_SLOT, this->selected_slot == UINT32_MAX); + NWidgetCore *remove_slot_widget = this->GetWidget(WID_SCHDISPATCH_REMOVE); remove_slot_widget->SetDisabled(no_editable_slots); if (no_editable_slots) { @@ -417,7 +444,13 @@ struct SchdispatchWindow : GeneralVehicleWindow { add_suffix(STR_SCHDISPATCH_REMOVE_SCHEDULE_TOOLTIP); add_suffix(STR_SCHDISPATCH_DUPLICATE_SCHEDULE_TOOLTIP); add_suffix(STR_SCHDISPATCH_APPEND_VEHICLE_SCHEDULES_TOOLTIP); - add_suffix(STR_SCHDISPATCH_APPEND_REUSE_DEPARTURE_SLOTS_TOOLTIP); + add_suffix(STR_SCHDISPATCH_REUSE_DEPARTURE_SLOTS_TOOLTIP); + GuiShowTooltips(this, SPECSTR_TEMP_START, close_cond); + return true; + } + + case WID_SCHDISPATCH_MANAGE_SLOT: { + _temp_special_strings[0] = GetString(STR_SCHDISPATCH_REUSE_THIS_DEPARTURE_SLOT_TOOLTIP); GuiShowTooltips(this, SPECSTR_TEMP_START, close_cond); return true; } @@ -436,7 +469,7 @@ struct SchdispatchWindow : GeneralVehicleWindow { * @param right Right side of the box to draw in. * @param y Top of the box to draw in. */ - void DrawScheduledTime(const DateTicksScaled time, int left, int right, int y, TextColour colour, bool last, bool next) const + void DrawScheduledTime(const DateTicksScaled time, int left, int right, int y, TextColour colour, bool last, bool next, bool flagged) const { bool rtl = _current_text_dir == TD_RTL; @@ -462,7 +495,7 @@ struct SchdispatchWindow : GeneralVehicleWindow { } SetDParam(0, time); - DrawString(text_left, text_right, y + 2, STR_JUST_DATE_WALLCLOCK_TINY, colour); + DrawString(text_left, text_right, y + 2, flagged ? STR_SCHDISPATCH_DATE_WALLCLOCK_TINY_FLAGGED : STR_JUST_DATE_WALLCLOCK_TINY, colour, SA_HOR_CENTER); } virtual void OnGameTick() override @@ -502,16 +535,26 @@ struct SchdispatchWindow : GeneralVehicleWindow { DateTicksScaled slot = GetScheduledDispatchTime(ds, _scaled_date_ticks); int32_t next_offset = (slot - ds.GetScheduledDispatchStartTick()).AsTicks() % ds.GetScheduledDispatchDuration(); + int32_t last_dispatch = ds.GetScheduledDispatchLastDispatch() % ds.GetScheduledDispatchDuration(); + for (int y = r.top + 1; num < maxval; y += this->resize.step_height) { /* Draw the rows */ for (uint i = 0; i < this->num_columns && num < maxval; i++, num++) { /* Draw all departure time in the current row */ if (current_schedule != ds.GetScheduledDispatch().end()) { int x = r.left + (rtl ? (this->num_columns - i - 1) : i) * this->resize.step_width; - DateTicksScaled draw_time = start_tick + *current_schedule; - bool last = ds.GetScheduledDispatchLastDispatch() == (int32_t)*current_schedule; - bool next = next_offset == (int32_t)*current_schedule; + DateTicksScaled draw_time = start_tick + current_schedule->offset; + bool last = last_dispatch == (int32_t)current_schedule->offset; + bool next = next_offset == (int32_t)current_schedule->offset; + TextColour colour; + if (this->selected_slot == current_schedule->offset) { + colour = TC_WHITE; + } else { + colour = draw_time >= end_tick ? TC_RED : TC_BLACK; + } + auto flags = current_schedule->flags; + if (ds.GetScheduledDispatchReuseSlots()) ClrBit(flags, DispatchSlot::SDSF_REUSE_SLOT); this->DrawScheduledTime(draw_time, x + WidgetDimensions::scaled.framerect.left, x + this->resize.step_width - 1 - (2 * WidgetDimensions::scaled.framerect.left), - y, draw_time >= end_tick ? TC_RED : TC_BLACK, last, next); + y, colour, last, next, flags != 0); current_schedule++; } else { break; @@ -699,8 +742,8 @@ struct SchdispatchWindow : GeneralVehicleWindow { y += GetCharacterHeight(FS_NORMAL); uint32_t duration = ds.GetScheduledDispatchDuration(); - for (uint32_t slot : ds.GetScheduledDispatch()) { - if (slot >= duration) { + for (const DispatchSlot &slot : ds.GetScheduledDispatch()) { + if (slot.offset >= duration) { draw_warning(STR_SCHDISPATCH_SLOT_OUTSIDE_SCHEDULE); break; } @@ -743,11 +786,26 @@ struct SchdispatchWindow : GeneralVehicleWindow { const DispatchSchedule &ds = this->GetSelectedSchedule(); - if (pos >= this->item_count || pos >= ds.GetScheduledDispatch().size()) return; + if (pos >= this->item_count || pos >= ds.GetScheduledDispatch().size()) { + if (this->selected_slot != UINT32_MAX) { + this->selected_slot = UINT32_MAX; + this->SetWidgetDirty(WID_SCHDISPATCH_MATRIX); + } + return; + } + + uint32_t offset = ds.GetScheduledDispatch()[pos].offset; if (xm <= this->header_width && this->remove_slot_mode) { - DoCommandP(0, this->vehicle->index | (this->schedule_index << 20), ds.GetScheduledDispatch()[pos], CMD_SCHEDULED_DISPATCH_REMOVE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); + DoCommandP(0, this->vehicle->index | (this->schedule_index << 20), offset, CMD_SCHEDULED_DISPATCH_REMOVE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE)); } + + if (this->selected_slot == offset) { + this->selected_slot = UINT32_MAX; + } else { + this->selected_slot = offset; + } + this->SetWidgetDirty(WID_SCHDISPATCH_MATRIX); } int32_t ProcessDurationForQueryString(int32_t duration) const @@ -842,20 +900,26 @@ struct SchdispatchWindow : GeneralVehicleWindow { add_item(STR_SCHDISPATCH_REMOVE_SCHEDULE, SCH_MD_REMOVE_SCHEDULE); add_item(STR_SCHDISPATCH_DUPLICATE_SCHEDULE, SCH_MD_DUPLICATE_SCHEDULE); add_item(STR_SCHDISPATCH_APPEND_VEHICLE_SCHEDULES, SCH_MD_APPEND_VEHICLE_SCHEDULES); - list.push_back(std::make_unique(schedule.GetScheduledDispatchReuseSlots(), STR_SCHDISPATCH_APPEND_REUSE_DEPARTURE_SLOTS, SCH_MD_REUSE_DEPARTURE_SLOTS, false)); + list.push_back(std::make_unique(schedule.GetScheduledDispatchReuseSlots(), STR_SCHDISPATCH_REUSE_DEPARTURE_SLOTS, SCH_MD_REUSE_DEPARTURE_SLOTS, false)); ShowDropDownList(this, std::move(list), -1, WID_SCHDISPATCH_MANAGEMENT); break; } case WID_SCHDISPATCH_PREV: if (!this->IsScheduleSelected()) break; - if (this->schedule_index > 0) this->schedule_index--; + if (this->schedule_index > 0) { + this->schedule_index--; + this->selected_slot = UINT32_MAX; + } this->ReInit(); break; case WID_SCHDISPATCH_NEXT: if (!this->IsScheduleSelected()) break; - if (this->schedule_index < (int)(this->vehicle->orders->GetScheduledDispatchScheduleCount() - 1)) this->schedule_index++; + if (this->schedule_index < (int)(this->vehicle->orders->GetScheduledDispatchScheduleCount() - 1)) { + this->schedule_index++; + this->selected_slot = UINT32_MAX; + } this->ReInit(); break; @@ -885,6 +949,18 @@ struct SchdispatchWindow : GeneralVehicleWindow { break; } + case WID_SCHDISPATCH_MANAGE_SLOT: { + const DispatchSlot *selected_slot = this->GetSelectedDispatchSlot(); + if (selected_slot == nullptr) break; + const DispatchSchedule &schedule = this->GetSelectedSchedule(); + + DropDownList list; + list.push_back(std::make_unique(HasBit(selected_slot->flags, DispatchSlot::SDSF_REUSE_SLOT), + STR_SCHDISPATCH_REUSE_THIS_DEPARTURE_SLOT, SCH_SMD_REUSE_DEPARTURE_SLOT, schedule.GetScheduledDispatchReuseSlots())); + ShowDropDownList(this, std::move(list), -1, WID_SCHDISPATCH_MANAGE_SLOT); + break; + } + case WID_SCHDISPATCH_MOVE_LEFT: if (!this->IsScheduleSelected()) break; if (this->schedule_index > 0) { @@ -963,6 +1039,23 @@ struct SchdispatchWindow : GeneralVehicleWindow { break; } } + break; + } + + case WID_SCHDISPATCH_MANAGE_SLOT: { + const DispatchSlot *selected_slot = this->GetSelectedDispatchSlot(); + if (selected_slot == nullptr) break; + + switch((SlotManagementDropdown)index) { + case SCH_SMD_REUSE_DEPARTURE_SLOT: { + uint64_t p3 = 0; + SetBit(p3, SCH_SMD_REUSE_DEPARTURE_SLOT + 16); + if (!HasBit(selected_slot->flags, SCH_SMD_REUSE_DEPARTURE_SLOT)) SetBit(p3, SCH_SMD_REUSE_DEPARTURE_SLOT); + DoCommandPEx(0, this->vehicle->index | (this->schedule_index << 20), this->selected_slot, p3, CMD_SCHEDULED_DISPATCH_SET_SLOT_FLAGS | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE), nullptr, nullptr, 0); + break; + } + } + break; } default: @@ -1145,6 +1238,7 @@ static constexpr NWidgetPart _nested_schdispatch_widgets[] = { NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCHDISPATCH_ADD), SetDataTip(STR_SCHDISPATCH_ADD, STR_SCHDISPATCH_ADD_TOOLTIP), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCHDISPATCH_ADJUST), SetDataTip(STR_SCHDISPATCH_ADJUST, STR_SCHDISPATCH_ADJUST_TOOLTIP), SetFill(1, 1), SetResize(1, 0), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCHDISPATCH_REMOVE), SetDataTip(STR_SCHDISPATCH_REMOVE, STR_SCHDISPATCH_REMOVE_TOOLTIP), SetFill(1, 1), SetResize(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_SCHDISPATCH_MANAGE_SLOT), SetDataTip(STR_SCHDISPATCH_MANAGE_SLOT, STR_NULL), SetFill(1, 1), SetResize(1, 0), EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY, WID_SCHDISPATCH_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(), NWidget(NWID_HORIZONTAL), diff --git a/src/sl/extended_ver_sl.cpp b/src/sl/extended_ver_sl.cpp index 3eae70844a..e8187ba341 100644 --- a/src/sl/extended_ver_sl.cpp +++ b/src/sl/extended_ver_sl.cpp @@ -117,7 +117,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_STATION_CATCHMENT_INC, XSCF_NULL, 1, 1, "station_catchment_inc", nullptr, nullptr, nullptr }, { XSLFI_CUSTOM_BRIDGE_HEADS, XSCF_NULL, 4, 4, "custom_bridge_heads", nullptr, nullptr, nullptr }, { XSLFI_CHUNNEL, XSCF_NULL, 2, 2, "chunnel", nullptr, nullptr, "TUNN" }, - { XSLFI_SCHEDULED_DISPATCH, XSCF_NULL, 6, 6, "scheduled_dispatch", nullptr, nullptr, nullptr }, + { XSLFI_SCHEDULED_DISPATCH, XSCF_NULL, 7, 7, "scheduled_dispatch", nullptr, nullptr, nullptr }, { XSLFI_MORE_TOWN_GROWTH_RATES, XSCF_NULL, 1, 1, "more_town_growth_rates", nullptr, nullptr, nullptr }, { XSLFI_MULTIPLE_DOCKS, XSCF_NULL, 2, 2, "multiple_docks", nullptr, nullptr, nullptr }, { XSLFI_TIMETABLE_EXTRA, XSCF_NULL, 7, 7, "timetable_extra", nullptr, nullptr, "ORDX" }, diff --git a/src/sl/order_sl.cpp b/src/sl/order_sl.cpp index a12f340098..c0e056d6db 100644 --- a/src/sl/order_sl.cpp +++ b/src/sl/order_sl.cpp @@ -22,6 +22,7 @@ std::vector _jokerpp_non_auto_separation; static uint16_t _old_scheduled_dispatch_start_full_date_fract; btree::btree_map _old_scheduled_dispatch_start_full_date_fract_map; +static std::vector _old_scheduled_dispatch_slots; /** * Converts this order from an old savegame's version; @@ -264,7 +265,7 @@ static void Ptrs_ORDR() SaveLoadTable GetDispatchScheduleDescription() { static const SaveLoad _dispatch_scheduled_info_desc[] = { - SLE_VARVEC(DispatchSchedule, scheduled_dispatch, SLE_UINT32), + SLEG_CONDVARVEC_X(_old_scheduled_dispatch_slots, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_SCHEDULED_DISPATCH, 1, 6)), SLE_VAR(DispatchSchedule, scheduled_dispatch_duration, SLE_UINT32), SLE_CONDVAR_X(DispatchSchedule, scheduled_dispatch_start_tick, SLE_FILE_I32 | SLE_VAR_I64, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_SCHEDULED_DISPATCH, 1, 4)), SLEG_CONDVAR_X(_old_scheduled_dispatch_start_full_date_fract, SLE_UINT16, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_SCHEDULED_DISPATCH, 1, 4)), @@ -278,6 +279,16 @@ SaveLoadTable GetDispatchScheduleDescription() return _dispatch_scheduled_info_desc; } +SaveLoadTable GetDispatchSlotDescription() +{ + static const SaveLoad _dispatch_slot_info_desc[] = { + SLE_VAR(DispatchSlot, offset, SLE_UINT32), + SLE_VAR(DispatchSlot, flags, SLE_UINT16), + }; + + return _dispatch_slot_info_desc; +} + SaveLoadTable GetOrderListDescription() { static const SaveLoad _orderlist_desc[] = { @@ -291,11 +302,13 @@ SaveLoadTable GetOrderListDescription() static std::vector _filtered_ordl_desc; static std::vector _filtered_ordl_sd_desc; +static std::vector _filtered_ordl_slot_desc; static void SetupDescs_ORDL() { _filtered_ordl_desc = SlFilterObject(GetOrderListDescription()); _filtered_ordl_sd_desc = SlFilterObject(GetDispatchScheduleDescription()); + _filtered_ordl_slot_desc = SlFilterObject(GetDispatchSlotDescription()); } static void Save_ORDL() @@ -309,6 +322,11 @@ static void Save_ORDL() SlWriteUint32(list->GetScheduledDispatchScheduleCount()); for (DispatchSchedule &ds : list->GetScheduledDispatchScheduleSet()) { SlObjectSaveFiltered(&ds, _filtered_ordl_sd_desc); + + SlWriteUint32((uint32_t)ds.GetScheduledDispatchMutable().size()); + for (DispatchSlot &slot : ds.GetScheduledDispatchMutable()) { + SlObjectSaveFiltered(&slot, _filtered_ordl_slot_desc); + } } }, list); } @@ -344,9 +362,23 @@ static void Load_ORDL() if (SlXvIsFeaturePresent(XSLFI_SCHEDULED_DISPATCH, 1, 4) && _old_scheduled_dispatch_start_full_date_fract != 0) { _old_scheduled_dispatch_start_full_date_fract_map[&ds] = _old_scheduled_dispatch_start_full_date_fract; } + + if (SlXvIsFeaturePresent(XSLFI_SCHEDULED_DISPATCH, 1, 6)) { + ds.GetScheduledDispatchMutable().reserve(_old_scheduled_dispatch_slots.size()); + for (uint32_t slot : _old_scheduled_dispatch_slots) { + ds.GetScheduledDispatchMutable().push_back({ slot, 0 }); + } + } else { + ds.GetScheduledDispatchMutable().resize(SlReadUint32()); + for (DispatchSlot &slot : ds.GetScheduledDispatchMutable()) { + SlObjectLoadFiltered(&slot, _filtered_ordl_slot_desc); + } + } } } } + + _old_scheduled_dispatch_slots.clear(); } void Ptrs_ORDL() diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp index f5975ce7bd..f1eb2274d0 100644 --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -807,10 +807,14 @@ DateTicksScaled GetScheduledDispatchTime(const DispatchSchedule &ds, DateTicksSc DateTicksScaled first_slot = -1; /* Find next available slots */ - for (auto current_offset : ds.GetScheduledDispatch()) { + for (const DispatchSlot &slot : ds.GetScheduledDispatch()) { + auto current_offset = slot.offset; if (current_offset >= dispatch_duration) continue; - if ((int32_t)current_offset <= last_dispatched_offset) { - current_offset += dispatch_duration * ((last_dispatched_offset + dispatch_duration - current_offset) / dispatch_duration); + + int32_t threshold = last_dispatched_offset; + if (HasBit(slot.flags, DispatchSlot::SDSF_REUSE_SLOT)) threshold--; + if ((int32_t)current_offset <= threshold) { + current_offset += dispatch_duration * ((threshold + dispatch_duration - current_offset) / dispatch_duration); } DateTicksScaled current_departure = begin_time + current_offset;