-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.nut
610 lines (546 loc) · 20.4 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
/**
* WormAI: An OpenTTD AI
* First version based on WrightAI
*
* @file main.nut Main class and loop of our AI for OpenTTD.
* License: GNU GPL - version 2 (see license.txt)
* Author: Wormnest (Jacob Boerema)
* Copyright: Jacob Boerema, 2013-2019.
*
*/
// Get the latest libversions
// This is autogenerated by a script to always have up to date version numbers
require("libversions.nut");
// Import SuperLib
import("util.superlib", "SuperLib", SUPERLIB_VERSION);
/** @name SuperLib imports */
/// @{
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 List library
import("AILib.List", "ExtendedList", AILIBLIST_VERSION);
// Get our required classes.
require("utils.nut");
require("money.nut");
require("strings.nut");
require("math.nut");
require("tiles.nut");
require("valuators.nut");
require("rail_pathfinder.nut");
require("airport.nut");
require("airportupgrade.nut");
require("airmanager.nut");
require("railmanager.nut");
require("railbuilder.nut");
require("planner.nut");
/* Default delays */
const SLEEPING_TIME = 100; ///< Default time to sleep between loops of our AI (NB: should be a multiple of 100).
const DEFAULT_DELAY_BUILD_AIRPORT = 500; ///< Default delay before building a new airport route.
const DEFAULT_DELAY_BUILD_RAIL = 300; ///< Default delay before building a new rail route.
/// @{
/** @name ERROR CODE constants */
const ALL_OK = 0;
const ERROR_FIND_AIRPORT1 = -1; ///< There was an error finding a spot for airport 1.
const ERROR_FIND_AIRPORT2 = -2; ///< There was an error finding a spot for airport 2.
const ERROR_BUILD_AIRPORT1 = -3; ///< There was an error building airport 1.
const ERROR_BUILD_AIRPORT2 = -4; ///< There was an error building airport 2.
const ERROR_FIND_AIRPORT_ACCEPTANCE = -5; ///< We couldn't find a suitable airport but we lowered our acceptance rate limit so we can try again.
const ERROR_FIND_AIRPORT_FINAL = -6; ///< We couldn't find a suitable airport and we are at the minimum acceptable acceptance limit.
const ERROR_NO_SUITABLE_AIRPORT = -7; ///< There is no suitable airport type available.
const ERROR_MAX_AIRCRAFT = -10; ///< We have reached the maximum allowed number of aircraft.
const ERROR_MAX_AIRPORTS = -11; ///< We have reached the maximum number of airports.
const ERROR_NOT_ENOUGH_MONEY = -20; ///< We don't have enough money.
const ERROR_BUILD_AIRCRAFT = -30; ///< General error trying to build an aircraft.
const ERROR_BUILD_AIRCRAFT_INVALID = -31; ///< No suitable aircraft found when trying to build an aircraft.
const ERROR_BUILD_TRAIN = -40; ///< General error trying to build a train (engine, wagon).
const ERROR_BUILD_TRAIN_BLACKLISTED = -41; ///< Train engine or wagon was blacklisted (unusable for our purposes).
const ERROR_INDUSTRY_DISAPPEARED = -50; ///< The industry near our station that we needed disappeared.
/// @}
/**
* Define the main class of our AI WormAI.
*/
class WormAI extends AIController {
/* Declare the variables here. */
name = null; ///< The name that we will give our AI
air_manager = null; ///< The Air Manager class
rail_manager = null; ///< The Rail Manager class
ai_speed_factor = 1; ///< speed factor for our ai actions (1=fast..3=slow)
delay_build_airport_route = 0;
delay_build_rail_route = 0;
use_air = false; ///< Whether we can use aircraft or not
use_trains = false; ///< Whether we can use trains or not
loaded_from_save = false;
save_version = 0;
save_not_ours = false; ///< Boolean: True if a loaded savegame is not from WormAI itself.
aircraft_disabled_shown = 0; ///< Has the aircraft disabled in game settings message been shown (1) or not (0).
aircraft_max0_shown = 0; ///< Has the max aircraft is 0 in game settings message been shown.
trains_disabled_shown = 0; ///< Has the trains disabled in game settings message been shown (1) or not (0).
trains_max0_shown = 0; ///< Has the max trains is 0 in game settings message been shown.
/** Create an instance of WormAI. */
constructor()
{
/* Initialize the class variables here (or later when possible). */
this.loaded_from_save = false;
/* Instantiate our Air and Rail Managers. */
this.air_manager = WormAirManager();
this.rail_manager = WormRailManager();
this.aircraft_disabled_shown = 0;
this.aircraft_max0_shown = 0;
this.trains_disabled_shown = 0;
this.trains_max0_shown = 0;
// Delays: we don't set them here but in start because we need to check the selected
// speed set in game settings
}
/// @{
/** @name Implementation of base class functions */
/**
* Start the main loop of WormAI.
*/
function Start();
/**
* Save all data that WormAI uses.
* @return The data to be saved.
*/
function Save();
/**
* Load previously saved information.
* @param version Which version of our AI saved the information.
* @param data The data that was saved.
*/
function Load(version, data);
/// @}
/** @name Initialization functions */
/// @{
/**
* InitSettings initializes a number of required variables based on the game settings of our AI.
*/
function InitSettings();
/**
* Welcome says hello to the user and prints out the current AI gamesettings.
*/
function Welcome();
/**
* This is our SuperLib.Log acceptable log levels functions for debugging, returning always true.
* Only to be used when we want to debug inside SuperLib.
*/
function SuperLib_Log_IsLevelAccepted_Function(log_level);
/**
* Checks if we can build an aircraft and if not outputs a string with the reason.
* @return true if we can build an aircraft, otherwise false.
*/
function CanBuildAircraft();
/**
* Checks if we can build trains and if not outputs a string with the reason.
* @return true if we can build trains, otherwise false.
*/
function CanBuildTrains();
/// @}
};
/**
* InitSettings initializes a number of required variables based on the game settings of our AI.
*/
function WormAI::InitSettings()
{
local ai_speed = GetSetting("ai_speed");
switch (ai_speed) {
case 1: {this.ai_speed_factor = 3;} break;
case 3: {this.ai_speed_factor = 1;} break;
default: {this.ai_speed_factor = 2;} break;
}
this.delay_build_airport_route = DEFAULT_DELAY_BUILD_AIRPORT * this.ai_speed_factor;
this.delay_build_rail_route = DEFAULT_DELAY_BUILD_RAIL * this.ai_speed_factor;
/* Since autorenew can change the vehicle id it may cause trouble to have it turned on,
* therefore we turn it off and will renew manually in the future. */
AICompany.SetAutoRenewStatus(false);
}
/**
* Welcome says hello to the user and prints out the current AI gamesettings.
*/
function WormAI::Welcome()
{
/* Say hello to the user */
AILog.Warning("Welcome to WormAI.");
AILog.Info("These are our current AI settings:");
AILog.Info("- Use planes: " + GetSetting("use_planes"));
AILog.Info("- Use trains: " + GetSetting("use_trains"));
AILog.Info("- AI speed/difficulty: " + GetSetting("ai_speed"));
AILog.Info("- Minimum Town Size: " + GetSetting("min_town_size"));
AILog.Info("- Minimum Airport Distance: " + GetSetting("min_airport_distance"));
AILog.Info("- Maximum Airport Distance: " + GetSetting("max_airport_distance"));
AILog.Info("----------------------------------");
}
/**
* Checks if we can build an aircraft and if not outputs a string with the reason.
* @return true if we can build an aircraft, otherwise false.
*/
function WormAI::CanBuildAircraft()
{
/* Need to check if we can build aircraft and how many. Since this can change we do it inside the loop. */
if (AIGameSettings.IsDisabledVehicleType(AIVehicle.VT_AIR)) {
if (this.aircraft_disabled_shown == 0) {
AILog.Warning("Using aircraft is disabled in your game settings.")
AILog.Warning("No air routes will be built until you change this setting.")
this.aircraft_disabled_shown = 1;
}
}
else if (Vehicle.IsVehicleTypeDisabledByAISettings(AIVehicle.VT_AIR)) {
if (this.aircraft_disabled_shown == 0) {
AILog.Warning("Using aircraft is disabled in this AI's settings.")
AILog.Warning("No air routes will be built until you change this setting.")
this.aircraft_disabled_shown = 1;
}
}
else if (Vehicle.GetVehicleLimit(AIVehicle.VT_AIR) == 0) {
if (this.aircraft_max0_shown == 0) {
AILog.Warning("Amount of allowed aircraft for AI is set to 0 in your game settings.")
AILog.Warning("No air routes will be built until you change this setting.")
this.aircraft_max0_shown = 1;
}
}
else {
return true;
}
return false;
}
/**
* Checks if we can build trains and if not outputs a string with the reason.
* @return true if we can build trains, otherwise false.
*/
function WormAI::CanBuildTrains()
{
/* Need to check if we can build trains and how many. Since this can change we do it inside the loop. */
if (AIGameSettings.IsDisabledVehicleType(AIVehicle.VT_RAIL)) {
if (this.trains_disabled_shown == 0) {
AILog.Warning("Using trains is disabled in your game settings.")
AILog.Warning("No train routes will be built until you change this setting.")
this.trains_disabled_shown = 1;
}
}
else if (Vehicle.IsVehicleTypeDisabledByAISettings(AIVehicle.VT_RAIL)) {
if (this.trains_disabled_shown == 0) {
AILog.Warning("Using trains is disabled in this AI's settings.")
AILog.Warning("No train routes will be built until you change this setting.")
this.trains_disabled_shown = 1;
}
}
else if (Vehicle.GetVehicleLimit(AIVehicle.VT_RAIL) == 0) {
if (this.trains_max0_shown == 0) {
AILog.Warning("Amount of allowed trains is set to 0 in your game settings.")
AILog.Warning("No train routes will be built until you change this setting.")
this.trains_max0_shown = 1;
}
}
else {
return true;
}
return false;
}
/**
* This is our SuperLib.Log acceptable log levels functions for debugging, returning always true.
* Only to be used when we want to debug inside SuperLib.
*/
function WormAI::SuperLib_Log_IsLevelAccepted_Function(log_level)
{
// Set to TRUE when we need to debug into SuperLib. Needs to be adapted when we decide to move
// all logging to SuperLib.Log.
return true;
}
/**
* Start the main loop of WormAI.
*/
function WormAI::Start()
{
if (air_manager.passenger_cargo_id == -1) {
AILog.Error("WormAI could not find the passenger cargo.");
return;
}
/* Give the AI a name */
if (!AICompany.SetName("WormAI")) {
local i = 2;
while (!AICompany.SetName("WormAI #" + i)) {
i++;
}
}
this.name = AICompany.GetName(AICompany.COMPANY_SELF);
InitSettings(); // Initialize some AI game settings.
Welcome(); // Write welcome and AI settings in log.
/* Try to detect whether we started as replacement of another AI from a savegame.
* In that case we don't get any savegame data but we will most likely have vehicles.
* Not expecting that can make us crash in certain places.
*/
if (!loaded_from_save) {
local vehicles = AIVehicleList();
if (vehicles.Count() > 0) {
AILog.Info("");
AILog.Warning("We were loaded into a savegame from a different AI.");
AILog.Warning("WormAI may not work correctly or crash.");
AILog.Info("");
save_not_ours = true;
air_manager.LoadFromScratch();
/// @todo load rail_manager data from scratch
loaded_from_save = true;
}
}
if (loaded_from_save) {
AILog.Warning("Savegame post processing...");
air_manager.AfterLoading();
rail_manager.AfterLoading();
AILog.Info("done.")
AILog.Info("");
}
/* We need our local tickers, as GetTick() will skip ticks */
local old_ticker = 0;
local old_ticker_rail = 0;
local cur_ticker = 0;
/* The amount of time we may sleep between loops.
Warning: don't change this value unless your understand the implications for all the delays!
*/
local sleepingtime = SLEEPING_TIME;
/* Factor to multiply the build delay with. */
local build_delay_factor = 1;
local rail_delay_factor = 1;
local cur_year = 0;
local new_year = 0;
local cur_month = 0;
local new_month = 0;
/// FOR DEBUGGING ONLY
//Log.SetIsLevelAcceptedFunction(WormAI.SuperLib_Log_IsLevelAccepted_Function);
/* Let's go on forever */
while (true) {
cur_ticker = GetTick();
/* Check if we can build aircraft or trains. If yes then handle some tasks. */
/* Since these values can be changed in game we need to re-check them regularly. */
this.use_air = this.CanBuildAircraft();
this.use_trains = this.CanBuildTrains();
if (!this.use_trains) {
if (this.use_air) {
this.air_manager.max_costfactor = WormAirManager.DEFAULT_ANY_COSTFACTOR;
}
}
else
this.air_manager.max_costfactor = WormAirManager.DEFAULT_MAX_COSTFACTOR;
if (this.use_air) {
/* Since settings can be changed during a game we need to recompute them every time. */
/* This is used both by building and upgrading airports. */
this.air_manager.ComputeDistances();
}
/* Task scheduling. */
new_year = AIDate.GetYear(AIDate.GetCurrentDate());
if (cur_year != new_year) { // Use != instead of < since user can cheat and turn back time
// Handle once a year tasks here.
AILog.Info(Helper.GetCurrentDateString() + " --- Yearly Tasks ---");
cur_year = new_year;
if (this.use_air) {
/* Evaluate best aircraft once in a while to see if there are better
airplanes available. Also make sure a warning is shown if no
suitable airplanes are found. */
this.air_manager.EvaluateAircraft(true);
/* Build a headquarter if it doesn't exist yet and our speed settings is at least medium. */
if (this.ai_speed_factor < 3) {
this.air_manager.BuildHQ();
/* Build statues only in fast, hard mode. */
if (this.ai_speed_factor < 2) {
this.air_manager.BuildStatues();
}
}
}
if (this.use_trains) {
/* Doing this once a year seems enough. */
this.rail_manager.UpdateRailType();
}
/* Some things we do more or less often depending on this.ai_speed_factor setting */
if (cur_year % this.ai_speed_factor == 0) {
/* Nothing for now. */
}
/* This seems like a good place to show some debugging info in case we turned
that setting on. Always once a year. */
if (GetSetting("debug_show_lists") == 1) {
/* Debugging info */
this.air_manager.DebugListTownsUsed();
//DebugListRouteInfo();
this.air_manager.DebugListRoutes();
//DebugListRoute(route_1);
//DebugListRoute(route_2);
}
AILog.Info(Helper.GetCurrentDateString() + " --- Yearly Tasks Done ---");
}
new_month = AIDate.GetMonth(AIDate.GetCurrentDate());
if (cur_month != new_month) { // Don't use < here since we need to handle December -> January
// Handle once a month tasks here.
AILog.Info(Helper.GetCurrentDateString() + " --- Monthly Tasks ---");
cur_month = new_month;
/* Some things we do more or less often depending on this.ai_speed_factor setting */
if (cur_month % this.ai_speed_factor == 0) {
if (this.use_air) {
/* Manage the routes once in a while */
this.air_manager.ManageAirRoutes();
this.air_manager.CheckForAirportsNeedingToBeUpgraded();
/* Update the list of blacklisted towns for airport consideration. */
this.air_manager.UpdateBlacklists();
}
if (this.use_trains) {
AILog.Info("++ Check Train Routes ++");
this.rail_manager.CheckRoutes();
AILog.Info("++ Check Train Profits ++");
this.rail_manager.CheckTrainProfits();
}
}
if (this.use_air) {
this.air_manager.ManageVehicleRenewal();
/* TEST ONCE A MONTH? SELL VEHICLES IN DEPOT */
this.air_manager.SellVehiclesInDepot();
}
/* Try to get rid of our loan once in a while */
AICompany.SetLoanAmount(0);
AILog.Info(Helper.GetCurrentDateString() + " --- Monthly Tasks Done ---");
}
/* Once in a while try to build something */
if (this.use_air) {
if ((cur_ticker - old_ticker > build_delay_factor * this.delay_build_airport_route) || old_ticker == 0) {
if (WormMoney.HasMoney(WormMoney.InflationCorrection(70000))) {
local ret = this.air_manager.BuildAirportRoute();
if ((ret == ERROR_FIND_AIRPORT1) || (ret == ERROR_MAX_AIRPORTS) ||
(ret == ERROR_MAX_AIRCRAFT) && old_ticker != 0) {
/* No more routes found or we have the max allowed aircraft, delay even more before trying to find an other */
if (build_delay_factor < 10)
build_delay_factor++;
}
else {
/* Set default delay back in case we had it increased, see above. */
build_delay_factor = 1;
}
old_ticker = cur_ticker;
}
else {
AILog.Info("Waiting for more money to build an air route...");
}
}
/* Check for events once in a while */
this.air_manager.HandleEvents();
/* Handle airport upgrading */
this.air_manager.airport_upgrader.UpgradeAirports();
}
if (this.use_trains) {
if ((cur_ticker - old_ticker_rail > rail_delay_factor * this.delay_build_rail_route) || old_ticker_rail == 0) {
if (WormMoney.HasMoney(WormMoney.InflationCorrection(60000))) {
AILog.Info("Try to build a railway...");
if (!this.rail_manager.BuildRailway()) {
rail_delay_factor++;
if (rail_delay_factor > 10)
rail_delay_factor = 10;
}
else
rail_delay_factor = 1;
}
else {
AILog.Info("Waiting for more money to build a train route...");
}
old_ticker_rail = cur_ticker;
}
}
/* Make sure we do not create infinite loops */
Sleep(sleepingtime);
} // END OF OUR MAIN LOOP
}
/**
* Save all data that WormAI uses.
* @return The data to be saved.
*/
function WormAI::Save()
{
/* Debugging info */
local MyOps1 = this.GetOpsTillSuspend();
local MyOps2 = 0;
/* only use for debugging:
AILog.Info("Ops till suspend: " + this.GetOpsTillSuspend());
*/
/* Save the data */
local table = {};
/* General data */
table.rawset("worm_save_version", 2); // Version of save data
/* Air manager data */
/// @todo This should be moved to a AirManager.SaveData function.
WormUtils.ListToTableEntry(table, "townsused", this.air_manager.towns_used);
WormUtils.ListToTableEntry(table, "route1", this.air_manager.route_1);
WormUtils.ListToTableEntry(table, "route2", this.air_manager.route_2);
/* Rail manager data */
rail_manager.SaveData(table);
/* only use for debugging:
AILog.Info("Tick: " + this.GetTick() );
*/
MyOps2 = this.GetOpsTillSuspend();
if (MyOps2 < 10000) {
AILog.Error("SAVE: Using almost all allowed ops: " + MyOps2 );
}
else if (MyOps2 < 20000) {
AILog.Warning("SAVE: Using a high amount of ops: " + MyOps2 );
}
else {
AILog.Info("Saving WormAI game data. Used ops: " + (MyOps1-MyOps2) );
}
return table;
}
/**
* Load previously saved information.
* @param version Which version of our AI saved the information.
* @param data The data that was saved.
*/
function WormAI::Load(version, data)
{
/* Debugging info */
local MyOps1 = this.GetOpsTillSuspend();
local MyOps2 = 0;
AILog.Info("Loading savegame saved by WormAI version " + version);
/// @todo load data in temp values then later unpack it because load has limited time available
if (data.rawin("worm_save_version")) {
this.save_version = data.rawget("worm_save_version");
AILog.Info("WormAI save data version " + this.save_version);
}
else {
if (version < 5)
AILog.Info("WormAI save data version 1.");
else
// Since OpenTTD doesn't send savegame data from a different AI
// it must be from WormAI but it has an unexpected version number.
AILog.Error("Unexpected WormAI savegame version!");
}
/// @todo This should call air_manager.LoadData for air related SaveGame data.
WormUtils.TableEntryToList(data, "townsused", this.air_manager.towns_used);
WormUtils.TableEntryToList(data, "route1", this.air_manager.route_1);
WormUtils.TableEntryToList(data, "route2", this.air_manager.route_2);
if (this.save_version > 1) {
/* Rail manager data */
rail_manager.LoadData(data, this.save_version);
}
loaded_from_save = true;
/* Debugging info */
MyOps2 = this.GetOpsTillSuspend();
if (MyOps2 < 10000) {
AILog.Error("LOAD: Using almost all allowed ops: " + MyOps2 );
}
else if (MyOps2 < 20000) {
AILog.Warning("LOAD: Using a high amount of ops: " + MyOps2 );
}
else {
AILog.Info("Loading WormAI game data. Used ops: " + (MyOps1-MyOps2) );
//AILog.Info("Loading: ops till suspend: " + MyOps2 + ", ops used in load: " + (MyOps1-MyOps2) );
}
AILog.Info("");
}