617b48d8368188d09284697a685e4505da2fc70f
[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 #include "gameloop.h"
22 #include "globals.h"
23 #include "player.h"
24 #include "defines.h"
25 #include "scene.h"
26 #include "tile.h"
27 #include "sprite.h"
28 #include "screen.h"
29
30 Surface* tux_life;
31
32 Sprite* smalltux_gameover;
33 Sprite* smalltux_star;
34 Sprite* largetux_star;
35
36 PlayerSprite smalltux;
37 PlayerSprite largetux;
38 PlayerSprite firetux;
39
40 PlayerKeymap keymap;
41
42 PlayerKeymap::PlayerKeymap()
43 {
44   keymap.jump  = SDLK_UP;
45   keymap.duck  = SDLK_DOWN;
46   keymap.left  = SDLK_LEFT;
47   keymap.right = SDLK_RIGHT;
48   keymap.fire  = SDLK_LCTRL;
49 }
50
51 void player_input_init(player_input_type* pplayer_input)
52 {
53   pplayer_input->down = UP;
54   pplayer_input->fire = UP;
55   pplayer_input->left = UP;
56   pplayer_input->old_fire = UP;
57   pplayer_input->right = UP;
58   pplayer_input->up = UP;
59   pplayer_input->old_up = UP;
60 }
61
62 void
63 Player::init()
64 {
65   Level* plevel = World::current()->get_level();
66
67   holding_something = false;
68
69   base.width = 32;
70   base.height = 32;
71
72   size = SMALL;
73   got_coffee = false;
74
75   base.x = plevel->start_pos_x;
76   base.y = plevel->start_pos_y;
77   base.xm = 0;
78   base.ym = 0;
79   previous_base = old_base = base;
80   dir = RIGHT;
81   old_dir = dir;
82   duck = false;
83
84   dying   = DYING_NOT;
85   jumping = false;
86   can_jump = true;
87
88   frame_main = 0;
89   frame_ = 0;
90   
91   player_input_init(&input);
92
93   invincible_timer.init(true);
94   skidding_timer.init(true);
95   safe_timer.init(true);
96   frame_timer.init(true);
97   kick_timer.init(true);
98
99   physic.reset();
100 }
101
102 int
103 Player::key_event(SDLKey key, int state)
104 {
105   if(key == keymap.right)
106     {
107       input.right = state;
108       return true;
109     }
110   else if(key == keymap.left)
111     {
112       input.left = state;
113       return true;
114     }
115   else if(key == keymap.jump)
116     {
117       input.up = state;
118       return true;
119     }
120   else if(key == keymap.duck)
121     {
122       input.down = state;
123       return true;
124     }
125   else if(key == keymap.fire)
126     {
127       if (state == UP)
128         input.old_fire = UP;
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   if (input.fire == UP)
164     holding_something = false;
165
166   /* Move tux: */
167   previous_base = base;
168
169   /* --- HANDLE TUX! --- */
170   if(dying == DYING_NOT)
171     handle_input();
172
173   physic.apply(frame_ratio, base.x, base.y);
174
175   if(dying == DYING_NOT) 
176     {
177       base_type target = base;
178
179       collision_swept_object_map(&old_base, &base);
180
181       // Don't accelerate Tux if he is running against a wall
182       if (target.x != base.x)
183         {
184           physic.set_velocity_x(0);
185         }
186
187       // special exception for cases where we're stuck under tiles after
188       // being ducked. In this case we drift out
189       if(!duck && on_ground() && old_base.x == base.x && old_base.y == base.y
190          && collision_object_map(base))
191         {
192           base.x += frame_ratio * WALK_SPEED * (dir ? 1 : -1);
193           previous_base = old_base = base;
194         }
195       check_bounds();
196
197       // Land:
198       if (!on_ground())
199         {
200           physic.enable_gravity(true);
201           if(under_solid())
202             {
203               // fall down
204               physic.set_velocity_y(0);
205               jumped_in_solid = true;
206             }
207         }
208       else
209         {
210           /* Land: */
211           if (physic.get_velocity_y() < 0)
212             {
213               base.y = (int)(((int)base.y / 32) * 32);
214               physic.set_velocity_y(0);
215             }
216
217           physic.enable_gravity(false);
218           /* Reset score multiplier (for multi-hits): */
219           player_status.score_multiplier = 1;
220         }
221
222       if(jumped_in_solid)
223         {
224           if (isbrick(base.x, base.y) ||
225               isfullbox(base.x, base.y))
226             {
227               World::current()->trygrabdistro(base.x, base.y - 32,BOUNCE);
228               World::current()->trybumpbadguy(base.x, base.y - 64);
229
230               World::current()->trybreakbrick(base.x, base.y, size == SMALL);
231
232               bumpbrick(base.x, base.y);
233               World::current()->tryemptybox(base.x, base.y, RIGHT);
234             }
235
236           if (isbrick(base.x+ 31, base.y) ||
237               isfullbox(base.x+ 31, base.y))
238             {
239               World::current()->trygrabdistro(base.x+ 31, base.y - 32,BOUNCE);
240               World::current()->trybumpbadguy(base.x+ 31, base.y - 64);
241
242               if(size == BIG)
243                 World::current()->trybreakbrick(base.x+ 31, base.y, size == SMALL);
244
245               bumpbrick(base.x+ 31, base.y);
246               World::current()->tryemptybox(base.x+ 31, base.y, LEFT);
247             }
248         }
249
250       grabdistros();
251
252       if (jumped_in_solid)
253         {
254           ++base.y;
255           ++old_base.y;
256           if(on_ground())
257             {
258               /* Make sure jumping is off. */
259               jumping = false;
260             }
261         }
262     }
263
264   /* ---- DONE HANDLING TUX! --- */
265
266   // check some timers
267   skidding_timer.check();
268   invincible_timer.check();
269   safe_timer.check();
270   kick_timer.check();
271 }
272
273 bool
274 Player::on_ground()
275 {
276   return ( issolid(base.x + base.width / 2, base.y + base.height) ||
277            issolid(base.x + 1, base.y + base.height) ||
278            issolid(base.x + base.width - 1, base.y + base.height)  );
279 }
280
281 bool
282 Player::under_solid()
283 {
284   return ( issolid(base.x + base.width / 2, base.y) ||
285            issolid(base.x + 1, base.y) ||
286            issolid(base.x + base.width - 1, base.y)  );
287 }
288
289 void
290 Player::handle_horizontal_input()
291 {
292   float vx = physic.get_velocity_x();
293   float vy = physic.get_velocity_y();
294   float ax = physic.get_acceleration_x();
295   float ay = physic.get_acceleration_y();
296
297   float dirsign = 0;
298   if(input.left == DOWN && input.right == UP && (!duck || physic.get_velocity_y() != 0)) {
299       old_dir = dir;
300       dir = LEFT;
301       dirsign = -1;
302   } else if(input.left == UP && input.right == DOWN && (!duck || physic.get_velocity_y() != 0)) {
303       old_dir = dir;
304       dir = RIGHT;
305       dirsign = 1;
306   }
307
308   if (input.fire == UP) {
309       ax = dirsign * WALK_ACCELERATION_X;
310       // limit speed
311       if(vx >= MAX_WALK_XM && dirsign > 0) {
312         vx = MAX_WALK_XM;
313         ax = 0;
314       } else if(vx <= -MAX_WALK_XM && dirsign < 0) {
315         vx = -MAX_WALK_XM;
316         ax = 0;
317       }
318   } else {
319       ax = dirsign * RUN_ACCELERATION_X;
320       // limit speed
321       if(vx >= MAX_RUN_XM && dirsign > 0) {
322         vx = MAX_RUN_XM;
323         ax = 0;
324       } else if(vx <= -MAX_RUN_XM && dirsign < 0) {
325         vx = -MAX_RUN_XM;
326         ax = 0;
327       }
328   }
329
330   // we can reach WALK_SPEED without any acceleration
331   if(dirsign != 0 && fabs(vx) < WALK_SPEED) {
332     vx = dirsign * WALK_SPEED;
333   }
334
335   // changing directions?
336   if(on_ground() && ((vx < 0 && dirsign >0) || (vx>0 && dirsign<0))) {
337       if(fabs(vx)>SKID_XM && !skidding_timer.check()) {
338           skidding_timer.start(SKID_TIME);
339           play_sound(sounds[SND_SKID], SOUND_CENTER_SPEAKER);
340           ax *= 2.5;
341       } else {
342           ax *= 2;
343       }
344   }
345
346   // we get slower when not pressing any keys
347   if(dirsign == 0) {
348       if(fabs(vx) < WALK_SPEED) {
349           vx = 0;
350           ax = 0;
351       } else if(vx < 0) {
352           ax = WALK_ACCELERATION_X * 1.5;
353       } else {
354           ax = WALK_ACCELERATION_X * -1.5;
355       }
356   }
357
358   // if we're on ice slow down acceleration or deceleration
359   if (isice(base.x, base.y + base.height))
360   {
361     /* the acceleration/deceleration rate on ice is inversely proportional to
362      * the current velocity.
363      */
364
365     // increasing 1 will increase acceleration/deceleration rate
366     // decreasing 1 will decrease acceleration/deceleration rate
367     //  must stay above zero, though
368     if (ax != 0) ax *= 1 / fabs(vx);
369   }
370
371   physic.set_velocity(vx, vy);
372   physic.set_acceleration(ax, ay);
373 }
374
375 void
376 Player::handle_vertical_input()
377 {
378   // Press jump key
379   if(input.up == DOWN && can_jump)
380     {
381       if (on_ground())
382         {
383           // jump higher if we are running
384           if (fabs(physic.get_velocity_x()) > MAX_WALK_XM)
385             physic.set_velocity_y(5.8);
386           else
387             physic.set_velocity_y(5.2);
388
389           --base.y;
390           jumping = true;
391           can_jump = false;
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   // Let go of jump key
399   else if(input.up == UP && jumping)
400     {
401       jumping = false;
402       if(physic.get_velocity_y() > 0) {
403         physic.set_velocity_y(0);
404       }
405     }
406
407   if ( (issolid(base.x + base.width / 2, base.y + base.height + 64) ||
408         issolid(base.x + 1, base.y + base.height + 64) ||
409         issolid(base.x + base.width - 1, base.y + base.height + 64))
410        && jumping  == false
411        && can_jump == false
412        && input.up == DOWN
413        && input.old_up == UP)
414     {
415       can_jump = true;
416     }
417
418   input.old_up = input.up;
419 }
420
421 void
422 Player::handle_input()
423 {
424   /* Handle horizontal movement: */
425     handle_horizontal_input();
426
427   /* Jump/jumping? */
428
429   if (on_ground() && input.up == UP)
430     can_jump = true;
431   if (input.up == DOWN || (input.up == UP && jumping))
432     {
433       handle_vertical_input();
434     }
435
436   /* Shoot! */
437
438   if (input.fire == DOWN && input.old_fire == UP && got_coffee)
439     {
440       World::current()->add_bullet(base.x, base.y, physic.get_velocity_x(), dir);
441       input.old_fire = DOWN;
442     }
443
444   /* tux animations: */
445   if(!frame_timer.check())
446     {
447       frame_timer.start(25);
448       if (input.right == UP && input.left == UP)
449         {
450           frame_main = 1;
451           frame_ = 1;
452         }
453       else
454         {
455           if ((input.fire == DOWN && (global_frame_counter % 2) == 0) ||
456               (global_frame_counter % 4) == 0)
457             frame_main = (frame_main + 1) % 4;
458
459           frame_ = frame_main;
460
461           if (frame_ == 3)
462             frame_ = 1;
463         }
464     }
465
466   /* Duck! */
467   if (input.down == DOWN && size == BIG && !duck && physic.get_velocity_y() == 0 && on_ground())
468     {
469       duck = true;
470       base.height = 32;                             
471       base.y += 32;
472       // changing base size confuses collision otherwise
473       old_base = previous_base = base;
474     }
475   else if(input.down == UP && size == BIG && duck && physic.get_velocity_y() == 0 && on_ground())
476     {
477       duck = false;
478       base.y -= 32;
479       base.height = 64;
480       // changing base size confuses collision otherwise
481       old_base = previous_base = base;                        
482     }
483 }
484
485 void
486 Player::grow()
487 {
488   if(size == BIG)
489     return;
490   
491   size = BIG;
492   base.height = 64;
493   base.y -= 32;
494
495   old_base = previous_base = base;
496 }
497
498 void
499 Player::grabdistros()
500 {
501   /* Grab distros: */
502   if (!dying)
503     {
504       World::current()->trygrabdistro(base.x, base.y, NO_BOUNCE);
505       World::current()->trygrabdistro(base.x+ 31, base.y, NO_BOUNCE);
506
507       World::current()->trygrabdistro(base.x, base.y + base.height, NO_BOUNCE);
508       World::current()->trygrabdistro(base.x+ 31, base.y + base.height, NO_BOUNCE);
509
510       if(size == BIG)
511         {
512           World::current()->trygrabdistro(base.x, base.y + base.height / 2, NO_BOUNCE);
513           World::current()->trygrabdistro(base.x+ 31, base.y + base.height / 2, NO_BOUNCE);
514         }
515
516     }
517
518   /* Enough distros for a One-up? */
519   if (player_status.distros >= DISTROS_LIFEUP)
520     {
521       player_status.distros = player_status.distros - DISTROS_LIFEUP;
522       if(player_status.lives < MAX_LIVES)
523         ++player_status.lives;
524       /*We want to hear the sound even, if MAX_LIVES is reached*/
525       play_sound(sounds[SND_LIFEUP], SOUND_CENTER_SPEAKER);
526     }
527 }
528
529 void
530 Player::draw()
531 {
532   if (!safe_timer.started() || (global_frame_counter % 2) == 0)
533     {
534       if (dying == DYING_SQUISHED)
535         {
536           smalltux_gameover->draw(base.x - scroll_x, base.y);
537         }
538       else
539         {
540           PlayerSprite* sprite;
541           
542           if (size == SMALL)
543             sprite = &smalltux;
544           else if (got_coffee)
545             sprite = &firetux;
546           else
547             sprite = &largetux;
548           
549           if (duck && size != SMALL)
550             {
551               if (dir == RIGHT)
552                 sprite->duck_right->draw(base.x - scroll_x, base.y);
553               else 
554                 sprite->duck_left->draw(base.x - scroll_x, base.y);
555             }
556           else if (skidding_timer.started())
557             {
558               if (dir == RIGHT)
559                 sprite->skid_right->draw(base.x - scroll_x, base.y);
560               else
561                 sprite->skid_left->draw(base.x - scroll_x, base.y); 
562             }
563           else if (kick_timer.started())
564             {
565               if (dir == RIGHT)
566                 sprite->kick_right->draw(base.x - scroll_x, base.y);
567               else
568                 sprite->kick_left->draw(base.x - scroll_x, base.y); 
569             }
570           else if (physic.get_velocity_y() != 0)
571             {
572               if (dir == RIGHT)
573                 sprite->jump_right->draw(base.x - scroll_x, base.y);
574               else
575                 sprite->jump_left->draw(base.x - scroll_x, base.y);                   
576             }
577           else
578             {
579               if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
580                 {
581                   if (dir == RIGHT)
582                     sprite->stand_right->draw( base.x - scroll_x, base.y);
583                   else
584                     sprite->stand_left->draw( base.x - scroll_x, base.y);
585                 }
586               else // moving
587                 {
588                   if (dir == RIGHT)
589                     sprite->walk_right->draw(base.x - scroll_x, base.y);
590                   else
591                     sprite->walk_left->draw(base.x - scroll_x, base.y);
592                 }
593             }
594                       
595           // Draw arm overlay graphics when Tux is holding something
596           if (holding_something && physic.get_velocity_y() == 0)
597             {
598               if (dir == RIGHT)
599                 sprite->grab_right->draw(base.x - scroll_x, base.y);
600               else
601                 sprite->grab_left->draw(base.x - scroll_x, base.y);
602             }
603
604           // Draw blinking star overlay
605           if (invincible_timer.started() &&
606              (invincible_timer.get_left() > TUX_INVINCIBLE_TIME_WARNING || global_frame_counter % 3))
607             {
608               if (size == SMALL || duck)
609                 smalltux_star->draw(base.x - scroll_x, base.y);
610               else
611                 largetux_star->draw(base.x - scroll_x, base.y);
612             }
613         }
614     }     
615   
616   if (debug_mode)
617     fillrect(base.x - scroll_x, base.y, 
618              base.width, base.height, 75,75,75, 150);
619 }
620
621 void
622 Player::collision(void* p_c_object, int c_object)
623 {
624   BadGuy* pbad_c = NULL;
625
626   switch (c_object)
627     {
628     case CO_BADGUY:
629       pbad_c = (BadGuy*) p_c_object;
630
631      /* Hurt player if he touches a badguy */
632       if (!pbad_c->dying && !dying &&
633           !safe_timer.started() &&
634           pbad_c->mode != BadGuy::HELD)
635         {
636           if (pbad_c->mode == BadGuy::FLAT && input.fire == DOWN
637                && !holding_something)
638             {
639               holding_something = true;
640               pbad_c->mode = BadGuy::HELD;
641               pbad_c->base.y-=8;
642             }
643           else if (pbad_c->mode == BadGuy::FLAT)
644             {
645               // Don't get hurt if we're kicking a flat badguy!
646             }
647           else if (pbad_c->mode == BadGuy::KICK)
648             {
649               /* Hurt if you get hit by kicked laptop: */
650               if (!invincible_timer.started())
651                 {
652                   kill(SHRINK);
653                 }
654               else
655                 {
656                    pbad_c->dying = DYING_FALLING;
657                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
658                    World::current()->add_score(pbad_c->base.x - scroll_x,
659                                                pbad_c->base.y,
660                                                25 * player_status.score_multiplier);
661                 }
662             }
663           else
664             {
665               if (!invincible_timer.started())
666                 {
667                   kill(SHRINK);
668                 }
669               else
670                 {
671                   pbad_c->kill_me(25);
672                 }
673             }
674           player_status.score_multiplier++;
675         }
676       break;
677     default:
678       break;
679     }
680
681 }
682
683 /* Kill Player! */
684
685 void
686 Player::kill(HurtMode mode)
687 {
688   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
689
690   physic.set_velocity_x(0);
691
692   if (mode == SHRINK && size == BIG)
693     {
694       if (got_coffee)
695         {
696           got_coffee = false;
697         }
698       else
699         {
700           size = SMALL;
701           base.height = 32;
702           duck = false;
703         }
704       safe_timer.start(TUX_SAFE_TIME);
705     }
706   else
707     {
708       physic.enable_gravity(true);
709       physic.set_acceleration(0, 0);
710       physic.set_velocity(0, 7);
711       dying = DYING_SQUISHED;
712     }
713 }
714
715 void
716 Player::is_dying()
717 {
718   remove_powerups();
719   dying = DYING_NOT;
720 }
721
722 bool Player::is_dead()
723 {
724   if(base.y > screen->h)
725     return true;
726   else
727     return false;
728 }
729
730 /* Remove Tux's power ups */
731 void
732 Player::remove_powerups()
733 {
734   got_coffee = false;
735   size = SMALL;
736   base.height = 32;
737 }
738
739 void
740 Player::check_bounds()
741 {
742   /* Keep tux in bounds: */
743   if (base.x < 0)
744     { // Lock Tux to the size of the level, so that he doesn't fall of
745       // on the left side
746       base.x = 0;
747     }
748
749   /* Keep in-bounds, vertically: */
750   if (base.y > screen->h)
751     {
752       kill(KILL);
753     }
754
755   if(base.x < scroll_x)  // can happen if back scrolling is disabled
756     base.x = scroll_x;
757 }
758
759 // EOF //
760