2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file schdispatch_cmd.cpp Commands related to scheduled dispatching. */
11 #include "command_func.h"
12 #include "company_func.h"
13 #include "date_func.h"
14 #include "date_type.h"
15 #include "window_func.h"
16 #include "vehicle_base.h"
17 #include "settings_type.h"
18 #include "cmd_helper.h"
19 #include "company_base.h"
20 #include "settings_type.h"
21 #include "schdispatch.h"
22 #include "vehicle_gui.h"
26 #include "table/strings.h"
28 #include "safeguards.h"
31 * Enable or disable scheduled dispatch
32 * @param tile Not used.
33 * @param flags Operation to perform.
34 * @param p1 Vehicle index.
35 * @param p2 Various bitstuffed elements
36 * - p2 = (bit 0) - Set to 1 to enable, 0 to disable scheduled dispatch.
38 * @return the cost of this operation or an error
40 CommandCost
CmdScheduledDispatch(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
42 VehicleID veh
= GB(p1
, 0, 20);
44 Vehicle
*v
= Vehicle::GetIfValid(veh
);
45 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
47 CommandCost ret
= CheckOwnership(v
->owner
);
48 if (ret
.Failed()) return ret
;
50 if (HasBit(p2
, 0) && (HasBit(v
->vehicle_flags
, VF_TIMETABLE_SEPARATION
) || v
->HasUnbunchingOrder())) return CommandCost(STR_ERROR_SEPARATION_MUTUALLY_EXCLUSIVE
);
52 if (flags
& DC_EXEC
) {
53 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
55 SetBit(v2
->vehicle_flags
, VF_SCHEDULED_DISPATCH
);
57 ClrBit(v2
->vehicle_flags
, VF_SCHEDULED_DISPATCH
);
60 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
67 * Add scheduled dispatch time offset
68 * @param tile Not used.
69 * @param flags Operation to perform.
70 * @param p1 Vehicle index.
71 * @param p2 Offset time to add.
72 * @param p3 various bitstuffed elements
73 * - p3 = (bit 0 - 31) - the offset for additional slots
74 * - p3 = (bit 32 - 47) - the number of additional slots to add
76 * @return the cost of this operation or an error
78 CommandCost
CmdScheduledDispatchAdd(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, uint64_t p3
, const char *text
, const CommandAuxiliaryBase
*aux_data
)
80 VehicleID veh
= GB(p1
, 0, 20);
81 uint schedule_index
= GB(p1
, 20, 12);
82 uint32_t offset
= GB(p3
, 0, 32);
83 uint32_t extra_slots
= GB(p3
, 32, 16);
85 Vehicle
*v
= Vehicle::GetIfValid(veh
);
86 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
88 CommandCost ret
= CheckOwnership(v
->owner
);
89 if (ret
.Failed()) return ret
;
91 if (v
->orders
== nullptr) return CMD_ERROR
;
93 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
95 if (extra_slots
> 512) return_cmd_error(STR_ERROR_SCHDISPATCH_TRIED_TO_ADD_TOO_MANY_SLOTS
);
96 if (extra_slots
> 0 && offset
== 0) return CMD_ERROR
;
98 if (flags
& DC_EXEC
) {
99 DispatchSchedule
&ds
= v
->orders
->GetDispatchScheduleByIndex(schedule_index
);
100 ds
.AddScheduledDispatch(p2
);
101 for (uint i
= 0; i
< extra_slots
; i
++) {
103 if (p2
>= ds
.GetScheduledDispatchDuration()) p2
-= ds
.GetScheduledDispatchDuration();
104 ds
.AddScheduledDispatch(p2
);
106 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
109 return CommandCost();
113 * Remove scheduled dispatch time offset
114 * @param tile Not used.
115 * @param flags Operation to perform.
116 * @param p1 Vehicle index.
117 * @param p2 Offset time to remove
119 * @return the cost of this operation or an error
121 CommandCost
CmdScheduledDispatchRemove(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
123 VehicleID veh
= GB(p1
, 0, 20);
124 uint schedule_index
= GB(p1
, 20, 12);
126 Vehicle
*v
= Vehicle::GetIfValid(veh
);
127 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
129 CommandCost ret
= CheckOwnership(v
->owner
);
130 if (ret
.Failed()) return ret
;
132 if (v
->orders
== nullptr) return CMD_ERROR
;
134 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
136 if (flags
& DC_EXEC
) {
137 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).RemoveScheduledDispatch(p2
);
138 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
141 return CommandCost();
145 * Set scheduled dispatch duration
147 * @param tile Not used.
148 * @param flags Operation to perform.
149 * @param p1 Vehicle index
150 * @param p2 Duration, in scaled tick
152 * @return the cost of this operation or an error
154 CommandCost
CmdScheduledDispatchSetDuration(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
156 VehicleID veh
= GB(p1
, 0, 20);
157 uint schedule_index
= GB(p1
, 20, 12);
159 Vehicle
*v
= Vehicle::GetIfValid(veh
);
160 if (v
== nullptr || !v
->IsPrimaryVehicle() || p2
== 0) return CMD_ERROR
;
162 CommandCost ret
= CheckOwnership(v
->owner
);
163 if (ret
.Failed()) return ret
;
165 if (v
->orders
== nullptr) return CMD_ERROR
;
167 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
169 if (flags
& DC_EXEC
) {
170 DispatchSchedule
&ds
= v
->orders
->GetDispatchScheduleByIndex(schedule_index
);
171 ds
.SetScheduledDispatchDuration(p2
);
172 ds
.UpdateScheduledDispatch(nullptr);
173 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
176 return CommandCost();
180 * Set scheduled dispatch start date
182 * @param tile Not used.
183 * @param flags Operation to perform.
184 * @param p1 Vehicle index
186 * @param p3 Start tick
188 * @return the cost of this operation or an error
190 CommandCost
CmdScheduledDispatchSetStartDate(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, uint64_t p3
, const char *text
, const CommandAuxiliaryBase
*aux_data
)
192 VehicleID veh
= GB(p1
, 0, 20);
193 uint schedule_index
= GB(p1
, 20, 12);
195 Vehicle
*v
= Vehicle::GetIfValid(veh
);
196 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
198 CommandCost ret
= CheckOwnership(v
->owner
);
199 if (ret
.Failed()) return ret
;
201 if (v
->orders
== nullptr) return CMD_ERROR
;
203 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
205 if (flags
& DC_EXEC
) {
206 DispatchSchedule
&ds
= v
->orders
->GetDispatchScheduleByIndex(schedule_index
);
207 ds
.SetScheduledDispatchStartTick((StateTicks
)p3
);
208 ds
.UpdateScheduledDispatch(nullptr);
209 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
212 return CommandCost();
216 * Set scheduled dispatch maximum allow delay
218 * @param tile Not used.
219 * @param flags Operation to perform.
220 * @param p1 Vehicle index
221 * @param p2 Maximum Delay, in scaled tick
223 * @return the cost of this operation or an error
225 CommandCost
CmdScheduledDispatchSetDelay(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
227 VehicleID veh
= GB(p1
, 0, 20);
228 uint schedule_index
= GB(p1
, 20, 12);
230 Vehicle
*v
= Vehicle::GetIfValid(veh
);
231 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
233 CommandCost ret
= CheckOwnership(v
->owner
);
234 if (ret
.Failed()) return ret
;
236 if (v
->orders
== nullptr) return CMD_ERROR
;
238 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
240 if (flags
& DC_EXEC
) {
241 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).SetScheduledDispatchDelay(p2
);
242 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
245 return CommandCost();
249 * Set scheduled dispatch maximum allow delay
251 * @param tile Not used.
252 * @param flags Operation to perform.
253 * @param p1 Vehicle index
254 * @param p2 Whether to re-use slots
256 * @return the cost of this operation or an error
258 CommandCost
CmdScheduledDispatchSetReuseSlots(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
260 VehicleID veh
= GB(p1
, 0, 20);
261 uint schedule_index
= GB(p1
, 20, 12);
263 Vehicle
*v
= Vehicle::GetIfValid(veh
);
264 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
266 CommandCost ret
= CheckOwnership(v
->owner
);
267 if (ret
.Failed()) return ret
;
269 if (v
->orders
== nullptr) return CMD_ERROR
;
271 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
273 if (flags
& DC_EXEC
) {
274 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).SetScheduledDispatchReuseSlots(p2
!= 0);
275 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
278 return CommandCost();
282 * Reset scheduled dispatch last dispatch vehicle time
284 * This is useful when the current duration is high, and the vehicle get dispatched at time in far future.
285 * Thus, the last dispatch time stays high so no new vehicle are dispatched between now and that time.
286 * By resetting this you set the last dispatch time to the current timetable start time,
287 * allowing new vehicle to be dispatched immediately.
289 * @param tile Not used.
290 * @param flags Operation to perform.
291 * @param p1 Vehicle index
294 * @return the cost of this operation or an error
296 CommandCost
CmdScheduledDispatchResetLastDispatch(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
298 VehicleID veh
= GB(p1
, 0, 20);
299 uint schedule_index
= GB(p1
, 20, 12);
301 Vehicle
*v
= Vehicle::GetIfValid(veh
);
302 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
304 CommandCost ret
= CheckOwnership(v
->owner
);
305 if (ret
.Failed()) return ret
;
307 if (v
->orders
== nullptr) return CMD_ERROR
;
309 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
311 if (flags
& DC_EXEC
) {
312 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).SetScheduledDispatchLastDispatch(INVALID_SCHEDULED_DISPATCH_OFFSET
);
313 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
316 return CommandCost();
320 * Clear scheduled dispatch schedule
322 * @param tile Not used.
323 * @param flags Operation to perform.
324 * @param p1 Vehicle index
327 * @return the cost of this operation or an error
329 CommandCost
CmdScheduledDispatchClear(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
331 VehicleID veh
= GB(p1
, 0, 20);
332 uint schedule_index
= GB(p1
, 20, 12);
334 Vehicle
*v
= Vehicle::GetIfValid(veh
);
335 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
337 CommandCost ret
= CheckOwnership(v
->owner
);
338 if (ret
.Failed()) return ret
;
340 if (v
->orders
== nullptr) return CMD_ERROR
;
342 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
344 if (flags
& DC_EXEC
) {
345 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).ClearScheduledDispatch();
346 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
349 return CommandCost();
353 * Add a new scheduled dispatch schedule
355 * @param tile Not used.
356 * @param flags Operation to perform.
357 * @param p1 Vehicle index
358 * @param p2 Duration, in scaled tick
359 * @param p3 Start tick
361 * @return the cost of this operation or an error
363 CommandCost
CmdScheduledDispatchAddNewSchedule(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, uint64_t p3
, const char *text
, const CommandAuxiliaryBase
*aux_data
)
365 VehicleID veh
= GB(p1
, 0, 20);
367 Vehicle
*v
= Vehicle::GetIfValid(veh
);
368 if (v
== nullptr || !v
->IsPrimaryVehicle() || p2
== 0) return CMD_ERROR
;
370 CommandCost ret
= CheckOwnership(v
->owner
);
371 if (ret
.Failed()) return ret
;
373 if (v
->orders
== nullptr) return CMD_ERROR
;
374 if (v
->orders
->GetScheduledDispatchScheduleCount() >= 4096) return CMD_ERROR
;
376 if (flags
& DC_EXEC
) {
377 v
->orders
->GetScheduledDispatchScheduleSet().emplace_back();
378 DispatchSchedule
&ds
= v
->orders
->GetScheduledDispatchScheduleSet().back();
379 ds
.SetScheduledDispatchDuration(p2
);
380 ds
.SetScheduledDispatchStartTick((StateTicks
)p3
);
381 ds
.UpdateScheduledDispatch(nullptr);
382 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
385 return CommandCost();
389 * Remove scheduled dispatch schedule
391 * @param tile Not used.
392 * @param flags Operation to perform.
393 * @param p1 Vehicle index
396 * @return the cost of this operation or an error
398 CommandCost
CmdScheduledDispatchRemoveSchedule(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
400 VehicleID veh
= GB(p1
, 0, 20);
401 uint schedule_index
= GB(p1
, 20, 12);
403 Vehicle
*v
= Vehicle::GetIfValid(veh
);
404 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
406 CommandCost ret
= CheckOwnership(v
->owner
);
407 if (ret
.Failed()) return ret
;
409 if (v
->orders
== nullptr) return CMD_ERROR
;
411 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
413 if (flags
& DC_EXEC
) {
414 std::vector
<DispatchSchedule
> &scheds
= v
->orders
->GetScheduledDispatchScheduleSet();
415 scheds
.erase(scheds
.begin() + schedule_index
);
416 for (Order
*o
: v
->Orders()) {
417 int idx
= o
->GetDispatchScheduleIndex();
418 if (idx
== (int)schedule_index
) {
419 o
->SetDispatchScheduleIndex(-1);
420 } else if (idx
> (int)schedule_index
) {
421 o
->SetDispatchScheduleIndex(idx
- 1);
423 if (o
->IsType(OT_CONDITIONAL
) && o
->GetConditionVariable() == OCV_DISPATCH_SLOT
) {
424 uint16_t order_schedule
= o
->GetConditionDispatchScheduleID();
425 if (order_schedule
== UINT16_MAX
) {
427 } else if (order_schedule
== schedule_index
) {
428 o
->SetConditionDispatchScheduleID(UINT16_MAX
);
429 } else if (order_schedule
> schedule_index
) {
430 o
->SetConditionDispatchScheduleID((uint16_t)(order_schedule
- 1));
434 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
435 if (v2
->dispatch_records
.empty()) continue;
437 btree::btree_map
<uint16_t, LastDispatchRecord
> new_records
;
438 for (auto &iter
: v2
->dispatch_records
) {
439 if (iter
.first
< schedule_index
) {
440 new_records
[iter
.first
] = std::move(iter
.second
);
441 } else if (iter
.first
> schedule_index
) {
442 new_records
[iter
.first
- 1] = std::move(iter
.second
);
445 v2
->dispatch_records
= std::move(new_records
);
447 SchdispatchInvalidateWindows(v
);
450 return CommandCost();
454 * Rename scheduled dispatch schedule
456 * @param tile Not used.
457 * @param flags Operation to perform.
458 * @param p1 Vehicle index
461 * @return the cost of this operation or an error
463 CommandCost
CmdScheduledDispatchRenameSchedule(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
465 VehicleID veh
= GB(p1
, 0, 20);
466 uint schedule_index
= GB(p1
, 20, 12);
468 Vehicle
*v
= Vehicle::GetIfValid(veh
);
469 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
471 CommandCost ret
= CheckOwnership(v
->owner
);
472 if (ret
.Failed()) return ret
;
474 if (v
->orders
== nullptr) return CMD_ERROR
;
476 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
478 bool reset
= StrEmpty(text
);
481 if (Utf8StringLength(text
) >= MAX_LENGTH_VEHICLE_NAME_CHARS
) return CMD_ERROR
;
484 if (flags
& DC_EXEC
) {
486 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).ScheduleName().clear();
488 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).ScheduleName() = text
;
490 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
| STWDF_ORDERS
);
493 return CommandCost();
497 * Rename scheduled dispatch departure tag
499 * @param tile Not used.
500 * @param flags Operation to perform.
501 * @param p1 Vehicle index
504 * @return the cost of this operation or an error
506 CommandCost
CmdScheduledDispatchRenameTag(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
508 VehicleID veh
= GB(p1
, 0, 20);
509 uint schedule_index
= GB(p1
, 20, 12);
511 Vehicle
*v
= Vehicle::GetIfValid(veh
);
512 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
514 CommandCost ret
= CheckOwnership(v
->owner
);
515 if (ret
.Failed()) return ret
;
517 if (v
->orders
== nullptr) return CMD_ERROR
;
519 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
520 if (p2
>= DispatchSchedule::DEPARTURE_TAG_COUNT
) return CMD_ERROR
;
523 if (!StrEmpty(text
)) {
524 if (Utf8StringLength(text
) >= MAX_LENGTH_VEHICLE_NAME_CHARS
) return CMD_ERROR
;
528 if (flags
& DC_EXEC
) {
529 v
->orders
->GetDispatchScheduleByIndex(schedule_index
).SetSupplementaryName(SDSNT_DEPARTURE_TAG
, static_cast<uint16_t>(p2
), std::move(name
));
530 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
| STWDF_ORDERS
);
533 return CommandCost();
537 * Duplicate scheduled dispatch schedule
539 * @param tile Not used.
540 * @param flags Operation to perform.
541 * @param p1 Vehicle index
544 * @return the cost of this operation or an error
546 CommandCost
CmdScheduledDispatchDuplicateSchedule(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
548 VehicleID veh
= GB(p1
, 0, 20);
549 uint schedule_index
= GB(p1
, 20, 12);
551 Vehicle
*v
= Vehicle::GetIfValid(veh
);
552 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
554 CommandCost ret
= CheckOwnership(v
->owner
);
555 if (ret
.Failed()) return ret
;
557 if (v
->orders
== nullptr) return CMD_ERROR
;
558 if (v
->orders
->GetScheduledDispatchScheduleCount() >= 4096) return CMD_ERROR
;
560 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
562 if (flags
& DC_EXEC
) {
563 DispatchSchedule
&ds
= v
->orders
->GetScheduledDispatchScheduleSet().emplace_back(v
->orders
->GetDispatchScheduleByIndex(schedule_index
));
564 ds
.SetScheduledDispatchLastDispatch(INVALID_SCHEDULED_DISPATCH_OFFSET
);
565 ds
.UpdateScheduledDispatch(nullptr);
566 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
569 return CommandCost();
573 * Append scheduled dispatch schedules from another vehicle
575 * @param tile Not used.
576 * @param flags Operation to perform.
577 * @param p1 Vehicle index to append to
578 * @param p2 Vehicle index to copy from
580 * @return the cost of this operation or an error
582 CommandCost
CmdScheduledDispatchAppendVehicleSchedules(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
584 VehicleID veh1
= GB(p1
, 0, 20);
585 VehicleID veh2
= GB(p2
, 0, 20);
587 Vehicle
*v1
= Vehicle::GetIfValid(veh1
);
588 if (v1
== nullptr || !v1
->IsPrimaryVehicle()) return CMD_ERROR
;
590 const Vehicle
*v2
= Vehicle::GetIfValid(veh2
);
591 if (v2
== nullptr || !v2
->IsPrimaryVehicle()) return CMD_ERROR
;
593 CommandCost ret
= CheckOwnership(v1
->owner
);
594 if (ret
.Failed()) return ret
;
596 if (v1
->orders
== nullptr || v2
->orders
== nullptr || v1
->orders
== v2
->orders
) return CMD_ERROR
;
598 if (v1
->orders
->GetScheduledDispatchScheduleCount() + v2
->orders
->GetScheduledDispatchScheduleCount() > 4096) return CMD_ERROR
;
600 if (flags
& DC_EXEC
) {
601 for (uint i
= 0; i
< v2
->orders
->GetScheduledDispatchScheduleCount(); i
++) {
602 DispatchSchedule
&ds
= v1
->orders
->GetScheduledDispatchScheduleSet().emplace_back(v2
->orders
->GetDispatchScheduleByIndex(i
));
603 ds
.SetScheduledDispatchLastDispatch(INVALID_SCHEDULED_DISPATCH_OFFSET
);
604 ds
.UpdateScheduledDispatch(nullptr);
606 SetTimetableWindowsDirty(v1
, STWDF_SCHEDULED_DISPATCH
);
609 return CommandCost();
613 * Adjust scheduled dispatch time offsets
615 * @param tile Not used.
616 * @param flags Operation to perform.
617 * @param p1 Vehicle index
618 * @param p2 Signed adjustment
620 * @return the cost of this operation or an error
622 CommandCost
CmdScheduledDispatchAdjust(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
624 VehicleID veh
= GB(p1
, 0, 20);
625 uint schedule_index
= GB(p1
, 20, 12);
626 int32_t adjustment
= p2
;
628 Vehicle
*v
= Vehicle::GetIfValid(veh
);
629 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
631 CommandCost ret
= CheckOwnership(v
->owner
);
632 if (ret
.Failed()) return ret
;
634 if (v
->orders
== nullptr) return CMD_ERROR
;
636 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
638 DispatchSchedule
&ds
= v
->orders
->GetDispatchScheduleByIndex(schedule_index
);
639 if (abs(adjustment
) >= (int)ds
.GetScheduledDispatchDuration()) return CommandCost(STR_ERROR_SCHDISPATCH_ADJUSTMENT_TOO_LARGE
);
641 if (flags
& DC_EXEC
) {
642 ds
.AdjustScheduledDispatch(adjustment
);
643 ds
.UpdateScheduledDispatch(nullptr);
644 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
647 return CommandCost();
651 * Swap two schedules in dispatch schedule list
653 * @param tile Not used.
654 * @param flags Operation to perform.
655 * @param p1 Vehicle index
656 * @param p2 various bitstuffed elements
657 * - p2 = (bit 0 - 15) - Schedule index 1
658 * - p2 = (bit 16 - 31) - Schedule index 2
660 * @return the cost of this operation or an error
662 CommandCost
CmdScheduledDispatchSwapSchedules(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, const char *text
)
664 VehicleID veh
= GB(p1
, 0, 20);
665 uint schedule_index_1
= GB(p2
, 0, 16);
666 uint schedule_index_2
= GB(p2
, 16, 16);
668 Vehicle
*v
= Vehicle::GetIfValid(veh
);
669 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
671 CommandCost ret
= CheckOwnership(v
->owner
);
672 if (ret
.Failed()) return ret
;
674 if (v
->orders
== nullptr) return CMD_ERROR
;
676 if (schedule_index_1
== schedule_index_2
) return CMD_ERROR
;
677 if (schedule_index_1
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
678 if (schedule_index_2
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
680 if (flags
& DC_EXEC
) {
681 std::swap(v
->orders
->GetDispatchScheduleByIndex(schedule_index_1
), v
->orders
->GetDispatchScheduleByIndex(schedule_index_2
));
682 for (Order
*o
: v
->Orders()) {
683 int idx
= o
->GetDispatchScheduleIndex();
684 if (idx
== (int)schedule_index_1
) {
685 o
->SetDispatchScheduleIndex((int)schedule_index_2
);
686 } else if (idx
== (int)schedule_index_2
) {
687 o
->SetDispatchScheduleIndex((int)schedule_index_1
);
689 if (o
->IsType(OT_CONDITIONAL
) && o
->GetConditionVariable() == OCV_DISPATCH_SLOT
) {
690 uint16_t order_schedule
= o
->GetConditionDispatchScheduleID();
691 if (order_schedule
== schedule_index_1
) {
692 o
->SetConditionDispatchScheduleID(schedule_index_2
);
693 } else if (order_schedule
== schedule_index_2
) {
694 o
->SetConditionDispatchScheduleID(schedule_index_1
);
698 for (Vehicle
*v2
= v
->FirstShared(); v2
!= nullptr; v2
= v2
->NextShared()) {
699 if (v2
->dispatch_records
.empty()) continue;
701 auto iter_1
= v2
->dispatch_records
.find(static_cast<uint16_t>(schedule_index_1
));
702 auto iter_2
= v2
->dispatch_records
.find(static_cast<uint16_t>(schedule_index_2
));
703 if (iter_1
!= v2
->dispatch_records
.end() && iter_2
!= v2
->dispatch_records
.end()) {
704 std::swap(iter_1
->second
, iter_2
->second
);
705 } else if (iter_1
!= v2
->dispatch_records
.end()) {
706 LastDispatchRecord r
= std::move(iter_1
->second
);
707 v2
->dispatch_records
.erase(iter_1
);
708 v2
->dispatch_records
[static_cast<uint16_t>(schedule_index_2
)] = std::move(r
);
709 } else if (iter_2
!= v2
->dispatch_records
.end()) {
710 LastDispatchRecord r
= std::move(iter_2
->second
);
711 v2
->dispatch_records
.erase(iter_2
);
712 v2
->dispatch_records
[static_cast<uint16_t>(schedule_index_1
)] = std::move(r
);
715 SchdispatchInvalidateWindows(v
);
716 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
719 return CommandCost();
723 * Add scheduled dispatch time offset
724 * @param tile Not used.
725 * @param flags Operation to perform.
726 * @param p1 Vehicle index.
727 * @param p2 Slot offset.
728 * @param p3 various bitstuffed elements
729 * - p3 = (bit 0 - 15) - flag values
730 * - p3 = (bit 16 - 31) - flag mask
732 * @return the cost of this operation or an error
734 CommandCost
CmdScheduledDispatchSetSlotFlags(TileIndex tile
, DoCommandFlag flags
, uint32_t p1
, uint32_t p2
, uint64_t p3
, const char *text
, const CommandAuxiliaryBase
*aux_data
)
736 VehicleID veh
= GB(p1
, 0, 20);
737 uint schedule_index
= GB(p1
, 20, 12);
738 uint32_t offset
= p2
;
739 uint16_t values
= (uint16_t)GB(p3
, 0, 16);
740 uint16_t mask
= (uint16_t)GB(p3
, 16, 16);
742 const uint16_t permitted_mask
= GetBitMaskSC
<uint16_t>(DispatchSlot::SDSF_REUSE_SLOT
, 1) | GetBitMaskFL
<uint16_t>(DispatchSlot::SDSF_FIRST_TAG
, DispatchSlot::SDSF_LAST_TAG
);
743 if ((mask
& permitted_mask
) != mask
) return CMD_ERROR
;
744 if ((values
& (~mask
)) != 0) return CMD_ERROR
;
746 Vehicle
*v
= Vehicle::GetIfValid(veh
);
747 if (v
== nullptr || !v
->IsPrimaryVehicle()) return CMD_ERROR
;
749 CommandCost ret
= CheckOwnership(v
->owner
);
750 if (ret
.Failed()) return ret
;
752 if (v
->orders
== nullptr) return CMD_ERROR
;
754 if (schedule_index
>= v
->orders
->GetScheduledDispatchScheduleCount()) return CMD_ERROR
;
756 DispatchSchedule
&ds
= v
->orders
->GetDispatchScheduleByIndex(schedule_index
);
757 for (DispatchSlot
&slot
: ds
.GetScheduledDispatchMutable()) {
758 if (slot
.offset
== offset
) {
759 if (flags
& DC_EXEC
) {
761 slot
.flags
|= values
;
762 SchdispatchInvalidateWindows(v
);
763 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
765 return CommandCost();
773 * Set scheduled dispatch slot list.
774 * @param dispatch_list The offset time list, must be correctly sorted.
776 void DispatchSchedule::SetScheduledDispatch(std::vector
<DispatchSlot
> dispatch_list
)
778 this->scheduled_dispatch
= std::move(dispatch_list
);
779 assert(std::is_sorted(this->scheduled_dispatch
.begin(), this->scheduled_dispatch
.end()));
780 if (this->IsScheduledDispatchValid()) this->UpdateScheduledDispatch(nullptr);
784 * Add new scheduled dispatch slot at offsets time.
785 * @param offset The offset time to add.
787 void DispatchSchedule::AddScheduledDispatch(uint32_t offset
)
789 /* Maintain sorted list status */
790 auto insert_position
= std::lower_bound(this->scheduled_dispatch
.begin(), this->scheduled_dispatch
.end(), DispatchSlot
{ offset
, 0 });
791 if (insert_position
!= this->scheduled_dispatch
.end() && insert_position
->offset
== offset
) {
794 this->scheduled_dispatch
.insert(insert_position
, { offset
, 0 });
795 this->UpdateScheduledDispatch(nullptr);
799 * Remove scheduled dispatch slot at offsets time.
800 * @param offset The offset time to remove.
802 void DispatchSchedule::RemoveScheduledDispatch(uint32_t offset
)
804 /* Maintain sorted list status */
805 auto erase_position
= std::lower_bound(this->scheduled_dispatch
.begin(), this->scheduled_dispatch
.end(), DispatchSlot
{ offset
, 0 });
806 if (erase_position
== this->scheduled_dispatch
.end() || erase_position
->offset
!= offset
) {
809 this->scheduled_dispatch
.erase(erase_position
);
813 * Adjust all scheduled dispatch slots by time adjustment.
814 * @param adjust The time adjustment to add to each time slot.
816 void DispatchSchedule::AdjustScheduledDispatch(int32_t adjust
)
818 for (DispatchSlot
&slot
: this->scheduled_dispatch
) {
819 int32_t t
= (int32_t)slot
.offset
+ adjust
;
820 if (t
< 0) t
+= GetScheduledDispatchDuration();
821 if (t
>= (int32_t)GetScheduledDispatchDuration()) t
-= (int32_t)GetScheduledDispatchDuration();
822 slot
.offset
= (uint32_t)t
;
824 std::sort(this->scheduled_dispatch
.begin(), this->scheduled_dispatch
.end());
827 bool DispatchSchedule::UpdateScheduledDispatchToDate(StateTicks now
)
829 bool update_windows
= false;
830 if (this->GetScheduledDispatchStartTick() == 0) {
831 StateTicks start
= now
- (now
.base() % this->GetScheduledDispatchDuration());
832 this->SetScheduledDispatchStartTick(start
);
833 int64_t last_dispatch
= -(start
.base());
834 if (last_dispatch
< INT_MIN
&& _settings_game
.game_time
.time_in_minutes
) {
835 /* Advance by multiples of 24 hours */
836 const int64_t day
= 24 * 60 * _settings_game
.game_time
.ticks_per_minute
;
837 this->scheduled_dispatch_last_dispatch
= last_dispatch
+ (CeilDivT
<int64_t>(INT_MIN
- last_dispatch
, day
) * day
);
839 this->scheduled_dispatch_last_dispatch
= ClampTo
<int32_t>(last_dispatch
);
842 /* Most of the time this loop does not run. It makes sure start date in in past */
843 while (this->GetScheduledDispatchStartTick() > now
) {
844 OverflowSafeInt32 last_dispatch
= this->scheduled_dispatch_last_dispatch
;
845 if (last_dispatch
!= INVALID_SCHEDULED_DISPATCH_OFFSET
) {
846 last_dispatch
+= this->GetScheduledDispatchDuration();
847 this->scheduled_dispatch_last_dispatch
= last_dispatch
;
849 this->SetScheduledDispatchStartTick(this->GetScheduledDispatchStartTick() - this->GetScheduledDispatchDuration());
850 update_windows
= true;
852 /* Most of the time this loop runs once. It makes sure the start date is as close to current time as possible. */
853 while (this->GetScheduledDispatchStartTick() + this->GetScheduledDispatchDuration() <= now
) {
854 OverflowSafeInt32 last_dispatch
= this->scheduled_dispatch_last_dispatch
;
855 if (last_dispatch
!= INVALID_SCHEDULED_DISPATCH_OFFSET
) {
856 last_dispatch
-= this->GetScheduledDispatchDuration();
857 this->scheduled_dispatch_last_dispatch
= last_dispatch
;
859 this->SetScheduledDispatchStartTick(this->GetScheduledDispatchStartTick() + this->GetScheduledDispatchDuration());
860 update_windows
= true;
862 return update_windows
;
866 * Update the scheduled dispatch start time to be the most recent possible.
868 void DispatchSchedule::UpdateScheduledDispatch(const Vehicle
*v
)
870 if (this->UpdateScheduledDispatchToDate(_state_ticks
) && v
!= nullptr) {
871 SetTimetableWindowsDirty(v
, STWDF_SCHEDULED_DISPATCH
);
875 static inline uint32_t SupplementaryNameKey(ScheduledDispatchSupplementaryNameType name_type
, uint16_t id
)
877 return (static_cast<uint32_t>(name_type
) << 16) | id
;
880 std::string_view
DispatchSchedule::GetSupplementaryName(ScheduledDispatchSupplementaryNameType name_type
, uint16_t id
) const
882 auto iter
= this->supplementary_names
.find(SupplementaryNameKey(name_type
, id
));
883 if (iter
== this->supplementary_names
.end()) return {};
887 void DispatchSchedule::SetSupplementaryName(ScheduledDispatchSupplementaryNameType name_type
, uint16_t id
, std::string name
)
889 uint32_t key
= SupplementaryNameKey(name_type
, id
);
891 this->supplementary_names
.erase(key
);
893 this->supplementary_names
[key
] = std::move(name
);