The m4nfo User manual and Technical Report

Industry callbacks

Using callbacks for industries

Introduction

This is an in-depth description of industry callbacks. More basic information about callbacks can be found here.

A 'callback' is a special action that TTDPatch 'calls' in order to modify various attributes of a feature (e.g., an industry) previously defined. Examples would be the modifications of the input or output cargo types, or the production change of an industry.

This is done by identifying the incidence and the possible type of a callback in the callback() function. Depending on the type of the callback, control is eventually handed over to a function which handles the callback, or lets it fail. In the latter case, the value previously set by the appropriate property function will be kept using.

In addition, all animation callbacks also allow to trigger sound effects by returning a sound-ID value in the high byte of the callback result. This could be achieved in m4nfo by shifting the sound-ID value 8 bits to the left and OR-ing it with the appropriate animation state value, like this: cbr(eval((<sound_ID> <<8) | <animstate>)).

Instead, one of m4nfo's helper functions could be used, supplying a second parameter (in addition to the callback result) which will be interpreted as a sound effect ID, like this: animcontrol(0, SND_HORN) if(CB_ACONTROL). The corresponding sound effect will then be played on the appropriate object tile, according to the state of the corresponding animation.

Here's a list of all available callbacks for industries:

Callback Description
CB_ARRIVE Production callback
CB_BUILD Determine whether the industry can be built
CB_CARGOSUBTEXT Cargo sub-type display
CB_COLOUR Decide industry colour
CB_EFFECTS Control special industry effects
CB_FUNDTEXT Additional text in industry fund window
CB_INCARGO/CB_OUTCARGO Decide input and output cargo types
CB_LOCATION Determine whether the industry can be built on given spot
CB_MONTHLYPROD Monthly random production change
CB_OPTOUT Opt out of accepting cargo
CB_PERIODIC Production callback
CB_RANDOMPROD Control random production changes
CB_TEXT Show additional text in industry window

Description

CB_BUILD - determine whether the industry can be built

Called when TTDPatch needs to know if a given industry type is available.

Return zero to make the industry type available, or any nonzero value to disable it. This callback is intended for limiting the industry type as a whole, so you can't access any industry-specific variables, just the global ones. A good example for the use of this callback is a nuclear power plant that isn't allowed to appear before 1970.

During this callback, function status() may return the following values:

Value Meaning
0TTD is generating a map and needs to know if your type can appear.
1TTD decided to build a new random industry during regular gameplay and needs to know if it can choose your type.
2The user tries to build/prospect for your industry via the new industry window. TTD needs to know if the player is allowed to do this.

CB_CARGOSUBTEXT - cargo sub-type display

This callback allows to display some text after the cargo name in the industry fund window and in industry windows. The return value must use the function grftext() to reference a previously declared text string set by defgrftext() in the same newGRF file. Returning 0xFF causes no text to be displayed.

During the callback, function cargosubtext() returns following values:

ValueLabelMeaning
0 INCARGO1Return subtext for first accepted cargo type
1 INCARGO2Return subtext for second accepted cargo type
2 INCARGO3Return subtext for third accepted cargo type
3 OUTCARGO1Return subtext for first produced cargo type
4 OUTCARGO2Return subtext for second produced cargo type

In addition, function cargosubtextdisplay() returns following values:

ValueLabelMeaning
0 FUNDWINDOWThe text is to be displayed in the industry fund window. The industry isn't built yet, so you can't access the industry performance functions
1 INDUSTRYWINDOWThe text is to be displayed in the window of the industry. You can use the industry performance functions here.
2 DIRWINDOWThe text is to be displayed in the industry directory window. You can use the industry performance functions here.

Since OpenTTD r17802, the contents of registers 0x100 .. 0x105 are copied onto the text reference stack.

CB_COLOUR - decide industry colour

This callback is called when the industry is being constructed, to override the selected colour() of the industry. This colour will be used for tile sprites that request recolouring, but don't supply a recolour sprite number. Since neither the industry nor its industry tiles are placed yet, almost none of the industry- or industrytile-specific variables are available, except colour() and owner().

Function owner() will return the number of the funder company (or 0x10 if the industry wasn't funded), and function colour() will return the number of the colour scheme randomly picked by TTD (a number between 0 and 15). If you don't want to change the colour picked by default, you can either make the callback fail or just supply the return value of colour() unchanged.

Other helpful functions to be used might be playerinfo() to get the company colour of the funder (if there's any), or a random function to select a random colour scheme from a given list, or of course decide on global variables like position or game year.

CB_EFFECTS - control special industry effects

This callback allows to control some aspects of the special effects enabled in property function flags(). Function effect() always contains the number of the special effect, derived from those being set in property function flags(). Currently only effects PLANTFIELDS and TREECUTTING are supported.

For effect PLANTFIELDS you should return zero to avoid planting fields and any other value to plant a field. The callback is called every 256 ticks for a given industry. Function randombits() returns 32 random bits that can be used to randomize the behaviour. Industry performance function prodcounter() can be used to make the plantings rarer, but please note that the low byte of its return value is always zero at this point. TTD's default behaviour is giving 1/8 chance to plant a new field every 256 ticks.

For effect TREECUTTING you should return zero to avoid cutting a tree and any other value to try cutting one. The callback is called every 256 ticks for a given industry. You don't get random values in this case, but you can still use function prodcounter(), but again the low byte of its return value is always zero here as well. TTD's default behaviour is trying to cut a tree every 512 ticks.

CB_FUNDTEXT - additional text in industry fund window

This callback allows to display extra information in the industry fund window. The return value must use the function grftext() to reference a previously declared text string set by defgrftext() in the same newGRF file. The text must begin with a colouring special character and should not be longer than three lines (automatic line breaks are provided, but you can use CRLF for a manual line break). Returning 0xFF causes no text to be displayed.

Since the industry isn't built yet, you can't access any industry variables during this callback.

Since TTDPatch r2354 and OpenTTD r20086, the contents of registers 0x100 .. 0x105 are copied onto the text reference stack.

CB_INCARGO/CB_OUTCARGO - decide input and output cargo types

These callbacks are called when the industry is built, and allow customizing the input and output cargo types dynamically. Both callbacks are called repeatedly, with the lowest byte of [variable 10] starting from zero and increasing after every call; you should return a cargo type each time, or 0xFF to terminate the list (a failed callback terminates the list, too). The same limitations apply as for callback CB_COLOUR: industry tiles aren't yet placed, and most industry variables are undefined. However, you can use random functions. The interpretation of the callback return value depends on two factors: the current newGRF version number and the presence of a cargo translation table:

GRF version Has CTT Interpretation
<= 6 Climate-dependent cargo slot number
>= 7NoCargo bit
>= 7YesIndex in the translation table

Although currently callback CB_INCARGO is called no more than three times, and callback CB_OUTCARGO no more than twice, this may change between versions/implementations, to allow more input/output types. To be safe, you should return 0xFF as the last element even when you use all three input types or both output types.

CB_LOCATION - determine whether the industry can be built on given spot

Called to decide if the industry can be built on a given spot. Since the industry isn't built yet, you can only use a subset of TTD's variables: you can access the data of the nearest town without any problems, as well as functions lclass(), height(), water(), terrain(), waterclass(), slope(), distindustry(), disttown(), townzone(), industrycount(), closest(), and an additional number of special functions:

VariableSizeMeaning
_position()WordCoordinates of the selected position
_layoutnum()ByteNumber of the selected layout, according to property 0A
_terrain()ByteGround type of the selected spot (see terrain() for details)
_townzone()ByteTown zone of the selected spot (see townzone() for details)
_disttown()ByteDistance between the closest town and the selected position.
_heightByteHeight of the lowest corner of the tile (between 0x00 and 0x80, one land height unit equals 8 units)
_distwater()WordDistance to the closest water tile if ONWATER in flags() is not set (i.e., built on land); distance to the closest empty dry land tile if ONWATER is set (built on water)
_disteuclid()WordSquare of the Euclidean distance between the closest town and the selected position
_randombits()Dword32 random bits (since r1816 and OpenTTD r11985)

Unless explicitly noted, distances are Manhattan distance, not Euclidean distance, ie. |x-x0|+|y-y0| instead of sqrt((x-x0)^2+(y-y0)^2).

Function _distwater() will always return values below 0x80 if property function flags() does not set ONWATER. If ONWATER is set, _distwater() will return a max value of 0x200 in the pathological case of an all-water map.

During this callback, function status() may return the following values:

Value Meaning
0TTD is generating a map.
1TTD decided to build a new random industry during regular gameplay.
2The user tries to build your industry via the new industry window.
3The user tries to prospect for your industry via the new industry window.

This callback must return a 15-bit value, which is interpreted as follows:

Value Meaning
0000-03ffIndustry can't be built, display the misc. GRF text Dxxx as error message
0400Industry can be built
0401Industry can't be built, display the default "site unsuitable" error message.
0402Industry can't be built, display the "...can only be built in rainforest areas" error message.
0403Industry can't be built, display the "...can only be built in desert areas" error message.

Since TTDPatch r1755, you can use the text reference stack for your error messages, similarly to callback CB_TEXT. The only difference is that only 4 registers are copied instead of 6 because error messages support 20 bytes of reference stack only, and two of those bytes may be used by TTD.

CB_MONTHLYPROD - monthly random production change

Works exactly the same way as callback CB_RANDOMPROD, except that it is called every month, allowing more frequent production changes.

CB_OPTOUT - opt out of accepting cargo

Using this callback, the industry can refuse accepting a cargo type even if it has been defined as one of its input cargo types. If there is another industry nearby that accepts this cargo type, that one will get it.

The lowest byte of var 18 contains the ID of the cargo delivered. If the newGRF has a cargo translation table installed, it will get the index from that table; otherwise, it will get the cargo bit associated with the cargo type. You must return zero if you don't want to accept the cargo, or 1 to accept it. Other return values are reserved for future use.

This callback should be used in conjunction with callback CB_CARGOTYPES for industry tiles, since the acceptance of the tiles and the acceptance of the industry itself should always agree. If you disable accepting a cargo via callback CB_OPTOUT, but forget to remove the acceptance from the tiles, the station will keep accepting the cargo and the player will keep getting the money, but the industry won't receive the cargo. On the other hand, if you remove acceptance from the tiles, but forget using callback CB_OPTOUT, the industry may still get the cargo. (For example, there may be another industry nearby whose tiles accept the cargo. This makes the station accept the cargo, and send it to the nearest industry, which happens to be yours, even though its tiles don't accept the cargo.)

CB_RANDOMPROD - Control random production changes

Called when TTD chooses the industry for a random production change. [Variable 18] Function randombits() returns 32 random bits that can be used to randomize the behaviour. The callback must return one of the following results:

ValueMeaning
0Do nothing
1Halve industry production. If production goes below the quarter of the default, the industry is closed instead, as if you returned 03.
2Double industry production if it hasn't reached eight times of the original yet.
3The industry announces imminent closure, and is physically removed from the map next month.
4Do the standard random production change as if this industry was a primary one.
5Divide production by 4
6Divide production by 8
7Divide production by 16
8Divide production by 32
9Multiply production by 4
10Multiply production by 8
11Multiply production by 16
12Multiply production by 32
13Decrement production by 1 (2.6 r2046, OTTD r11532)
14Increment production by 1 (2.6 r2046, OTTD r11532)
15Set production as returned in bits 16 .. 23 of register 0x100 (clamped to 4 .. 128) (2.6 r2068, OTTD r14561)

Since TTDPatch 2.5 beta 1, you can set bit 7 of this value to suppress the news message announcing the production change. Be careful with this, especially for closedown messages; most players wouldn't like those being suppressed.

For return values 5 .. 12, the double/halve news messages are displayed. You should probably replace the defaults with some text that doesn't explicitly say "double" or "halve", or use the message override feature (see below). If a divide operation brings the production below the quarter of the default, the industry is closed down instead. Multiply operations that would bring the production above 8 times of the original will only increase it to that value.

Since r1306, you can override the default news messages that appear after production changes. To do this, you must set bit 8 of the returned result, and put the textID into the low word of GRF register 0x100 (the high word of the register is ignored). The textID can be of a default TTD text or a DCxx text; it cannot be a D0xx text. If the given action generates a message, it will be the one you specified. It may, however, not generate messages at all; for example, operation 4 may decide not to change the production, and then your message won't be shown. There is one exception: operation 0 would never generate a message, but it still will when bit 8 is set. This is useful in conjunction with callback CB_MONTHLYPROD, to tell the player about things that don't change the production multiplier, but are still important enough to require a news message.

By using return values 13 .. 15 you can adjust production rates more smoothly. The production rate is a value between 4 and 128 (initial value 16). Returning 13 when the production is at 4 will close the industry. But returning 15 will never close the industry, instead the value is clamped to the allowed range. Note that OTTD before r15103 did not properly updated production rates provided by property function productionmultipliers().

CB_TEXT - Show additional text in industry window

This callback allows to display extra information in the industry window about the state of the industry. The return value should be the number of a D0xx text to be displayed. The text must begin with a colouring special character and should not be longer than one line. For example, you can use this callback to display the current production limit of a secondary industry.

Note that since r11987, OpenTTD allows resizing the window when this callback is enabled. It allows to specify a text containing more than one line.

Since TTDPatch 2.6 r1370, the contents of registers 0x100 .. 0x105 are copied onto the text reference stack. This allows to display dynamically calculated values in the text by using the according StringCodes. Please note, though, that the text handler will see it as an array of bytes, not as an array of DWORDs, so you will have to pack two words into a register when you use codes that read words from the stack. In the case of \7D (which gets a byte), you may have to pack more into a register, but it's probably easier to use \7C instead. Please also note that you can use \80 to display textIDs calculated on the fly, but DCxx textIDs won't work correctly. When you need to choose from your own texts dynamically, you must use D0xx IDs, and add 400h to them. (That is, use D400 to refer to D000, D401 for D001 etc.)