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