-
Notifications
You must be signed in to change notification settings - Fork 0
/
airmanager.nut
3546 lines (3317 loc) · 150 KB
/
airmanager.nut
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
/**
* This file is part of WormAI: An OpenTTD AI.
*
* @file airmanager.nut Class containing the Air Manager for WormAI.
*
* License: GNU GPL - version 2 (see license.txt)
* Author: Wormnest (Jacob Boerema)
* Copyright: Jacob Boerema, 2013-2016.
*
*/
/* Define some constants for easier maintenance. */
/* Airport/Aircraft handling. */
const MINIMUM_BALANCE_BUILD_AIRPORT = 70000; ///< Minimum bank balance to start building airports.
const MINIMUM_BALANCE_AIRCRAFT = 25000; ///< Minimum bank balance to allow buying a new aircraft.
const MINIMUM_BALANCE_TWO_AIRCRAFT = 5000000; ///< Minimum bank balance to allow buying 2 aircraft at once.
const MINIMUM_BALANCE_BUILD_STATUE = 750000; ///< Minimum bank balance to allow building of statues.
const BUILD_OVERHEAD = 20000; ///< Add a little extra for building (certain terrain costs more to build on).
const AIRCRAFT_LOW_PRICE_CUT = 500000; ///< Bank balance below which we will try to buy a low price aircraft.
const AIRCRAFT_MEDIUM_PRICE_CUT = 2000000; ///< Bank balance below which we will try to buy a medium price aircraft.
const AIRCRAFT_LOW_PRICE = 50000; ///< Maximum price of a low price aircraft.
const AIRCRAFT_MEDIUM_PRICE = 250000; ///< Maximum price of a medium price aircraft.
const AIRCRAFT_HIGH_PRICE = 1500000; ///< Maximum price of a high price aircraft.
const STARTING_ACCEPTANCE_LIMIT = 150; ///< Starting limit in acceptance for finding suitable airport tile.
const BAD_YEARLY_PROFIT = 10000; ///< Yearly profit limit below which profit is deemed bad.
const AIRPORT_LIMIT_FACTOR = 4; ///< We limit airports to max aircraft / FACTOR * 2 (2 needed per route).
const AIRPORT_CARGO_WAITING_LOW_LIMIT = 250; ///< Limit of waiting cargo (passengers) on airport above which we add an aircraft.
const AIRPORT_CARGO_WAITING_HIGH_LIMIT = 1250; ///< Limit of waiting cargo (passengers) on airport above which we add 2 aircraft.
const AIRPORT2_WAITING_DIFF = 150; ///< Cargo waiting diff (less) value at the other station to allow extra aircraft.
const VEHICLE_AGE_LEFT_LIMIT = 150; ///< Number of days limit before maximum age for vehicle to get sent to depot for selling.
const MAX_STATUES_BUILD_COUNT = 3; ///< Maximum number of statues we will build at one time.
/// @{
/**
* @name Reasons for selling vehicles
* @todo Maybe convert to enum.
*/
const VEH_OLD_AGE = 0; ///< Vehicle is sold because of its old age.
const VEH_LOW_PROFIT = 1; ///< Vehicle is sold because it has low profits.
const VEH_STATION_REMOVAL = 2; ///< Vehicle is sold because one of its stations got removed and could not be replaced.
const VEH_TOO_MANY = 3; ///< Vehicle is sold because there are too many on this route
/// @}
/**
* Define the WormAirManager class which handles airports and airplanes.
*/
class WormAirManager
{
static DEFAULT_MAX_COSTFACTOR = 350; ///< We don't want airplanes that have a higher costfactor than this unless we have nothing but planes to play with.
static DEFAULT_ANY_COSTFACTOR = 10000; ///< Used in case we want to accept any costfactor.
/* Variables used by WormAirManager */
/* 1. Variables that will be saved in a savegame. */
towns_used = null; ///< town id, airport station tile
route_1 = null; ///< vehicle id, station_tile of first station in an order
route_2 = null; ///< vehicle id, station_tile of last station in an order
/* 2. Variables that will NOT be saved. */
/// @todo Change distance_of_route to not register distance PER VEHICLE but only once PER ROUTE
distance_of_route = {}; ///< vehicle id, distance between first/last order stations
vehicle_to_depot = {}; ///< vehicle id, boolean always true currently
engine_usefulness = null;
acceptance_limit = 0; ///< Starting limit for passenger acceptance for airport finding.
passenger_cargo_id = -1;
no_aircraft_warning_shown = false; ///< Whether we have shown the no available aircraft warning
route_without_aircraft = false; ///< True if we have built airports but failed to buy airplanes due to lack of money.
incomplete_route_tile1 = 0; ///< Tile of first airport of incomplete air route without aircraft.
incomplete_route_tile2 = 0; ///< Tile of second airport of incomplete air route without aircraft.
towns_blacklist = null; ///< List of towns where we already tried to build an airport.
upgrade_blacklist = null; ///< List of towns where we failed to upgrade the airport.
low_price_small = 0; ///< Lowest price of a small airplane.
low_price_big = 0; ///< Lowest price of a big airplane.
max_costfactor = 0;
upgrade_wanted = null; ///< List of airports we would like to upgrade.
min_distance_squared = 0; ///< Precomputed minimum distance squared based on current settings.
max_distance_squared = 0; ///< Precomputed maximum distance squared based on current settings.
max_aircraft_distance = 0; ///< Highest distance that an aircraft can fly.
max_preferred_distance = 0; ///< Max preferred distance based on speed and reliability.
airport_upgrader = null; ///< Airport upgrading class
/** Create an instance of WormAirManager and initialize our variables. */
constructor()
{
this.max_costfactor = this.DEFAULT_MAX_COSTFACTOR;
this.distance_of_route = {};
this.vehicle_to_depot = {};
this.towns_used = AIList();
this.towns_blacklist = AIList();
this.upgrade_blacklist = AIList();
this.route_1 = AIList();
this.route_2 = AIList();
this.engine_usefulness = AIList();
this.acceptance_limit = STARTING_ACCEPTANCE_LIMIT;
/* Get the correct Passengers id even when an industry NewGRF is used. */
this.passenger_cargo_id = Helper.GetPAXCargo();
/* Create the handler for airport upgrading. */
this.airport_upgrader = WormAirportUpgrade(this);
}
/// @{
/** @name Debugging output functions */
/** List of towns used and stations near those towns. */
function DebugListTownsUsed();
/** List all towns in the supplied list. */
function DebugListTowns(towns_list);
/** List all routes: per route all stations and all vehicles on that route with relevant info. */
function DebugListRoutes();
/** List all route info in the supplied list. */
function DebugListRoute(route);
/** List all our air routes. */
function DebugListRouteInfo();
/** List all vehicles that were sent to depot to be sold. */
function DebugListVehiclesSentToDepot();
/**
* Show info about the specified vehicle. It's start and end town and distance between them.
* @param veh = Vehicle id
*/
function DebugVehicleInfo(veh);
/// @}
/** @name Airport handling functions */
/// @{
/**
* Get tile of airport at the other end of the route.
* @param town_id Town id of town at this end of route.
* @param station_tile tile of station at this end of route.
* @return tile of airport or -1 if not found.
*/
function GetAiportTileOtherEndOfRoute(town_id, station_tile);
/**
* Update the airport station tile info in our lists after upgrading airport.
* Expects a valid station_id.
* @param town_idx Index into towns_used list.
* @param station_id Id of the Airport station that got upgraded.
* @param old_tile The old tile for the airport before upgrading.
*/
function UpdateAirportTileInfo(town_idx, station_id, old_tile);
/**
* Replace the airport town and station tile info in our lists and update orders.
* @param old_town_idx Index into towns_used list of town/station being replaced
* @param old_tile The old tile for the airport being replaced.
* @param new_tile The tile of the new airport.
* @param other_end_of_route_tile Tile of other end of route (needed to access vehicles of route)
*/
function ReplaceAirportTileInfo(old_town_idx, old_tile, new_tile, other_end_of_route_tile);
/**
* Checks all airports to see if they should be upgraded.
* If they can it tries to upgrade the airport. If it fails after removing the old airport
* it will first try to replace it with another airport at another spot. If that also fails
* it will send the aircraft on the now crippled route to depot to be sold. After all
* aircraft are sold the remaining airport will be sold too.
*/
function CheckForAirportsNeedingToBeUpgraded();
/**
* Update the @ref upgrade_wanted list of towns with airports that can and should be upgraded.
*
*/
function UpdateAirportUpgradeList();
/**
* Build an airport route. Find 2 cities that are big enough and try to build airport in both cities.
* Then we can build an aircraft and try to make some money.
* We limit our amount of airports to max aircraft / AIRPORT_LIMIT_FACTOR * 2.
* (2 airports for a route, and AIRPORT_LIMIT_FACTOR planes per route)
*/
function BuildAirportRoute();
/**
* Get a list of towns that is suitable to be searched for an airport location.
* @return AITownList with towns that can be used.
*/
function GetTownListForAirportSearch();
/**
* Find a suitable spot for an airport, walking all towns hoping to find one.
* When a town is used, it is marked as such and not re-used.
* @param airport_type The type of airport we want to build.
* @param center_tile The tile around which we will search for a spot for the airport.
* @param towns List of towns to search.
* @return tile where we can build the airport or an error code.
*/
function FindSuitableAirportSpot(airport_type, center_tile, towns);
/**
* Find a candidate spot in the specified town to build an airport of the specified type.
* @param town The town id of the town where we should search.
* @param airport_type For which type of airport.
* @param airport_width The width of the airport.
* @param airport_height The height of the airport.
* @param coverageradius The coverage radius of the airport.
* @param center_tile The tile of the airport at the other end of the route or 0 if this is the first airport on the route.
* @param minimum_acceptance The minimum cargo acceptance we should allow for suitable spots.
* @param add_to_blacklist Boolean (default true) If true adds town to blacklist if no suitable spot could be found.
* @param old_airport_type (default=AIAirport.AT_INVALID) If not invalid noise limits are checked for replacing old_airport_type with airport type.
* @return The tile where an airport can be built or ERROR_FIND_AIRPORT1 or ERROR_FIND_AIRPORT2.
*/
function FindAirportSpotInTown(town, airport_type, airport_width, airport_height,
coverage_radius, center_tile, minimum_acceptance, add_to_blacklist=true, old_airport_type=AIAirport.AT_INVALID);
/**
* Sells the airports at tile_1 and tile_2. Removes towns from towns_used list too.
* @param airport_1_tile The tile of the first airport to remove
* @param airport_2_tile The tile of the other airport to remove
* @note The airport tiles are allowed to be invalid. Removal will be ignored in that
* case but the towns_used will be updated.
*/
function SellAirports(airport_1_tile, airport_2_tile);
/**
* Try to build an airport.
* @param tile_1 The tile of the proposed first airport on a route.
* @param tile_2 The tile of the proposed second airport on a route.
* @param is_first_airport Boolean: true if it's the first airport we want to build, false if the second should be built.
* @param airport_type The type of airport to build.
* @return The actual tile where the airport was built or ERROR_BUILD_AIRPORT1.
* @note The actual tile where the airport got built can be different than tile_1/tile_2 because
* if building there fails we will try to find a second spot to build. If that succeeds the tile of
* that spot is returned.
*/
function TryToBuildAirport(tile_1, tile_2, is_first_airport, airport_type);
/**
* Try to upgrade an airport in specified town where old airport is at station_tile.
* @param town The town connected to the airport.
* @param station_tile The tile of the current airport.
* @param optimal_airport The airport type we want to upgrade to.
* @return false if we failed to upgrade, true if airport got upgraded.
*/
function TryUpgradeAirport(town, station_tile, optimal_airport);
/**
* Tries to upgrade an airport.
* @param nearest_town The nearest town according to town influence.
* @param station_id The id of the airport to upgrade.
* @param station_tile The tile of the airport.
* @param airport_type The type of airport we want to upgrade to.
* @param other_station_tile The tile of the airport at the other end of the route.
* @return WormAirport.BUILD_SUCCESS if we succeed, or else one of the BUILD_XXX error codes.
*/
function UpgradeAirport(nearest_town, station_id, station_tile, airport_type, other_station_tile);
/*
* Check if we can upgrade an airport that is saturated.
* @param st_id The station id of the airport.
* @param st_tile The tile location of the airport.
*/
function CanWeUpgradeSaturatedAirport(st_id, st_tile);
/**
* Compute the optimal maximum distance an aircraft should fly based on the supplied maximum speed.
* @param speed The maximum speed of the aircraft.
* @param reliability The reliability of the aircraft.
* @return The maximum distance in tiles.
*/
function GetPreferredMaxDistance(speed, reliability);
/// @}
/** @name Order handling */
/// @{
/**
* Check whether the airport at a certain town is used as the first order of a route.
* @param town_id The id of the town to check if it's the first order.
* @return true if it is the first order, else false if it is the last order.
*/
function IsTownFirstOrder(town_id);
/**
* Replace orders of a vehicle, either the first station or last station is replaced.
* @param veh Vehicle to replace the orders for.
* @param is_first_order Whether to replace the orders for the first or last station.
* @param breakdowns Whether breakdowns are on; if they are on we will add maintenance orders.
* @param station_tile Tile of station for the new order.
*/
function ReplaceOrders(veh, is_first_order, breakdowns, station_tile);
/**
* Insert go to station order for airport at station_tile.
* @param veh Vehicle to set the order for.
* @param order_pos Position in the order list where order should be inserted.
* @param station_tile Tile for the Airport of the to be inserted order.
* @return true if order got inserted; false in case of failure to insert.
*/
function InsertGotoStationOrder(veh, order_pos, station_tile);
/**
* Insert Maintenance order for airport at station_tile.
* @param veh Vehicle to set the order for
* @param order_pos Position in the order list where order should be inserted
* @param station_tile Tile for the Airport (not the hangar) of the order to be inserted.
* @return true if order got inserted; false in case of failure to insert.
*/
function InsertMaintenanceOrder(veh, order_pos, station_tile);
/**
* Replace go to station order for airport at station_tile.
* @param veh Vehicle to set the order for.
* @param order_pos Position in the order list where order should be inserted.
* @param station_tile Tile for the Airport of the new to be inserted order.
*/
function ReplaceGotoStationOrder(veh, order_pos, station_tile);
/// @}
/** @name Aircraft handling */
/// @{
/**
* Get the minimum price of an aircraft.
* @param IsForSmallAirport Boolean True if only aircraft that can land on small airports should be considered.
* @return The lowest price of an aircraft.
*/
function GetAircraftMinimumPrice(IsForSmallAirport);
/**
* Get the maximum distance this aircraft can safely fly without landing.
* @param engine The engine id for which we want to know the maximum distance.
* @return The maximum distance.
*/
function GetMaximumDistance(engine);
/**
* Build an aircraft with orders from tile_1 to tile_2.
* The best available aircraft will be bought.
* @param tile_1 Airport tile that should be used as the first order.
* @param tile_2 Airport tile that should be used as the last order.
* @param start_tile The Airport tile where the airplane should start. If this is 0 then
* it will start at tile_1. To let it start at tile_2 use the same value as tile_2.
*/
function BuildAircraft(tile_1, tile_2, start_tile);
/**
* Send all vehicles belonging to a station to depot for selling.
* @param station_id The id of the station.
* @param sell_reason The reason for selling. Valid reasons are
* @ref VEH_OLD_AGE, @ref VEH_LOW_PROFIT, @ref VEH_STATION_REMOVAL, @ref VEH_TOO_MANY
*/
function SendAllVehiclesOfStationToDepot(station_id, sell_reason);
/**
* Send a vehicle to depot to be sold when it arrives.
* @param vehicle The vehicle id of the vehicle to be sold.
* @param sell_reason The reason for selling. Valid reasons are
* @ref VEH_OLD_AGE, @ref VEH_LOW_PROFIT, @ref VEH_STATION_REMOVAL, @ref VEH_TOO_MANY
*/
function SendToDepotForSelling(vehicle,sell_reason);
/**
* Remove a vehicle from our route lists and to depot list.
* @note If this is the last vehicle serving a certain route then after selling
* the vehicle we will also sell the airports.
* @param vehicle The vehicle id that should be removed from the lists.
*/
function RemoveVehicleFromLists(vehicle);
/**
* Sell the vehicle provided it's in depot. If it's not yet in depot it will fail silently.
* @param vehicle The id of the vehicle that should be sold.
*/
function SellVehicleInDepot(vehicle);
/**
* Sell all vehicles in depot that are marked to be sold.
*/
function SellVehiclesInDepot();
/**
* Send all airplanes that are currently on this (assumed closed) airport to their next order.
* @param town_id The id of the town this airport belongs to.
* @param station_id The id of the airport station.
*/
function SendAirplanesOffAirport(town_id, station_id);
/// @}
/** @name Task related functions */
/// @{
/**
* Check all vehicles for being old or needing upgrading to a newer type.
* It will send all vehicles that are non optimal to depot for selling.
*/
function ManageVehicleRenewal();
/**
* Check for airports that don't have any vehicles anymore and delete them.
*/
function CheckAirportsWithoutVehicles();
/**
* Remove towns from the blacklists where blacklisting has expired.
*/
function UpdateBlacklists();
/**
* Manage air routes:
* ------------------
* - Checks for airports without vehicles.
* - Send unprofitable aircraft to depot for selling.
* - Add aircraft to routes that have a lot of waiting cargo.
* @return Error code if something went wrong or ok.
* @todo Refactor the parts of this function into separate functions.
*/
function ManageAirRoutes();
/**
* Check if this airport route is overcrowded.
* @note Current SuperLib version 40 and older versions have incorrect GetAircraftInHangar and
* GetNumNonStopedAircraftInAirportDepot. We can't use those so we create our own function.
* @param st1 First station of a route.
* @param st2 Second station of a route or -1 if we should only check the first station.
* @return 0 = ok, < 0 = over saturated (too many planes waiting in hangar)
*/
function RouteSaturationStatus(st1, st2);
/**
* Check for routes that are over saturated, i.e. have aircraft waiting in hangar or flying in queue around airport.
* There's some things we can do about it.
* 1. Remove full load order; Note that usually the town that doesn't have a lot of waiting aircraft
* is the one whose order we should change.
* 2. Remove some aircraft (the lowest in capacity first, but not aicraft younger than 2 years)
* 3. Upgrade to larger airport (may be problematic because of extra noise)
* @note Only 2. is currently implemented.
* @todo Points 1 and 3.
*/
function CheckOversaturatedRoutes();
/**
* Callback that handles events. Currently only AIEvent.ET_VEHICLE_CRASHED is handled.
*/
function HandleEvents();
/**
* Compute squared min and max route distances based on our AI settings, adjusted for the range of the aircraft we can afford.
*/
function ComputeDistances();
/**
* Get the lowest prices of the current available big and small airplanes.
* @param engine_list List of airplanes that can transport passengers.
*/
function CheckAirplanePrices(engine_list);
/**
* Task that evaluates all available aircraft for how suited they are
* for our purposes. The suitedness values for aircraft which we can use are saved in
* @ref engine_usefulness.
* @param clear_warning_shown_flag Whether to clear the @ref no_aircraft_warning_shown flag.
*/
function EvaluateAircraft(clear_warning_shown_flag);
/**
* Build the company headquarters if there isn't one yet.
* @note Adapted from the version in AdmiralAI.
*/
function BuildHQ();
/**
* Build statues in towns where we have a station as long as we have a reasonable amount of money.
* We limit the amount of statues we build at any one time.
*/
function BuildStatues();
/// @}
/** @name General functions */
/// @{
/**
* Get Town id in our towns_used list based on tile of station built near it.
* @return id of town or null if not found.
*/
function GetTownFromStationTile(st_tile);
/**
* Determine if a station is valid based on the station tile.
* @param st_tile The tile of the station.
* @return true if station is valid, otherwise false.
*/
function IsValidStationFromTile(st_tile);
/**
* Determine if the first station of the route of a vehicle is valid.
* @param veh Vehicle to determine the validity of the station for.
* @return true if station is valid, otherwise false.
*/
function IsValidFirstStation(veh);
/**
* Determine if the last station of the route of a vehicle is valid.
* @param veh Vehicle to determine the validity of the station for.
* @return true if station is valid, otherwise false.
*/
function IsValidLastStation(veh);
/// @}
/** @name Valuator functions */
/// @{
/**
* Valuator function to get the cost factor of an aircraft.
* @param engine The engine id of the aircraft.
* @param costfactor_list The list (usually @ref engine_usefulness) that holds the cost factors.
* @return The cost factor.
*/
function GetCostFactor(engine, costfactor_list);
/// @}
/**
* Do any necessary processing after a savegame has been loaded.
* Currently recomputes the values for the @ref distance_of_route table.
*/
function AfterLoading();
/**
* Try to load existing air routes and towns used from scratch.
* This can be needed when we get loaded into a savegame from a different AI.
*/
function LoadFromScratch();
}
//////////////////////////////////////////////////////////////////////////
// Debugging functions
/** List of towns used and stations near those towns. */
function WormAirManager::DebugListTownsUsed()
{
AILog.Info("---------- DEBUG towns_used and related stations ----------");
if (!this.towns_used) {
AILog.Warning("WARNING: towns_used is null!");
}
else {
AILog.Info("Number of towns used: " + this.towns_used.Count())
//foreach(t in towns_used) {
for (local t = towns_used.Begin(); !towns_used.IsEnd(); t = towns_used.Next()) {
local tile = towns_used.GetValue(t);
local is_city = (AITown.IsCity(t) ? "city" : "town");
AILog.Info("Town: " + AITown.GetName(t) + " (id: " + t + "), " + is_city +
", population: " + AITown.GetPopulation(t) + ", houses: " + AITown.GetHouseCount(t) +
", grows every " + AITown.GetGrowthRate(t) + " days");
AILog.Info(" Location: " + WormStrings.WriteTile(AITown.GetLocation(t)) +
", station tile " + WormStrings.WriteTile(tile) + ").")
local sid = AIStation.GetStationID(tile);
local st_veh = AIVehicleList_Station(sid);
AILog.Info("Station: " + AIStation.GetName(sid) + " (id: " + sid + "), waiting cargo: " +
AIStation.GetCargoWaiting(sid, passenger_cargo_id) + ", cargo rating: " +
AIStation.GetCargoRating(sid, passenger_cargo_id) + ", aircraft: " +
st_veh.Count());
}
}
AILog.Info("");
}
/** List all towns in the supplied list. */
function WormAirManager::DebugListTowns(towns_list)
{
AILog.Info("---------- DEBUG list towns in list----------");
if (!this.towns_used) {
AILog.Warning("WARNING: towns_list is null!");
}
else {
AILog.Info("Number of towns in list: " + towns_list.Count());
for (local t = towns_list.Begin(); !towns_list.IsEnd(); t = towns_list.Next()) {
local is_city = (AITown.IsCity(t) ? "city" : "town");
AILog.Info("Town: " + AITown.GetName(t) + " (id: " + t + "), " + is_city +
", population: " + AITown.GetPopulation(t) + ", houses: " + AITown.GetHouseCount(t) +
", grows every " + AITown.GetGrowthRate(t) + " days");
AILog.Info(" Location: " + WormStrings.WriteTile(AITown.GetLocation(t)) );
}
}
AILog.Info("");
}
/** List all routes: per route all stations and all vehicles on that route with relevant info. */
function WormAirManager::DebugListRoutes()
{
AILog.Info("---------- DEBUG route info ----------");
local expected_route_count = this.towns_used.Count() / 2;
local route_count = 0;
local veh_count = 0;
local veh_check = AIList();
veh_check.AddList(route_1); // Check to see if all vehicles are accounted for
AILog.Info("Number of routes: " + expected_route_count );
for (local t = towns_used.Begin(); !towns_used.IsEnd(); t = towns_used.Next()) {
local st_tile = towns_used.GetValue(t);
// Find out whether this station is the first or last order
local route1 = AIList();
route1.AddList(route_1);
// Keep only those with our station tile
route1.KeepValue(st_tile);
if (route1.Count() == 0) {
/* Don't warn here since a station can be either on route_1 or route_2. */
continue;
}
// List from and to station names, and distance between them, and total profit of
// all planes on route in the last year
local st_id = AIStation.GetStationID(st_tile);
local st_veh = AIVehicleList_Station(st_id);
route_count += 1;
if (st_veh.Count() == 0) {
/* Might happen after a failed upgrading of stations. The other one will then get
removed after all aircraft haven been sold. */
AILog.Warning("Station " + AIStation.GetName(st_id) + " near town " + AITown.GetName(t)
+ " has 0 aircraft!");
continue;
}
local first = true;
local total_profit = 0;
// Sort vehicle list on last years profit
st_veh.Valuate(AIVehicle.GetProfitLastYear);
for (local veh = st_veh.Begin(); !st_veh.IsEnd(); veh = st_veh.Next()) {
veh_check.RemoveItem(veh); // Remove vehicle from our checklist
if (first) {
// Get list of stations this vehicle has in its orders
local veh_stations = AIStationList_Vehicle(veh);
local st_end_id = -1;
foreach(veh_st_id, dummy_val in veh_stations) {
// Since we have only 2 stations in our orders any id not the same
// as st_id will be our target station id
if (veh_st_id != st_id) {
st_end_id = veh_st_id;
break;
}
}
local st_end_tile = route_2.GetValue(veh);
local sq_dist = AITile.GetDistanceSquareToTile(st_tile, st_end_tile)
AILog.Info( "Route from " + AIStation.GetName(st_id) +" ("+st_id+ ") to " +
AIStation.GetName(st_end_id) +" ("+st_end_id+") "+
", distance: " + sqrt(sq_dist).tointeger());
first = false;
}
// Show info about aircraft
AILog.Info(" " + AIVehicle.GetName(veh) + " (id: " + veh + "), age: " +
WormStrings.GetAgeString(AIVehicle.GetAge(veh)) + ", capacity: " +
AIVehicle.GetCapacity(veh, passenger_cargo_id) + ", size: " +
WormStrings.GetAircraftTypeAsText(AIVehicle.GetEngineType(veh)) );
local last_profit = AIVehicle.GetProfitLastYear(veh);
// Increment total profit for this route
total_profit += last_profit;
AILog.Info(" Profit last year: " + last_profit + ", this year: " +
AIVehicle.GetProfitThisYear(veh));
}
veh_count += st_veh.Count();
AILog.Warning(" Total " + st_veh.Count() + " aircraft. Total profit last year: " + total_profit + ", average: " + (total_profit / st_veh.Count()));
}
if (route_count != expected_route_count) {
AILog.Error("Attention! Route count: " + route_count + " is not the same as the expected route count: " +
expected_route_count);
//DebugListRoute(route_1);
//DebugListRoute(route_2);
}
if (veh_count != this.route_1.Count()) {
AILog.Error("Attention! Vehicle count on our stations: " + veh_count +
" is not the same as vehicles on routes count: " + this.route_1.Count());
//DebugListRoute(route_1);
//DebugListRoute(route_2);
}
if (veh_check.Count() > 0) {
/* Should result in the same amount as the previous check. */
/* A reason for showing up here is vehicles that are sent to depot to be sold
because of a failed upgrade of a station of a route. */
AILog.Warning("The following vehicles were not used by the airports listed above.");
DebugListRoute(veh_check);
DebugListVehiclesSentToDepot();
}
AILog.Info("--------------------");
AILog.Info("");
}
/** List all route info in the supplied list. */
function WormAirManager::DebugListRoute(route)
{
AILog.Info("---------- DEBUG route ----------");
if (!route) {
AILog.Error("ERROR: route is null!");
}
else {
AILog.Info("Number of aircraft in this list: " + route.Count());
for (local r = route.Begin(); !route.IsEnd(); r = route.Next()) {
local st_tile = route.GetValue(r);
AILog.Info("Aircraft: " + AIVehicle.GetName(r) + " (id: " + r + ", tile " +
WormStrings.WriteTile(st_tile) + " = station " +
AIStation.GetName(AIStation.GetStationID(st_tile)) + ").");
}
}
AILog.Info("");
}
/** List all our air routes. */
function WormAirManager::DebugListRouteInfo()
{
//this.route_1.AddItem(vehicle, tile_1);
//this.route_2.AddItem(vehicle, tile_2);
local temp_route = AIList();
temp_route.AddList(this.route_1); // so that we don't sort the original list
AILog.Info("---------- DEBUG route info ----------");
if (!temp_route) {
AILog.Warning("WARNING: route list is null!");
}
else {
temp_route.Sort(AIList.SORT_BY_ITEM, true);
AILog.Info("Number of aircraft used: " + temp_route.Count());
for (local r = temp_route.Begin(); !temp_route.IsEnd(); r = temp_route.Next()) {
local tile1 = 0;
local tile2 = 0;
local t1 = 0;
local t2 = 0;
local route_start = AIList();
local route_end = AIList();
route_start.AddList(this.towns_used);
route_end.AddList(this.towns_used);
tile1 = temp_route.GetValue(r);
tile2 = route_2.GetValue(r);
route_start.KeepValue(tile1);
t1 = route_start.Begin();
route_end.KeepValue(tile2);
t2 = route_end.Begin();
local dist = this.distance_of_route.rawget(r);
AILog.Info("Aircraft: " + AIVehicle.GetName(r) + " (id: " + r + "), from: " +
AITown.GetName(t1) + ", to: " + AITown.GetName(t2) + ", distance: " + dist);
}
}
AILog.Info("");
}
/**
* Show info about the specified vehicle. It's start and end town and distance between them.
* @param veh = Vehicle id
*/
function WormAirManager::DebugVehicleInfo(veh) {
local output_str = "Aircraft: ";
if (AIVehicle.IsValidVehicle(veh)) {
output_str += AIVehicle.GetName(veh);
}
else {
output_str += "<invalid aircraft id>";
}
output_str += " (id: " + veh + "), from: ";
/* Get route start and end points. */
local tile1 = route_1.GetValue(veh);
local tile2 = route_2.GetValue(veh);
local town1 = GetTownFromStationTile(tile1);
local town2 = GetTownFromStationTile(tile2);
if (town1 > -1)
{ output_str += AITown.GetName(town1); }
else
{ output_str += "<invalid town id>"; }
output_str += ", to: ";
if (town2 > -1)
{ output_str += AITown.GetName(town2); }
else
{ output_str += "<invalid town id>"; }
local dist = this.distance_of_route.rawget(veh);
output_str += ", distance: " + dist + ".";
/* If vehicle was sent to depot to be sold give some info about that. */
if (vehicle_to_depot.rawin(veh)) {
output_str += " [On the way to depot to be sold.]";
}
AILog.Info(output_str);
}
/**
* List all vehicles that were sent to depot to be sold.
*/
function WormAirManager::DebugListVehiclesSentToDepot() {
AILog.Info("The following vehicles are on the way to depot to be sold.");
// veh_id = vehicle id, veh_value = boolean, always true currently
foreach( veh_id, veh_value in vehicle_to_depot) {
DebugVehicleInfo(veh_id);
}
}
// End debugging functions
//////////////////////////////////////////////////////////////////////////
/**
* Get Town id in our towns_used list based on tile of station built near it.
* @return id of town or null if not found.
*/
function WormAirManager::GetTownFromStationTile(st_tile) {
local towns = AIList();
towns.AddList(this.towns_used);
towns.KeepValue(st_tile);
if (towns.Count() > 0) {
return towns.Begin();
}
else
{ return null; }
}
/**
* Check whether the airport at a certain town is used as the first order of a route.
* @param town_id The id of the town to check if it's the first order.
* @return true if it is the first order, else false if it is the last order.
*/
function WormAirManager::IsTownFirstOrder(town_id)
{
local station_tile = towns_used.GetValue(town_id);
// Copy the list of First order routes
local route1 = AIList();
route1.AddList(route_1);
// Keep only those with our station tile
route1.KeepValue(station_tile);
/* AILog.Info("Town: " + AITown.GetName(town_id) + ", station at tile: " + WormStrings.WriteTile(station_tile) +
", route1 count: " + route1.Count()); */
// return true if found (not 0) in route_1
return (route1.Count() != 0);
}
/**
* Get tile of airport at the other end of the route.
* @param town_id Town id of town at this end of route.
* @param station_tile tile of station at this end of route.
* @return tile of airport or -1 if not found.
*/
function WormAirManager::GetAiportTileOtherEndOfRoute(town_id, station_tile)
{
local route1 = AIList();
local route2 = AIList();
if (IsTownFirstOrder(town_id)) {
route1.AddList(route_1);
route2 = route_2;
}
else {
route1.AddList(route_2);
route2 = route_1;
}
// Keep only those with our station tile
route1.KeepValue(station_tile);
if (route1.Count() == 0) {
if (!route_without_aircraft)
AILog.Warning(" No routes found that contain this station!");
return -1;
}
/* Return tile for other end of route from first vehicle belonging to other end of route. */
return route2.GetValue(route1.Begin());
}
/**
* Update the airport station tile info in our lists after upgrading airport.
* Expects a valid station_id.
* @param town_idx Index into towns_used list.
* @param station_id Id of the Airport station that got upgraded.
* @param old_tile The old tile for the airport before upgrading.
*/
function WormAirManager::UpdateAirportTileInfo(town_idx, station_id, old_tile)
{
/* Determine if old town data belongs to route 1 or 2. */
local route = AIList();
if (IsTownFirstOrder(town_idx)) {
route = route_1;
//AILog.Info("route 1");
}
else {
route = route_2;
//AILog.Info("route 2");
}
/* Note: IsTownFirstOrder must be called BEFORE towns_used.SetValue because it uses the
old tile value to determine if it belongs to route 1. */
/* Get the new tile for the airport after upgrading. */
local new_airport_tile = Airport.GetAirportTile(station_id);
/* Update the airport tile in our towns_used list. */
this.towns_used.SetValue(town_idx, new_airport_tile);
//AILog.Warning("Update route tile " + WormStrings.WriteTile(old_tile) + " to " + WormStrings.WriteTile(new_airport_tile));
/* Loop through the route info that should contain our airport. */
for (local r = route.Begin(); !route.IsEnd(); r = route.Next()) {
/* Update airport station tiles. */
if (route.GetValue(r) == old_tile) {
//AILog.Info("Updating info for vehicle: " + AIVehicle.GetName(r));
route.SetValue(r, new_airport_tile);
}
}
/* DEBUG:
if (IsTownFirstOrder(t)) {
DebugListRoute(route_1);
}
else {
DebugListRoute(route_2);
} */
}
/**
* Replace the airport town and station tile info in our lists and update orders.
* @param old_town_idx Index into towns_used list of town/station being replaced
* @param old_tile The old tile for the airport being replaced.
* @param new_tile The tile of the new airport.
* @param other_end_of_route_tile Tile of other end of route (needed to access vehicles of route)
*/
function WormAirManager::ReplaceAirportTileInfo(old_town_idx, old_tile, new_tile, other_end_of_route_tile)
{
/* Determine which end of a route is being replaced. */
local route = AIList();
local is_first_order = true;
if (IsTownFirstOrder(old_town_idx)) {
route = route_1;
}
else {
route = route_2;
is_first_order = false;
}
/* Remove old town from towns_used. */
this.towns_used.RemoveValue(old_tile);
/* Loop through the route info that should contain our airport. */
for (local r = route.Begin(); !route.IsEnd(); r = route.Next()) {
/* Update airport station tiles. */
if (route.GetValue(r) == old_tile) {
route.SetValue(r, new_tile);
}
}
/* Change the orders of vehicles belonging to this route. */
/* Since we group vehicles with the same orders we only have to do that once. */
/* First get a vehicle belonging to our station. */
local st_veh = AIVehicleList_Station(AIStation.GetStationID(other_end_of_route_tile));
if (st_veh.Count() > 0) {
local veh = st_veh.Begin();
local breakdowns = AIGameSettings.GetValue("difficulty.vehicle_breakdowns") > 0;
/* Now update the vehicle orders. */
ReplaceOrders(veh, is_first_order, breakdowns, new_tile);
}
}
/**
* Insert Maintenance order for airport at station_tile.
* @param veh Vehicle to set the order for
* @param order_pos Position in the order list where order should be inserted
* @param station_tile Tile for the Airport (not the hangar) of the order to be inserted.
* @return true if order got inserted; false in case of failure to insert.
*/
function WormAirManager::InsertMaintenanceOrder(veh, order_pos, station_tile)
{
/* Get the hangar tile. */
local Depot_Airport = AIAirport.GetHangarOfAirport(station_tile);
/* Add maintenance order for our station. */
if (!AIOrder.InsertOrder(veh, order_pos, Depot_Airport, AIOrder.OF_SERVICE_IF_NEEDED )) {
AILog.Warning("Failed to insert go to depot order at order postion " + order_pos +
", depot tile " + WormStrings.WriteTile(Depot_Airport));
return false;
}
else
{ return true; }
}
/**
* Insert go to station order for airport at station_tile.
* @param veh Vehicle to set the order for.
* @param order_pos Position in the order list where order should be inserted.
* @param station_tile Tile for the Airport of the to be inserted order.
* @return true if order got inserted; false in case of failure to insert.
*/
function WormAirManager::InsertGotoStationOrder(veh, order_pos, station_tile)
{
if (!AIOrder.InsertOrder(veh, order_pos, station_tile, AIOrder.OF_FULL_LOAD_ANY )) {
AILog.Warning("Failed to add order for station tile " + WormStrings.WriteTile(station_tile));
return false;
}
else
{ return true; }
}
/**
* Replace go to station order for airport at station_tile.
* @param veh Vehicle to set the order for.
* @param order_pos Position in the order list where order should be inserted.
* @param station_tile Tile for the Airport of the new to be inserted order.
*/
function WormAirManager::ReplaceGotoStationOrder(veh, order_pos, station_tile)
{
/* Replace the orders for the station at order_pos. */
/* First insert new order below current order. */
InsertGotoStationOrder(veh, order_pos+1, station_tile);
/* Delete order to old station. By doing it in this order the aircraft going to the old order
will now go to the new one. */
AIOrder.RemoveOrder(veh, order_pos);
}
/**
* Replace orders of a vehicle, either the first station or last station is replaced.
* @param veh Vehicle to replace the orders for.
* @param is_first_order Whether to replace the orders for the first or last station.
* @param breakdowns Whether breakdowns are on; if they are on we will add maintenance orders.
* @param station_tile Tile of station for the new order.
*/
function WormAirManager::ReplaceOrders(veh, is_first_order, breakdowns, station_tile)
{
/* Order of aircraft orders (with breakdowns on):
0/0. Goto station 1
1/-. Maintain at hangar of station 2
2/1. Goto station 2
3/-. Maintain at hangar of station 1
*/
local order_pos = 0;
/* Beware that the current setting of breakdowns doesn't have to be the same as when
the orders were created. We determine if there are maintenance orders by looking
at the order count. */
local has_maint_orders = AIOrder.GetOrderCount(veh) == 4;
if (is_first_order) {
/* Replace the orders for the first station. */
ReplaceGotoStationOrder(veh, 0, station_tile);
order_pos = 1;
/* If we had maintenance before and not now then remove maintenance of station 2. */
if (has_maint_orders) {
if (!breakdowns) {
/* Delete maintenance order of station 2. */
AIOrder.RemoveOrder(veh, order_pos);
order_pos = 2;
}
else {
order_pos = 3;
}
/* Delete maintenance order of station 1.*/
AIOrder.RemoveOrder(veh, order_pos);
}
if (breakdowns) {
/* If old station 2 order didn't have a maintenance order then add it. */
if (!has_maint_orders) {
/* We need destination of station 2. */
local st2 = AIOrder.GetOrderDestination(veh, 1);
/* Insert maintenance order for station 2. */
InsertMaintenanceOrder(veh, 2, st2);
}
/* Insert maintenance order for station 1. */
InsertMaintenanceOrder(veh, 3, station_tile);
}
}
else {
if (has_maint_orders)
{ order_pos = 2; }
else
{ order_pos = 1; }
/* Replace the orders for the second station. */