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