Coding Tutorial: Monthly Costs Per Track Tile

Forum for technical discussions regarding development. If you have a general suggestion, problem or comment, please use one of the other forums.

Moderator: OpenTTD Developers

User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Coding Tutorial: Monthly Costs Per Track Tile

Post by smallfly »

hello together!

i'm looking for a openttd coding tutorial. i mean a tutorial that helps people which have successfully set up a development environment with mingw, msys, a code editor etc and are already able to compile openttd as well as apply patches.

i need a tutorial that shows me the structure of the openttd source code step by step and explains me with some examples how to change things in code, i.e. an example that shows how to change vehicle speeds, ingame menus etc. so that a new openttd developer has the chance to get closer to the code to understand it.

in my case, i want to develop a patch that counts all railway tracks of a player and generates maintenance costs of 10 dollars per month and per track. is there anybody, that already implemented something like that and can tell me which source code files to change?

side notice: im a php, c#, vb, java etc. coder for many years. i started c++ about one year ago and have read several books on this subject.

thanks for your help! i love openttd and want to help to make it even better.

smallfly
Last edited by smallfly on 19 Oct 2009 15:37, edited 1 time in total.
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
Kogut
Tycoon
Tycoon
Posts: 2493
Joined: 26 Aug 2009 06:33
Location: Poland

Re: Is there a OpenTTD Coding Tutorial?

Post by Kogut »

I can't help but i can said that idea is great!
Airports also need (high) upkeep cost.
Correct me If I am wrong - PM me if my English is bad
AIAI - AI for OpenTTD
Roujin
Tycoon
Tycoon
Posts: 1884
Joined: 08 Apr 2007 04:07

Re: Is there a OpenTTD Coding Tutorial?

Post by Roujin »

Well, afaik there's no such thing as a tutorial on that - what would you write in such a tutorial? You can change everything, so a tutorial on how to change things in the OpenTTD code could only cover a few things.

But as you seem to be an experienced programmer and not a newbie that has never seen code before I guess you'll find out yourself where to look for what in the code. The source files are all named pretty descriptive.. you can also look at the Doxygen Documentation. Also, be sure to follow the Coding Style.
More various information about certain aspects of coding in OpenTTD: http://wiki.openttd.org/Development_Documentation

To the examples you mentioned:
Changing the vehicle speeds by hardcoding is discouraged, because that's something exposed to the NewGRF system. Anyone (who's willing to learn nfo coding) can create a newGRF that changes the speed of an existing vehicle to something else, or define a new vehicle with any stats he likes.
Generally, stuff that's exposed to newGRF should not be changed by means of hardcoding.

Ingame windows
For those, look at the *_gui.cpp files. Currently a new window system is being worked on that defines windows in a nested manner, instead of telling pixel by pixel where what goes (the old system). This will hopefully make it more flexible wrt. different fonts, localizations, resizing etc.
I didn't find information on the new window system right now. Some (probably outdated) info on the old window system is found here.
* @Belugas wonders what is worst... a mom or a wife...
<Lakie> Well, they do the same thing but the code is different.

______________
My patches
check my wiki page (sticky button) for a complete list

ImageImage
ImageImageImageImageImageImageImage
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Monthly Track Maintenance Costs

Post by smallfly »

First of all thanks for your fast replies! :wink:

To get more specific, lets make this thread a tutorial on how to implement a patch, that realizes monthly costs for tracks or other player related things. I think this is an option that will find its way to the trunk, because there are several players out there, wishing to config values like:

- Monthly costs per track tile
- Monthly costs per airport
- Monthly costs per railway station tile
etc.

And if these cost values are set to "0" by default, but can be set to any value in "patch settings" in game then no one has any reason to prevent this patch from getting in trunk.

So ill make a start and because i have no idea of openttd development i appreciate your help by giving specific hints (real source code not just a suggestion)

We need a new expense type:
- EXPENSES_TRACK_RUN
or to make it easier (not defining a new type), we just use the type EXPENSES_TRAIN_RUN.

We need a place in source code that is called periodically, lets say every month and a algorythm that walks through all track tiles and multiplies the sum of all tiles by a factor like MONTHLY_COST_PER_TRACK_TILE.

For this purpose we look at "date.cpp" there we find a call of the method "CompaniesMonthlyLoop()" which is called, when a new month is entered. The method itself is in "economy.cpp" where we go now. Above the line "CompaniesPayInterest();" in the method "CompaniesMonthlyLoop" we insert a new line called "CompaniesPayMonthlyTrackFees()" and outside the method we create a new method:

Code: Select all

static void CompaniesPayMonthlyTrackFees()
{
	const Company *c;

	FOR_ALL_COMPANIES(c) {
		_current_company = c->index;

                // count all track tiles of the current company
                int numberOfTrackTiles;
                numberOfTrackTiles = 0;
                foreach (track in tracks)
                {
                      if (track->companyOfThatTrack == c->index)
                             numberOfTrackTiles++;
                }

		SubtractMoneyFromCompany(CommandCost(EXPENSES_TRAIN_RUN, numberOfTrackTiles * MONTHLY_COST_PER_TRACK_TILE));
	}
}
So guys. That was a quick thought. Please correct me and call me stupid :wink:
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
Roujin
Tycoon
Tycoon
Posts: 1884
Joined: 08 Apr 2007 04:07

Re: Monthly Track Maintenance Costs

Post by Roujin »

smallfly wrote:So guys. That was a quick thought. Please correct me and call me stupid :wink:
No reason to ;)

I wouldn't use the EXPENSES_TRAIN_RUN type though, as the type determines where in the finance window (which category) the cost will appear.
A better category would be "Property Maintenance". I don't know what EXPENSES_* corresponds with this, but I guess you'll find it quickly in the same place EXPENSES_TRAIN_RUN is defined.
edit: Looked it up, it's EXPENSES_PROPERTY.

While you're at it, you could search the source code for places where costs in that category ("Property Maintenance") is already added, and expand it the way you want to have it, instead of writing a whole new function.
Currently in OpenTTD, there are already periodic costs (monthly/quarterly/yearly I don't know) for each Station a player owns.
edit: it's inside CompanyGenStatistics(), which is called inside CompaniesMonthlyLoop().

Oh and one more thing in your code snippet (just now read it carefully, sorry): the "foreach (track in tracks)" won't work because tracks (unlike stations) are not stored like that in a list - you'll have to iterate over all the tiles of the map and pick out those those that have the correct TileType (see doxygen on tile_map.h) and the correct owner.
Maybe this could get quite expensive on a big map though, 2048^2 is 4 million tiles to iterate through. You'd have to check if this causes any problems.
An alternative to this expensive check would be caching the number of rail tiles a company owns in a variable. Every time this a rail tile is built or removed by any means, this variable would have to be updated accordingly. Then you'll have to make yourself familiar with the saveload system though, as this variable has to be stored in the savegame.
* @Belugas wonders what is worst... a mom or a wife...
<Lakie> Well, they do the same thing but the code is different.

______________
My patches
check my wiki page (sticky button) for a complete list

ImageImage
ImageImageImageImageImageImageImage
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Re: Monthly Track Maintenance Costs

Post by smallfly »

Roujin wrote:A better category would be "Property Maintenance" ... EXPENSES_PROPERTY.
You're right.

Roujin wrote:it's inside CompanyGenStatistics(), which is called inside CompaniesMonthlyLoop().
So I will place the algorythm below these lines of code:

Code: Select all

	FOR_ALL_STATIONS(st) {
		_current_company = st->owner;
		CommandCost cost(EXPENSES_PROPERTY, _price.station_value >> 1);
		SubtractMoneyFromCompany(cost);
	}
Roujin wrote:Oh and one more thing in your code snippet (just now read it carefully, sorry): the "foreach (track in tracks)" won't work because tracks (unlike stations) are not stored like that in a list
I expected that. I will have a look for the walk-trough-all-tiles snippet now.
Roujin wrote: An alternative to this expensive check would be caching the number of rail tiles a company owns in a variable. Every time this a rail tile is built or removed by any means, this variable would have to be updated accordingly. Then you'll have to make yourself familiar with the saveload system though, as this variable has to be stored in the savegame.
OK, I will implement the function by using a cache variable. That makes sense. But I dont want this cache variable to be saved in savegame. Loading up a savegame should force the game to count the tiles again and save it to a cache variable. (Reason: If the variable gets out of sync (is not the real sum of all tiles anymore because of any unexpected events) a the user can refresh the variable by saving und loading the game)

//EDIT: some snippets ill need

Code: Select all

#include "map_func.h"
#include "tile_type.h" // MP_RAILWAY + MP_ROAD for level crossings
#include "tile_map.h"
static bool IsTileOwner(TileIndex tile, Owner owner)
static bool IsTileType(TileIndex tile, TileType type)
Last edited by smallfly on 19 Oct 2009 17:44, edited 9 times in total.
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
Kogut
Tycoon
Tycoon
Posts: 2493
Joined: 26 Aug 2009 06:33
Location: Poland

Re: Monthly Track Maintenance Costs

Post by Kogut »

smallfly wrote: - Monthly costs per airport
- Monthly costs per railway station tile
Maybe airport tile (intercontinental should cost more than commuter)
Correct me If I am wrong - PM me if my English is bad
AIAI - AI for OpenTTD
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by smallfly »

Kogut wrote:
smallfly wrote: - Monthly costs per airport
- Monthly costs per railway station tile
Maybe airport tile (intercontinental should cost more than commuter)
Please dont go into detail. Otherwise we will lose the thread fast. (But you're right of course :wink: )
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
frosch
OpenTTD Developer
OpenTTD Developer
Posts: 991
Joined: 20 Dec 2006 13:31
Location: Aschaffenburg

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by frosch »

For periodically processing all tiles TileLoop_Xxx (e.g. TileLoop_Track)... functions are suited best.
⢇⡸⢸⠢⡇⡇⢎⡁⢎⡱⢸⡱⢸⣭⠀⢸⢜⢸⢸⣀⢸⣀⢸⣭⢸⡱⠀⢰⠭⡆⣫⠰⣉⢸⢸⠀⢰⠭⡆⡯⡆⢹⠁⠀⢐⠰⡁
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by smallfly »

frosch wrote:For periodically processing all tiles TileLoop_Xxx (e.g. TileLoop_Track)... functions are suited best.
I looked at the function, but I dont know how to handle it. What benefit should this function provide compared to just run through all tile indices and ask for owner and tile type in the end of a month like:

Code: Select all

	uint maxTileIndex = MapSizeX() * MapSizeY();

	FOR_ALL_COMPANIES(c) {
		for (uint tile = 0; tile < maxTileIndex; tile++) {
			if (IsTileOwner(tile, c) && IsTileType(tile, MP_RAILWAY)) {
                                _current_company = c;
				CommandCost cost(EXPENSES_PROPERTY, 100);	
                        }
		}
	}
(the code will not function; i dont know how to code it at the moment, but you know what i mean)
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
frosch
OpenTTD Developer
OpenTTD Developer
Posts: 991
Joined: 20 Dec 2006 13:31
Location: Aschaffenburg

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by frosch »

- The function already runs over all tiles,
- it does this in a more fine grained way than monthly,
- it does not run over the whole map at once.

To see its effect, clear some grass and watch it growing. It won't grow synchronously on all tiles.
⢇⡸⢸⠢⡇⡇⢎⡁⢎⡱⢸⡱⢸⣭⠀⢸⢜⢸⢸⣀⢸⣀⢸⣭⢸⡱⠀⢰⠭⡆⣫⠰⣉⢸⢸⠀⢰⠭⡆⡯⡆⢹⠁⠀⢐⠰⡁
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by smallfly »

frosch wrote:- The function already runs over all tiles,
- it does this in a more fine grained way than monthly,
- it does not run over the whole map at once.

To see its effect, clear some grass and watch it growing. It won't grow synchronously on all tiles.
Ok, you got me. I should implement it in the loop you mentioned, but first of all i need to understand some basics...
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Incredible!

Post by smallfly »

Yeehaw! That works great! Here is what i coded to make it function:

Code: Select all

uint maxTileIndex = MapSize();
	uint countTracks = 0;
	const uint MONTHLY_TRACK_FEE = 10 >> 1;

	FOR_ALL_COMPANIES(c) {
		_current_company = c->index;

		for (uint tile = 0; tile < maxTileIndex; tile++) {
			if (IsValidTile(tile) &&
				!IsTileType(tile, MP_HOUSE) &&
				!IsTileType(tile, MP_INDUSTRY) &&
				IsTileOwner(tile, _current_company) &&
				IsTileType(tile, MP_RAILWAY)
				) 	
				countTracks++;
		}

		SubtractMoneyFromCompany(CommandCost(EXPENSES_PROPERTY, countTracks * MONTHLY_TRACK_FEE)); 	
				
		IConsolePrintF(CC_INFO, "Total tracks of your company: %i, Fee per Tile and Month: %i", countTracks, MONTHLY_TRACK_FEE);
	}
In the game that looks like the following screenshot. There is one problem: The currency. I set the MONTHLY_TRACK_FEE to 10 >> 1 which is 10/2 = 5 (pounds because pounds is the default currency). The currency in game is for example us dollar (one pound = two us dollars), so that the finance overview correctly says "10 $" (because its only one tile).

How do i say the console that it sould output the value MONTHLY_TRACK_FEE not in pounds but in us dollars inclusive the dollar sign? I think its the function FormatGenericCurrency() but i do not know how to use it. HELP! ;)
trackfee.jpg
trackfee.jpg (44.55 KiB) Viewed 5847 times
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
petert
Tycoon
Tycoon
Posts: 3008
Joined: 02 Apr 2009 22:43
Location: Massachusetts, USA

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by petert »

Could you post a diff pleaes? In tortoiseSVN: right-click on folder w/ changes, then click TortoiseSVN -> Create Patch -> Ok
Rubidium
OpenTTD Developer
OpenTTD Developer
Posts: 3815
Joined: 09 Feb 2006 19:15

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by Rubidium »

The 'base' currency is GBP.
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by smallfly »

petert wrote:Could you post a diff pleaes? In tortoiseSVN: right-click on folder w/ changes, then click TortoiseSVN -> Create Patch -> Ok
Of course. Here it is. But you wont get happy with it, if you dont have 0.7.3 tag source code downloaded ;)

Does anyone know the function to display costs in user selected currency in console? (see my above question)
Attachments
track_maintenance_costs_0.7.3_v1.diff
(1.24 KiB) Downloaded 139 times
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
Roujin
Tycoon
Tycoon
Posts: 1884
Joined: 08 Apr 2007 04:07

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by Roujin »

Why? The console is not there for regular output, but for debugging (And for console commands, obviously). For debugging info it's okay (or even appropriate imo) to use the internal value, instead of whatever currency the player has chosen.

I'd also suggest using the DEBUG(...) macro instead of IConsolePrintF. Just search the source for other occurrencies of "DEBUG(" to see how it's used. It gets a category passed (e.g. misc), a level, and then your (formatted) text.
The text will then only be displayed in the console if you run OpenTTD with at least the debug level you specified

Code: Select all

openttd.exe -d misc=2
or set the debug level in the console

Code: Select all

debug_level misc=2

Also, don't hardcode the cost like this. Check the source for other occurrencies of SubtractMoneyFromCompany: you'll find that they all use one of the Prices defined in economy_type.h, their values set in src/table/pricebase.h
example: the station property payment you already quoted in a previous post

Code: Select all

CommandCost cost(EXPENSES_PROPERTY, _price.station_value >> 1);
The base costs are exposed to newGRF, however I don't know what measures you have to take if you want to add a new one. For simplicity, you could just use (a fraction of) station_value, like the station property payment does.

Finally, two suggestions to simplify your current code: don't iterate over all companies and check if the rail belongs to that company inside, but just get the owner of the tile. Second: Your TileType checks are redundant. Each tile has exactly one of those types.

Oh and one more thing I just found: you're not covering rail+road crossings. According to this, they count as MP_ROAD.
* @Belugas wonders what is worst... a mom or a wife...
<Lakie> Well, they do the same thing but the code is different.

______________
My patches
check my wiki page (sticky button) for a complete list

ImageImage
ImageImageImageImageImageImageImage
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by smallfly »

Thanks for you nice help :wink: I will give back something to the community by writing some easy-to-understand wiki pages about topics i messed around with during the next weeks.

To get back to this thread:
Roujin wrote:Why? The console is not there for regular output, but for debugging (And for console commands, obviously). For debugging info it's okay (or even appropriate imo) to use the internal value, instead of whatever currency the player has chosen.
Thats my opinion too, at least for a final patch. At the moment I do it quick and dirty, because i dont know it better, i.e. i dont know how to add a button to finance window that leads you to a new window with additional financial information like track maintenance costs. Another way would be to make the finance menu like a tree, where you can open a point like "running costs", below that there are "track maintenance costs", "station maintenance costs" etc.
Roujin wrote:I'd also suggest using the DEBUG(...) macro instead of IConsolePrintF.
Thanks for the hint. I will use it. :wink: Also thanks for the debug level hint.
Roujin wrote:Also, don't hardcode the cost like this.
Of course. In final patch i want to make the running costs alterable by the server by using "advanced settings". At the moment, i dont know how to manage it. :( But i will learn it! :D
Roujin wrote:Finally, two suggestions to simplify your current code: don't iterate over all companies and check if the rail belongs to that company inside, but just get the owner of the tile.
So you want to iterate over the tiles (but not the companies) then ask for the owner and say SubtractMoneyFromCompany()? That way the SubtractMoneyFromCompany() Function gets called very often. Isnt that a problem? But perhaps youre right... If you have 20 companies, there would be 20 iterations over the tile iterations ...
Roujin wrote:Second: Your TileType checks are redundant. Each tile has exactly one of those types.
i had to do those checks. otherwise an assert failure appears in game because the "IsTileOwner" Function requires those checks. or what do you mean?
Roujin wrote:Oh and one more thing I just found: you're not covering rail+road crossings. According to this, they count as MP_ROAD.
I know that. (That sounds if a know much about the topic, but i just stepped over it during my doxygen search ;) )
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
Yexo
Tycoon
Tycoon
Posts: 3663
Joined: 20 Dec 2007 12:49

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by Yexo »

smallfly wrote:So you want to iterate over the tiles (but not the companies) then ask for the owner and say SubtractMoneyFromCompany()? That way the SubtractMoneyFromCompany() Function gets called very often. Isnt that a problem? But perhaps youre right... If you have 20 companies, there would be 20 iterations over the tile iterations ...
If you think calling SubtractMoneyFromCompany is a problem you can also count the number of tracks in a local array and call it once for each company after the tileloop. Doing the tileloop 20 times is definitely an inefficient solution.
Roujin wrote:Second: Your TileType checks are redundant. Each tile has exactly one of those types.
i had to do those checks. otherwise an assert failure appears in game because the "IsTileOwner" Function requires those checks. or what do you mean?[/quote]The following if check is equivalent to your check:

Code: Select all

         if (IsValidTile(tile) &&
            IsTileType(tile, MP_RAILWAY) &&
            IsTileOwner(tile, _current_company)
            )    
A tile always has 1 (and exactly 1) tiletipe, so if you're sure it's MP_RAILWAY you're also sure it isn't MP_HOUSE or MP_INDUSTRY
User avatar
smallfly
Chairman
Chairman
Posts: 892
Joined: 19 Oct 2009 13:29
Location: Germany

Re: Coding Tutorial: Monthly Costs Per Track Tile

Post by smallfly »

Yexo wrote:

Code: Select all

         if (IsValidTile(tile) &&
            IsTileType(tile, MP_RAILWAY) &&
            IsTileOwner(tile, _current_company)
            )    
Ah. Now i now what you mean. Yeah youre right. In this case it is absolutely redundant. I coded it like that, because i also tested to use the patch in that way, that you have to pay for each tile you own, independent of tile type. So you dont "own" anything anymore. You "rent" everything. But that was just a test. The final patch shouldnt have redundant checks. Thanks for the hint.
Yexo wrote:you can also count the number of tracks in a local array and call it once for each company after the tileloop. Doing the tileloop 20 times is definitely an inefficient solution.
Thats the way i will do it. And i have to get to know the tile loop functions that the user "frosch" mentioned.
www.p1sim.org - P1SIM - Traffic, Logistics, City-Building & more
Join the discussions here on tt-forums.net!
Post Reply

Return to “OpenTTD Development”

Who is online

Users browsing this forum: No registered users and 16 guests