-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.nut
2455 lines (2040 loc) · 75.8 KB
/
main.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
//////////////////////////////////////////////////////////////////////
// //
// CluelessPlus - an noai-AI //
// //
//////////////////////////////////////////////////////////////////////
//
// CluelessPlus is based on original Clueless which was written in
// the beginning of 2008, when NoAI API was announced.
//
// In contrast to original Clueless, CluelessPlus make use of the
// library pathfinder, so it can play on maps with build on slope
// enabled. Building of stations has been changed so that it try
// many locations in a town using the old algorithm, but without
// executing any construction. Then afterwards it builds the station
// at the best found location. Clueless original built *lots* of
// stations all over the town and then removed all but the one it
// wanted to keep.
//
// Author: Zuu (Leif Linse), user Zuu @ tt-forums.net
// Purpose: To play around with the noai framework.
// - Not to make the next big thing.
// Copyright: Leif Linse - 2008-2011
// License: GNU GPL - version 2
// Import SuperLib
import("util.superlib", "SuperLib", 40);
Result <- SuperLib.Result;
Log <- SuperLib.Log;
Helper <- SuperLib.Helper;
Data <- SuperLib.DataStore;
ScoreList <- SuperLib.ScoreList;
Money <- SuperLib.Money;
Tile <- SuperLib.Tile;
Direction <- SuperLib.Direction;
Engine <- SuperLib.Engine;
Vehicle <- SuperLib.Vehicle;
Station <- SuperLib.Station;
Airport <- SuperLib.Airport;
Industry <- SuperLib.Industry;
Town <- SuperLib.Town;
Order <- SuperLib.Order;
OrderList <- SuperLib.OrderList;
Road <- SuperLib.Road;
RoadBuilder <- SuperLib.RoadBuilder;
// Import SCP
import("Library.SCPLib", "SCPLib", 45);
import("Library.SCPClient_NoCarGoal", "SCPClient_NoCarGoal", 1);
// Import other libraries
import("queue.fibonacci_heap", "FibonacciHeap", 3);
// Import all CluelessPlus files
require("version.nut");
require("pairfinder.nut");
require("clue_helper.nut");
require("stationstatistics.nut");
require("strategy.nut");
require("transport_mode_stats.nut");
require("connection.nut");
require("timer.nut");
require("scp_manager.nut");
STATION_SAVE_VERSION <- "0";
STATION_SAVE_AIRCRAFT_DUMP <- "Air Service";
MIN_VEHICLES_TO_BUILD_NEW <- 5; // minimum number of vehicles left in order to allow building new connections for a given transport mode
// Airport where aircrafts are "dumped" while airports are upgraded
g_aircraft_dump_airport_small <- null;
g_aircraft_dump_airport_large <- null;
g_num_connection_airport_upgrade <- 0;
// SCP clients
g_no_car_goal <- null;
g_communication_tile <- null; // used by ClearAllSigns to avoid this tile
//////////////////////////////////////////////////////////////////////
g_timers <- {
manage_vehicles = Timer("manage vehicles"),
manage_stations = Timer("manage stations"),
manage_state = Timer("manage state"),
all_manage = Timer("all manage"),
build_check = Timer("build check"),
state_build = Timer("state build"),
build_pathfinding = Timer("build - pathfinding"),
build_buildroad = Timer("build - buildroad"),
build_infra_connect = Timer("build - infra connect"),
build_buy_vehicles = Timer("build - vehicles"),
build_stations = Timer("build - stations"),
build_performance = Timer("build - calc performance"),
build_abort = Timer("abort building connection"),
repair_connection = Timer("repair connection"),
rail_crossings = Timer("rail crossings"),
scan_depots = Timer("scan depots"),
pairfinding = Timer("pair finding"),
connect_pair = Timer("connect pair"),
handle_events = Timer("handle events"),
manage_loan = Timer("manage loan"),
all = Timer("all"),
};
// table_name is eg. build_check which is a key in the g_timers table
function TimerStart(table_name)
{
if(AIController.GetSetting("enable_timers") == 1)
{
g_timers.rawget(table_name).Start();
}
}
function TimerStop(table_name)
{
if(AIController.GetSetting("enable_timers") == 1)
{
g_timers.rawget(table_name).Stop();
}
}
//////////////////////////////////////////////////////////////////////
// Helper for clearing all signs, except those used by SCP
function ClearAllSigns()
{
if (g_communication_tile != null) {
Helper.ClearAllSigns(g_communication_tile);
} else {
Helper.ClearAllSigns();
}
}
function GetAvailableTransportModes(min_vehicles_left = 1)
{
local tm_list = [];
if(Vehicle.GetVehiclesLeft(AIVehicle.VT_AIR) >= min_vehicles_left)
tm_list.append(TM_AIR);
if(Vehicle.GetVehiclesLeft(AIVehicle.VT_ROAD) >= min_vehicles_left)
tm_list.append(TM_ROAD);
return tm_list;
}
function GetSupportedVehicleTypeList()
{
return [AIVehicle.VT_AIR, AIVehicle.VT_ROAD];
}
function GetVehiclesWithoutOrders()
{
local empty_orders = AIVehicleList();
empty_orders.Valuate(AIOrder.GetOrderCount);
empty_orders.KeepValue(0);
return empty_orders;
}
function HasVehicleInvalidOrders(vehicleId)
{
for(local i = 0; i < AIOrder.GetOrderCount(vehicleId); ++i)
{
if( (AIOrder.rawin("IsVoidOrder") && AIOrder.IsVoidOrder(vehicleId, i)) /* ||
AIOrder.GetOrderFlags(vehicleId, i) == 0*/) // <-- for backward compatibility <-- The backward compatibility code caused problems with new OpenTTD versions.
return true;
}
return false;
}
function IsVehicleGoingToADepot(vehicleId)
{
local order_dest = Order.GetCurrentOrderDestination(vehicleId);
switch(AIVehicle.GetVehicleType(vehicleId))
{
case AIVehicle.VT_ROAD:
return AIRoad.IsRoadDepotTile(order_dest);
case AIVehicle.VT_AIR:
return AIAirport.IsHangarTile(order_dest);
case AIVehicle.VT_RAIL:
return AIRail.IsRailDepotTile(order_dest);
case AIVehicle.VT_WATER:
return AIMarine.IsWaterDepotTile(order_dest);
}
Log.Error("Invalid vehicle type of vehicle " + AIVehicle.GetName(vehicleId) + " (IsVehicleGoingToADepot)", Log.LVL_INFO);
return false;
}
function GetVehiclesWithInvalidOrders()
{
local invalid_orders = AIVehicleList();
invalid_orders.Valuate(HasVehicleInvalidOrders);
invalid_orders.KeepValue(1);
return invalid_orders;
}
function GetVehiclesWithUpgradeStatus()
{
local list = AIVehicleList();
list.Valuate(Connection.IsVehicleToldToUpgrade);
list.KeepValue(1);
return list;
}
function GetCargoFromStation(station_id)
{
local save_str = Data.ReadStrFromStationName(station_id);
local space = save_str.find(" ");
local save_version = -1;
local node_save_str = null;
if(space == null)
return -1;
save_version = save_str.slice(0, space);
node_save_str = save_str.slice(space + 1);
local node = Node.CreateFromSaveString(node_save_str);
return node == null? -1 : node.cargo_id;
}
function AnyVehicleTypeBuildable()
{
local vt_available = false;
local vt_list = GetSupportedVehicleTypeList();
foreach(vt in vt_list)
{
vt_available = vt_available || Vehicle.GetVehicleLimit(vt) > 0;
}
return vt_available;
}
// Call this function if AnyVehicleTypeBuildable returns false
function DisplayEnableVehicleTypesTips()
{
local vt_list = GetSupportedVehicleTypeList();
foreach(vt in vt_list)
{
local label = TransportModeToString( VehicleTypeToTransportMode(vt) );
label = label.toupper();
local s = Vehicle.GetVehicleTypeDisabledBySettingString(vt);
if(s != null)
Log.Info(label + " is disabled by: " + s);
else
{
if(Vehicle.GetVehiclesLeft(vt) == 0)
Log.Info(label + " is enabled, but have zero vehicles left");
else
Log.Info(label + " is enabled");
}
}
}
// if avoid_small_airports is true, the result will only contain small airports
// if there are no large ones.
function GetAirportTypeList_AllowedAndBuildable(avoid_small_airports = false)
{
local airport_type_list = Airport.GetAirportTypeList(); // Todo: refine airport type selection
airport_type_list.Valuate(AIAirport.IsValidAirportType); // can airport be built?
airport_type_list.KeepValue(1);
//airport_type_list.Valuate(AIAirport.GetNumHangars); // for some reason this don't remove the heliport
//airport_type_list.RemoveValue(0);
airport_type_list.RemoveItem(AIAirport.AT_HELIPORT);
airport_type_list.RemoveItem(AIAirport.AT_HELISTATION);
airport_type_list.RemoveItem(AIAirport.AT_HELIDEPOT);
airport_type_list.RemoveItem(AIAirport.AT_INTERCON); // the intercon has worse performance than international and is larger
if(avoid_small_airports)
{
// Only remove small airports if there is at least one large
local skip_small = false;
foreach(ap_type in airport_type_list)
{
if(!Airport.IsSmallAirportType(ap_type))
{
skip_small = true;
break;
}
}
if(skip_small)
{
airport_type_list.Valuate(Airport.IsSmallAirportType);
airport_type_list.KeepValue(0);
}
}
airport_type_list.Valuate(Helper.ItemValuator);
return airport_type_list;
}
function IsAirportTypeBetterThan(ap_type, other_ap_type)
{
return ap_type > other_ap_type;
}
function IsAircraftDumpStation(station_id)
{
local save_str = Data.ReadStrFromStationName(station_id);
local space = save_str.find(" ");
local save_version = -1;
local node_save_str = null;
if(space == null)
return false;
save_version = save_str.slice(0, space);
node_save_str = save_str.slice(space + 1);
return node_save_str == STATION_SAVE_AIRCRAFT_DUMP;
}
function IsGoToAircraftDumpOrder(vehicle_id, order_id)
{
local dest = AIOrder.GetOrderDestination(vehicle_id, order_id);
local station_id = AIStation.GetStationID(dest);
return IsAircraftDumpStation(station_id);
}
function HasVehicleGoToAircraftDumpOrder(vehicle_id)
{
for(local i = 0; i < AIOrder.GetOrderCount(vehicle_id); ++i)
{
if(IsGoToAircraftDumpOrder(vehicle_id, i))
return true;
}
return false;
}
function GetNoiseBudgetOverrun()
{
return AIGameSettings.GetValue("station_noise_level") == 1? 2 : 0; // allow airports to go at maximum 2 over noise budget, to allow for new airports to be placed further away.
}
/*
* Will return a station id of an airport that has no other purpose
* than holding aircrafts while upgrading other airports
*
* need_large_airport = true or false
*/
function GetAircraftDumpAirport(need_large_airport)
{
// if have large, give large
if(g_aircraft_dump_airport_large != null && AIStation.IsValidStation(g_aircraft_dump_airport_large))
return g_aircraft_dump_airport_large;
// if have small and small is enough, give small
if(!need_large_airport && g_aircraft_dump_airport_small != null && AIStation.IsValidStation(g_aircraft_dump_airport_small))
return g_aircraft_dump_airport_small;
// we don't have a dump airport
local avoid_small = need_large_airport;
local airport_type_list = GetAirportTypeList_AllowedAndBuildable(avoid_small);
// Use a small airport if possible (no large is needed and a small is available)
if(!need_large_airport)
{
airport_type_list.Valuate(Airport.IsSmallAirportType);
if(Helper.GetListMinValue(airport_type_list) == 0) // have small airport?
{
airport_type_list.KeepValue(0); // keep only small airports
}
}
// pick cheapest airport that is fulfills the requirements
airport_type_list.Valuate(AIAirport.GetPrice);
airport_type_list.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
local selected_type = airport_type_list.Begin();
// Build airport in HQ-town if possible, else in the smallest town
local hq_tile = AICompany.GetCompanyHQ(AICompany.COMPANY_SELF);
local town = null;
local town_list = AITownList();
if(AIMap.IsValidTile(hq_tile))
{
town = AITile.GetClosestTown(hq_tile);
}
else
{
town_list.Valuate(AITown.GetPopulation);
town_list.Sort(AIList.SORT_BY_VALUE, AIList.SORT_ASCENDING);
town = town_list.Begin();
town_list.RemoveTop(1);
}
local no_cargo = -1;
local ap_tile = Airport.BuildAirportInTown(town, selected_type, no_cargo, no_cargo);
if(ap_tile == null)
{
// Fall back to iterating town list if HQ / smallest town failed
foreach(town, _ in town_list)
{
ap_tile = Airport.BuildAirportInTown(town, selected_type, no_cargo, no_cargo);
if(ap_tile != null)
break;
if(AIError.GetLastError() == AIError.ERR_NOT_ENOUGH_CASH)
break;
}
}
if(ap_tile != null)
{
local station_id = AIStation.GetStationID(ap_tile);
if(Airport.IsSmallAirportType(selected_type))
g_aircraft_dump_airport_small = station_id;
else
g_aircraft_dump_airport_large = station_id;
Data.StoreInStationName(station_id, STATION_SAVE_VERSION + " " + STATION_SAVE_AIRCRAFT_DUMP);
return station_id;
}
return null;
}
function CheckIfDumpStationsAreUnused()
{
if(g_num_connection_airport_upgrade > 0) return;
// Don't remove unused dump stations if we are rich
if(AICompany.GetBankBalance(AICompany.COMPANY_SELF) > AICompany.GetMaxLoanAmount() * 1.5) return;
if(g_aircraft_dump_airport_small != null && AIVehicleList_Station(g_aircraft_dump_airport_small).IsEmpty())
{
// Remove unused dump station
if(AITile.DemolishTile(Airport.GetAirportTile(g_aircraft_dump_airport_small)))
{
g_aircraft_dump_airport_small = null;
}
}
if(g_aircraft_dump_airport_large != null && AIVehicleList_Station(g_aircraft_dump_airport_large).IsEmpty())
{
// Remove unused dump station
if(AITile.DemolishTile(Airport.GetAirportTile(g_aircraft_dump_airport_large)))
{
g_aircraft_dump_airport_large = null;
}
}
}
//////////////////////////////////////////////////////////////////////
// //
// CLASS: CluelessPlus - the AI main class //
// //
//////////////////////////////////////////////////////////////////////
class CluelessPlus extends AIController {
stop = false;
loaded_from_save = false;
pair_finder = null;
connection_list = [];
detected_rail_crossings = null;
state_build = false;
state_ai_name = null;
state_desperateness = 0;
state_connect_performance = 0;
conf_ai_name = null
conf_min_balance = 0;
scp = null;
// All variables should be initiated with their values below!: (the assigned values above is only there because Squirrel demands it)
constructor() {
stop = false;
loaded_from_save = false;
pair_finder = PairFinder();
connection_list = [];
detected_rail_crossings = AIList();
state_build = false;
state_ai_name = null;
state_desperateness = 0;
state_connect_performance = 0;
conf_ai_name = ["Clueless", "Cluemore", "Not so Clueless", "Average IQ",
"Almost smart", "Smart", "Even more smart", "Almost intelligent",
"Intelligent", "Even more intelligent", "Expert", "Better than expert",
"Geek", "Master of universe", "Logistic king", "Ultimate logistics"];
conf_min_balance = 20000;
scp = null;
}
// WARNING: the arguments of the following functions might be wrong. Look at the implementation for the exact arguments.
// Squirrel don't complain if the arguments of the definitions and implementation don't match.
// Squirrel basically don't look at the definitions below:
function Start();
function Stop();
function HandleEvents();
function SendLostVehicleForSelling(vehicle_id);
function CheckDepotsForStopedVehicles();
function GetNewPairMoneyLimitPerTransportMode();
function GetNewPairMoneyLimit();
function ConnectPair(budget);
function Save();
function Load(version, data);
function ReadConnectionsFromMap(); // Instead of storing data in the save game, the connections are made out from groups of vehicles that share orders.
function SetCompanyName(nameArray);
function ManageLoan();
function RoundLoanDown(loanAmount); // Helper
function GetMaxMoney();
function BuyNewConnectionVehicles(connection);
function BuildHQ();
// if tile is not a road-tile it will search for closest road-tile and then start searching for a location to place it from there.
function PlaceHQ(nearby_tile);
function FindRoadExtensionTile(road_tile, target_tile, min_loops, max_loops); // road_tile = start search here
// target_tile = when searching for a place to extend existing road, we want to get as close as possible to target_tile
// min_loops = search at least for this amount of loops even if one possible extension place is found (return best found)
// max_loops = maximum search loops before forced return
function FindRoadExtensionTile_SortByDistanceToTarget(a, b); // Helper
}
function CluelessPlus::Start()
{
this.Sleep(1);
local last_timer_print = AIDate.GetCurrentDate();
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);
AILog.Info(""); // Call AILog directly in order to not include date in this message
if(AIController.GetSetting("slow_ai") == 1)
{
Log.Info("I'm a slow AI, so sometimes I will take a nap and rest a bit so that I don't get exhausted.", Log.LVL_INFO);
Log.Info("", Log.LVL_INFO);
AIController.Sleep(50);
}
// Company Name
if(this.loaded_from_save)
{
this.state_ai_name = AICompany.GetName(AICompany.COMPANY_SELF);
}
else
{
this.state_ai_name = this.SetCompanyName(this.conf_ai_name);
}
// Init SCP
if(AIController.GetSetting("scp_enabled")) {
this.scp = SCPLib("CLUP", SELF_VERSION);
this.scp.SCPLogging_Info(Log.IsLevelAccepted(Log.LVL_DEBUG));
this.scp.SCPLogging_Error(true);
g_communication_tile = this.scp.SCPGetCommunicationTile();
} else {
g_communication_tile = null;
this.scp = null;
}
// Always create g_no_car_goal which will just act as if no NoCarGoal
// gs has been found if this.scp is null.
g_no_car_goal = SCPClient_NoCarGoal(this.scp);
// Rebuild the connections structure if loading a save game
if(this.loaded_from_save)
{
Log.Info("Map loaded => Read connections from the map ...", Log.LVL_INFO);
ReadConnectionsFromMap();
Log.Info("", Log.LVL_INFO);
Log.Info("All connections have been read from the map", Log.LVL_INFO);
Log.Info("----------------------------------------------------------------------", Log.LVL_INFO);
Log.Info("", Log.LVL_INFO);
Log.Info("", Log.LVL_INFO);
}
if(!AnyVehicleTypeBuildable())
{
Log.Error("All transport modes that are supported by this AI are disabled for AIs (or have vehicle limit = 0).", Log.LVL_INFO);
Log.Info("Enable road or air transport mode in advanced settings if you want that this AI should build something", Log.LVL_INFO);
Log.Info("", Log.LVL_INFO);
DisplayEnableVehicleTypesTips();
Log.Info("", Log.LVL_INFO);
}
state_build = false;
local last_manage_time = AIDate.GetCurrentDate();
local not_build_info_printed = false;
local last_yearly_manage = AIDate.GetCurrentDate();
local i = 0;
while(!this.stop)
{
TimerStart("all");
i++;
this.Sleep(1);
HandleEvents();
// Read incoming SCP messages (up to 5 per loop)
if(this.scp != null) {
this.scp.SCPLogging_Info(Log.IsLevelAccepted(Log.LVL_DEBUG));
for(local j = 0; j < 5 && this.scp.Check(); j++){}
}
// Sometimes...
if(i%10 == 1)
{
// ... manage our loan
ManageLoan();
/*
// Get a list of available buses, so we don't construct if there are no buses
local engine_list = AIEngineList(AIVehicle.VT_ROAD);
engine_list.Valuate(AIEngine.GetCargoType)
local cargo_type = Helper.GetPAXCargo();
engine_list.KeepValue(cargo_type);
engine_list.Valuate(AIEngine.IsArticulated)
engine_list.KeepValue(0);
*/
TimerStart("build_check");
// Also only manage our vehicles if we have any.
local vehicle_list = AIVehicleList();
local allow_build_road = Vehicle.GetVehiclesLeft(AIVehicle.VT_ROAD) >= MIN_VEHICLES_TO_BUILD_NEW;
local allow_build_air = Vehicle.GetVehiclesLeft(AIVehicle.VT_AIR) >= MIN_VEHICLES_TO_BUILD_NEW;
local new_pair_money_limit = this.GetNewPairMoneyLimit();
// ... check if we can afford to build some stuff (or we don't have anything -> need to build to either succeed or go bankrupt and restart)
// AND road vehicles are not disabled
// AND at least 5 more buses/trucks can be built before reaching the limit (a 1 bus/truck connection will not become good)
if((this.GetMaxMoney() > new_pair_money_limit || AIStationList(AIStation.STATION_ANY).IsEmpty() ) &&
AnyVehicleTypeBuildable() &&
(allow_build_road || allow_build_air) )
{
state_build = true;
not_build_info_printed = false;
}
else
{
if(!not_build_info_printed)
{
not_build_info_printed = true;
Log.Info("Not enough money to construct (will check every now and then, but only this message is printed to not spam the log)", Log.LVL_INFO);
}
}
TimerStop("build_check");
TimerStart("all_manage");
if(!vehicle_list.IsEmpty())
{
TimerStart("scan_depots");
this.CheckDepotsForStopedVehicles();
TimerStop("scan_depots");
// check if we should manage the connections
local now = AIDate.GetCurrentDate();
if(now - last_manage_time > AIDate.GetDate(0, 1, 0))
{
Log.Info("Time to manage connections", Log.LVL_DEBUG);
last_manage_time = now;
local connection = null;
// Remember the indexes in connection_list for connections to remove
local remove_connection_idx_list = [];
local i = -1;
foreach(connection in connection_list)
{
++i;
// Detect failed or closed down connections
if(connection.state == Connection.STATE_FAILED || connection.state == connection.STATE_CLOSED_DOWN)
{
continue;
}
// But also connections which has invalid array lengths
if(connection.station.len() != 2 || connection.depot.len() != 2 || connection.town.len() != 2)
{
Log.Warning("Connection::ManageVehicles: Wrong number of bus stations or depots. " +
connection.station.len() + " stations and " + connection.depot.len() + " depots", Log.LVL_INFO);
connection.state = Connection.STATE_FAILED;
continue;
}
if(connection.state == Connection.STATE_CLOSED_DOWN)
{
// Mark fully closed down connections for removal
remove_connection_idx_list.append(i);
}
else
{
local old_balance = Money.MaxLoan();
TimerStart("manage_state");
connection.ManageState();
TimerStop("manage_state");
TimerStart("manage_stations");
connection.ManageStations();
TimerStop("manage_stations");
TimerStart("manage_vehicles");
connection.ManageVehicles();
TimerStop("manage_vehicles");
TimerStart("scan_depots");
this.CheckDepotsForStopedVehicles(); // Sell / upgrade vehicles even while managing connections - good when there are a huge amount of connections
TimerStop("scan_depots");
Money.RestoreLoan(old_balance);
}
}
// Remove all connections that was marked for removal
foreach(remove_idx in remove_connection_idx_list)
{
connection_list.remove(remove_idx);
}
// Check for rail crossings that couldn't be fixed just after a crash event
TimerStart("rail_crossings");
this.detected_rail_crossings.Valuate(Helper.ItemValuator);
foreach(crash_tile, _ in this.detected_rail_crossings)
{
Log.Info("Trying to fix a railway crossing that had an accident before", Log.LVL_INFO);
Helper.SetSign(crash_tile, "crash_tile");
local neighbours = Tile.GetNeighbours4MainDir(crash_tile);
neighbours.Valuate(AIRoad.AreRoadTilesConnected, crash_tile);
neighbours.KeepValue(1);
local road_tile_next_to_crossing = neighbours.Begin();
if(neighbours.IsEmpty() ||
!AIMap.IsValidTile(road_tile_next_to_crossing) ||
!AITile.HasTransportType(crash_tile, AITile.TRANSPORT_ROAD) ||
!AITile.HasTransportType(road_tile_next_to_crossing, AITile.TRANSPORT_ROAD))
{
this.detected_rail_crossings.RemoveValue(crash_tile);
}
local bridge_result = Road.ConvertRailCrossingToBridge(crash_tile, road_tile_next_to_crossing);
if(bridge_result.succeeded == true || bridge_result.permanently == true)
{
// Succeded to build rail crossing or failed permanently -> don't try again
this.detected_rail_crossings.RemoveValue(crash_tile);
}
}
TimerStop("rail_crossings");
}
}
TimerStop("all_manage");
if(state_build)
{
TimerStart("state_build");
// Simulate the time it takes to look for a connection
if(AIController.GetSetting("slow_ai"))
AIController.Sleep(1000); // a bit more than a month
TimerStart("connect_pair");
local ret = this.ConnectPair(new_pair_money_limit * 15 / 10);
state_build = false;
TimerStop("connect_pair");
if(ret && !AIMap.IsValidTile(AICompany.GetCompanyHQ(AICompany.COMPANY_SELF)))
{
this.BuildHQ();
}
else
{
Log.Warning("Could not find two towns/industries to connect", Log.LVL_INFO);
}
TimerStop("state_build");
}
}
// Pay back unused money
ManageLoan();
// Yearly management
if(last_yearly_manage + 365 < AIDate.GetCurrentDate())
{
Log.Info("Yearly manage", Log.LVL_INFO);
// Check if we have any unused aircraft dump stations (that can be removed to save money)
//CheckIfDumpStationsAreUnused();
// wait a year for next yearly manage
last_yearly_manage = AIDate.GetCurrentDate();
Log.Info("Yearly manage - done", Log.LVL_SUB_DECISIONS);
}
TimerStop("all");
// Show timers + restart once a year
if(last_timer_print + 365 * 5 < AIDate.GetCurrentDate())
{
Log.Info("Timer counts:", Log.LVL_DEBUG);
foreach(_, timer in g_timers)
{
timer.PrintTotal();
timer.Reset();
}
Log.Info("------- Timer counts end -------", Log.LVL_DEBUG);
last_timer_print = AIDate.GetCurrentDate();
}
}
}
function CluelessPlus::Stop()
{
Log.Info("CluelessPlus::Stop()", Log.LVL_INFO);
this.stop = true;
}
function CluelessPlus::Save()
{
// Store an empty table to please the NoAI API.
// CluelessPlus loading reads everything from the map.
local table = {};
return table;
}
function CluelessPlus::Load(version, data)
{
// CluelessPlus does not support save/load, so kill ourself if
// a user tries to load a savegame with CluelessPlus as AI.
this.loaded_from_save = true;
Log.Info("Loading..", Log.LVL_INFO);
Log.Info("Previously saved with AI version " + version, Log.LVL_INFO);
}
function CluelessPlus::HandleEvents()
{
TimerStart("handle_events");
if(AIEventController.IsEventWaiting())
{
local ev = AIEventController.GetNextEvent();
if(ev == null)
{
TimerStop("handle_events");
return;
}
local ev_type = ev.GetEventType();
if(ev_type == AIEvent.ET_VEHICLE_LOST)
{
local lost_event = AIEventVehicleLost.Convert(ev);
local lost_veh = lost_event.GetVehicleID();
if(AIVehicle.IsValidVehicle(lost_veh))
{
Log.Info("Vehicle lost event detected - lost vehicle: " + AIVehicle.GetName(lost_veh), Log.LVL_INFO);
// This is not a pointer to the regular connection object. Instead a new object
// is created with enough data to use RepairRoadConnection.
local connection = ReadConnectionFromVehicle(lost_veh);
if(connection != null && connection.station.len() >= 2 && connection.state == Connection.STATE_ACTIVE)
{
Log.Info("Try to connect the stations again", Log.LVL_SUB_DECISIONS);
if(!connection.RepairRoadConnection())
this.SendLostVehicleForSelling(lost_veh);
// TODO:
// If a vehicle is stuck somewhere but the connection succeeds to repair every time without letting the vehicles out,
// they will never be sent for selling nor will any depot be constructed nearby in order to sell the vehicles and
// reduce the cost + vehicle usage. (except for if the connection decide to sell the vehicles in the vehicle management
// procedure)
//
// If many vehicles are lost, they can possible also cause a management hell.
}
else
{
this.SendLostVehicleForSelling(lost_veh);
}
}
}
else if(ev_type == AIEvent.ET_VEHICLE_CRASHED)
{
local crash_event = AIEventVehicleCrashed.Convert(ev);
local crash_reason = crash_event.GetCrashReason();
local vehicle_id = crash_event.GetVehicleID();
local crash_tile = crash_event.GetCrashSite();
if(crash_reason == AIEventVehicleCrashed.CRASH_RV_LEVEL_CROSSING)
{
Log.Info("Vehicle " + AIVehicle.GetName(vehicle_id) + " crashed at level crossing", Log.LVL_INFO);
local neighbours = Tile.GetNeighbours4MainDir(crash_tile);
neighbours.Valuate(AIRoad.AreRoadTilesConnected, crash_tile);
neighbours.KeepValue(1);
local road_tile_next_to_crossing = neighbours.Begin();
if(!neighbours.IsEmpty() &&
AIMap.IsValidTile(road_tile_next_to_crossing) &&
AITile.HasTransportType(crash_tile, AITile.TRANSPORT_ROAD) &&
AITile.HasTransportType(road_tile_next_to_crossing, AITile.TRANSPORT_ROAD))
{
local bridge_result = Road.ConvertRailCrossingToBridge(crash_tile, road_tile_next_to_crossing);
if(bridge_result.succeeded == false && bridge_result.permanently == false)
{
// couldn't fix it right now, so put in in a wait list as there were no permanent problems (only vehicles in the way or lack of funds)
this.detected_rail_crossings.AddItem(crash_tile, road_tile_next_to_crossing);
}
}
}
}
else if(ev_type == AIEvent.ET_INDUSTRY_CLOSE)
{
local close_event = AIEventIndustryClose.Convert(ev);
local close_industry = close_event.GetIndustryID();
local close_tile = AIIndustry.GetLocation(close_industry);
local close_tile_is_valid = AIMap.IsValidTile(close_tile);
foreach(connection in connection_list)
{
// Close connections that use this industry
local match = false;
foreach(node in connection.node)
{
// Ignore town nodes
if(node.IsTown())
continue;
if(close_tile_is_valid)
{
if(node.industry_id == close_industry && node.node_location != close_industry)
{
// The node has the close_industry id, but not the right location => we know the close_industry id has been
// reused by another industry located elsewhere => we know the node is dead.
match = true;
}
else
{
// The close industry location is valid, but it could either be that the industry at this node is still existing
// or that it has been closed and reused for a new industry elsewhere.
// -> we don't know if close_tile is for the new or old industry
if(node.industry_id == close_industry)
{
if(AIDate.GetCurrentDate() - connection.date_built > 365)
{
// The connection is older than 365 days, so assume this can not be a new connection that was built after
// the event was triggered, the industry closed and a new one opened used the same id.
match = true;
}
else
{
// The connection is either a new connection that has reused the industry id or the industry will soon be closed
// -> do nothing, rely on the detection of broken nodes
}
}
}
}
else
{
// The close_industry is not a valid industry => if there is a match for this industry id, the node is dead
if(node.industry_id == close_industry)
match = true;
}
}
if(match)
{
connection.CloseConnection();
}
}
}
else if(ev_type == AIEvent.ET_COMPANY_IN_TROUBLE)
{
local company_in_trouble_event = AIEventCompanyInTrouble.Convert(ev);
local company = company_in_trouble_event.GetCompanyID();