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