1 #include "decor.h"
2 #include "sound.h"
3 #include "tank.h"
4
5 #include <cassert>
6
7 /// @brief Default constructor
DECOR(double x_,double y_,double xv_,double yv_,int32_t maxRadius,int32_t type_,int32_t delay_)8 DECOR::DECOR(double x_, double y_, double xv_, double yv_,
9 int32_t maxRadius, int32_t type_, int32_t delay_) :
10 PHYSICAL_OBJECT(false),
11 curWind(global.wind),
12 delay(delay_),
13 maxGravAccel(-4. * env.gravity * env.FPS_mod),
14 maxWind(env.windstrength),
15 maxWindAccel(global.wind * env.FPS_mod),
16 radius(maxRadius),
17 type(type_)
18 {
19 x = x_;
20 y = y_;
21 xv = xv_;
22 yv = yv_;
23
24 if (DECOR_DIRT == type) {
25 // The core data is taken from the meteors.
26 weapType = SML_METEOR
27 + (maxRadius / 2); // results in (0, 1, 1, 2, 2) for radius [1;5]
28 mass = naturals[weapType - WEAPONS].mass;
29 drag = naturals[weapType - WEAPONS].drag / 5.;
30
31 // Special physics for dirt debris:
32 physType = PT_DIRTBOUNCE;
33
34 // Only keep dirt alive while it is really moving,
35 // if it becomes too slow, only keep it for 2 seconds
36 maxAge = 2 * env.frames_per_second;
37
38 // The diameter is just used so it does not have to
39 // be calculated each time updateDirt() is called.
40 diameter = radius * 2;
41
42 // Calculate how many pixels are needed per call to updateDirt()
43 grabPerCall = ((diameter + 1) * (diameter + 1)) / (delay > 1 ? delay : 1);
44 } else if (DECOR_SMOKE == type) {
45 int32_t tempCol = 128 + (rand () % 64);
46
47 if (maxRadius <= 3)
48 radius = 3;
49 else
50 radius = 3 + (rand () % (maxRadius - 2));
51
52 color = makecol (tempCol, tempCol, tempCol);
53 mass = 1.0 + (static_cast<double>(rand () % 5) / 10.);
54 drag = 0.9 + (static_cast<double>(rand () % 90) / 100.);
55
56 // maximum age depends on the maximum radius and the real radius,
57 // plus 0 to 2 extra seconds.
58 maxAge = ( (maxRadius - (maxRadius - radius)) / 3) + (rand() % 3);
59 maxAge *= env.frames_per_second;
60
61 // Special physics for smoke, only for repulsion
62 physType = PT_SMOKE;
63
64 // Smoke does not need the dirt grabber
65 ready = true;
66 } else
67 destroy = true;
68
69 maxVel = env.maxVelocity * (1.20 + (mass / (.01 * MAX_POWER)));
70
71 // Add to the chain:
72 global.addObject(this);
73 }
74
75
76 /// @brief Constructor with bitmap
DECOR(double x_,double y_,double xv_,double yv_,int32_t maxRadius,int32_t type_,int32_t delay_,sDebrisItem * deb_item,sDebrisItem * met_item)77 DECOR::DECOR(double x_, double y_, double xv_, double yv_,
78 int32_t maxRadius, int32_t type_, int32_t delay_,
79 sDebrisItem* deb_item, sDebrisItem* met_item) :
80 DECOR(x_, y_, xv_, yv_, maxRadius, type_, delay_)
81 {
82 // Everything done in delegated ctor, only img to set
83 dirt = deb_item;
84 setBitmap(dirt ? dirt->bmp : nullptr);
85 // Note: It is safe to distribute dirt->bmp, because bitmap normally holds
86 // global graphics and must not be destroyed.
87 meteor = met_item;
88
89 if ( (nullptr == dirt) || !hasBitmap() )
90 // Can't work without...
91 destroy = true;
92 }
93
94
95 /// @brief default destructor
~DECOR()96 DECOR::~DECOR()
97 {
98 if (DECOR_DIRT == type) {
99 // Draw dirt on terrain and add land slide
100 rotate_sprite (global.terrain, dirt->bmp, x - radius, y - radius, itofix (angle));
101 global.addLandSlide(x - radius - 1, x + radius + 1, false);
102 }
103
104 if (dirt) {
105 global.free_debris_item(dirt);
106 dirt = nullptr;
107 }
108
109 if (meteor) {
110 global.free_debris_item(meteor);
111 meteor = nullptr;
112 }
113
114 // Update the last drawing area
115 int32_t calcRadius = radius;
116
117 if (DECOR_DIRT == type)
118 ++calcRadius;
119 else if (DECOR_SMOKE == type)
120 // The older, the larger...
121 calcRadius = static_cast<int32_t>(radius * (4.0 * age / maxAge));
122
123 setUpdateArea ( x - calcRadius - 1, y - calcRadius - 1,
124 (calcRadius * 2) + 2, (calcRadius * 2) + 2);
125 requireUpdate ();
126 this->update();
127
128 // Take out of the chain:
129 global.removeObject(this);
130 }
131
132
133 /// @brief let smoke drift and disperse with the wind
applyPhysics()134 void DECOR::applyPhysics ()
135 {
136 if (destroy)
137 return;
138
139 if (delay > 0) {
140 --delay;
141 return;
142 }
143
144 // for detecting bounces
145 double old_yv = yv;
146
147 if (DECOR_DIRT == type) {
148
149 // Check whether movement ended
150 double movement = FABSDISTANCE2(xv, yv, 0, 0);
151 bool on_floor = isOnFloor(); // Needed again below.
152
153 if ( on_floor
154 && ( (hitSomething && (movement < 0.8))
155 || (movement < 0.2) ) ) {
156
157 // It ended!
158
159 // fix y:
160 int32_t dirt_bottom = y + dirt->bmp->h;
161 if ( ( (y - radius) > MENUHEIGHT)
162 && ( dirt_bottom < env.screenHeight)
163 && (PINK != getpixel(global.terrain, x, dirt_bottom)) )
164 --y;
165
166 xv = 0.;
167 yv = 0.;
168 destroy = true;
169 requireUpdate();
170
171 } else {
172 hitSomething = false; // Enable checking.
173
174 // Now apply physics
175 repulseDecor();
176 PHYSICAL_OBJECT::applyPhysics();
177
178 // Be sure x/y values are sane (Can drift into walls
179 // on rare wind conditions.)
180 if (x < 2) x = 2;
181 if (x > (env.screenWidth - 2)) x = env.screenWidth - 2;
182 if (y > (env.screenHeight - 2)) y = env.screenHeight - 2;
183
184 // Maybe play a sound on bounce
185 if ( !global.skippingComputerPlay
186 && (old_yv > .5)
187 && (yv < -0.1) )
188 play_natural_sound(DIRT_FRAGMENT, x, radius * 32,
189 1200 - (radius * 50));
190 }
191
192 // raise age if movement is below 0.5
193 if ( ( on_floor || (FABSDISTANCE2(xv, yv, 0, 0) < .5) )
194 && (++age > maxAge) )
195 destroy = true;
196
197 } else if (DECOR_SMOKE == type) {
198 // Apply wind first
199 int32_t ageMod = ROUND(std::abs(curWind / (maxWind / 2.0))) + 1;
200
201 /* This produces: (with max wind = 8)
202 * wind = 0 : round(0 / (8 / 2)) + 1 = round(0 / 4) + 1 = 0 + 1 = 1 <-- normal aging
203 * wind = 1 : round(1 / (8 / 2)) + 1 = round(1 / 4) + 1 = 0 + 1 = 1 <-- normal aging
204 * wind = 4 : round(4 / (8 / 2)) + 1 = round(4 / 4) + 1 = 1 + 1 = 2 <-- raised aging
205 * wind = 6 : round(6 / (8 / 2)) + 1 = round(6 / 4) + 1 = 2 + 1 = 3 <-- fast aging
206 * wind = 8 : round(8 / (8 / 2)) + 1 = round(8 / 4) + 1 = 2 + 1 = 3 <-- fast aging
207 */
208 age += ageMod;
209
210 // Set further values
211 // Try to reach half distance to the maximum values per second
212 double xaccel = ((xv + maxWindAccel) / 2)
213 / static_cast<double>(env.frames_per_second);
214 double yaccel = ((yv + maxGravAccel) / 2)
215 / static_cast<double>(env.frames_per_second / 10.);
216
217 // Apply current acceleration
218 xv += xaccel;
219 yv += yaccel;
220
221 // Add repulsion:
222 repulseDecor();
223
224 // Be sure that neither xv outruns wind nor yv is
225 // higher than reverse gravity
226 if (std::abs(xv) > std::abs(curWind))
227 xv = curWind;
228 if (yv < maxGravAccel)
229 yv = maxGravAccel;
230
231 // Don't push through the floor
232 if ( (y + yv) >= env.screenHeight) {
233 yv *= -0.5;
234 xv *= 0.95;
235 }
236
237 // The faster the smoke is blown by the wind, the less it rises:
238 if ( (yv < -1.) && (std::abs(xv) > 1.) )
239 yv = (yv + (yv / std::abs(xv))) / 2.;
240 // and if the smoke is going down, halve yv
241 else if (yv > 0.)
242 yv /= 2.;
243
244 // Now the velocity can be applied.
245 x += xv;
246 y += yv;
247
248 // Destroy the smoke if it goes off-screen or is diffused
249 int32_t calcRadius = static_cast<int32_t>(radius * (4.0 * age / maxAge));
250
251 if ( (x < (1 - calcRadius))
252 || (x >= (env.screenWidth + calcRadius))
253 || (y < (MENUHEIGHT - calcRadius))
254 || (age > maxAge) )
255 destroy = true;
256 }
257 }
258
259
260 /// @brief draw decor according to current settings and type.
draw()261 void DECOR::draw()
262 {
263 if (!ready && !destroy) {
264 updateDirt();
265 if (ready) {
266 // finished! See whether there are enough pixels
267 if (gotPixels <= radius)
268 // nope.
269 destroy = true;
270 }
271 }
272
273 if (destroy || (delay > 0))
274 return;
275
276 int32_t calcRadius = radius;
277
278 if (DECOR_DIRT == type) {
279 // Rotate according to xv and yv
280 angle += yv + ((SIGNd(xv) * 5.) - xv);
281
282 // Be sure the angle is in order:
283 if (angle < 0) angle += 360;
284 if (angle > 360) angle -= 360;
285
286 // And draw it:
287 if (y > MENUHEIGHT) {
288 VIRTUAL_OBJECT::draw();
289 ++calcRadius;
290 }
291 } else if (DECOR_SMOKE == type) {
292 // The older, the larger...
293 calcRadius = static_cast<int32_t>(radius * (4.0 * age / maxAge));
294
295 drawing_mode (DRAW_MODE_TRANS, NULL, 0, 0);
296 set_trans_blender (0, 0, 0, 255 - (255 * age / maxAge));
297 circlefill (global.canvas, x, y, calcRadius, color);
298 }
299
300 drawing_mode (global.current_drawing_mode, NULL, 0, 0);
301
302 setUpdateArea ( x - calcRadius - 1, y - calcRadius - 1,
303 (calcRadius * 2) + 2, (calcRadius * 2) + 2);
304 requireUpdate ();
305 }
306
307
308 /// In case of too much decor for the machine, allow forced ageing
force_aging(int32_t frames)309 void DECOR::force_aging(int32_t frames)
310 {
311 age += frames;
312 if (age > maxAge)
313 destroy = true;
314 }
315
316
317 /// return true if a dirt debris item "lies" on the floor, or is squeezed in a
318 /// dirt slide.
isOnFloor()319 bool DECOR::isOnFloor()
320 {
321 int32_t scr_r = env.screenWidth - 2; // shortcut;
322 int32_t scr_b = env.screenHeight - 2; // ditto;
323
324 // If the debris is above the screen or directly on the floor,
325 // return at once:
326 if (y <= MENUHEIGHT)
327 return false;
328 if (y >= (scr_b - radius))
329 return true;
330
331 // Use safe values:
332 int32_t round_x = ROUND(x);
333 int32_t round_y = ROUND(y);
334
335 // sanitize x value:
336 if (round_x < 1) round_x = 1;
337 else if (round_x > scr_r) round_x = scr_r;
338
339 // rounded boundaries, clipped to the screen:
340 int32_t left = std::max(1, round_x - radius);
341 int32_t top = std::max(MENUHEIGHT, round_y - radius);
342 int32_t right = std::min(scr_r, round_x + radius);
343 int32_t bottom = std::min(scr_b, round_y + radius);
344
345 // Go from left to right and check whether the surface is above the bottom.
346 int32_t surf_hits = 0;
347 bool on_floor = false;
348 for (int32_t i = left; !on_floor && (i <= right); ++i) {
349 if (global.surface[i].load(ATOMIC_READ) <= bottom) {
350 // Actually this could mean that the debris is within
351 // a hole in a mountain that hasn't been slid down, yet.
352 bool in_dirt = false;
353
354 for (int32_t j = bottom; !in_dirt && (j >= top) ; --j) {
355 if (PINK != getpixel(global.terrain, i, j))
356 in_dirt = true;
357 }
358
359 if (in_dirt && (++surf_hits >= radius) )
360 on_floor = true;
361 }
362 }
363
364 return on_floor;
365 }
366
367
368 /// DIRT and Smoke (somewhat) can be repulsed, too
repulseDecor()369 void DECOR::repulseDecor()
370 {
371 TANK* lt = nullptr;
372 double xaccel = 0;
373 double yaccel = 0;
374
375 global.getHeadOfClass(CLASS_TANK, <);
376
377 while (lt) {
378 if (!lt->destroy) {
379
380 if (lt->repulse (x + xv, y + yv, &xaccel, &yaccel, physType)) {
381 xv += xaccel;
382 yv += yaccel;
383 }
384 }
385 lt->getNext(<);
386 }
387 }
388
389
390 /// Small scale dirt grabber
updateDirt()391 void DECOR::updateDirt()
392 {
393 int32_t togo = grabPerCall + 1;
394 double deb_rad = static_cast<double>(radius);
395
396 while (togo) {
397 double deb_dist = FABSDISTANCE2(static_cast<double>(grab_x),
398 static_cast<double>(grab_y),
399 deb_rad,
400 deb_rad);
401 if (deb_dist <= deb_rad) {
402 int32_t tcol = getpixel(dirt->bmp, grab_x, grab_y);
403
404 // If this is a meteor and the terrain had no pixel
405 // there, take one out of the rock instead.
406 if ( (PINK == tcol) && meteor)
407 tcol = getpixel(meteor->bmp, grab_x, grab_y);
408
409 // If this is valid, scorch the colour and put it back.
410 if (PINK != tcol) {
411 double deb_mod = deb_dist / deb_rad;
412 int32_t new_r = getr(tcol) / (1.25 + deb_mod);
413 int32_t new_g = getg(tcol) / (1.66 + deb_mod);
414 int32_t new_b = getb(tcol) / (1.33 + deb_mod);
415 putpixel(dirt->bmp, grab_x, grab_y, makecol(new_r, new_g, new_b));
416 ++gotPixels;
417 }
418 } // End of position in range
419
420 else
421 // If the position is not in range, erase the surplus pixel
422 putpixel(dirt->bmp, grab_x, grab_y, PINK);
423
424 // This point is done
425 --togo;
426
427 // Advance coordinates
428 if (++grab_x > diameter) {
429 grab_x = 0;
430 if (++grab_y > diameter) {
431 // end of work
432 togo = 0;
433 ready = true;
434 if (meteor) {
435 global.free_debris_item(meteor);
436 meteor = nullptr;
437 }
438 }
439 }
440 } // End of having pixels to grab
441 }
442