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