MacOS: Bump MacOS deployment target to 10.15
[openttd-jgr.git] / src / schdispatch_cmd.cpp
blobeb0c33d7d89e1377d3bc56bd2bcb84b928f6d5d3
1 /*
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/>.
6 */
8 /** @file schdispatch_cmd.cpp Commands related to scheduled dispatching. */
10 #include "stdafx.h"
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"
24 #include <algorithm>
26 #include "table/strings.h"
28 #include "safeguards.h"
30 /**
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.
37 * @param text unused
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()) {
54 if (HasBit(p2, 0)) {
55 SetBit(v2->vehicle_flags, VF_SCHEDULED_DISPATCH);
56 } else {
57 ClrBit(v2->vehicle_flags, VF_SCHEDULED_DISPATCH);
60 SetTimetableWindowsDirty(v, STWDF_SCHEDULED_DISPATCH);
63 return CommandCost();
66 /**
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
75 * @param text unused
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++) {
102 p2 += offset;
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
118 * @param text unused
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
151 * @param text unused
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
185 * @param p2 Unused.
186 * @param p3 Start tick
187 * @param text unused
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
222 * @param text unused
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
255 * @param text unused
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
292 * @param p2 Not used
293 * @param text unused
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
325 * @param p2 Not used
326 * @param text unused
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
360 * @param text unused
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
394 * @param p2 Not used
395 * @param text unused
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) {
426 /* do nothing */
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
459 * @param p2 Not used
460 * @param text name
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);
480 if (!reset) {
481 if (Utf8StringLength(text) >= MAX_LENGTH_VEHICLE_NAME_CHARS) return CMD_ERROR;
484 if (flags & DC_EXEC) {
485 if (reset) {
486 v->orders->GetDispatchScheduleByIndex(schedule_index).ScheduleName().clear();
487 } else {
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
502 * @param p2 Tag ID
503 * @param text name
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;
522 std::string name;
523 if (!StrEmpty(text)) {
524 if (Utf8StringLength(text) >= MAX_LENGTH_VEHICLE_NAME_CHARS) return CMD_ERROR;
525 name = text;
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
542 * @param p2 Not used
543 * @param text name
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
579 * @param text name
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
619 * @param text name
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
659 * @param unused
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
731 * @param text unused
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) {
760 slot.flags &= ~mask;
761 slot.flags |= values;
762 SchdispatchInvalidateWindows(v);
763 SetTimetableWindowsDirty(v, STWDF_SCHEDULED_DISPATCH);
765 return CommandCost();
769 return CMD_ERROR;
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) {
792 return;
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) {
807 return;
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);
838 } else {
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 {};
884 return iter->second;
887 void DispatchSchedule::SetSupplementaryName(ScheduledDispatchSupplementaryNameType name_type, uint16_t id, std::string name)
889 uint32_t key = SupplementaryNameKey(name_type, id);
890 if (name.empty()) {
891 this->supplementary_names.erase(key);
892 } else {
893 this->supplementary_names[key] = std::move(name);