/* $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.MY_COMPANY) + (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.MY_COMPANY) > money) return; local loan = money - AICompany.GetBankBalance(AICompany.MY_COMPANY) + 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.VEHICLE_AIR); /* When bank balance < 300000, buy cheaper planes */ local balance = AICompany.GetBankBalance(AICompany.MY_COMPANY); 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.MY_COMPANY); /* 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; } }