- changed/clean up level end detection a bit, still not really as it should be
[supertux.git] / src / player.cpp
1 //
2 // C Implementation: player/tux
3 //
4 // Description:
5 //
6 //
7 // Author: Tobias Glaesser <tobi.web@gmx.de> & Bill Kendrick, (C) 2004
8 //
9 // Copyright: See COPYING file that comes with this distribution
10 //
11 //
12 #include <math.h>
13
14 #include "gameloop.h"
15 #include "globals.h"
16 #include "player.h"
17 #include "defines.h"
18 #include "scene.h"
19 #include "tile.h"
20 #include "screen.h"
21
22 Surface* tux_life;
23 std::vector<Surface*> tux_right;
24 std::vector<Surface*> tux_left;
25 Surface* smalltux_jump_left;
26 Surface* smalltux_jump_right;
27 Surface* smalltux_stand_left;
28 Surface* smalltux_stand_right;
29
30 Surface* bigtux_right[3];
31 Surface* bigtux_left[3];
32 Surface* bigtux_right_jump;
33 Surface* bigtux_left_jump;
34 Surface* ducktux_right;
35 Surface* ducktux_left;
36 Surface* skidtux_right;
37 Surface* skidtux_left;
38 Surface* firetux_right[3];
39 Surface* firetux_left[3];
40 Surface* bigfiretux_right[3];
41 Surface* bigfiretux_left[3];
42 Surface* bigfiretux_right_jump;
43 Surface* bigfiretux_left_jump;
44 Surface* duckfiretux_right;
45 Surface* duckfiretux_left;
46 Surface* skidfiretux_right;
47 Surface* skidfiretux_left;
48 Surface* cape_right[2];
49 Surface* cape_left[2];
50 Surface* bigcape_right[2];
51 Surface* bigcape_left[2];
52
53 void player_input_init(player_input_type* pplayer_input)
54 {
55   pplayer_input->down = UP;
56   pplayer_input->fire = UP;
57   pplayer_input->left = UP;
58   pplayer_input->old_fire = UP;
59   pplayer_input->right = UP;
60   pplayer_input->up = UP;
61 }
62
63 void
64 Player::init()
65 {
66   base.width = 32;
67   base.height = 32;
68
69   size = SMALL;
70   got_coffee = false;
71
72   // FIXME: Make the start position configurable via the levelfile
73   base.x = 100;
74   base.y = 240;
75   base.xm = 0;
76   base.ym = 0;
77   previous_base = old_base = base;
78   dir = RIGHT;
79   duck = false;
80
81   dying   = DYING_NOT;
82   jumping = false;
83
84   frame_main = 0;
85   frame_ = 0;
86   
87   player_input_init(&input);
88
89   keymap.jump  = SDLK_UP;
90   keymap.duck  = SDLK_DOWN;
91   keymap.left  = SDLK_LEFT;
92   keymap.right = SDLK_RIGHT;
93   keymap.fire  = SDLK_LCTRL;
94
95   invincible_timer.init(true);
96   skidding_timer.init(true);
97   safe_timer.init(true);
98   frame_timer.init(true);
99
100   physic.reset();
101 }
102
103 int
104 Player::key_event(SDLKey key, int state)
105 {
106   if(key == keymap.right)
107     {
108       input.right = state;
109       return true;
110     }
111   else if(key == keymap.left)
112     {
113       input.left = state;
114       return true;
115     }
116   else if(key == keymap.jump)
117     {
118       input.up = state;
119       return true;
120     }
121   else if(key == keymap.duck)
122     {
123       input.down = state;
124       return true;
125     }
126   else if(key == keymap.fire)
127     {
128       input.fire = state;
129       return true;
130     }
131   else
132     return false;
133 }
134
135 void
136 Player::level_begin()
137 {
138   base.x  = 100;
139   base.y  = 240;
140   base.xm = 0;
141   base.ym = 0;
142   previous_base = old_base = base;
143   duck = false;
144
145   dying = DYING_NOT;
146
147   player_input_init(&input);
148
149   invincible_timer.init(true);
150   skidding_timer.init(true);
151   safe_timer.init(true);
152   frame_timer.init(true);
153
154   physic.reset();
155 }
156
157 void
158 Player::action(double frame_ratio)
159 {
160   bool jumped_in_solid = false;
161
162   /* --- HANDLE TUX! --- */
163
164   if(dying == DYING_NOT)
165     handle_input();
166
167   /* Move tux: */
168   previous_base = base;
169
170   physic.apply(frame_ratio, base.x, base.y);
171   if(dying == DYING_NOT) {
172       collision_swept_object_map(&old_base, &base);
173       // special exception for cases where we're stuck under tiles after
174       // being ducked. In this case we drift out
175       if(!duck && on_ground() && old_base.x == base.x && old_base.y == base.y
176               && collision_object_map(&base)) {
177           base.x += frame_ratio * WALK_SPEED * (dir ? 1 : -1);
178           previous_base = old_base = base;
179       }
180       keep_in_bounds();
181   }
182
183   if (dying == DYING_NOT)
184     {
185       /* Land: */
186
187
188       if( !on_ground())
189         {
190           physic.enable_gravity(true);
191           if(under_solid())
192             {
193               // fall down
194               physic.set_velocity(physic.get_velocity_x(), 0);
195               jumped_in_solid = true;
196             }
197         }
198       else
199         {
200           /* Land: */
201           if (physic.get_velocity_y() < 0)
202             {
203               base.y = (int)(((int)base.y / 32) * 32);
204               physic.set_velocity(physic.get_velocity_x(), 0);
205             }
206
207           physic.enable_gravity(false);
208           /* Reset score multiplier (for multi-hits): */
209           player_status.score_multiplier = 1;
210         }
211
212       if(jumped_in_solid)
213         {
214           if (isbrick(base.x, base.y) ||
215               isfullbox(base.x, base.y))
216             {
217               World::current()->trygrabdistro(base.x, base.y - 32,BOUNCE);
218               World::current()->trybumpbadguy(base.x, base.y - 64);
219
220               World::current()->trybreakbrick(base.x, base.y, size == SMALL);
221
222               bumpbrick(base.x, base.y);
223               World::current()->tryemptybox(base.x, base.y, RIGHT);
224             }
225
226           if (isbrick(base.x+ 31, base.y) ||
227               isfullbox(base.x+ 31, base.y))
228             {
229               World::current()->trygrabdistro(base.x+ 31, base.y - 32,BOUNCE);
230               World::current()->trybumpbadguy(base.x+ 31, base.y - 64);
231
232               if(size == BIG)
233                 World::current()->trybreakbrick(base.x+ 31, base.y, size == SMALL);
234
235               bumpbrick(base.x+ 31, base.y);
236               World::current()->tryemptybox(base.x+ 31, base.y, LEFT);
237             }
238         }
239
240       grabdistros();
241
242       if (jumped_in_solid)
243         {
244           ++base.y;
245           ++old_base.y;
246           if(on_ground())
247             {
248               /* Make sure jumping is off. */
249               jumping = false;
250             }
251         }
252     }
253
254
255   /* ---- DONE HANDLING TUX! --- */
256
257   /* Handle invincibility timer: */
258   if (get_current_music() == HERRING_MUSIC && !invincible_timer.check())
259     {
260       /*
261          no, we are no more invincible
262          or we were not in invincible mode
263          but are we in hurry ?
264        */
265
266       // FIXME: Move this to gamesession
267       if (GameSession::current()->time_left.get_left() < TIME_WARNING)
268         {
269           /* yes, we are in hurry
270              stop the herring_song, prepare to play the correct
271              fast level_song !
272            */
273           set_current_music(HURRYUP_MUSIC);
274         }
275       else
276         {
277           set_current_music(LEVEL_MUSIC);
278         }
279
280       /* start playing it */
281       play_current_music();
282     }
283
284   // check some timers
285   skidding_timer.check();
286   invincible_timer.check();
287   safe_timer.check();
288 }
289
290 bool
291 Player::on_ground()
292 {
293   return ( issolid(base.x + base.width / 2, base.y + base.height) ||
294            issolid(base.x + 1, base.y + base.height) ||
295            issolid(base.x + base.width - 1, base.y + base.height)  );
296 }
297
298 bool
299 Player::under_solid()
300 {
301   return ( issolid(base.x + base.width / 2, base.y) ||
302            issolid(base.x + 1, base.y) ||
303            issolid(base.x + base.width - 1, base.y)  );
304 }
305
306 void
307 Player::handle_horizontal_input()
308 {
309   float vx = physic.get_velocity_x();
310   float vy = physic.get_velocity_y();
311   float ax = physic.get_acceleration_x();
312   float ay = physic.get_acceleration_y();
313
314   float dirsign = 0;
315   if(!duck && input.left == DOWN && input.right == UP) {
316       dir = LEFT;
317       dirsign = -1;
318   } else if(!duck && input.left == UP && input.right == DOWN) {
319       dir = RIGHT;
320       dirsign = 1;
321   }
322
323   if (input.fire == UP) {
324       ax = dirsign * WALK_ACCELERATION_X;
325       // limit speed
326       if(vx >= MAX_WALK_XM && dirsign > 0) {
327         vx = MAX_WALK_XM;
328         ax = 0;
329       } else if(vx <= -MAX_WALK_XM && dirsign < 0) {
330         vx = -MAX_WALK_XM;
331         ax = 0;
332       }
333   } else {
334       ax = dirsign * RUN_ACCELERATION_X;
335       // limit speed
336       if(vx >= MAX_RUN_XM && dirsign > 0) {
337         vx = MAX_RUN_XM;
338         ax = 0;
339       } else if(vx <= -MAX_RUN_XM && dirsign < 0) {
340         vx = -MAX_RUN_XM;
341         ax = 0;
342       }
343   }
344
345   // we can reach WALK_SPEED without any acceleration
346   if(dirsign != 0 && fabs(vx) < WALK_SPEED) {
347     vx = dirsign * WALK_SPEED;
348   }
349
350   // changing directions?
351   if(on_ground() && ((vx < 0 && dirsign >0) || (vx>0 && dirsign<0))) {
352       if(fabs(vx)>SKID_XM && !skidding_timer.check()) {
353           skidding_timer.start(SKID_TIME);
354           play_sound(sounds[SND_SKID], SOUND_CENTER_SPEAKER);
355           ax *= 2.5;
356       } else {
357           ax *= 2;
358       }
359   }
360
361   // we get slower when not pressing any keys
362   if(dirsign == 0) {
363       if(fabs(vx) < WALK_SPEED) {
364           vx = 0;
365           ax = 0;
366       } else if(vx < 0) {
367           ax = WALK_ACCELERATION_X * 1.5;
368       } else {
369           ax = WALK_ACCELERATION_X * -1.5;
370       }
371   }
372
373   // if we're on ice slow down acceleration or deceleration
374   if (isice(base.x, base.y + base.height))
375   {
376     /* the acceleration/deceleration rate on ice is inversely proportional to
377      * the current velocity.
378      */
379
380     // increasing 1 will increase acceleration/deceleration rate
381     // decreasing 1 will decrease acceleration/deceleration rate
382     //  must stay above zero, though
383     if (ax != 0) ax *= 1 / fabs(vx);
384   }
385
386   physic.set_velocity(vx, vy);
387   physic.set_acceleration(ax, ay);
388 }
389
390 void
391 Player::handle_vertical_input()
392 {
393   if(input.up == DOWN)
394     {
395       if (on_ground() && !duck)
396         {
397           // jump
398           physic.set_velocity(physic.get_velocity_x(), 5.5);
399           --base.y;
400           jumping = true;
401           if (size == SMALL)
402             play_sound(sounds[SND_JUMP], SOUND_CENTER_SPEAKER);
403           else
404             play_sound(sounds[SND_BIGJUMP], SOUND_CENTER_SPEAKER);
405         }
406     }
407   else if(input.up == UP && jumping)
408     {
409       jumping = false;
410       if(physic.get_velocity_y() > 0) {
411         physic.set_velocity(physic.get_velocity_x(), 0);
412       }
413     }
414 }
415
416 void
417 Player::handle_input()
418 {
419   /* Handle horizontal movement: */
420     handle_horizontal_input();
421
422   /* Jump/jumping? */
423
424   if ( input.up == DOWN || (input.up == UP && jumping))
425     {
426       handle_vertical_input();
427     }
428
429   /* Shoot! */
430
431   if (input.fire == DOWN && input.old_fire == UP && got_coffee)
432     {
433       World::current()->add_bullet(base.x, base.y, physic.get_velocity_x(), dir);
434     }
435
436   /* tux animations: */
437   if(!frame_timer.check())
438     {
439       frame_timer.start(25);
440       if (input.right == UP && input.left == UP)
441         {
442           frame_main = 1;
443           frame_ = 1;
444         }
445       else
446         {
447           if ((input.fire == DOWN && (global_frame_counter % 2) == 0) ||
448               (global_frame_counter % 4) == 0)
449             frame_main = (frame_main + 1) % 4;
450
451           frame_ = frame_main;
452
453           if (frame_ == 3)
454             frame_ = 1;
455         }
456     }
457
458   /* Duck! */
459   if (input.down == DOWN && size == BIG && !duck)
460     {
461       duck = true;
462       base.height = 32;                             
463       base.y += 32;
464       // changing base size confuses collision otherwise
465       old_base = previous_base = base;
466     }
467   else if(input.down == UP && size == BIG && duck)
468     {
469       duck = false;
470       base.y -= 32;
471       base.height = 64;
472       old_base = previous_base = base;
473     }
474 }
475
476 void
477 Player::grabdistros()
478 {
479   /* Grab distros: */
480   if (!dying)
481     {
482       World::current()->trygrabdistro(base.x, base.y, NO_BOUNCE);
483       World::current()->trygrabdistro(base.x+ 31, base.y, NO_BOUNCE);
484
485       World::current()->trygrabdistro(base.x, base.y + base.height, NO_BOUNCE);
486       World::current()->trygrabdistro(base.x+ 31, base.y + base.height, NO_BOUNCE);
487
488       if(size == BIG)
489         {
490           World::current()->trygrabdistro(base.x, base.y + base.height / 2, NO_BOUNCE);
491           World::current()->trygrabdistro(base.x+ 31, base.y + base.height / 2, NO_BOUNCE);
492         }
493
494     }
495
496   /* Enough distros for a One-up? */
497   if (player_status.distros >= DISTROS_LIFEUP)
498     {
499       player_status.distros = player_status.distros - DISTROS_LIFEUP;
500       if(player_status.lives < MAX_LIVES)
501         ++player_status.lives;
502       /*We want to hear the sound even, if MAX_LIVES is reached*/
503       play_sound(sounds[SND_LIFEUP], SOUND_CENTER_SPEAKER);
504     }
505 }
506
507 void
508 Player::draw()
509 {
510   if (!safe_timer.started() || (global_frame_counter % 2) == 0)
511     {
512       if (size == SMALL)
513         {
514           if (invincible_timer.started())
515             {
516               /* Draw cape: */
517
518               if (dir == RIGHT)
519                 {
520                  cape_right[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
521                 }
522               else
523                 {
524                   cape_left[global_frame_counter % 2]->draw(
525                                base.x- scroll_x, base.y);
526                 }
527             }
528
529
530           if (!got_coffee)
531             {
532               if (physic.get_velocity_y() != 0)
533                 {
534                   if (dir == RIGHT)
535                     smalltux_jump_right->draw( base.x - scroll_x, base.y - 10);
536                   else
537                     smalltux_jump_left->draw( base.x - scroll_x, base.y - 10);                   
538                 }
539               else
540                 {
541                   if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
542                     {
543                       if (dir == RIGHT)
544                         smalltux_stand_right->draw( base.x - scroll_x, base.y - 9);
545                       else
546                         smalltux_stand_left->draw( base.x - scroll_x, base.y - 9);
547                     }
548                   else // moving
549                     {
550                       if (dir == RIGHT)
551                         tux_right[(global_frame_counter/2) % tux_right.size()]->draw( 
552                                      base.x - scroll_x, base.y - 9);
553                       else
554                         tux_left[(global_frame_counter/2) % tux_left.size()]->draw( 
555                                      base.x - scroll_x, base.y - 9);
556                     }
557                 }
558             }
559           else
560             {
561               /* Tux got coffee! */
562
563               if (dir == RIGHT)
564                 {
565                   firetux_right[frame_]->draw( base.x- scroll_x, base.y);
566                 }
567               else
568                 {
569                   firetux_left[frame_]->draw( base.x- scroll_x, base.y);
570                 }
571             }
572         }
573       else
574         {
575           if (invincible_timer.started())
576             {
577               float capex = base.x + (base.width - bigcape_right[0]->w) / 2;
578               capex -= scroll_x;
579               float capey = base.y + (base.height - bigcape_right[0]->h) / 2;
580                 
581               /* Draw cape (just not in ducked mode since that looks silly): */
582               if (dir == RIGHT)
583                 {
584                   bigcape_right[global_frame_counter % 2]->draw(
585                           capex, capey);
586                 }
587               else
588                 {
589                   bigcape_left[global_frame_counter % 2]->draw(
590                           capex, capey);
591                 }
592             }
593
594           if (!got_coffee)
595             {
596               if (!duck)
597                 {
598                   if (!skidding_timer.started())
599                     {
600                       if (!jumping || physic.get_velocity_y() > 0)
601                         {
602                           if (dir == RIGHT)
603                             {
604                               bigtux_right[frame_]->draw(
605                                            base.x- scroll_x - 8, base.y);
606                             }
607                           else
608                             {
609                               bigtux_left[frame_]->draw(
610                                            base.x- scroll_x - 8, base.y);
611                             }
612                         }
613                       else
614                         {
615                           if (dir == RIGHT)
616                             {
617                               bigtux_right_jump->draw(
618                                            base.x- scroll_x - 8, base.y);
619                             }
620                           else
621                             {
622                               bigtux_left_jump->draw(
623                                            base.x- scroll_x - 8, base.y);
624                             }
625                         }
626                     }
627                   else
628                     {
629                       if (dir == RIGHT)
630                         {
631                           skidtux_right->draw(
632                                        base.x- scroll_x - 8, base.y);
633                         }
634                       else
635                         {
636                           skidtux_left->draw(
637                                        base.x- scroll_x - 8, base.y);
638                         }
639                     }
640                 }
641               else
642                 {
643                   if (dir == RIGHT)
644                     {
645                       ducktux_right->draw( base.x- scroll_x - 8, base.y - 16);
646                     }
647                   else
648                     {
649                       ducktux_left->draw( base.x- scroll_x - 8, base.y - 16);
650                     }
651                 }
652             }
653           else
654             {
655               /* Tux has coffee! */
656               if (!duck)
657                 {
658                   if (!skidding_timer.started())
659                     {
660                       if (!jumping || physic.get_velocity_y() > 0)
661                         {
662                           if (dir == RIGHT)
663                             {
664                               bigfiretux_right[frame_]->draw(
665                                            base.x- scroll_x - 8, base.y);
666                             }
667                           else
668                             {
669                               bigfiretux_left[frame_]->draw(
670                                            base.x- scroll_x - 8, base.y);
671                             }
672                         }
673                       else
674                         {
675                           if (dir == RIGHT)
676                             {
677                               bigfiretux_right_jump->draw(
678                                            base.x- scroll_x - 8, base.y);
679                             }
680                           else
681                             {
682                               bigfiretux_left_jump->draw(
683                                            base.x- scroll_x - 8, base.y);
684                             }
685                         }
686                     }
687                   else
688                     {
689                       if (dir == RIGHT)
690                         {
691                           skidfiretux_right->draw(
692                                        base.x- scroll_x - 8, base.y);
693                         }
694                       else
695                         {
696                           skidfiretux_left->draw(
697                                        base.x- scroll_x - 8, base.y);
698                         }
699                     }
700                 }
701               else
702                 {
703                   if (dir == RIGHT)
704                     {
705                       duckfiretux_right->draw( base.x- scroll_x - 8, base.y - 16);
706                     }
707                   else
708                     {
709                       duckfiretux_left->draw( base.x- scroll_x - 8, base.y - 16);
710                     }
711                 }
712             }
713         }
714     }
715
716   if (debug_mode)
717     fillrect(base.x - scroll_x, base.y, 32, 32, 75,75,75, 150);
718 }
719
720 void
721 Player::collision(void* p_c_object, int c_object)
722 {
723   BadGuy* pbad_c = NULL;
724
725   switch (c_object)
726     {
727     case CO_BADGUY:
728       pbad_c = (BadGuy*) p_c_object;
729       /* Hurt the player if he just touched it: */
730
731       if (!pbad_c->dying && !dying &&
732           !safe_timer.started() &&
733           pbad_c->mode != HELD)
734         {
735           if (pbad_c->mode == FLAT && input.fire == DOWN)
736             {
737               pbad_c->mode = HELD;
738               pbad_c->base.y-=8;
739             }
740           else if (pbad_c->mode == KICK)
741             {
742               if (base.y < pbad_c->base.y - 16)
743                 {
744                   /* Step on (stop being kicked) */
745
746                   pbad_c->mode = FLAT;
747                   play_sound(sounds[SND_STOMP], SOUND_CENTER_SPEAKER);
748                 }
749               else
750                 {
751                   /* Hurt if you get hit by kicked laptop: */
752                   if (!invincible_timer.started())
753                     {
754                       kill(SHRINK);
755                     }
756                   else
757                     {
758                       pbad_c->dying = DYING_FALLING;
759                       play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
760                       World::current()->add_score(pbad_c->base.x - scroll_x,
761                                                   pbad_c->base.y,
762                                                   25 * player_status.score_multiplier);
763                     }
764                 }
765             }
766           else
767             {
768               if (!invincible_timer.started())
769                 {
770                   kill(SHRINK);
771                 }
772               else
773                 {
774                   pbad_c->kill_me();
775                 }
776             }
777           player_status.score_multiplier++;
778         }
779       break;
780     default:
781       break;
782     }
783
784 }
785
786 /* Kill Player! */
787
788 void
789 Player::kill(int mode)
790 {
791   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
792
793   physic.set_velocity(0, physic.get_velocity_y());
794
795   if (mode == SHRINK && size == BIG)
796     {
797       if (got_coffee)
798         got_coffee = false;
799
800       size = SMALL;
801       base.height = 32;
802       duck = false;
803
804       safe_timer.start(TUX_SAFE_TIME);
805     }
806   else
807     {
808       physic.enable_gravity(true);
809       physic.set_acceleration(0, 0);
810       physic.set_velocity(0, 7);
811       dying = DYING_SQUISHED;
812     }
813 }
814
815 void
816 Player::is_dying()
817 {
818   remove_powerups();
819   dying = DYING_NOT;
820 }
821
822 bool Player::is_dead()
823 {
824   if(base.y > screen->h)
825     return true;
826   else
827     return false;
828 }
829
830 /* Remove Tux's power ups */
831 void
832 Player::remove_powerups()
833 {
834   got_coffee = false;
835   size = SMALL;
836   base.height = 32;
837 }
838
839 void
840 Player::keep_in_bounds()
841 {
842   Level* plevel = World::current()->get_level();
843
844   /* Keep tux in bounds: */
845   if (base.x < 0)
846     { // Lock Tux to the size of the level, so that he doesn't fall of
847       // on the left side
848       base.x = 0;
849     }
850   else if (base.x < scroll_x)
851     { 
852       base.x = scroll_x;
853     }
854
855   /* Keep in-bounds, vertically: */
856   if (base.y > screen->h)
857     {
858       kill(KILL);
859     }
860
861   int scroll_threshold = screen->w/2 - 80;
862   if (debug_mode)
863     {
864       scroll_x += screen->w/2;
865       // Backscrolling for debug mode
866       if (scroll_x < base.x - 80)
867         scroll_x = base.x - 80;
868       else if (scroll_x > base.x + 80)
869         scroll_x = base.x + 80;
870       scroll_x -= screen->w/2;
871
872       if(scroll_x < 0)
873         scroll_x = 0;
874     }
875   else
876     {
877       if (base.x > scroll_threshold + scroll_x
878           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
879         {
880           // FIXME: Scrolling needs to be handled by a seperate View
881           // class, doing it as a player huck is ugly
882           
883           // Scroll the screen in past center:
884           scroll_x = base.x - scroll_threshold;
885           
886           // Lock the scrolling to the levelsize, so that we don't
887           // scroll over the right border
888           if (scroll_x > 32 * plevel->width - screen->w)
889             scroll_x = 32 * plevel->width - screen->w;
890         }
891     }
892 }
893
894 // EOF //
895