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