- added new bigtux
[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 Surface* ducktux_right;
36 Surface* 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                 {
521                  cape_right[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
522                 }
523               else
524                 {
525                   cape_left[global_frame_counter % 2]->draw(
526                                base.x- scroll_x, base.y);
527                 }
528             }
529
530
531           if (!got_coffee)
532             {
533               if (physic.get_velocity_y() != 0)
534                 {
535                   if (dir == RIGHT)
536                     smalltux_jump_right->draw( base.x - scroll_x, base.y - 10);
537                   else
538                     smalltux_jump_left->draw( base.x - scroll_x, base.y - 10);                   
539                 }
540               else
541                 {
542                   if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
543                     {
544                       if (dir == RIGHT)
545                         smalltux_stand_right->draw( base.x - scroll_x, base.y - 9);
546                       else
547                         smalltux_stand_left->draw( base.x - scroll_x, base.y - 9);
548                     }
549                   else // moving
550                     {
551                       if (dir == RIGHT)
552                         tux_right[(global_frame_counter/2) % tux_right.size()]->draw( 
553                                      base.x - scroll_x, base.y - 9);
554                       else
555                         tux_left[(global_frame_counter/2) % tux_left.size()]->draw( 
556                                      base.x - scroll_x, base.y - 9);
557                     }
558                 }
559             }
560           else
561             {
562               /* Tux got coffee! */
563
564               if (dir == RIGHT)
565                 {
566                   firetux_right[frame_]->draw( base.x- scroll_x, base.y);
567                 }
568               else
569                 {
570                   firetux_left[frame_]->draw( base.x- scroll_x, base.y);
571                 }
572             }
573         }
574       else
575         {
576           if (invincible_timer.started())
577             {
578               float capex = base.x + (base.width - bigcape_right[0]->w) / 2;
579               capex -= scroll_x;
580               float capey = base.y + (base.height - bigcape_right[0]->h) / 2;
581                 
582               /* Draw cape (just not in ducked mode since that looks silly): */
583               if (dir == RIGHT)
584                 {
585                   bigcape_right[global_frame_counter % 2]->draw(
586                           capex, capey);
587                 }
588               else
589                 {
590                   bigcape_left[global_frame_counter % 2]->draw(
591                           capex, capey);
592                 }
593             }
594
595           if (!got_coffee)
596             {
597               if (!duck)
598                 {
599                   if (!skidding_timer.started())
600                     {
601                       if (physic.get_velocity_y() == 0)
602                         {
603                           if (dir == RIGHT)
604                             bigtux_right->draw(base.x- scroll_x - 8, base.y);
605                           else
606                               bigtux_left->draw(base.x- scroll_x - 8, base.y);
607                         }
608                       else
609                         {
610                           if (dir == RIGHT)
611                             bigtux_right_jump->draw(base.x- scroll_x - 8, base.y);
612                           else
613                             bigtux_left_jump->draw(base.x- scroll_x - 8, base.y);
614                         }
615                     }
616                   else
617                     {
618                       if (dir == RIGHT)
619                         skidtux_right->draw(base.x- scroll_x - 8, base.y);
620                       else
621                         skidtux_left->draw(base.x- scroll_x - 8, base.y);
622                     }
623                 }
624               else
625                 {
626                   if (dir == RIGHT)
627                     {
628                       ducktux_right->draw( base.x- scroll_x - 8, base.y - 16);
629                     }
630                   else
631                     {
632                       ducktux_left->draw( base.x- scroll_x - 8, base.y - 16);
633                     }
634                 }
635             }
636           else
637             {
638               /* Tux has coffee! */
639               if (!duck)
640                 {
641                   if (!skidding_timer.started())
642                     {
643                       if (!jumping || physic.get_velocity_y() > 0)
644                         {
645                           if (dir == RIGHT)
646                             {
647                               bigfiretux_right[frame_]->draw(
648                                            base.x- scroll_x - 8, base.y);
649                             }
650                           else
651                             {
652                               bigfiretux_left[frame_]->draw(
653                                            base.x- scroll_x - 8, base.y);
654                             }
655                         }
656                       else
657                         {
658                           if (dir == RIGHT)
659                             {
660                               bigfiretux_right_jump->draw(
661                                            base.x- scroll_x - 8, base.y);
662                             }
663                           else
664                             {
665                               bigfiretux_left_jump->draw(
666                                            base.x- scroll_x - 8, base.y);
667                             }
668                         }
669                     }
670                   else
671                     {
672                       if (dir == RIGHT)
673                         {
674                           skidfiretux_right->draw(
675                                        base.x- scroll_x - 8, base.y);
676                         }
677                       else
678                         {
679                           skidfiretux_left->draw(
680                                        base.x- scroll_x - 8, base.y);
681                         }
682                     }
683                 }
684               else
685                 {
686                   if (dir == RIGHT)
687                     {
688                       duckfiretux_right->draw( base.x- scroll_x - 8, base.y - 16);
689                     }
690                   else
691                     {
692                       duckfiretux_left->draw( base.x- scroll_x - 8, base.y - 16);
693                     }
694                 }
695             }
696         }
697     }
698
699   if (debug_mode)
700     fillrect(base.x - scroll_x, base.y, 32, 32, 75,75,75, 150);
701 }
702
703 void
704 Player::collision(void* p_c_object, int c_object)
705 {
706   BadGuy* pbad_c = NULL;
707
708   switch (c_object)
709     {
710     case CO_BADGUY:
711       pbad_c = (BadGuy*) p_c_object;
712
713      /* Hurt player if he touches a badguy */
714       if (!pbad_c->dying && !dying &&
715           !safe_timer.started() &&
716           pbad_c->mode != HELD)
717         {
718           if (pbad_c->mode == FLAT && input.fire == DOWN)
719             {
720               pbad_c->mode = HELD;
721               pbad_c->base.y-=8;
722             }
723           else if (pbad_c->mode == FLAT)
724             {
725               // Don't get hurt if we're kicking a flat badguy!
726             }
727           else if (pbad_c->mode == KICK)
728             {
729               /* Hurt if you get hit by kicked laptop: */
730               if (!invincible_timer.started())
731                 {
732                   kill(SHRINK);
733                 }
734               else
735                 {
736                    pbad_c->dying = DYING_FALLING;
737                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
738                    World::current()->add_score(pbad_c->base.x - scroll_x,
739                                                pbad_c->base.y,
740                                                25 * player_status.score_multiplier);
741                 }
742             }
743           else
744             {
745               if (!invincible_timer.started())
746                 {
747                   kill(SHRINK);
748                 }
749               else
750                 {
751                   pbad_c->kill_me();
752                 }
753             }
754           player_status.score_multiplier++;
755         }
756       break;
757     default:
758       break;
759     }
760
761 }
762
763 /* Kill Player! */
764
765 void
766 Player::kill(int mode)
767 {
768   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
769
770   physic.set_velocity(0, physic.get_velocity_y());
771
772   if (mode == SHRINK && size == BIG)
773     {
774       if (got_coffee)
775         got_coffee = false;
776
777       size = SMALL;
778       base.height = 32;
779       duck = false;
780
781       safe_timer.start(TUX_SAFE_TIME);
782     }
783   else
784     {
785       physic.enable_gravity(true);
786       physic.set_acceleration(0, 0);
787       physic.set_velocity(0, 7);
788       dying = DYING_SQUISHED;
789     }
790 }
791
792 void
793 Player::is_dying()
794 {
795   remove_powerups();
796   dying = DYING_NOT;
797 }
798
799 bool Player::is_dead()
800 {
801   if(base.y > screen->h)
802     return true;
803   else
804     return false;
805 }
806
807 /* Remove Tux's power ups */
808 void
809 Player::remove_powerups()
810 {
811   got_coffee = false;
812   size = SMALL;
813   base.height = 32;
814 }
815
816 void
817 Player::keep_in_bounds()
818 {
819   Level* plevel = World::current()->get_level();
820
821   /* Keep tux in bounds: */
822   if (base.x < 0)
823     { // Lock Tux to the size of the level, so that he doesn't fall of
824       // on the left side
825       base.x = 0;
826     }
827   else if (base.x < scroll_x)
828     { 
829       base.x = scroll_x;
830     }
831
832   /* Keep in-bounds, vertically: */
833   if (base.y > screen->h)
834     {
835       kill(KILL);
836     }
837
838   int scroll_threshold = screen->w/2 - 80;
839   if (debug_mode)
840     {
841       scroll_x += screen->w/2;
842       // Backscrolling for debug mode
843       if (scroll_x < base.x - 80)
844         scroll_x = base.x - 80;
845       else if (scroll_x > base.x + 80)
846         scroll_x = base.x + 80;
847       scroll_x -= screen->w/2;
848
849       if(scroll_x < 0)
850         scroll_x = 0;
851     }
852   else
853     {
854       if (base.x > scroll_threshold + scroll_x
855           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
856         {
857           // FIXME: Scrolling needs to be handled by a seperate View
858           // class, doing it as a player huck is ugly
859           
860           // Scroll the screen in past center:
861           scroll_x = base.x - scroll_threshold;
862           
863           // Lock the scrolling to the levelsize, so that we don't
864           // scroll over the right border
865           if (scroll_x > 32 * plevel->width - screen->w)
866             scroll_x = 32 * plevel->width - screen->w;
867         }
868     }
869 }
870
871 // EOF //
872