forked from OpenTTD/OpenTTD
/
order_cmd.cpp
2261 lines (1926 loc) · 73.3 KB
/
order_cmd.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* $Id$ */
/*
* This file is part of OpenTTD.
* 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.
* 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.
* 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/>.
*/
/** @file order_cmd.cpp Handling of orders. */
#include "stdafx.h"
#include "debug.h"
#include "cmd_helper.h"
#include "command_func.h"
#include "company_func.h"
#include "news_func.h"
#include "strings_func.h"
#include "timetable.h"
#include "vehicle_func.h"
#include "depot_base.h"
#include "core/pool_func.hpp"
#include "core/random_func.hpp"
#include "aircraft.h"
#include "roadveh.h"
#include "station_base.h"
#include "waypoint_base.h"
#include "company_base.h"
#include "order_backup.h"
#include "cheat_type.h"
#include "table/strings.h"
#include "safeguards.h"
/* DestinationID must be at least as large as every these below, because it can
* be any of them
*/
assert_compile(sizeof(DestinationID) >= sizeof(DepotID));
assert_compile(sizeof(DestinationID) >= sizeof(StationID));
OrderPool _order_pool("Order");
INSTANTIATE_POOL_METHODS(Order)
OrderListPool _orderlist_pool("OrderList");
INSTANTIATE_POOL_METHODS(OrderList)
/** Clean everything up. */
Order::~Order()
{
if (CleaningPool()) return;
/* We can visit oil rigs and buoys that are not our own. They will be shown in
* the list of stations. So, we need to invalidate that window if needed. */
if (this->IsType(OT_GOTO_STATION) || this->IsType(OT_GOTO_WAYPOINT)) {
BaseStation *bs = BaseStation::GetIfValid(this->GetDestination());
if (bs != nullptr && bs->owner == OWNER_NONE) InvalidateWindowClassesData(WC_STATION_LIST, 0);
}
}
/**
* 'Free' the order
* @note ONLY use on "current_order" vehicle orders!
*/
void Order::Free()
{
this->type = OT_NOTHING;
this->flags = 0;
this->dest = 0;
this->next = nullptr;
}
/**
* Makes this order a Go To Station order.
* @param destination the station to go to.
*/
void Order::MakeGoToStation(StationID destination)
{
this->type = OT_GOTO_STATION;
this->flags = 0;
this->dest = destination;
}
/**
* Makes this order a Go To Depot order.
* @param destination the depot to go to.
* @param order is this order a 'default' order, or an overridden vehicle order?
* @param non_stop_type how to get to the depot?
* @param action what to do in the depot?
* @param cargo the cargo type to change to.
*/
void Order::MakeGoToDepot(DepotID destination, OrderDepotTypeFlags order, OrderNonStopFlags non_stop_type, OrderDepotActionFlags action, CargoID cargo)
{
this->type = OT_GOTO_DEPOT;
this->SetDepotOrderType(order);
this->SetDepotActionType(action);
this->SetNonStopType(non_stop_type);
this->dest = destination;
this->SetRefit(cargo);
}
/**
* Makes this order a Go To Waypoint order.
* @param destination the waypoint to go to.
*/
void Order::MakeGoToWaypoint(StationID destination)
{
this->type = OT_GOTO_WAYPOINT;
this->flags = 0;
this->dest = destination;
}
/**
* Makes this order a Loading order.
* @param ordered is this an ordered stop?
*/
void Order::MakeLoading(bool ordered)
{
this->type = OT_LOADING;
if (!ordered) this->flags = 0;
}
/**
* Makes this order a Leave Station order.
*/
void Order::MakeLeaveStation()
{
this->type = OT_LEAVESTATION;
this->flags = 0;
}
/**
* Makes this order a Dummy order.
*/
void Order::MakeDummy()
{
this->type = OT_DUMMY;
this->flags = 0;
}
/**
* Makes this order an conditional order.
* @param order the order to jump to.
*/
void Order::MakeConditional(VehicleOrderID order)
{
this->type = OT_CONDITIONAL;
this->flags = order;
this->dest = 0;
}
/**
* Makes this order an implicit order.
* @param destination the station to go to.
*/
void Order::MakeImplicit(StationID destination)
{
this->type = OT_IMPLICIT;
this->dest = destination;
}
/**
* Make this depot/station order also a refit order.
* @param cargo the cargo type to change to.
* @pre IsType(OT_GOTO_DEPOT) || IsType(OT_GOTO_STATION).
*/
void Order::SetRefit(CargoID cargo)
{
this->refit_cargo = cargo;
}
/**
* Does this order have the same type, flags and destination?
* @param other the second order to compare to.
* @return true if the type, flags and destination match.
*/
bool Order::Equals(const Order &other) const
{
/* In case of go to nearest depot orders we need "only" compare the flags
* with the other and not the nearest depot order bit or the actual
* destination because those get clear/filled in during the order
* evaluation. If we do not do this the order will continuously be seen as
* a different order and it will try to find a "nearest depot" every tick. */
if ((this->IsType(OT_GOTO_DEPOT) && this->type == other.type) &&
((this->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0 ||
(other.GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0)) {
return this->GetDepotOrderType() == other.GetDepotOrderType() &&
(this->GetDepotActionType() & ~ODATFB_NEAREST_DEPOT) == (other.GetDepotActionType() & ~ODATFB_NEAREST_DEPOT);
}
return this->type == other.type && this->flags == other.flags && this->dest == other.dest;
}
/**
* Pack this order into a 32 bits integer, or actually only
* the type, flags and destination.
* @return the packed representation.
* @note unpacking is done in the constructor.
*/
uint32 Order::Pack() const
{
return this->dest << 16 | this->flags << 8 | this->type;
}
/**
* Pack this order into a 16 bits integer as close to the TTD
* representation as possible.
* @return the TTD-like packed representation.
*/
uint16 Order::MapOldOrder() const
{
uint16 order = this->GetType();
switch (this->type) {
case OT_GOTO_STATION:
if (this->GetUnloadType() & OUFB_UNLOAD) SetBit(order, 5);
if (this->GetLoadType() & OLFB_FULL_LOAD) SetBit(order, 6);
if (this->GetNonStopType() & ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS) SetBit(order, 7);
order |= GB(this->GetDestination(), 0, 8) << 8;
break;
case OT_GOTO_DEPOT:
if (!(this->GetDepotOrderType() & ODTFB_PART_OF_ORDERS)) SetBit(order, 6);
SetBit(order, 7);
order |= GB(this->GetDestination(), 0, 8) << 8;
break;
case OT_LOADING:
if (this->GetLoadType() & OLFB_FULL_LOAD) SetBit(order, 6);
break;
}
return order;
}
/**
* Create an order based on a packed representation of that order.
* @param packed the packed representation.
*/
Order::Order(uint32 packed)
{
this->type = (OrderType)GB(packed, 0, 8);
this->flags = GB(packed, 8, 8);
this->dest = GB(packed, 16, 16);
this->next = nullptr;
this->refit_cargo = CT_NO_REFIT;
this->wait_time = 0;
this->travel_time = 0;
this->max_speed = UINT16_MAX;
}
/**
*
* Updates the widgets of a vehicle which contains the order-data
*
*/
void InvalidateVehicleOrder(const Vehicle *v, int data)
{
SetWindowDirty(WC_VEHICLE_VIEW, v->index);
if (data != 0) {
/* Calls SetDirty() too */
InvalidateWindowData(WC_VEHICLE_ORDERS, v->index, data);
InvalidateWindowData(WC_VEHICLE_TIMETABLE, v->index, data);
return;
}
SetWindowDirty(WC_VEHICLE_ORDERS, v->index);
SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
}
/**
*
* Assign data to an order (from another order)
* This function makes sure that the index is maintained correctly
* @param other the data to copy (except next pointer).
*
*/
void Order::AssignOrder(const Order &other)
{
this->type = other.type;
this->flags = other.flags;
this->dest = other.dest;
this->refit_cargo = other.refit_cargo;
this->wait_time = other.wait_time;
this->travel_time = other.travel_time;
this->max_speed = other.max_speed;
}
/**
* Recomputes everything.
* @param chain first order in the chain
* @param v one of vehicle that is using this orderlist
*/
void OrderList::Initialize(Order *chain, Vehicle *v)
{
this->first = chain;
this->first_shared = v;
this->num_orders = 0;
this->num_manual_orders = 0;
this->num_vehicles = 1;
this->timetable_duration = 0;
this->total_duration = 0;
for (Order *o = this->first; o != nullptr; o = o->next) {
++this->num_orders;
if (!o->IsType(OT_IMPLICIT)) ++this->num_manual_orders;
this->timetable_duration += o->GetTimetabledWait() + o->GetTimetabledTravel();
this->total_duration += o->GetWaitTime() + o->GetTravelTime();
}
for (Vehicle *u = this->first_shared->PreviousShared(); u != nullptr; u = u->PreviousShared()) {
++this->num_vehicles;
this->first_shared = u;
}
for (const Vehicle *u = v->NextShared(); u != nullptr; u = u->NextShared()) ++this->num_vehicles;
}
/**
* Free a complete order chain.
* @param keep_orderlist If this is true only delete the orders, otherwise also delete the OrderList.
* @note do not use on "current_order" vehicle orders!
*/
void OrderList::FreeChain(bool keep_orderlist)
{
Order *next;
for (Order *o = this->first; o != nullptr; o = next) {
next = o->next;
delete o;
}
if (keep_orderlist) {
this->first = nullptr;
this->num_orders = 0;
this->num_manual_orders = 0;
this->timetable_duration = 0;
} else {
delete this;
}
}
/**
* Get a certain order of the order chain.
* @param index zero-based index of the order within the chain.
* @return the order at position index.
*/
Order *OrderList::GetOrderAt(int index) const
{
if (index < 0) return nullptr;
Order *order = this->first;
while (order != nullptr && index-- > 0) {
order = order->next;
}
return order;
}
/**
* Get the next order which will make the given vehicle stop at a station
* or refit at a depot or evaluate a non-trivial condition.
* @param next The order to start looking at.
* @param hops The number of orders we have already looked at.
* @return Either of
* \li a station order
* \li a refitting depot order
* \li a non-trivial conditional order
* \li nullptr if the vehicle won't stop anymore.
*/
const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const
{
if (hops > this->GetNumOrders() || next == nullptr) return nullptr;
if (next->IsType(OT_CONDITIONAL)) {
if (next->GetConditionVariable() != OCV_UNCONDITIONALLY) return next;
/* We can evaluate trivial conditions right away. They're conceptually
* the same as regular order progression. */
return this->GetNextDecisionNode(
this->GetOrderAt(next->GetConditionSkipToOrder()),
hops + 1);
}
if (next->IsType(OT_GOTO_DEPOT)) {
if (next->GetDepotActionType() == ODATFB_HALT) return nullptr;
if (next->IsRefit()) return next;
}
if (!next->CanLoadOrUnload()) {
return this->GetNextDecisionNode(this->GetNext(next), hops + 1);
}
return next;
}
/**
* Recursively determine the next deterministic station to stop at.
* @param v The vehicle we're looking at.
* @param first Order to start searching at or nullptr to start at cur_implicit_order_index + 1.
* @param hops Number of orders we have already looked at.
* @return Next stoppping station or INVALID_STATION.
* @pre The vehicle is currently loading and v->last_station_visited is meaningful.
* @note This function may draw a random number. Don't use it from the GUI.
*/
StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, const Order *first, uint hops) const
{
const Order *next = first;
if (first == nullptr) {
next = this->GetOrderAt(v->cur_implicit_order_index);
if (next == nullptr) {
next = this->GetFirstOrder();
if (next == nullptr) return INVALID_STATION;
} else {
/* GetNext never returns nullptr if there is a valid station in the list.
* As the given "next" is already valid and a station in the list, we
* don't have to check for nullptr here. */
next = this->GetNext(next);
assert(next != nullptr);
}
}
do {
next = this->GetNextDecisionNode(next, ++hops);
/* Resolve possibly nested conditionals by estimation. */
while (next != nullptr && next->IsType(OT_CONDITIONAL)) {
/* We return both options of conditional orders. */
const Order *skip_to = this->GetNextDecisionNode(
this->GetOrderAt(next->GetConditionSkipToOrder()), hops);
const Order *advance = this->GetNextDecisionNode(
this->GetNext(next), hops);
if (advance == nullptr || advance == first || skip_to == advance) {
next = (skip_to == first) ? nullptr : skip_to;
} else if (skip_to == nullptr || skip_to == first) {
next = (advance == first) ? nullptr : advance;
} else {
StationIDStack st1 = this->GetNextStoppingStation(v, skip_to, hops);
StationIDStack st2 = this->GetNextStoppingStation(v, advance, hops);
while (!st2.IsEmpty()) st1.Push(st2.Pop());
return st1;
}
++hops;
}
/* Don't return a next stop if the vehicle has to unload everything. */
if (next == nullptr || ((next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT)) &&
next->GetDestination() == v->last_station_visited &&
(next->GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) {
return INVALID_STATION;
}
} while (next->IsType(OT_GOTO_DEPOT) || next->GetDestination() == v->last_station_visited);
return next->GetDestination();
}
/**
* Insert a new order into the order chain.
* @param new_order is the order to insert into the chain.
* @param index is the position where the order is supposed to be inserted.
*/
void OrderList::InsertOrderAt(Order *new_order, int index)
{
if (this->first == nullptr) {
this->first = new_order;
} else {
if (index == 0) {
/* Insert as first or only order */
new_order->next = this->first;
this->first = new_order;
} else if (index >= this->num_orders) {
/* index is after the last order, add it to the end */
this->GetLastOrder()->next = new_order;
} else {
/* Put the new order in between */
Order *order = this->GetOrderAt(index - 1);
new_order->next = order->next;
order->next = new_order;
}
}
++this->num_orders;
if (!new_order->IsType(OT_IMPLICIT)) ++this->num_manual_orders;
this->timetable_duration += new_order->GetTimetabledWait() + new_order->GetTimetabledTravel();
this->total_duration += new_order->GetWaitTime() + new_order->GetTravelTime();
/* We can visit oil rigs and buoys that are not our own. They will be shown in
* the list of stations. So, we need to invalidate that window if needed. */
if (new_order->IsType(OT_GOTO_STATION) || new_order->IsType(OT_GOTO_WAYPOINT)) {
BaseStation *bs = BaseStation::Get(new_order->GetDestination());
if (bs->owner == OWNER_NONE) InvalidateWindowClassesData(WC_STATION_LIST, 0);
}
}
/**
* Remove an order from the order list and delete it.
* @param index is the position of the order which is to be deleted.
*/
void OrderList::DeleteOrderAt(int index)
{
if (index >= this->num_orders) return;
Order *to_remove;
if (index == 0) {
to_remove = this->first;
this->first = to_remove->next;
} else {
Order *prev = GetOrderAt(index - 1);
to_remove = prev->next;
prev->next = to_remove->next;
}
--this->num_orders;
if (!to_remove->IsType(OT_IMPLICIT)) --this->num_manual_orders;
this->timetable_duration -= (to_remove->GetTimetabledWait() + to_remove->GetTimetabledTravel());
this->total_duration -= (to_remove->GetWaitTime() + to_remove->GetTravelTime());
delete to_remove;
}
/**
* Move an order to another position within the order list.
* @param from is the zero-based position of the order to move.
* @param to is the zero-based position where the order is moved to.
*/
void OrderList::MoveOrder(int from, int to)
{
if (from >= this->num_orders || to >= this->num_orders || from == to) return;
Order *moving_one;
/* Take the moving order out of the pointer-chain */
if (from == 0) {
moving_one = this->first;
this->first = moving_one->next;
} else {
Order *one_before = GetOrderAt(from - 1);
moving_one = one_before->next;
one_before->next = moving_one->next;
}
/* Insert the moving_order again in the pointer-chain */
if (to == 0) {
moving_one->next = this->first;
this->first = moving_one;
} else {
Order *one_before = GetOrderAt(to - 1);
moving_one->next = one_before->next;
one_before->next = moving_one;
}
}
/**
* Removes the vehicle from the shared order list.
* @note This is supposed to be called when the vehicle is still in the chain
* @param v vehicle to remove from the list
*/
void OrderList::RemoveVehicle(Vehicle *v)
{
--this->num_vehicles;
if (v == this->first_shared) this->first_shared = v->NextShared();
}
/**
* Checks whether a vehicle is part of the shared vehicle chain.
* @param v is the vehicle to search in the shared vehicle chain.
*/
bool OrderList::IsVehicleInSharedOrdersList(const Vehicle *v) const
{
for (const Vehicle *v_shared = this->first_shared; v_shared != nullptr; v_shared = v_shared->NextShared()) {
if (v_shared == v) return true;
}
return false;
}
/**
* Gets the position of the given vehicle within the shared order vehicle list.
* @param v is the vehicle of which to get the position
* @return position of v within the shared vehicle chain.
*/
int OrderList::GetPositionInSharedOrderList(const Vehicle *v) const
{
int count = 0;
for (const Vehicle *v_shared = v->PreviousShared(); v_shared != nullptr; v_shared = v_shared->PreviousShared()) count++;
return count;
}
/**
* Checks whether all orders of the list have a filled timetable.
* @return whether all orders have a filled timetable.
*/
bool OrderList::IsCompleteTimetable() const
{
for (Order *o = this->first; o != nullptr; o = o->next) {
/* Implicit orders are, by definition, not timetabled. */
if (o->IsType(OT_IMPLICIT)) continue;
if (!o->IsCompletelyTimetabled()) return false;
}
return true;
}
/**
* Checks for internal consistency of order list. Triggers assertion if something is wrong.
*/
void OrderList::DebugCheckSanity() const
{
VehicleOrderID check_num_orders = 0;
VehicleOrderID check_num_manual_orders = 0;
uint check_num_vehicles = 0;
Ticks check_timetable_duration = 0;
Ticks check_total_duration = 0;
DEBUG(misc, 6, "Checking OrderList %hu for sanity...", this->index);
for (const Order *o = this->first; o != nullptr; o = o->next) {
++check_num_orders;
if (!o->IsType(OT_IMPLICIT)) ++check_num_manual_orders;
check_timetable_duration += o->GetTimetabledWait() + o->GetTimetabledTravel();
check_total_duration += o->GetWaitTime() + o->GetTravelTime();
}
assert(this->num_orders == check_num_orders);
assert(this->num_manual_orders == check_num_manual_orders);
assert(this->timetable_duration == check_timetable_duration);
assert(this->total_duration == check_total_duration);
for (const Vehicle *v = this->first_shared; v != nullptr; v = v->NextShared()) {
++check_num_vehicles;
assert(v->orders.list == this);
}
assert(this->num_vehicles == check_num_vehicles);
DEBUG(misc, 6, "... detected %u orders (%u manual), %u vehicles, %i timetabled, %i total",
(uint)this->num_orders, (uint)this->num_manual_orders,
this->num_vehicles, this->timetable_duration, this->total_duration);
}
/**
* Checks whether the order goes to a station or not, i.e. whether the
* destination is a station
* @param v the vehicle to check for
* @param o the order to check
* @return true if the destination is a station
*/
static inline bool OrderGoesToStation(const Vehicle *v, const Order *o)
{
return o->IsType(OT_GOTO_STATION) ||
(v->type == VEH_AIRCRAFT && o->IsType(OT_GOTO_DEPOT) && !(o->GetDepotActionType() & ODATFB_NEAREST_DEPOT));
}
/**
* Delete all news items regarding defective orders about a vehicle
* This could kill still valid warnings (for example about void order when just
* another order gets added), but assume the company will notice the problems,
* when (s)he's changing the orders.
*/
static void DeleteOrderWarnings(const Vehicle *v)
{
DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_TOO_FEW_ORDERS);
DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_VOID_ORDER);
DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_DUPLICATE_ENTRY);
DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_INVALID_ENTRY);
DeleteVehicleNews(v->index, STR_NEWS_PLANE_USES_TOO_SHORT_RUNWAY);
}
/**
* Returns a tile somewhat representing the order destination (not suitable for pathfinding).
* @param v The vehicle to get the location for.
* @param airport Get the airport tile and not the station location for aircraft.
* @return destination of order, or INVALID_TILE if none.
*/
TileIndex Order::GetLocation(const Vehicle *v, bool airport) const
{
switch (this->GetType()) {
case OT_GOTO_WAYPOINT:
case OT_GOTO_STATION:
case OT_IMPLICIT:
if (airport && v->type == VEH_AIRCRAFT) return Station::Get(this->GetDestination())->airport.tile;
return BaseStation::Get(this->GetDestination())->xy;
case OT_GOTO_DEPOT:
if ((this->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) return INVALID_TILE;
return (v->type == VEH_AIRCRAFT) ? Station::Get(this->GetDestination())->xy : Depot::Get(this->GetDestination())->xy;
default:
return INVALID_TILE;
}
}
/**
* Get the distance between two orders of a vehicle. Conditional orders are resolved
* and the bigger distance of the two order branches is returned.
* @param prev Origin order.
* @param cur Destination order.
* @param v The vehicle to get the distance for.
* @param conditional_depth Internal param for resolving conditional orders.
* @return Maximum distance between the two orders.
*/
uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int conditional_depth)
{
if (cur->IsType(OT_CONDITIONAL)) {
if (conditional_depth > v->GetNumOrders()) return 0;
conditional_depth++;
int dist1 = GetOrderDistance(prev, v->GetOrder(cur->GetConditionSkipToOrder()), v, conditional_depth);
int dist2 = GetOrderDistance(prev, cur->next == nullptr ? v->orders.list->GetFirstOrder() : cur->next, v, conditional_depth);
return max(dist1, dist2);
}
TileIndex prev_tile = prev->GetLocation(v, true);
TileIndex cur_tile = cur->GetLocation(v, true);
if (prev_tile == INVALID_TILE || cur_tile == INVALID_TILE) return 0;
return v->type == VEH_AIRCRAFT ? DistanceSquare(prev_tile, cur_tile) : DistanceManhattan(prev_tile, cur_tile);
}
/**
* Add an order to the orderlist of a vehicle.
* @param tile unused
* @param flags operation to perform
* @param p1 various bitstuffed elements
* - p1 = (bit 0 - 19) - ID of the vehicle
* - p1 = (bit 24 - 31) - the selected order (if any). If the last order is given,
* the order will be inserted before that one
* the maximum vehicle order id is 254.
* @param p2 packed order to insert
* @param text unused
* @return the cost of this operation or an error
*/
CommandCost CmdInsertOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
VehicleID veh = GB(p1, 0, 20);
VehicleOrderID sel_ord = GB(p1, 20, 8);
Order new_order(p2);
Vehicle *v = Vehicle::GetIfValid(veh);
if (v == nullptr || !v->IsPrimaryVehicle()) return CMD_ERROR;
CommandCost ret = CheckOwnership(v->owner);
if (ret.Failed()) return ret;
/* Check if the inserted order is to the correct destination (owner, type),
* and has the correct flags if any */
switch (new_order.GetType()) {
case OT_GOTO_STATION: {
const Station *st = Station::GetIfValid(new_order.GetDestination());
if (st == nullptr) return CMD_ERROR;
if (st->owner != OWNER_NONE) {
CommandCost ret = CheckOwnership(st->owner);
if (ret.Failed()) return ret;
}
if (!CanVehicleUseStation(v, st)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER);
for (Vehicle *u = v->FirstShared(); u != nullptr; u = u->NextShared()) {
if (!CanVehicleUseStation(u, st)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER_SHARED);
}
/* Non stop only allowed for ground vehicles. */
if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR;
/* Filter invalid load/unload types. */
switch (new_order.GetLoadType()) {
case OLF_LOAD_IF_POSSIBLE: case OLFB_FULL_LOAD: case OLF_FULL_LOAD_ANY: case OLFB_NO_LOAD: break;
default: return CMD_ERROR;
}
switch (new_order.GetUnloadType()) {
case OUF_UNLOAD_IF_POSSIBLE: case OUFB_UNLOAD: case OUFB_TRANSFER: case OUFB_NO_UNLOAD: break;
default: return CMD_ERROR;
}
/* Filter invalid stop locations */
switch (new_order.GetStopLocation()) {
case OSL_PLATFORM_NEAR_END:
case OSL_PLATFORM_MIDDLE:
if (v->type != VEH_TRAIN) return CMD_ERROR;
FALLTHROUGH;
case OSL_PLATFORM_FAR_END:
break;
default:
return CMD_ERROR;
}
break;
}
case OT_GOTO_DEPOT: {
if ((new_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) {
if (v->type == VEH_AIRCRAFT) {
const Station *st = Station::GetIfValid(new_order.GetDestination());
if (st == nullptr) return CMD_ERROR;
CommandCost ret = CheckOwnership(st->owner);
if (ret.Failed()) return ret;
if (!CanVehicleUseStation(v, st) || !st->airport.HasHangar()) {
return CMD_ERROR;
}
} else {
const Depot *dp = Depot::GetIfValid(new_order.GetDestination());
if (dp == nullptr) return CMD_ERROR;
CommandCost ret = CheckOwnership(GetTileOwner(dp->xy));
if (ret.Failed()) return ret;
switch (v->type) {
case VEH_TRAIN:
if (!IsRailDepotTile(dp->xy)) return CMD_ERROR;
break;
case VEH_ROAD:
if (!IsRoadDepotTile(dp->xy)) return CMD_ERROR;
break;
case VEH_SHIP:
if (!IsShipDepotTile(dp->xy)) return CMD_ERROR;
break;
default: return CMD_ERROR;
}
}
}
if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR;
if (new_order.GetDepotOrderType() & ~(ODTFB_PART_OF_ORDERS | ((new_order.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) != 0 ? ODTFB_SERVICE : 0))) return CMD_ERROR;
if (new_order.GetDepotActionType() & ~(ODATFB_HALT | ODATFB_NEAREST_DEPOT)) return CMD_ERROR;
if ((new_order.GetDepotOrderType() & ODTFB_SERVICE) && (new_order.GetDepotActionType() & ODATFB_HALT)) return CMD_ERROR;
break;
}
case OT_GOTO_WAYPOINT: {
const Waypoint *wp = Waypoint::GetIfValid(new_order.GetDestination());
if (wp == nullptr) return CMD_ERROR;
switch (v->type) {
default: return CMD_ERROR;
case VEH_TRAIN: {
if (!(wp->facilities & FACIL_TRAIN)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER);
CommandCost ret = CheckOwnership(wp->owner);
if (ret.Failed()) return ret;
break;
}
case VEH_SHIP:
if (!(wp->facilities & FACIL_DOCK)) return_cmd_error(STR_ERROR_CAN_T_ADD_ORDER);
if (wp->owner != OWNER_NONE) {
CommandCost ret = CheckOwnership(wp->owner);
if (ret.Failed()) return ret;
}
break;
}
/* Order flags can be any of the following for waypoints:
* [non-stop]
* non-stop orders (if any) are only valid for trains */
if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && v->type != VEH_TRAIN) return CMD_ERROR;
break;
}
case OT_CONDITIONAL: {
VehicleOrderID skip_to = new_order.GetConditionSkipToOrder();
if (skip_to != 0 && skip_to >= v->GetNumOrders()) return CMD_ERROR; // Always allow jumping to the first (even when there is no order).
if (new_order.GetConditionVariable() >= OCV_END) return CMD_ERROR;
OrderConditionComparator occ = new_order.GetConditionComparator();
if (occ >= OCC_END) return CMD_ERROR;
switch (new_order.GetConditionVariable()) {
case OCV_REQUIRES_SERVICE:
if (occ != OCC_IS_TRUE && occ != OCC_IS_FALSE) return CMD_ERROR;
break;
case OCV_UNCONDITIONALLY:
if (occ != OCC_EQUALS) return CMD_ERROR;
if (new_order.GetConditionValue() != 0) return CMD_ERROR;
break;
case OCV_LOAD_PERCENTAGE:
case OCV_RELIABILITY:
if (new_order.GetConditionValue() > 100) return CMD_ERROR;
FALLTHROUGH;
default:
if (occ == OCC_IS_TRUE || occ == OCC_IS_FALSE) return CMD_ERROR;
break;
}
break;
}
default: return CMD_ERROR;
}
if (sel_ord > v->GetNumOrders()) return CMD_ERROR;
if (v->GetNumOrders() >= MAX_VEH_ORDER_ID) return_cmd_error(STR_ERROR_TOO_MANY_ORDERS);
if (!Order::CanAllocateItem()) return_cmd_error(STR_ERROR_NO_MORE_SPACE_FOR_ORDERS);
if (v->orders.list == nullptr && !OrderList::CanAllocateItem()) return_cmd_error(STR_ERROR_NO_MORE_SPACE_FOR_ORDERS);
if (flags & DC_EXEC) {
Order *new_o = new Order();
new_o->AssignOrder(new_order);
InsertOrder(v, new_o, sel_ord);
}
return CommandCost();
}
/**
* Insert a new order but skip the validation.
* @param v The vehicle to insert the order to.
* @param new_o The new order.
* @param sel_ord The position the order should be inserted at.
*/
void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord)
{
/* Create new order and link in list */
if (v->orders.list == nullptr) {
v->orders.list = new OrderList(new_o, v);
} else {
v->orders.list->InsertOrderAt(new_o, sel_ord);
}
Vehicle *u = v->FirstShared();
DeleteOrderWarnings(u);
for (; u != nullptr; u = u->NextShared()) {
assert(v->orders.list == u->orders.list);
/* If there is added an order before the current one, we need
* to update the selected order. We do not change implicit/real order indices though.
* If the new order is between the current implicit order and real order, the implicit order will
* later skip the inserted order. */
if (sel_ord <= u->cur_real_order_index) {
uint cur = u->cur_real_order_index + 1;
/* Check if we don't go out of bound */
if (cur < u->GetNumOrders()) {
u->cur_real_order_index = cur;
}
}
if (sel_ord == u->cur_implicit_order_index && u->IsGroundVehicle()) {
/* We are inserting an order just before the current implicit order.
* We do not know whether we will reach current implicit or the newly inserted order first.
* So, disable creation of implicit orders until we are on track again. */
uint16 &gv_flags = u->GetGroundVehicleFlags();
SetBit(gv_flags, GVF_SUPPRESS_IMPLICIT_ORDERS);
}
if (sel_ord <= u->cur_implicit_order_index) {
uint cur = u->cur_implicit_order_index + 1;
/* Check if we don't go out of bound */
if (cur < u->GetNumOrders()) {
u->cur_implicit_order_index = cur;
}
}
/* Update any possible open window of the vehicle */
InvalidateVehicleOrder(u, INVALID_VEH_ORDER_ID | (sel_ord << 8));
}
/* As we insert an order, the order to skip to will be 'wrong'. */
VehicleOrderID cur_order_id = 0;
Order *order;
FOR_VEHICLE_ORDERS(v, order) {
if (order->IsType(OT_CONDITIONAL)) {
VehicleOrderID order_id = order->GetConditionSkipToOrder();
if (order_id >= sel_ord) {
order->SetConditionSkipToOrder(order_id + 1);
}
if (order_id == cur_order_id) {
order->SetConditionSkipToOrder((order_id + 1) % v->GetNumOrders());
}
}
cur_order_id++;
}
/* Make sure to rebuild the whole list */
InvalidateWindowClassesData(GetWindowClassForVehicleType(v->type), 0);
}
/**
* Declone an order-list
* @param *dst delete the orders of this vehicle
* @param flags execution flags
*/
static CommandCost DecloneOrder(Vehicle *dst, DoCommandFlag flags)
{
if (flags & DC_EXEC) {
DeleteVehicleOrders(dst);
InvalidateVehicleOrder(dst, VIWD_REMOVE_ALL_ORDERS);
InvalidateWindowClassesData(GetWindowClassForVehicleType(dst->type), 0);
}
return CommandCost();
}
/**
* Delete an order from the orderlist of a vehicle.
* @param tile unused
* @param flags operation to perform
* @param p1 the ID of the vehicle
* @param p2 the order to delete (max 255)