Callbacks

Discussions about the technical aspects of graphics development, including NewGRF tools and utilities.

Moderator: Graphics Moderators

Post Reply
User avatar
Ben1338
Traffic Manager
Traffic Manager
Posts: 171
Joined: 17 Nov 2007 17:23
Skype: ben313371

Callbacks

Post by Ben1338 »

Hey there

I couldn't find much on the GRF spec wiki about what callbacks are and how to use them within a GRF so this is why I'm making this short thread about them.
Transportman
Tycoon
Tycoon
Posts: 2781
Joined: 22 Feb 2011 18:34

Re: Callbacks

Post by Transportman »

Callbacks basically are calls to other pieces of code in your NewGRF. Those pieces can just be a simple value like 42, but also references to strings in the language files or spritesets, or switches to determine the result based on some variables. If you write NewGRFs you automatically use them, even without knowing it. In NML, they are in the graphics and livery_override blocks.
Coder of the Dutch Trackset | Development support for the Dutch Trainset | Coder of the 2cc TrainsInNML
User avatar
andythenorth
Tycoon
Tycoon
Posts: 5656
Joined: 31 Mar 2007 14:23
Location: Lost in Music

Re: Callbacks

Post by andythenorth »

NewGRF Callbacks are triggered by events in OpenTTD. Broadly these events are:

- something in the game loop on a regular frequency
- user interaction (via UI)
- other game events, such as vehicle arriving in station

When the event is triggered OpenTTD calls the callback chain in the NewGRF, passing certain additional CB information. The NewGRF code calculates a result (optionally using the additional CB information), and returns a result to OpenTTD. OpenTTD then uses the result. Different values for the result will result in different behaviours from OpenTTD.

Examples are:
- text in industry windows
- mutable vehicle properties
- tile animation
- cargo processing at industries
...and many more.
User avatar
Ben1338
Traffic Manager
Traffic Manager
Posts: 171
Joined: 17 Nov 2007 17:23
Skype: ben313371

Re: Callbacks

Post by Ben1338 »

Is there anywhere where I can learn the callback feature in more detail?
Cadde
Transport Coordinator
Transport Coordinator
Posts: 290
Joined: 07 Oct 2004 12:51

Re: Callbacks

Post by Cadde »

Depends on what flavor you are working with...

If you are doing NFO then i would refer you to the documentation on that: https://newgrf-specs.tt-wiki.net/wiki/Main_Page
If you are doing NML then you could look here: https://newgrf-specs.tt-wiki.net/wiki/NML:Main

In the case of NML, a callback is simply a piece of code that evaluates whatever value you want for your property in question.
Now, mind you this will mostly be from memory...

Ok, say you want to alter the running cost of a train dynamically based on the train's current speed.

First, in the graphics block of the train you would add:

Code: Select all

graphics {
    running_cost_factor: switch_my_train_running_cost_factor;
}
running_cost_factor - The property we want to assign. In the properties section of your train, that particular property takes values ranging from 0 to 255.
switch_my_train_running_cost_factor - Is the name (identifier) of a switch block that you have defined previously in your NML file.

The switch could look something like this:

Code: Select all

switch(FEAT_TRAINS, PARENT, switch_my_train_running_cost_factor, 0) {
    return current_speed * 100 / max_speed * 255 / 100;
}
If you don't know how switch blocks works then see the NML reference i linked at the top. I am not going to explain that in full here!
But essentially, switch blocks make decisions based on their fourth parameter (which in this case i've set to zero as i am not making a decision) and then evaluates an expression, returning the result of that evaluated expression to the caller. In other words, calling back or a callback if you will!

FEAT_TRAINS - The feature we are working on. In this case, trains, which gives us access to a subset of variables related to trains. It basically sets the stage for what we want to use in the following expressions.
PARENT - We want to get information about the parent vehicle of our train. Or in this case... the head, the first, the one that actually has all the data on it or the locomotive if you will... rather than each individual wagon. If we used SELF, we wouldn't be able to query the speed variables of the train.
switch_my_train_running_cost_factor - Again, the name (Identifier) of the switch block in question. Same as the identifier in the graphics block above.
0 - As i said, the fourth parameter is used to make decisions. Read about the switch block in the NML documentation for more details on that. We are making no decisions so i have hardcoded this to zero as one cannot omit the fourth parameter in a switch block.

return - Not necessary but essentially means we want to return a value back to the caller at this point. I could type "return 42;" and the returning value would be "42". Or i could simply omit the return statement and type "42;" here. Or, i could even input the identifier of another switch block in here for a chain.
current_speed - This is a variable that the locomotive knows about. Essentially, it's the same as typing "var[0xB4]" there. Or get variable B4 (180 in decimal) from the table of variables that exist for the current feature. It's the current speed of the vehicle in internal speed units.
max_speed - Same as current_speed but instead we are getting variable 0x4C for the vehicle's maximum speed.

Then, what we are doing is multiplying the current_speed variable by 100 and dividing that with the maximum speed of the train.
Why multiply, you might ask? Well, because the return from callbacks is ALWAYS integers. In other words, you cannot return 0.5 or 0.2 etc. If you do, the value will be floored to 0. Same if you return 0.9, the value is floored, not rounded! The same goes for any calculations inside the expression. Each step of the way, the values are turned to integers. Hence...

Code: Select all

10 * (1/2) = 0
The (1/2) does NOT resolve to 0.5, it resolves to 0. Sure, it's possible to use floating point numbers in an expression. But then the whole expression has to be a constant, as soon as you bring variables into the mix, you cannot use floating point numbers in the expression.
Or in other words...

Code: Select all

VALID: 5.5*0.2594;
INVALID: current_speed*5.5*0.2594
Either way, the returned result is turned (except for a few properties that actually are floats) into an integer in the end. The result therefore of the valid expression above would be 0 anyways.
So, essentially, we have to turn integers into numbers that can be used to represent the decimal points of any operation, remembering to truncate those decimal points in the end.
Which is why there's a division by 100 at the end.

Inside the little decimal point trick, we also multiply the result by 255. This to conform to the range of 0-255 of the running cost factor. Not that you need to anymore, you could return 25500 here. The vehicle would then have a running cost that is 25.5 thousand times as expensive to run as it's base running cost. Or at least that's what i have found out today.

There you have it. Good luck if you are trying to do this in NFO. ;)
It's possible but it's a PITA.

TL;DR

A callback is like saying, in math terms:

Code: Select all

f(x) = 2x
f(1) = 2
f(2) = 4
f(3) = 6
...
f(factor) = current_speed * 100 / max_speed * factor / 100
running_cost_factor: 25; // = 25
or...
running_cost_factor: f(25); // = 0 when speed is 0, = 12 when speed is 50% of max speed, = 25 when speed is 100% of max speed
It's a function. A callback function.
Post Reply

Return to “NewGRF Technical Discussions”

Who is online

Users browsing this forum: No registered users and 8 guests