This is an in-depth description of callbacks for houses. 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., a house) previously defined. An example being the control of animation frames or the manipulation of accepted cargoes.
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 handed over eventually 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 house tile, according to the state of the corresponding animation.
Here's is a list of all available callbacks for houses:
| Callback | Description |
| CB_ACONTROL | Periodically start/stop the animation |
| CB_AFRAME | Decide next animation frame |
| CB_ASPEED | Decide animation speed |
| CB_ASTAGE | Change animation when construction state changes |
| CB_AUTOSLOPE | Allow/disallow autosloping |
| CB_BUILD | Decide whether the house can be built on a given tile |
| CB_CARGOAMOUNTS | Decide the cargos amounts accepted |
| CB_CARGOPROD | Custom cargo production |
| CB_CARGOTYPES | Decide the cargo types accepted |
| CB_COLOUR | Decide colour of building |
| CB_DESTRUCT | Trigger destruction of building |
| CB_FOUNDATION | Decide if default foundations need to be drawn |
| CB_PROTECT | Conditional protection |
| CB_WATCH | Watched cargo accepted |
CB_ACONTROL - periodically start/stop the animation
Called periodically, in a time interval specified in property function refresh(). Returns the number of the frame the animation should jump to, or one of the following special values:
| Value | Label | Meaning | 255 | A_STOP | stop animation in its current frame | 254 | A_START | start animation with its current frame | 253 | A_NOP | leave the animation in its current state (do nothing) |
Callback values have to be specified as usual by cbr(), or by using the helper function animcontrol().
CB_AFRAME - set next animation frame
Called in every animation frame, this callback returns the number of the next frame to display. Additionally, it can return these special values:
| Value | Label | Meaning | 255 | A_STOP | stop animation. The current frame stays on screen until the animation is restarted. | 254 | A_NOP | continue with next frame as usual. You can return this for stages where you don't want to do anything special. |
Callback values have to be specified as usual by cbr(), or by using the helper function animframe().
From TTDPatch 2.5 beta 2, you can ask for random bits. To enable this, set RANDOMBITS in property function flags().
If any of the above animation callbacks return a nonzero value in the high byte, it will be interpreted as a sound effect number, and the corresponding sound effect will be played on the house tile.
CB_ASPEED - set animation speed
Called to decide how long the current animation frame should last. The value of the delay should be given in the same way as for property function anim_speed(). Decreasing the return value speeds the animation up instantly. Increasing, on the other hand, doesn't slow it down instantly: the actual duration of the current frame will be somewhere between the old and the new delays. The new delay is applied correctly for later frames.
Note: This is one of the most time consuming callbacks as it is called for every animated tile every ~30 milliseconds. For better performance try to avoid using it where reasonable, e.g. try to set animation speed only by its property and put multiple identical looking animation frames after each other.
CB_ASTAGE - change animation when construction state changes
Called when the building changes its construction stage (during normal gameplay, that means exactly four times). Can return the same values as callback CB_ACONTROL. Function constructionstage() contains the new stage. You can return "255" for stages that don't have animated graphics to save resources.
CB_AUTOSLOPE - allow/disallow autosloping
With this callback, you can prevent the autoslope feature from altering the ground below a house tile. To allow altering the ground, return cbr(0) (or ALLOW). Returning anything other than zero will disallow land modifications.
def(50) callback( DISALLOW if(CB_AUTOSLOPE) // do not allow change of slope ref(49) else // graphics )
CB_BUILD - decide whether the house can be built on a given tile
This callback is used to determine if the given building type can be built on a position. It should return a nonzero value to allow building and zero to cancel it.
Since the building is not created yet, you can't use functions constructionstage() and age() (they'd return zero). You can use functions townzone(), terraintype(), townexpansion(), or the diverse numhouse_*() functions, as well as all town.related functions, though. Since OpenTTD r19744 you can also access the random bits via randomrel()/randomabs() or variable 5F.
Property timeframe(), climate(), and townzone() are checked even if the callback returns a nonzero value; they can cancel building as well.
For multi-tile buildings, this callback is always called for the north tile type, but the position you can access through functions constructionstage(), townzize() and terraintype() is not guaranteed to be the final position of the north tile. It is guaranteed, though, that if the building really gets built, one of its tiles will occupy that tile. So, for example, if you return zero for desert tiles, this neither means that your building won't occupy any desert tiles, nor that its north tile will be on a non-desert tile; it only means that at least one tile of the building will be on a non-desert tile, e.g. it can still be built on the very edge of a desert. This restriction was removed in OTTD r13489, the callback is now called for the final position of the north tile.
def(7) townpopulation( ALLOW if(>192) DISALLOW else ) def(8) townpopulation( ALLOW if(>448) DISALLOW else ) def(9) townpopulation( ALLOW if(>960) DISALLOW else ) def(10) house_idcount(INTOWN, ref(7) if(0) ref(8) if(1) ref(9) if(2 .. 3) DISALLOW else ) def(11) callback( ref(10) if(CB_BUILD) ref(6) else )
CB_CARGOAMOUNTS - decide the cargo amounts accepted
Called to decide what cargo amount the building tile can accept. The returned WORD should have the layout of 'Sgmp', where the letters mean the following:
| Nibble | Meaning |
| S | "1" if the building should accept food/fizzy drinks instead of goods |
| g | If S is "1", the food acceptance in 1/8 units, If S is "0", the goods acceptance in 1/8 units |
| m | mail acceptance in 1/8 units |
| p | passenger acceptance in 1/8 units |
So, for example, returning cbr(0x426) means "4/8 goods, 2/8 mail, 6/8 passenger", while returning cbr(0x1205) means "2/8 food, 5/8 passenger".
If property function cargotypes() and/or callback CB_CARGOTYPES is used, the meaning changes slightly. In this case, 'p' means the acceptance of the first cargo type given, 'm' means the same for the second cargo type, and 'g' is the same for the third cargo type, while 'S' must be zero.
CB_CARGOPROD - custom cargo production
This callback can be used to customize cargo production of house tiles. It is called every 256 ticks when the tile can produce cargo. Because a tile can produce many types of cargo, this callback is called in a loop until it returns 0x20FF (the loop count is limited to 256 to avoid endless loops). If the returned value is not 0x20FF, the high byte must be a cargo type and the low byte must be the amount to be distributed. You can return a cargo type more than once if needed, so you can distribute more than 255 units from it. During the callback, function iterations() returns the number of iterations happened so far and function randombits() contains a random value to help randomizing the production.
This is how the original passenger generation is done: In each periodic processing (i.e. every 256 ticks), a random value 0<=X<=255 is generated for each house tile. If X isn't smaller than the population of the tile, no passengers are generated. Otherwise, X/8+1 passengers are generated (rounded down). If there is a recession going on, the number of generated passengers is halved, but this division gets rounded up instead of down. Mail generation happens in a similar manner, but with a new random value, and checking against the mail generation multiplier instead of the population.
Uses 15 return bits.
From GRF version 7 and above, the interpretation of the high byte in the returned value changes: instead of a climate-dependent cargo slot number, you have to return a climate-independent cargo ID. If your newGRF has a cargo translation table, then this ID is the index in that table; otherwise, it's the cargo bit. The callback can only return a selection of the first 32 entries of the cargo translation table. Trying to produce a cargo not currently present is not an error, but will be ignored.
CB_CARGOTYPES - decide the cargo types accepted
Called when TTD needs to know what cargoes a building tile can accept. The bits of the result must be filled in the following way:
| Bit range | Meaning |
| 0 .. 4 | First cargo type |
| 5 .. 9 | Second cargo type |
| 10 .. 14 | Third cargo type |
You may want to use helper function customcargo() to assist in filling in the bits above.
If the callback fails, the according values from property function cargotypes() is used instead. This callback is most useful in combination with CB_CARGOAMOUNTS, so you can fully control the acceptance of your house, even allowing dynamic changes.
From GRF version 7 and above, the interpretation of the returned value changes: instead of climate-dependent cargo slot numbers, you have to return climate-independent cargo IDs. If your GRF has a cargo translation table, then this ID is the index in that table; otherwise, it's the cargo bit. The callback can only return a selection of the first 32 entries of the cargo translation table. Acceptance of cargoes not currently present will automatically be disabled.
CB_COLOUR - set colour of building
Called to decide what colour mapping the building should use. Should return the number of the colour mapping to be used, for example, 775 for blue->dark blue mapping.
This can be useful if you want something more complex than four random colours.
From 2.0.1 alpha 67, you can return a value of colour_2*16 + colour_1 plus bit 14 set, where colour_1 and colour_2 are two company colours. For example, a return value of "0x4000 + 16*7 + 3" uses colour_1=3=yellow and colour_2=7=dark green. If no two-company-colour maps have been installed, the colours from randmocolours() are used instead.
CB_DESTRUCT - trigger destruction of building
Called periodically, in a time interval specified by property function refresh(). If it returns a nonzero value, the building gets removed from the map as if the town decided to destroy it. You can use this for example to remove a historical building, since those cannot be normally removed by towns.
CB_FOUNDATION - decide if default foundations need to be drawn
This callback is called when TTD starts drawing the house tile on sloped land. It should return zero to disable the default slope graphics and any other value to enable them.
If you chose to disable the foundation, you can draw your own by specifying
def(0) spriteset( set(NOSPRITE, notransparency(2), xyz(0,0,0), dxdydz(16,16,7)) // foundation set(notransparency(1), xyoff(0,1)) // real groundsprite set(normal(0), xyz(0,0,0), dxdydz(16,16,16)) // building )
Please note that TTD doesn't have suitable foundation graphics for steep slopes.
CB_PROTECT - conditional protection
This callback is called when someone tries to remove the building from the map. You can use this callback to prevent the town or AI players from removing the building if certain conditions are met. You can return cbr(0) (or ALLOW) to allow the destruction, or cbr(1) (or DISALLOW) to disallow it, except for human players, who can always remove any building.
Constantly returning "1" by this callback achieves the same effect as turning on PROTECTED in property function flags(). OTOH, when this callback is enabled, PROTECTED is ignored. If the callback fails, the destruction is allowed.
For multi-tile buildings, the callback will be called for the type of the tile the player wants to destroy. Therefore, to get consistent behaviour, you must enable this callback for every tile and respond it the same way no matter which tile it is called for.
Since TTDPatch 2.6 r1705, you can use this callback to prevent building "town" industries (banks, water towers, etc.) over your house. Function removaltype() returns "0" for "normal" demolition and "1" when TTD wants to remove the house for the sake of a new industry.
CB_WATCH - watched cargo accepted
Called when a cargo type specified in property function cargowatch() is accepted by a house tile, or to be more specific, in a station that has the house tile in its acceptance area. It will be called for each tile of a multi-tile building whenever a tile with property cargowatch() set accepts cargo. This means if more than one tile has cargoes specified in cargowatch(), the callback can be called multiple times on the same tile in the same tick. The low WORD of the return value contains the offset of the trigger tile relative to the current tile; the low BYTE contains the X offset, the high BYTE the Y offset. (0 means it's the same tile, negative coordinates mean it's northward, positive southward.) With this, you can tell apart the callings within the same tick. The high WORD of contains random bits, the bits are the same for each tile of multi-tile buildings. The return values can be the same as for callback CB_ACONTROL.
Due to implementation details, up to 250 game ticks can pass between the actual acceptance and triggering this callback.
This callback isn't available if the station2 structure isn't present.