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 effectvehicle.cpp Implementation of everything generic to vehicles. */
11 #include "landscape.h"
12 #include "core/random_func.hpp"
13 #include "industry_map.h"
14 #include "vehicle_func.h"
15 #include "sound_func.h"
16 #include "animated_tile_func.h"
17 #include "effectvehicle_func.h"
18 #include "effectvehicle_base.h"
20 #include "safeguards.h"
24 * Increment the sprite unless it has reached the end of the animation.
25 * @param v Vehicle to increment sprite of.
26 * @param last Last sprite of animation.
27 * @return true if the sprite was incremented, false if the end was reached.
29 static bool IncrementSprite(EffectVehicle
*v
, SpriteID last
)
31 if (v
->sprite_cache
.sprite_seq
.seq
[0].sprite
!= last
) {
32 v
->sprite_cache
.sprite_seq
.seq
[0].sprite
++;
39 static void ChimneySmokeInit(EffectVehicle
*v
)
41 uint32_t r
= Random();
42 v
->sprite_cache
.sprite_seq
.Set(SPR_CHIMNEY_SMOKE_0
+ GB(r
, 0, 3));
43 v
->progress
= GB(r
, 16, 3);
46 static bool ChimneySmokeTick(EffectVehicle
*v
)
48 if (v
->progress
> 0) {
51 TileIndex tile
= TileVirtXY(v
->x_pos
, v
->y_pos
);
52 if (!IsTileType(tile
, MP_INDUSTRY
)) {
57 if (!IncrementSprite(v
, SPR_CHIMNEY_SMOKE_7
)) {
58 v
->sprite_cache
.sprite_seq
.Set(SPR_CHIMNEY_SMOKE_0
);
61 v
->UpdatePositionAndViewport();
67 static void SteamSmokeInit(EffectVehicle
*v
)
69 v
->sprite_cache
.sprite_seq
.Set(SPR_STEAM_SMOKE_0
);
73 static bool SteamSmokeTick(EffectVehicle
*v
)
79 if ((v
->progress
& 7) == 0) {
84 if ((v
->progress
& 0xF) == 4) {
85 if (!IncrementSprite(v
, SPR_STEAM_SMOKE_4
)) {
92 if (moved
) v
->UpdatePositionAndViewport();
97 static void DieselSmokeInit(EffectVehicle
*v
)
99 v
->sprite_cache
.sprite_seq
.Set(SPR_DIESEL_SMOKE_0
);
103 static bool DieselSmokeTick(EffectVehicle
*v
)
107 if ((v
->progress
& 3) == 0) {
109 v
->UpdatePositionAndViewport();
110 } else if ((v
->progress
& 7) == 1) {
111 if (!IncrementSprite(v
, SPR_DIESEL_SMOKE_5
)) {
115 v
->UpdatePositionAndViewport();
121 static void ElectricSparkInit(EffectVehicle
*v
)
123 v
->sprite_cache
.sprite_seq
.Set(SPR_ELECTRIC_SPARK_0
);
127 static bool ElectricSparkTick(EffectVehicle
*v
)
129 if (v
->progress
< 2) {
134 if (!IncrementSprite(v
, SPR_ELECTRIC_SPARK_5
)) {
138 v
->UpdatePositionAndViewport();
144 static void SmokeInit(EffectVehicle
*v
)
146 v
->sprite_cache
.sprite_seq
.Set(SPR_SMOKE_0
);
150 static bool SmokeTick(EffectVehicle
*v
)
156 if ((v
->progress
& 3) == 0) {
161 if ((v
->progress
& 0xF) == 4) {
162 if (!IncrementSprite(v
, SPR_SMOKE_4
)) {
169 if (moved
) v
->UpdatePositionAndViewport();
174 static void ExplosionLargeInit(EffectVehicle
*v
)
176 v
->sprite_cache
.sprite_seq
.Set(SPR_EXPLOSION_LARGE_0
);
180 static bool ExplosionLargeTick(EffectVehicle
*v
)
183 if ((v
->progress
& 3) == 0) {
184 if (!IncrementSprite(v
, SPR_EXPLOSION_LARGE_F
)) {
188 v
->UpdatePositionAndViewport();
194 static void BreakdownSmokeInit(EffectVehicle
*v
)
196 v
->sprite_cache
.sprite_seq
.Set(SPR_BREAKDOWN_SMOKE_0
);
200 static bool BreakdownSmokeTick(EffectVehicle
*v
)
203 if ((v
->progress
& 7) == 0) {
204 if (!IncrementSprite(v
, SPR_BREAKDOWN_SMOKE_3
)) {
205 v
->sprite_cache
.sprite_seq
.Set(SPR_BREAKDOWN_SMOKE_0
);
207 v
->UpdatePositionAndViewport();
210 v
->animation_state
--;
211 if (v
->animation_state
== 0) {
219 static void ExplosionSmallInit(EffectVehicle
*v
)
221 v
->sprite_cache
.sprite_seq
.Set(SPR_EXPLOSION_SMALL_0
);
225 static bool ExplosionSmallTick(EffectVehicle
*v
)
228 if ((v
->progress
& 3) == 0) {
229 if (!IncrementSprite(v
, SPR_EXPLOSION_SMALL_B
)) {
233 v
->UpdatePositionAndViewport();
239 static void BulldozerInit(EffectVehicle
*v
)
241 v
->sprite_cache
.sprite_seq
.Set(SPR_BULLDOZER_NE
);
243 v
->animation_state
= 0;
244 v
->animation_substate
= 0;
247 struct BulldozerMovement
{
253 static const BulldozerMovement _bulldozer_movement
[] = {
276 static const struct {
286 static bool BulldozerTick(EffectVehicle
*v
)
289 if ((v
->progress
& 7) == 0) {
290 const BulldozerMovement
*b
= &_bulldozer_movement
[v
->animation_state
];
292 v
->sprite_cache
.sprite_seq
.Set(SPR_BULLDOZER_NE
+ b
->image
);
294 v
->x_pos
+= _inc_by_dir
[b
->direction
].x
;
295 v
->y_pos
+= _inc_by_dir
[b
->direction
].y
;
297 v
->animation_substate
++;
298 if (v
->animation_substate
>= b
->duration
) {
299 v
->animation_substate
= 0;
300 v
->animation_state
++;
301 if (v
->animation_state
== lengthof(_bulldozer_movement
)) {
306 v
->UpdatePositionAndViewport();
312 static void BubbleInit(EffectVehicle
*v
)
314 v
->sprite_cache
.sprite_seq
.Set(SPR_BUBBLE_GENERATE_0
);
319 struct BubbleMovement
{
326 #define MK(x, y, z, i) { x, y, z, i }
327 #define ME(i) { i, 4, 0, 0 }
329 static const BubbleMovement _bubble_float_sw
[] = {
338 static const BubbleMovement _bubble_float_ne
[] = {
346 static const BubbleMovement _bubble_float_se
[] = {
354 static const BubbleMovement _bubble_float_nw
[] = {
362 static const BubbleMovement _bubble_burst
[] = {
370 static const BubbleMovement _bubble_absorb
[] = {
460 static const BubbleMovement
* const _bubble_movement
[] = {
469 static bool BubbleTick(EffectVehicle
*v
)
474 if ((v
->progress
& 3) != 0) return true;
476 if (v
->spritenum
== 0) {
477 v
->sprite_cache
.sprite_seq
.seq
[0].sprite
++;
478 if (v
->sprite_cache
.sprite_seq
.seq
[0].sprite
< SPR_BUBBLE_GENERATE_3
) {
479 v
->UpdatePositionAndViewport();
482 if (v
->animation_substate
!= 0) {
483 v
->spritenum
= GB(Random(), 0, 2) + 1;
489 anim_state
= v
->animation_state
+ 1;
492 const BubbleMovement
*b
= &_bubble_movement
[v
->spritenum
- 1][anim_state
];
494 if (b
->y
== 4 && b
->x
== 0) {
499 if (b
->y
== 4 && b
->x
== 1) {
500 if (v
->z_pos
> 180 || Chance16I(1, 96, Random())) {
502 if (_settings_client
.sound
.ambient
) SndPlayVehicleFx(SND_2F_BUBBLE_GENERATOR_FAIL
, v
);
507 if (b
->y
== 4 && b
->x
== 2) {
511 if (_settings_client
.sound
.ambient
) SndPlayVehicleFx(SND_31_BUBBLE_GENERATOR_SUCCESS
, v
);
513 tile
= TileVirtXY(v
->x_pos
, v
->y_pos
);
514 if (IsTileType(tile
, MP_INDUSTRY
) && GetIndustryGfx(tile
) == GFX_BUBBLE_CATCHER
) AddAnimatedTile(tile
);
517 v
->animation_state
= anim_state
;
518 b
= &_bubble_movement
[v
->spritenum
- 1][anim_state
];
523 v
->sprite_cache
.sprite_seq
.Set(SPR_BUBBLE_0
+ b
->image
);
525 v
->UpdatePositionAndViewport();
531 using InitProc
= void(EffectVehicle
*);
532 using TickProc
= bool(EffectVehicle
*);
534 InitProc
*init_proc
; ///< Function to initialise an effect vehicle after construction.
535 TickProc
*tick_proc
; ///< Functions for controlling effect vehicles at each tick.
536 TransparencyOption transparency
; ///< Transparency option affecting the effect.
538 constexpr EffectProcs(InitProc
*init_proc
, TickProc
*tick_proc
, TransparencyOption transparency
)
539 : init_proc(init_proc
), tick_proc(tick_proc
), transparency(transparency
) {}
542 /** Per-EffectVehicleType handling. */
543 static std::array
<EffectProcs
, EV_END
> _effect_procs
= {{
544 { ChimneySmokeInit
, ChimneySmokeTick
, TO_INDUSTRIES
}, // EV_CHIMNEY_SMOKE
545 { SteamSmokeInit
, SteamSmokeTick
, TO_INVALID
}, // EV_STEAM_SMOKE
546 { DieselSmokeInit
, DieselSmokeTick
, TO_INVALID
}, // EV_DIESEL_SMOKE
547 { ElectricSparkInit
, ElectricSparkTick
, TO_INVALID
}, // EV_ELECTRIC_SPARK
548 { SmokeInit
, SmokeTick
, TO_INVALID
}, // EV_CRASH_SMOKE
549 { ExplosionLargeInit
, ExplosionLargeTick
, TO_INVALID
}, // EV_EXPLOSION_LARGE
550 { BreakdownSmokeInit
, BreakdownSmokeTick
, TO_INVALID
}, // EV_BREAKDOWN_SMOKE
551 { ExplosionSmallInit
, ExplosionSmallTick
, TO_INVALID
}, // EV_EXPLOSION_SMALL
552 { BulldozerInit
, BulldozerTick
, TO_INVALID
}, // EV_BULLDOZER
553 { BubbleInit
, BubbleTick
, TO_INDUSTRIES
}, // EV_BUBBLE
554 { SmokeInit
, SmokeTick
, TO_INVALID
}, // EV_BREAKDOWN_SMOKE_AIRCRAFT
555 { SmokeInit
, SmokeTick
, TO_INDUSTRIES
}, // EV_COPPER_MINE_SMOKE
559 * Create an effect vehicle at a particular location.
560 * @param x The x location on the map.
561 * @param y The y location on the map.
562 * @param z The z location on the map.
563 * @param type The type of effect vehicle.
564 * @return The effect vehicle.
566 EffectVehicle
*CreateEffectVehicle(int x
, int y
, int z
, EffectVehicleType type
)
568 if (!Vehicle::CanAllocateItem()) return nullptr;
570 EffectVehicle
*v
= new EffectVehicle();
577 v
->vehstatus
= VS_UNCLICKABLE
;
579 _effect_procs
[type
].init_proc(v
);
581 v
->UpdatePositionAndViewport();
587 * Create an effect vehicle above a particular location.
588 * @param x The x location on the map.
589 * @param y The y location on the map.
590 * @param z The offset from the ground.
591 * @param type The type of effect vehicle.
592 * @return The effect vehicle.
594 EffectVehicle
*CreateEffectVehicleAbove(int x
, int y
, int z
, EffectVehicleType type
)
596 int safe_x
= Clamp(x
, 0, Map::MaxX() * TILE_SIZE
);
597 int safe_y
= Clamp(y
, 0, Map::MaxY() * TILE_SIZE
);
598 return CreateEffectVehicle(x
, y
, GetSlopePixelZ(safe_x
, safe_y
) + z
, type
);
602 * Create an effect vehicle above a particular vehicle.
603 * @param v The vehicle to base the position on.
604 * @param x The x offset to the vehicle.
605 * @param y The y offset to the vehicle.
606 * @param z The z offset to the vehicle.
607 * @param type The type of effect vehicle.
608 * @return The effect vehicle.
610 EffectVehicle
*CreateEffectVehicleRel(const Vehicle
*v
, int x
, int y
, int z
, EffectVehicleType type
)
612 return CreateEffectVehicle(v
->x_pos
+ x
, v
->y_pos
+ y
, v
->z_pos
+ z
, type
);
615 bool EffectVehicle::Tick()
617 return _effect_procs
[this->subtype
].tick_proc(this);
620 void EffectVehicle::UpdateDeltaXY()
630 * Determines the transparency option affecting the effect.
631 * @return Transparency option, or TO_INVALID if none.
633 TransparencyOption
EffectVehicle::GetTransparencyOption() const
635 return _effect_procs
[this->subtype
].transparency
;