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