lsnes rr2-β24
[lsnes.git] / src / emulation / sky / physics.cpp
blob6f6acaf401f8e1a6449114f6c6595a2a83ac825f
1 #include "physics.hpp"
2 #include "sound.hpp"
3 #include "util.hpp"
4 #include <cstdlib>
6 namespace sky
8 noise_maker::~noise_maker()
12 void physics::force_flag(uint8_t b, bool s)
14 if(s)
15 flags |= b;
16 else
17 flags &= ~b;
20 void physics::set_flag(uint8_t b)
22 flags |= b;
25 void physics::clear_flag(uint8_t b)
27 flags &= ~b;
30 bool physics::is_masked(uint8_t b, uint8_t c)
32 return ((flags & b) == c);
35 bool physics::is_set(uint8_t b)
37 return ((flags & b) == b);
40 bool physics::is_any_of(uint8_t b)
42 return ((flags & b) != 0);
45 bool physics::is_clear(uint8_t b)
47 return ((flags & b) == 0);
50 void physics::adjust_speed(level& stage, int adjust) throw()
52 lspeed += adjust;
53 if(lspeed < 0)
54 lspeed = 0;
55 if(lspeed > 10922)
56 lspeed = 10922;
58 void physics::die(level& stage, uint8_t cause)
60 if(death)
61 return;
62 death = cause;
63 deathframe = framecounter;
65 void physics::explode(level& stage, noise_maker& noise)
67 if(expframe)
68 return;
69 expframe = 1;
70 noise(sound_explode);
72 void physics::apply_floor_effects(level& stage, noise_maker& noise, unsigned floor)
74 if(is_clear(flag_landed)) {
75 clear_flag(flag_sticky);
76 return;
78 switch(floor) {
79 case tile::sticky:
80 if(!expframe)
81 adjust_speed(stage, -303);
82 break;
83 case tile::suppiles:
84 if(death)
85 break;
86 if(o2_left < 27000 || fuel_left < 27000)
87 noise(sound_suppiles);
88 o2_left = 30000;
89 fuel_left = 30000;
90 break;
91 case tile::boost:
92 if(!expframe)
93 adjust_speed(stage, 303);
94 break;
95 case tile::burning:
96 die(stage, death_burning);
97 explode(stage, noise);
98 break;
100 force_flag(flag_slippery, floor == tile::slippery);
101 force_flag(flag_sticky, floor == tile::sticky);
103 void physics::check_exit(level& stage) throw()
105 if(lpos < stage.finish_line())
106 return;
107 if(!stage.in_pipe(lpos, hpos, vpos))
108 return;
109 //Die preserves death reason if one exists.
110 die(stage, death_finished);
112 void physics::use_suppiles(level& stage) throw()
114 if(death)
115 return;
116 o2_left -= o2_factor;
117 if(o2_left > 30000)
118 o2_left = 0;
119 fuel_left -= (fuel_factor * lspeed) / 65536;
120 if(fuel_left > 30000)
121 fuel_left = 0;
123 void physics::check_death(level& stage) throw()
125 if(!death) {
126 if(vpos < 10240)
127 die(stage, death_drifting);
128 else if(fuel_left == 0)
129 die(stage, death_fuel);
130 else if(o2_left == 0)
131 die(stage, death_o2);
132 } else
133 postdeath++;
134 if(expframe)
135 expframe++;
137 void physics::apply_steering(level& stage, int lr, int ad, bool jump) throw()
139 if(death)
140 return;
141 adjust_speed(stage, 75 * ad);
142 if(is_clear(flag_slippery)) {
143 if(is_any_of(flag_blank | flag_jumping)) {
144 if(hspeed == 0 && vspeed > 0 && vpos - jump_ground < 3840)
145 hspeed = 29 * lr;
146 } else
147 hspeed = 29 * lr;
149 if(jump && is_clear(flag_blank | flag_jumping | flag_no_jump)) {
150 vspeed = 1152;
151 set_flag(flag_jumping);
152 jump_ground = vpos;
155 void physics::apply_gravity(level& stage) throw()
157 if(!expframe) {
158 if(vpos >= 10240)
159 vspeed = vspeed + gravity_accel;
160 else if(vspeed > -106)
161 vspeed = -106;
162 } else {
163 if(vspeed < 0)
164 vspeed = 0;
165 else if(vspeed < 71)
166 vspeed += 39;
167 else
168 vspeed = 71;
171 void physics::project_position(level& stage) throw()
173 lprojected = lpos + lspeed;
174 hprojected = hpos + (hspeed * (lspeed + (is_set(flag_sticky) ? 0 : 1560))) / 512 + hdrift;
175 vprojected = vpos + vspeed;
176 //Don't wrap around horizontally.
177 if((hpos < 12160 && hprojected >= 53376) && (hpos >= 53376 && hprojected < 12160))
178 hprojected = hpos;
180 uint8_t physics::get_death(level& stage) throw()
182 if(!death)
183 return 0; //Still alive.
184 else if(death == death_finished) {
185 framecounter++;
186 lpos += lspeed;
187 return (++postdeath > 72) ? death : 0;
188 } else if(death == death_collided || death == death_burning)
189 return (expframe > 42) ? death : 0;
190 else if(death == death_drifting)
191 return (expframe > 42 || postdeath > 108) ? death : 0;
192 else
193 return (postdeath > 108) ? death : 0;
195 void physics::check_scratching(level& stage) throw()
197 if(hprojected == hpos)
198 return;
199 hspeed = 0;
200 if(hdrift < 0 && hpos > hprojected)
201 hdrift = 0;
202 if(hdrift > 0 && hpos < hprojected)
203 hdrift = 0;
204 adjust_speed(stage, -151);
206 void physics::check_collisions(level& stage, noise_maker& noise) throw()
208 if(lprojected == lpos)
209 return;
210 if(lspeed >= 3640) {
211 die(stage, death_collided);
212 explode(stage, noise);
213 } else {
214 if(lpos > lprojected - lspeed)
215 noise(sound_blow);
217 lspeed = 0;
219 void physics::try_locking(level& stage, int lr, int ad, bool jump) throw()
221 if(vpos < 14080 || !is_masked(flag_tried_lock | flag_jumping, flag_jumping))
222 return;
223 set_flag(flag_tried_lock);
224 if(!dangerous_jump(stage, ad, lpos, hpos, vpos, lspeed, hspeed, vspeed, hdrift))
225 return;
226 for(int32_t a = 1; a < 7; a++) {
227 if(!dangerous_jump(stage, ad, lpos, hpos, vpos, lspeed, hspeed + (a * hspeed) / 10, vspeed,
228 hdrift)) {
229 hspeed = hspeed + (a * hspeed) / 10;
230 set_flag(flag_locked);
231 return;
233 if(!dangerous_jump(stage, ad, lpos, hpos, vpos, lspeed, hspeed - (a * hspeed) / 10, vspeed,
234 hdrift)) {
235 hspeed = hspeed - (a * hspeed) / 10;
236 set_flag(flag_locked);
237 return;
239 int32_t tmp = lspeed + (a * lspeed) / 10;
240 if(tmp < 10922)
241 if(!dangerous_jump(stage, ad, lpos, hpos, vpos, tmp, hspeed, vspeed, hdrift)) {
242 speedbias = tmp - lspeed;
243 lspeed = tmp;
244 set_flag(flag_locked);
245 return;
247 if(!dangerous_jump(stage, ad, lpos, hpos, vpos, lspeed - (a * lspeed) / 10, hspeed, vspeed,
248 hdrift)) {
249 speedbias = - (a * lspeed) / 10;
250 lspeed = lspeed - (a * lspeed) / 10;
251 set_flag(flag_locked);
252 return;
256 void physics::check_horizontal_eject(level& stage, noise_maker& noise) throw()
258 if(lprojected == lpos || hpos != hprojected)
259 return;
260 if(!stage.collides(lprojected, hpos, vpos))
261 return;
262 if(!stage.collides(lprojected, hpos - 928, vpos)) {
263 hpos -= 928;
264 lprojected = lpos;
265 noise(sound_blow);
266 } else if(!stage.collides(lprojected, hpos + 928, vpos)) {
267 hpos += 928;
268 lprojected = lpos;
269 noise(sound_blow);
272 void physics::apply_bounce(level& stage, noise_maker& noise) throw()
274 if(vprojected == vpos)
275 return;
276 if(hdrift != 0 && nosupport < 2) {
277 vspeed = 0;
278 return;
280 if(expframe || std::abs(vspeed) < bounce_limit) {
281 vspeed = 0;
282 return;
284 if(!death && vspeed < 0)
285 noise(sound_bounce, false);
286 vspeed = -((5 * static_cast<int16_t>(vspeed)) / 10);
288 void physics::check_landing(level& stage) throw()
290 if(vprojected == vpos || vspeed >= 0)
291 return;
292 clear_flag(flag_locked | flag_tried_lock | flag_jumping);
293 set_flag(flag_landed);
294 adjust_speed(stage, -speedbias);
295 speedbias = 0;
296 nosupport_d = 0;
297 for(int i = 1; i <= 14; i++)
298 if(!stage.collides(lpos, hpos + 128 * i, vpos - 1)) {
299 nosupport = i;
300 nosupport_d++;
301 break;
303 for(int i = 1; i <= 14; i++)
304 if(!stage.collides(lpos, hpos - 128 * i, vpos - 1)) {
305 nosupport = i;
306 nosupport_d--;
307 break;
309 if(nosupport_d)
310 hdrift += 17 * nosupport_d;
311 else
312 hdrift = 0;
314 void physics::move_ship(level& stage) throw()
316 if(lprojected == lpos && hprojected == hpos && vprojected == vpos)
317 return;
318 uint32_t tmp_l;
319 uint16_t tmp_h;
320 int32_t tmp_v;
321 int32_t delta_l = lprojected - lpos;
322 int16_t delta_h = hprojected - hpos;
323 int16_t delta_v = vprojected - vpos;
324 int i;
325 for(i = 1; i <= 5; i++) {
326 tmp_l = lpos + i * delta_l / 5;
327 tmp_h = hpos + i * delta_h / 5;
328 tmp_v = vpos + i * delta_v / 5;
329 if(stage.collides(tmp_l, tmp_h, tmp_v)) {
330 break;
333 lpos = lpos + (i - 1) * delta_l / 5;
334 hpos = hpos + (i - 1) * delta_h / 5;
335 vpos = vpos + (i - 1) * delta_v / 5;
336 for(i = 16384; i > 0; i /= 2)
337 if(i <= abs(lprojected - lpos))
338 if(!stage.collides(lpos + sgn(delta_l) * i, hpos, vpos))
339 lpos = lpos + sgn(delta_l) * i;
340 for(i = 16384; i > 0; i /= 2)
341 if(i <= abs(hprojected - hpos))
342 if(!stage.collides(lpos, hpos + sgn(delta_h) * i, vpos))
343 hpos = hpos + sgn(delta_h) * i;
344 for(i = 16384; i > 0; i /= 2)
345 if(i <= abs(vprojected - vpos))
346 if(!stage.collides(lpos, hpos, vpos + sgn(delta_v) * i))
347 vpos = vpos + sgn(delta_v) * i;
349 bool physics::dangerous_jump(level& stage, int ad, uint32_t lp, uint16_t hp, int16_t vp, int32_t lv,
350 int16_t hv, int16_t vv, int16_t hd)
352 uint16_t old_hp;
353 uint32_t old_lp;
354 do {
355 old_hp = hp;
356 old_lp = lp;
357 vv = vv + gravity_accel;
358 lp = lp + lv;
359 hp = hp + hv * (lv + 1560) / 512 + hd;
360 if(hp < 12160 || hp > 53376)
361 return true;
362 vp = vp + vv;
363 lv = lv + 75 * ad;
364 if(lv < 0)
365 lv = 0;
366 if(lv > 10922)
367 lv = 10922;
368 } while(vp > 10240);
369 tile A = stage.at(old_lp, old_hp);
370 tile B = stage.at(lp, hp);
371 return A.is_dangerous() || B.is_dangerous();
373 uint8_t physics::simulate_frame(level& stage, noise_maker& noise, int lr, int ad, bool jump)
375 uint8_t cod = get_death(stage);
376 if(cod)
377 return cod;
378 if(death == death_finished)
379 return 0; //The animation to scroll.
380 tile t = stage.at(lpos, hpos);
381 force_flag(flag_blank, t.is_blank());
382 apply_floor_effects(stage, noise, t.surface_type(vpos));
383 check_exit(stage);
384 if(death == death_finished)
385 return 0; //If check_exit changed things.
386 apply_bounce(stage, noise);
387 apply_steering(stage, lr, ad, jump);
388 try_locking(stage, lr, ad, jump);
389 apply_gravity(stage);
390 project_position(stage);
391 move_ship(stage);
392 check_horizontal_eject(stage, noise);
393 check_collisions(stage, noise);
394 check_scratching(stage);
395 clear_flag(flag_landed);
396 check_landing(stage);
397 if(vpos < 0) vpos = 0;
398 use_suppiles(stage);
399 check_death(stage);
400 framecounter++;
401 return 0;
403 void physics::level_init(level& stage)
405 gravity = stage.get_gravity();
406 o2_amount = stage.get_o2_amount();
407 fuel_amount = stage.get_fuel_amount();
408 gravity_accel = -((72 * static_cast<int32_t>(gravity) / 5) & 0xFFFF);
409 bounce_limit = (260 * static_cast<int32_t>(gravity) / 8) & 0xFFFF;
410 if(o2_amount)
411 o2_factor = 30000 / ((36 * (int32_t)o2_amount) & 0xFFFF);
412 else
413 o2_factor = 30000;
414 if(fuel_amount)
415 fuel_factor = 30000 / fuel_amount;
416 else
417 fuel_factor = 65535;
418 framecounter = 0;
419 deathframe = 0;
420 lpos = 3 << 16;
421 lprojected = 1999185; //Crap from memory.
422 lspeed = 0;
423 speedbias = 0;
424 expframe = 0;
425 postdeath = 0;
426 hpos = 32768;
427 vpos = 10240;
428 hprojected = 47370; //Crap from memory.
429 vprojected = 0; //Crap from memory.
430 hspeed = 0;
431 vspeed = 0;
432 hdrift = 0;
433 jump_ground = 10240;
434 nosupport = 19422; //Crap from memory.
435 nosupport_d = 16199; //Crap from memory.
436 jump_ground = 0;
437 fuel_left = 30000;
438 o2_left = 30000;
439 death = 0;
440 flags = flag_landed | ((gravity >= 20) ? flag_no_jump : 0);