How Many Passengers?
I'm currently writing an AI to play OpenTTD, and I'm trying to find out how many people & mail will be available if I build a station covering an entire town. (Such as 100 passengers and 20 mail every month)
To calculate this, since I haven't seen any way to get this directly using the API, I need to use the formulas OpenTTD uses to distribute cargo.
Cargo is split based on the top local rating of each competitor in a city.
For example: If company A has two stations (Rating 50% and Rating 40%), and company B has one station (Rating 50%), half of the cargo goes to A and is split between their two stations, and the other half goes to B's single station.
Unfortunately, I haven't yet been able to find a way to find a way to access any of this data using AIStationList
.
AIStationList(AIStation.STATION_ANY).IsEmpty()
Returns true, despite there being stations from competitors.
I then attempted a workaround by getting the stationID using:
local industry_id = 0; //Industry ID to target, using a coal mine to test
local tile_list = AITileList_IndustryProducing(industry_id, AIStation.GetCoverageRadius(AIStation.STATION_TRAIN));
for (local tile = tile_list.Begin(); tile != 0; tile = tile_list.Next()) { //Tile is 0 when list exhausted
local station_id = AIStation.GetStationID(tile);
if (station_id != 65535){ //Empty tiles given ID 65535
AILog.Info("Tile! | "+tile+" | StationID: "+station_id+" | CargoPlanned: "+AIStation.GetCargoPlanned(station_id, 1)+" | CargoRating: "+AIStation.GetCargoRating(station_id, 1));
}}
This essentially gets checks the tiles around the industry to find any stations, however even with the station IDs, I still can't get any information about the station ratings, cargo loaded, or the company that owns them.
So my overall question: How can I find how many passengers and mail will go to my station?
Passengers:
The problem with using city pop:
Cargo generation is not exactly proportional to it. First, there is a random factor, but you'll notice that even if you note down say 10,000 passenger generation figures that it doesn't quite line up. So what's the real relation?
Use town.cmd.cpp
By looking at what the code does it's possible to write a math formula that will approximate cargo generation over time.
You can pretty well approximate the cargo generated in a town with the following process, when using the standard generation method:
Sum the square of the populations in each of the town's buildings, multiplied by a constant (calls or 'ticks' per day), multiplied by the length of the month under investigation.
Note: Consider houses with more than 255 pop to have exactly 255 pop. Use integer divisions (openTTD never uses any floating-point math!).
These formulas should produce passenger generation times passenger generation rate. I believe the default rate is 256 in regular economy (divide by 256 to get the rate) and 512 during recession. (The cargo generation happens once every 256 ticks). I don't currently have the game ready to test so I'll verify and update them later.
You can check the code in town_cmd.cpp. Look for the function TileLoop_Town()
. You'll also notice the much more complicated 'bitcount'
version. That's the new formula for Cnew, which is linear in population.
The way it works is that it generates a random number for each building and checks if it is below the building's population, then if it is generates passengers proportionate to said population. It's why cities tend to generate more passengers proportional to their pop.
You may need to implement a table with the list of all the buildings in each building GRF, or automated method to extract it.
Aside from a population, each house also has a mail_generation constant in its HouseSpec. It's used just like population is, and the formulas are as above.
Resources
So how do we get our AI to know these numbers for the various buildings? By having an internal table (which you'll have to periodically automatically update/scrape as people create more or update their house sets), or reading the NML/NewGRF files in use.
You can get more information about the houseSpec here. The important properties are binary slots 0B
and 0C
. You could read out the newGRFs beforehand and cache them in data files to speed up the initial loading part of your AI, or do the work at the start of the game. What's important is to end up with a table like this:
+-----------+------+------+
| house | pop | mail |
+-----------+------+------+
| office | 100 | 40 |
| house | 30 | 8 |
| (etc.) | ... | ... |
+-----------+------+------+
Which can then be fed into the formula. The numbers above are examples; they aren't the real values... These values can be found in the wiki over here for the default house set.
You can also check out the developer topic from when the new algorithm was patched in where both algorithms are discussed in detail to learn more (it was a useful source in compiling this answer).
How to figure out which house is on a tile?
By using the HouseSpec values, we can for example see that house #00h, Tall Office Block, has a "Remove Cost multiplier" of 150, and it is the only house with such a multiplier. Thus, a pseudocode procedure of finding out whether a house tile would be an Office Block, could be:
# Suppose we want to know what the population is of theTile.
testMode = AITestMode();
startAccounting();
AITile::DemolishTile(theTile);
ch = stopAccounting();
cb = AITile::BuildType(BT_CLEAR_HOUSE )
if ch / cb == 150 {
#Tall Office Block, so
Px = 187;
}
testMode = null;
You can complement this using AITile::GetCargoAcceptance
. This function will give you the cargo acceptance of a house tile, allowing you to further narrow down which house a tile contains.
Rinse and repeat for every other house in the table and you can know precisely what the pop is for any tile.
Station rating
So now that we have the passengers generated by the town, the next part is much easier. We have to get the value our station rating will be (the rating that we can achieve, assuming the vehicles are managed optimally). Assuming there's always at least one vehicle loading, the only variables that are really interesting are:
- The speed of the vehicle.
- The age of the vehicle
- If it is a boat (these get a bonus)
- Do we have a statue?
Look at the station rating in the wiki.
Start with 170 'free' points. For small towns with non-boats, it may be better to use 135 (cargo not generated often enough). Add 10, 20, or 33 for 2-year old, 1-year-old, and 0-year old construction respectively. Add 26 if you have a statue. Then add 1 point for every 4 km/h speed above 85. Multiply the cargo generation by 256 (<<=8
), then divide by the resulting number.
You can also simply roll advertising campaigns (you're an AI, it can click real fast...) in towns that generate a lot. In that case this number is simply always (nearly) 100% or 256.
Competition?
Pre-patch 1.10
Before 1.10, a station (for any producer) can have at most one competitor.
You will have to decide if your AI is going to be evil, and break commonly accepted multiplayer rules. You see, the stations are collected in a stationList
data structure, which is appended to. Since a naïve algorithm is used to select the best rating, if each competitor performs the same, the list is LIFO. Thus, whoever built the last station gets half the cargo. Thus, the best practice for an evil AI is to just build two stations if it spots competition, and assume it gets 100% of the cargo, making sure it uses the fastest engine and uses ad campaigns (where applicable) and statues.
If the evil AI notices that its vehicles slowed down loading, it can demolish and rebuild its stations.
If the AI is not going to be evil and abide by common multiplayer rules (to prevent destructive escalation), I would say that if there is one competitor, it should build one station and assume it gets half the cargo. If there is more than one competitor, the AI should never build a station there (essentially: first two come, first two served).
If the competition will have the same rating we have, we will get half of the cargo generated. Thus, you can assume any shared house will produce half its normal cargo for your AI, via assuming the competition is competent.
Since 1.10
see also https://github.com/OpenTTD/OpenTTD/commit/1225693b9c904144555bb009c7836ef214f4cf87#diff-6f68813e77c5367caff0a0f43ffd7fb5c9d9d4707c66c05e5fa66a939383e3bb
producers can supply more than 2 competitors, and ratings are the thing used to determine distribution.
So if station A has 60 rating and station B has 50, then station A will receive 6/11th of the cargo, station B will receive 5/11th. Total production will be about 60% of maximum (uses the best rating). So if total production is 100, A will receive 32 units (rounded down), and B receives 27.
Assuming the AI achieves the same ratings its competitors do, it will simply achieve a fair share as a lower bound. Thus, w.r.t. economic calculations this is an acceptable estimate for estimating how many vehicles it should use. Thus, you can simply use:
Competitors Amount
0 100%
1 50%
2 33%
3 25%
... (etc)