1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 #include "Particle.h"
10 
11 #include "../audio/audio.h"
12 #include "../paint/sprite/Paint.Sprite.h"
13 #include "../scenario/Scenario.h"
14 #include "Sprite.h"
15 
16 #include <iterator>
17 
Is() const18 template<> bool EntityBase::Is<VehicleCrashParticle>() const
19 {
20     return Type == EntityType::CrashedVehicleParticle;
21 }
22 
Is() const23 template<> bool EntityBase::Is<CrashSplashParticle>() const
24 {
25     return Type == EntityType::CrashSplash;
26 }
27 /**
28  *
29  *  rct2: 0x006735A1
30  */
Create(rct_vehicle_colour colours,const CoordsXYZ & vehiclePos)31 void VehicleCrashParticle::Create(rct_vehicle_colour colours, const CoordsXYZ& vehiclePos)
32 {
33     VehicleCrashParticle* sprite = CreateEntity<VehicleCrashParticle>();
34     if (sprite != nullptr)
35     {
36         sprite->colour[0] = colours.body_colour;
37         sprite->colour[1] = colours.trim_colour;
38         sprite->sprite_width = 8;
39         sprite->sprite_height_negative = 8;
40         sprite->sprite_height_positive = 8;
41         sprite->MoveTo(vehiclePos);
42 
43         sprite->frame = (scenario_rand() & 0xFF) * 12;
44         sprite->time_to_live = (scenario_rand() & 0x7F) + 140;
45         sprite->crashed_sprite_base = scenario_rand_max(static_cast<uint32_t>(std::size(vehicle_particle_base_sprites)));
46         sprite->acceleration_x = (static_cast<int16_t>(scenario_rand() & 0xFFFF)) * 4;
47         sprite->acceleration_y = (static_cast<int16_t>(scenario_rand() & 0xFFFF)) * 4;
48         sprite->acceleration_z = (scenario_rand() & 0xFFFF) * 4 + 0x10000;
49         sprite->velocity_x = 0;
50         sprite->velocity_y = 0;
51         sprite->velocity_z = 0;
52     }
53 }
54 
55 /**
56  *
57  *  rct2: 0x00673298
58  */
Update()59 void VehicleCrashParticle::Update()
60 {
61     Invalidate();
62     time_to_live--;
63     if (time_to_live == 0)
64     {
65         sprite_remove(this);
66         return;
67     }
68 
69     // Apply gravity
70     acceleration_z -= 5041;
71 
72     // Apply air resistance
73     acceleration_x -= (acceleration_x / 256);
74     acceleration_y -= (acceleration_y / 256);
75     acceleration_z -= (acceleration_z / 256);
76 
77     // Update velocity and position
78     int32_t vx = velocity_x + acceleration_x;
79     int32_t vy = velocity_y + acceleration_y;
80     int32_t vz = velocity_z + acceleration_z;
81 
82     CoordsXYZ newLoc = { x + (vx >> 16), y + (vy >> 16), z + (vz >> 16) };
83 
84     velocity_x = vx & 0xFFFF;
85     velocity_y = vy & 0xFFFF;
86     velocity_z = vz & 0xFFFF;
87 
88     // Check collision with land / water
89     int16_t landZ = tile_element_height(newLoc);
90     int16_t waterZ = tile_element_water_height(newLoc);
91 
92     if (waterZ != 0 && z >= waterZ && newLoc.z <= waterZ)
93     {
94         // Splash
95         OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Water2, { x, y, waterZ });
96         CrashSplashParticle::Create({ x, y, waterZ });
97         sprite_remove(this);
98         return;
99     }
100 
101     if (z >= landZ && newLoc.z <= landZ)
102     {
103         // Bounce
104         acceleration_z *= -1;
105         newLoc.z = landZ;
106     }
107     MoveTo(newLoc);
108 
109     frame += 85;
110     if (frame >= 3072)
111     {
112         frame = 0;
113     }
114 }
115 
116 /**
117  *
118  *  rct2: 0x00673699
119  */
Create(const CoordsXYZ & splashPos)120 void CrashSplashParticle::Create(const CoordsXYZ& splashPos)
121 {
122     auto* sprite = CreateEntity<CrashSplashParticle>();
123     if (sprite != nullptr)
124     {
125         sprite->sprite_width = 33;
126         sprite->sprite_height_negative = 51;
127         sprite->sprite_height_positive = 16;
128         sprite->MoveTo(splashPos + CoordsXYZ{ 0, 0, 3 });
129         sprite->frame = 0;
130     }
131 }
132 
133 /**
134  *
135  *  rct2: 0x0067339D
136  */
Update()137 void CrashSplashParticle::Update()
138 {
139     Invalidate();
140     frame += 85;
141     if (frame >= 7168)
142     {
143         sprite_remove(this);
144     }
145 }
146 
147 /**
148  *
149  *  rct2: 0x006734B2
150  */
Create(const CoordsXYZ & coords)151 void SteamParticle::Create(const CoordsXYZ& coords)
152 {
153     auto surfaceElement = map_get_surface_element_at(coords);
154     if (surfaceElement != nullptr && coords.z > surfaceElement->GetBaseZ())
155     {
156         SteamParticle* steam = CreateEntity<SteamParticle>();
157         if (steam == nullptr)
158             return;
159 
160         steam->sprite_width = 20;
161         steam->sprite_height_negative = 18;
162         steam->sprite_height_positive = 16;
163         steam->frame = 256;
164         steam->time_to_move = 0;
165         steam->MoveTo(coords);
166     }
167 }
168 
169 /**
170  *
171  *  rct2: 0x00673200
172  */
Update()173 void SteamParticle::Update()
174 {
175     // Move up 1 z every 3 ticks (Starts after 4 ticks)
176     Invalidate();
177     time_to_move++;
178     if (time_to_move >= 4)
179     {
180         time_to_move = 1;
181         MoveTo({ x, y, z + 1 });
182     }
183     frame += 64;
184     if (frame >= (56 * 64))
185     {
186         sprite_remove(this);
187     }
188 }
189 
190 /**
191  *
192  *  rct2: 0x0067363D
193  */
Create(const CoordsXYZ & cloudPos)194 void ExplosionCloud::Create(const CoordsXYZ& cloudPos)
195 {
196     auto* entity = CreateEntity<ExplosionCloud>();
197     if (entity != nullptr)
198     {
199         entity->sprite_width = 44;
200         entity->sprite_height_negative = 32;
201         entity->sprite_height_positive = 34;
202         entity->MoveTo(cloudPos + CoordsXYZ{ 0, 0, 4 });
203         entity->frame = 0;
204     }
205 }
206 
207 /**
208  *
209  *  rct2: 0x00673385
210  */
Update()211 void ExplosionCloud::Update()
212 {
213     Invalidate();
214     frame += 128;
215     if (frame >= (36 * 128))
216     {
217         sprite_remove(this);
218     }
219 }
220 
221 /**
222  *
223  *  rct2: 0x0067366B
224  */
Create(const CoordsXYZ & flarePos)225 void ExplosionFlare::Create(const CoordsXYZ& flarePos)
226 {
227     auto* entity = CreateEntity<ExplosionFlare>();
228     if (entity != nullptr)
229     {
230         entity->sprite_width = 25;
231         entity->sprite_height_negative = 85;
232         entity->sprite_height_positive = 8;
233         entity->MoveTo(flarePos + CoordsXYZ{ 0, 0, 4 });
234         entity->frame = 0;
235     }
236 }
237 
238 /**
239  *
240  *  rct2: 0x006733B4
241  */
Update()242 void ExplosionFlare::Update()
243 {
244     Invalidate();
245     frame += 64;
246     if (frame >= (124 * 64))
247     {
248         sprite_remove(this);
249     }
250 }
251