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