summaryrefslogtreecommitdiff
path: root/bin/ai/wrightai/main.nut
blob: 3c10aeca98def3628a19c0c830bfc5ead0c5f545 (plain)
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
/* $Id$ */

class WrightAI extends AIController {
	name = null;
	towns_used = null;
	route_1 = null;
	route_2 = null;
	distance_of_route = {};
	vehicle_to_depot = {};
	delay_build_airport_route = 1000;
	passenger_cargo_id = -1;

	function Start();

	constructor() {
		this.towns_used = AIList();
		this.route_1 = AIList();
		this.route_2 = AIList();

		local list = AICargoList();
		for (local i = list.Begin(); list.HasNext(); i = list.Next()) {
			if (AICargo.HasCargoClass(i, AICargo.CC_PASSENGERS)) {
				this.passenger_cargo_id = i;
				break;
			}
		}
	}
};

/**
 * Check if we have enough money (via loan and on bank).
 */
function WrightAI::HasMoney(money)
{
	if (AICompany.GetBankBalance(AICompany.COMPANY_SELF) + (AICompany.GetMaxLoanAmount() - AICompany.GetLoanAmount()) > money) return true;
	return false;
}

/**
 * Get the amount of money requested, loan if needed.
 */
function WrightAI::GetMoney(money)
{
	if (!this.HasMoney(money)) return;
	if (AICompany.GetBankBalance(AICompany.COMPANY_SELF) > money) return;

	local loan = money - AICompany.GetBankBalance(AICompany.COMPANY_SELF) + AICompany.GetLoanInterval() + AICompany.GetLoanAmount();
	loan = loan - loan % AICompany.GetLoanInterval();
	AILog.Info("Need a loan to get " + money + ": " + loan);
	AICompany.SetLoanAmount(loan);
}

/**
 * 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 make some money.
 */
function WrightAI::BuildAirportRoute()
{
	local airport_type = (AIAirport.AirportAvailable(AIAirport.AT_SMALL) ? AIAirport.AT_SMALL : AIAirport.AT_LARGE);

	/* Get enough money to work with */
	this.GetMoney(150000);

	AILog.Info("Trying to build an airport route");

	local tile_1 = this.FindSuitableAirportSpot(airport_type, 0);
	if (tile_1 < 0) return -1;
	local tile_2 = this.FindSuitableAirportSpot(airport_type, tile_1);
	if (tile_2 < 0) {
		this.towns_used.RemoveValue(tile_1);
		return -2;
	}

	/* Build the airports for real */
	if (!AIAirport.BuildAirport(tile_1, airport_type, true)) {
		AILog.Error("Although the testing told us we could build 2 airports, it still failed on the first airport at tile " + tile_1 + ".");
		this.towns_used.RemoveValue(tile_1);
		this.towns_used.RemoveValue(tile_2);
		return -3;
	}
	if (!AIAirport.BuildAirport(tile_2, airport_type, true)) {
		AILog.Error("Although the testing told us we could build 2 airports, it still failed on the second airport at tile " + tile_2 + ".");
		AIAirport.RemoveAirport(tile_1);
		this.towns_used.RemoveValue(tile_1);
		this.towns_used.RemoveValue(tile_2);
		return -4;
	}

	local ret = this.BuildAircraft(tile_1, tile_2);
	if (ret < 0) {
		AIAirport.RemoveAirport(tile_1);
		AIAirport.RemoveAirport(tile_2);
		this.towns_used.RemoveValue(tile_1);
		this.towns_used.RemoveValue(tile_2);
		return ret;
	}

	AILog.Info("Done building a route");
	return ret;
}

/**
 * Build an aircraft with orders from tile_1 to tile_2.
 *  The best available aircraft of that time will be bought.
 */
function WrightAI::BuildAircraft(tile_1, tile_2)
{
	/* Build an aircraft */
	local hangar = AIAirport.GetHangarOfAirport(tile_1);
	local engine = null;

	local engine_list = AIEngineList(AIVehicle.VT_AIR);

	/* When bank balance < 300000, buy cheaper planes */
	local balance = AICompany.GetBankBalance(AICompany.COMPANY_SELF);
	engine_list.Valuate(AIEngine.GetPrice);
	engine_list.KeepBelowValue(balance < 300000 ? 50000 : (balance < 1000000 ? 300000 : 1000000));

	engine_list.Valuate(AIEngine.GetCargoType);
	engine_list.KeepValue(this.passenger_cargo_id);

	engine_list.Valuate(AIEngine.GetCapacity);
	engine_list.KeepTop(1);

	engine = engine_list.Begin();

	if (!AIEngine.IsValidEngine(engine)) {
		AILog.Error("Couldn't find a suitable engine");
		return -5;
	}
	local vehicle = AIVehicle.BuildVehicle(hangar, engine);
	if (!AIVehicle.IsValidVehicle(vehicle)) {
		AILog.Error("Couldn't build the aircraft");
		return -6;
	}
	/* Send him on his way */
	AIOrder.AppendOrder(vehicle, tile_1, AIOrder.AIOF_NONE);
	AIOrder.AppendOrder(vehicle, tile_2, AIOrder.AIOF_NONE);
	AIVehicle.StartStopVehicle(vehicle);
	this.distance_of_route.rawset(vehicle, AIMap.DistanceManhattan(tile_1, tile_2));
	this.route_1.AddItem(vehicle, tile_1);
	this.route_2.AddItem(vehicle, tile_2);

	AILog.Info("Done building an aircraft");

	return 0;
}

/**
 * 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.
 */
function WrightAI::FindSuitableAirportSpot(airport_type, center_tile)
{
	local airport_x, airport_y, airport_rad;

	airport_x = AIAirport.GetAirportWidth(airport_type);
	airport_y = AIAirport.GetAirportHeight(airport_type);
	airport_rad = AIAirport.GetAirportCoverageRadius(airport_type);

	local town_list = AITownList();
	/* Remove all the towns we already used */
	town_list.RemoveList(this.towns_used);

	town_list.Valuate(AITown.GetPopulation);
	town_list.KeepAboveValue(GetSetting("min_town_size"));
	/* Keep the best 10, if we can't find 2 stations in there, just leave it anyway */
	town_list.KeepTop(10);
	town_list.Valuate(AIBase.RandItem);

	/* Now find 2 suitable towns */
	for (local town = town_list.Begin(); town_list.HasNext(); town = town_list.Next()) {
		/* Don't make this a CPU hog */
		Sleep(1);

		local tile = AITown.GetLocation(town);

		/* Create a 30x30 grid around the core of the town and see if we can find a spot for a small airport */
		local list = AITileList();
		/* XXX -- We assume we are more than 15 tiles away from the border! */
		list.AddRectangle(tile - AIMap.GetTileIndex(15, 15), tile + AIMap.GetTileIndex(15, 15));
		list.Valuate(AITile.IsBuildableRectangle, airport_x, airport_y);
		list.KeepValue(1);
		if (center_tile != 0) {
			/* If we have a tile defined, we don't want to be within 25 tiles of this tile */
			list.Valuate(AITile.GetDistanceSquareToTile, center_tile);
			list.KeepAboveValue(625);
		}
		/* Sort on acceptance, remove places that don't have acceptance */
		list.Valuate(AITile.GetCargoAcceptance, this.passenger_cargo_id, airport_x, airport_y, airport_rad);
		list.RemoveBelowValue(10);

		/* Couldn't find a suitable place for this town, skip to the next */
		if (list.Count() == 0) continue;
		/* Walk all the tiles and see if we can build the airport at all */
		{
			local test = AITestMode();
			local good_tile = 0;

			for (tile = list.Begin(); list.HasNext(); tile = list.Next()) {
				Sleep(1);
				if (!AIAirport.BuildAirport(tile, airport_type, true)) continue;
				good_tile = tile;
				break;
			}

			/* Did we found a place to build the airport on? */
			if (good_tile == 0) continue;
		}

		AILog.Info("Found a good spot for an airport in town " + town + " at tile " + tile);

		/* Make the town as used, so we don't use it again */
		this.towns_used.AddItem(town, tile);

		return tile;
	}

	AILog.Info("Couldn't find a suitable town to build an airport in");
	return -1;
}

function WrightAI::ManageAirRoutes()
{
	local list = AIVehicleList();
	list.Valuate(AIVehicle.GetAge);
	/* Give the plane at least 2 years to make a difference */
	list.KeepAboveValue(365 * 2);
	list.Valuate(AIVehicle.GetProfitLastYear);

	for (local i = list.Begin(); list.HasNext(); i = list.Next()) {
		local profit = list.GetValue(i);
		/* Profit last year and this year bad? Let's sell the vehicle */
		if (profit < 10000 && AIVehicle.GetProfitThisYear(i) < 10000) {
			/* Send the vehicle to depot if we didn't do so yet */
			if (!vehicle_to_depot.rawin(i) || vehicle_to_depot.rawget(i) != true) {
				AILog.Info("Sending " + i + " to depot as profit is: " + profit + " / " + AIVehicle.GetProfitThisYear(i));
				AIVehicle.SendVehicleToDepot(i);
				vehicle_to_depot.rawset(i, true);
			}
		}
		/* Try to sell it over and over till it really is in the depot */
		if (vehicle_to_depot.rawin(i) && vehicle_to_depot.rawget(i) == true) {
			if (AIVehicle.SellVehicle(i)) {
				AILog.Info("Selling " + i + " as it finally is in a depot.");
				/* Check if we are the last one serving those airports; else sell the airports */
				local list2 = AIVehicleList_Station(AIStation.GetStationID(this.route_1.GetValue(i)));
				if (list2.Count() == 0) this.SellAirports(i);
				vehicle_to_depot.rawdelete(i);
			}
		}
	}

	/* Don't try to add planes when we are short on cash */
	if (!this.HasMoney(50000)) return;

	list = AIStationList(AIStation.STATION_AIRPORT);
	list.Valuate(AIStation.GetCargoWaiting, this.passenger_cargo_id);
	list.KeepAboveValue(250);

	for (local i = list.Begin(); list.HasNext(); i = list.Next()) {
		local list2 = AIVehicleList_Station(i);
		/* No vehicles going to this station, abort and sell */
		if (list2.Count() == 0) {
			this.SellAirports(i);
			continue;
		};

		/* Find the first vehicle that is going to this station */
		local v = list2.Begin();
		local dist = this.distance_of_route.rawget(v);

		list2.Valuate(AIVehicle.GetAge);
		list2.KeepBelowValue(dist);
		/* Do not build a new vehicle if we bought a new one in the last DISTANCE days */
		if (list2.Count() != 0) continue;

		AILog.Info("Station " + i + " (" + AIStation.GetLocation(i) + ") has too many cargo, adding a new vehicle for the route.");

		/* Make sure we have enough money */
		this.GetMoney(50000);

		return this.BuildAircraft(this.route_1.GetValue(v), this.route_2.GetValue(v));
	}
}

/**
  * Sells the airports from route index i
  * Removes towns from towns_used list too
  */
function WrightAI::SellAirports(i) {
	/* Remove the airports */
	AILog.Info("Removing airports as nobody serves them anymore.");
	AIAirport.RemoveAirport(this.route_1.GetValue(i));
	AIAirport.RemoveAirport(this.route_2.GetValue(i));
	/* Free the towns_used entries */
	this.towns_used.RemoveValue(this.route_1.GetValue(i));
	this.towns_used.RemoveValue(this.route_2.GetValue(i));
	/* Remove the route */
	this.route_1.RemoveItem(i);
	this.route_2.RemoveItem(i);
}

function WrightAI::HandleEvents()
{
	while (AIEventController.IsEventWaiting()) {
		local e = AIEventController.GetNextEvent();
		switch (e.GetEventType()) {
			case AIEvent.AI_ET_VEHICLE_CRASHED: {
				local ec = AIEventVehicleCrashed.Convert(e);
				local v = ec.GetVehicleID();
				AILog.Info("We have a crashed vehicle (" + v + "), buying a new one as replacement");
				this.BuildAircraft(this.route_1.GetValue(v), this.route_2.GetValue(v));
				this.route_1.RemoveItem(v);
				this.route_2.RemoveItem(v);
			} break;

			default:
				break;
		}
	}
}

function WrightAI::Start()
{
	if (this.passenger_cargo_id == -1) {
		AILog.Error("WrightAI could not find the passenger cargo");
		return;
	}

	/* Give the boy a name */
	if (!AICompany.SetName("WrightAI")) {
		local i = 2;
		while (!AICompany.SetName("WrightAI #" + i)) {
			i++;
		}
	}
	this.name = AICompany.GetName(AICompany.COMPANY_SELF);
	/* Say hello to the user */
	AILog.Info("Welcome to WrightAI. I will be building airports all day long.");
	AILog.Info("  - Minimum Town Size: " + GetSetting("min_town_size"));

	/* We start with almost no loan, and we take a loan when we want to build something */
	AICompany.SetLoanAmount(AICompany.GetLoanInterval());

	/* We need our local ticker, as GetTick() will skip ticks */
	local ticker = 0;
	/* Determine time we may sleep */
	local sleepingtime = 100;
	if (this.delay_build_airport_route < sleepingtime)
		sleepingtime = this.delay_build_airport_route;

	/* Let's go on for ever */
	while (true) {
		/* Once in a while, with enough money, try to build something */
		if ((ticker % this.delay_build_airport_route == 0 || ticker == 0) && this.HasMoney(100000)) {
			local ret = this.BuildAirportRoute();
			if (ret == -1 && ticker != 0) {
				/* No more route found, delay even more before trying to find an other */
				this.delay_build_airport_route = 10000;
			}
			else if (ret < 0 && ticker == 0) {
				/* The AI failed to build a first airport and is deemed */
				AICompany.SetName("Failed " + this.name);
				AILog.Error("Failed to build first airport route, now giving up building. Repaying loan. Have a nice day!");
				AICompany.SetLoanAmount(0);
				return;
			}
		}
		/* Manage the routes once in a while */
		if (ticker % 2000 == 0) {
			this.ManageAirRoutes();
		}
		/* Try to get ride of our loan once in a while */
		if (ticker % 5000 == 0) {
			AICompany.SetLoanAmount(0);
		}
		/* Check for events once in a while */
		if (ticker % 100 == 0) {
			this.HandleEvents();
		}
		/* Make sure we do not create infinite loops */
		Sleep(sleepingtime);
		ticker += sleepingtime;
	}
}