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