2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file newgrf_animation_base.h Function implementations related to NewGRF animation. */
10 /* No inclusion guards as this file must only be included from .cpp files. */
12 #include "animated_tile_func.h"
13 #include "core/random_func.hpp"
14 #include "timer/timer_game_tick.h"
15 #include "viewport_func.h"
16 #include "newgrf_animation_type.h"
17 #include "newgrf_callbacks.h"
20 template <typename Tobj
>
21 struct TileAnimationFrameAnimationHelper
{
22 static uint8_t Get(Tobj
*, TileIndex tile
) { return GetAnimationFrame(tile
); }
23 static bool Set(Tobj
*, TileIndex tile
, uint8_t frame
)
25 uint8_t prev_frame
= GetAnimationFrame(tile
);
26 if (prev_frame
== frame
) return false;
28 SetAnimationFrame(tile
, frame
);
34 * Helper class for a unified approach to NewGRF animation.
35 * @tparam Tbase Instantiation of this class.
36 * @tparam Tspec NewGRF specification related to the animated tile.
37 * @tparam Tobj Object related to the animated tile.
38 * @tparam Textra Custom extra callback data.
39 * @tparam GetCallback The callback function pointer.
40 * @tparam Tframehelper The animation frame get/set helper.
42 template <typename Tbase
, typename Tspec
, typename Tobj
, typename Textra
, uint16_t (*GetCallback
)(CallbackID callback
, uint32_t param1
, uint32_t param2
, const Tspec
*statspec
, Tobj
*st
, TileIndex tile
, Textra extra_data
), typename Tframehelper
>
43 struct AnimationBase
{
45 * Animate a single tile.
46 * @param spec Specification related to the tile.
47 * @param obj Object related to the tile.
48 * @param tile Tile to animate changes for.
49 * @param random_animation Whether to pass random bits to the "next frame" callback.
50 * @param extra_data Custom extra callback data.
52 static void AnimateTile(const Tspec
*spec
, Tobj
*obj
, TileIndex tile
, bool random_animation
, Textra extra_data
= 0)
54 assert(spec
!= nullptr);
56 /* Acquire the animation speed from the NewGRF. */
57 uint8_t animation_speed
= spec
->animation
.speed
;
58 if (HasBit(spec
->callback_mask
, Tbase::cbm_animation_speed
)) {
59 uint16_t callback
= GetCallback(Tbase::cb_animation_speed
, 0, 0, spec
, obj
, tile
, extra_data
);
60 if (callback
!= CALLBACK_FAILED
) {
61 if (callback
>= 0x100 && spec
->grf_prop
.grffile
->grf_version
>= 8) ErrorUnknownCallbackResult(spec
->grf_prop
.grffile
->grfid
, Tbase::cb_animation_speed
, callback
);
62 animation_speed
= Clamp(callback
& 0xFF, 0, 16);
66 /* An animation speed of 2 means the animation frame changes 4 ticks, and
67 * increasing this value by one doubles the wait. 0 is the minimum value
68 * allowed for animation_speed, which corresponds to 30ms, and 16 is the
69 * maximum, corresponding to around 33 minutes. */
70 if (TimerGameTick::counter
% (1ULL << animation_speed
) != 0) return;
72 uint8_t frame
= Tframehelper::Get(obj
, tile
);
73 uint8_t num_frames
= spec
->animation
.frames
;
75 bool frame_set_by_callback
= false;
77 if (HasBit(spec
->callback_mask
, Tbase::cbm_animation_next_frame
)) {
78 uint16_t callback
= GetCallback(Tbase::cb_animation_next_frame
, random_animation
? Random() : 0, 0, spec
, obj
, tile
, extra_data
);
80 if (callback
!= CALLBACK_FAILED
) {
81 frame_set_by_callback
= true;
83 switch (callback
& 0xFF) {
85 DeleteAnimatedTile(tile
);
89 frame_set_by_callback
= false;
93 frame
= callback
& 0xFF;
97 /* If the lower 7 bits of the upper byte of the callback
98 * result are not empty, it is a sound effect. */
99 if (GB(callback
, 8, 7) != 0 && _settings_client
.sound
.ambient
) PlayTileSound(spec
->grf_prop
.grffile
, GB(callback
, 8, 7), tile
);
103 if (!frame_set_by_callback
) {
104 if (frame
< num_frames
) {
106 } else if (frame
== num_frames
&& spec
->animation
.status
== ANIM_STATUS_LOOPING
) {
107 /* This animation loops, so start again from the beginning */
110 /* This animation doesn't loop, so stay here */
111 DeleteAnimatedTile(tile
);
115 bool changed
= Tframehelper::Set(obj
, tile
, frame
);
116 if (changed
) MarkTileDirtyByTile(tile
);
120 * Check a callback to determine what the next animation step is and
121 * execute that step. This includes stopping and starting animations
122 * as well as updating animation frames and playing sounds.
123 * @param cb The callback to actually call.
124 * @param spec Specification related to the tile.
125 * @param obj Object related to the tile.
126 * @param tile Tile to consider animation changes for.
127 * @param random_bits Random bits for this update. To be passed as parameter to the NewGRF.
128 * @param trigger What triggered this update? To be passed as parameter to the NewGRF.
129 * @param extra_data Custom extra data for callback processing.
131 static void ChangeAnimationFrame(CallbackID cb
, const Tspec
*spec
, Tobj
*obj
, TileIndex tile
, uint32_t random_bits
, uint32_t trigger
, Textra extra_data
= 0)
133 uint16_t callback
= GetCallback(cb
, random_bits
, trigger
, spec
, obj
, tile
, extra_data
);
134 if (callback
== CALLBACK_FAILED
) return;
136 switch (callback
& 0xFF) {
137 case 0xFD: /* Do nothing. */ break;
138 case 0xFE: AddAnimatedTile(tile
, false); break;
139 case 0xFF: DeleteAnimatedTile(tile
); break;
141 bool changed
= Tframehelper::Set(obj
, tile
, callback
);
142 AddAnimatedTile(tile
, changed
);
146 /* If the lower 7 bits of the upper byte of the callback
147 * result are not empty, it is a sound effect. */
148 if (GB(callback
, 8, 7) != 0 && _settings_client
.sound
.ambient
) PlayTileSound(spec
->grf_prop
.grffile
, GB(callback
, 8, 7), tile
);