4ff4db4aa968d9b67fcb7bd6230806235f2cc582
[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
22 #include "gameloop.h"
23 #include "globals.h"
24 #include "player.h"
25 #include "defines.h"
26 #include "scene.h"
27 #include "tile.h"
28 #include "sprite.h"
29 #include "screen.h"
30
31 Surface* tux_life;
32
33 Sprite* smalltux_gameover;
34 Sprite* smalltux_star;
35 Sprite* largetux_star;
36
37 PlayerSprite smalltux;
38 PlayerSprite largetux;
39 PlayerSprite firetux;
40
41 PlayerKeymap keymap;
42
43 PlayerKeymap::PlayerKeymap()
44 {
45   keymap.jump  = SDLK_UP;
46   keymap.duck  = SDLK_DOWN;
47   keymap.left  = SDLK_LEFT;
48   keymap.right = SDLK_RIGHT;
49   keymap.fire  = SDLK_LCTRL;
50 }
51
52 void player_input_init(player_input_type* pplayer_input)
53 {
54   pplayer_input->down = UP;
55   pplayer_input->fire = UP;
56   pplayer_input->left = UP;
57   pplayer_input->old_fire = UP;
58   pplayer_input->right = UP;
59   pplayer_input->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       keep_in_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 (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       can_jump = true;
400       if(physic.get_velocity_y() > 0) {
401         physic.set_velocity_y(0);
402       }
403     }
404 }
405
406 void
407 Player::handle_input()
408 {
409   /* Handle horizontal movement: */
410     handle_horizontal_input();
411
412   /* Jump/jumping? */
413
414   if (on_ground() && input.up == UP)
415     can_jump = true;
416   if (input.up == DOWN || (input.up == UP && jumping))
417     {
418       handle_vertical_input();
419     }
420
421   /* Shoot! */
422
423   if (input.fire == DOWN && input.old_fire == UP && got_coffee)
424     {
425       World::current()->add_bullet(base.x, base.y, physic.get_velocity_x(), dir);
426       input.old_fire = DOWN;
427     }
428
429   /* tux animations: */
430   if(!frame_timer.check())
431     {
432       frame_timer.start(25);
433       if (input.right == UP && input.left == UP)
434         {
435           frame_main = 1;
436           frame_ = 1;
437         }
438       else
439         {
440           if ((input.fire == DOWN && (global_frame_counter % 2) == 0) ||
441               (global_frame_counter % 4) == 0)
442             frame_main = (frame_main + 1) % 4;
443
444           frame_ = frame_main;
445
446           if (frame_ == 3)
447             frame_ = 1;
448         }
449     }
450
451   /* Duck! */
452   if (input.down == DOWN && size == BIG && !duck && physic.get_velocity_y() == 0 && on_ground())
453     {
454       duck = true;
455       base.height = 32;                             
456       base.y += 32;
457       // changing base size confuses collision otherwise
458       old_base = previous_base = base;
459     }
460   else if(input.down == UP && size == BIG && duck && physic.get_velocity_y() == 0 && on_ground())
461     {
462       duck = false;
463       base.y -= 32;
464       base.height = 64;
465       // changing base size confuses collision otherwise
466       old_base = previous_base = base;                        
467     }
468 }
469
470 void
471 Player::grow()
472 {
473   if(size == BIG)
474     return;
475   
476   size = BIG;
477   base.height = 64;
478   base.y -= 32;
479
480   old_base = previous_base = base;
481 }
482
483 void
484 Player::grabdistros()
485 {
486   /* Grab distros: */
487   if (!dying)
488     {
489       World::current()->trygrabdistro(base.x, base.y, NO_BOUNCE);
490       World::current()->trygrabdistro(base.x+ 31, base.y, NO_BOUNCE);
491
492       World::current()->trygrabdistro(base.x, base.y + base.height, NO_BOUNCE);
493       World::current()->trygrabdistro(base.x+ 31, base.y + base.height, NO_BOUNCE);
494
495       if(size == BIG)
496         {
497           World::current()->trygrabdistro(base.x, base.y + base.height / 2, NO_BOUNCE);
498           World::current()->trygrabdistro(base.x+ 31, base.y + base.height / 2, NO_BOUNCE);
499         }
500
501     }
502
503   /* Enough distros for a One-up? */
504   if (player_status.distros >= DISTROS_LIFEUP)
505     {
506       player_status.distros = player_status.distros - DISTROS_LIFEUP;
507       if(player_status.lives < MAX_LIVES)
508         ++player_status.lives;
509       /*We want to hear the sound even, if MAX_LIVES is reached*/
510       play_sound(sounds[SND_LIFEUP], SOUND_CENTER_SPEAKER);
511     }
512 }
513
514 void
515 Player::draw()
516 {
517   if (!safe_timer.started() || (global_frame_counter % 2) == 0)
518     {
519       if (dying == DYING_SQUISHED)
520         {
521           smalltux_gameover->draw(base.x - scroll_x, base.y);
522         }
523       else
524         {
525           PlayerSprite* sprite;
526           
527           if (size == SMALL)
528             sprite = &smalltux;
529           else if (got_coffee)
530             sprite = &firetux;
531           else
532             sprite = &largetux;
533           
534           if (duck && size != SMALL)
535             {
536               if (dir == RIGHT)
537                 sprite->duck_right->draw(base.x - scroll_x, base.y);
538               else 
539                 sprite->duck_left->draw(base.x - scroll_x, base.y);
540             }
541           else if (skidding_timer.started())
542             {
543               if (dir == RIGHT)
544                 sprite->skid_right->draw(base.x - scroll_x, base.y);
545               else
546                 sprite->skid_left->draw(base.x - scroll_x, base.y); 
547             }
548           else if (kick_timer.started())
549             {
550               if (dir == RIGHT)
551                 sprite->kick_right->draw(base.x - scroll_x, base.y);
552               else
553                 sprite->kick_left->draw(base.x - scroll_x, base.y); 
554             }
555           else if (physic.get_velocity_y() != 0)
556             {
557               if (dir == RIGHT)
558                 sprite->jump_right->draw(base.x - scroll_x, base.y);
559               else
560                 sprite->jump_left->draw(base.x - scroll_x, base.y);                   
561             }
562           else
563             {
564               if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
565                 {
566                   if (dir == RIGHT)
567                     sprite->stand_right->draw( base.x - scroll_x, base.y);
568                   else
569                     sprite->stand_left->draw( base.x - scroll_x, base.y);
570                 }
571               else // moving
572                 {
573                   if (dir == RIGHT)
574                     sprite->walk_right->draw(base.x - scroll_x, base.y);
575                   else
576                     sprite->walk_left->draw(base.x - scroll_x, base.y);
577                 }
578             }
579                       
580           // Draw arm overlay graphics when Tux is holding something
581           if (holding_something && physic.get_velocity_y() == 0)
582             {
583               if (dir == RIGHT)
584                 sprite->grab_right->draw(base.x - scroll_x, base.y);
585               else
586                 sprite->grab_left->draw(base.x - scroll_x, base.y);
587             }
588
589           // Draw blinking star overlay
590           if (invincible_timer.started() && (invincible_timer.get_left() > TUX_INVINCIBLE_TIME_WARNING || global_frame_counter % 2))
591             {
592               if (size == SMALL || duck)
593                 smalltux_star->draw(base.x - scroll_x, base.y);
594               else
595                 largetux_star->draw(base.x - scroll_x, base.y);
596             }
597         }
598     }     
599   
600   if (debug_mode)
601     fillrect(base.x - scroll_x, base.y, 
602              base.width, base.height, 75,75,75, 150);
603 }
604
605 void
606 Player::collision(void* p_c_object, int c_object)
607 {
608   BadGuy* pbad_c = NULL;
609
610   switch (c_object)
611     {
612     case CO_BADGUY:
613       pbad_c = (BadGuy*) p_c_object;
614
615      /* Hurt player if he touches a badguy */
616       if (!pbad_c->dying && !dying &&
617           !safe_timer.started() &&
618           pbad_c->mode != BadGuy::HELD)
619         {
620           if (pbad_c->mode == BadGuy::FLAT && input.fire == DOWN
621                && !holding_something)
622             {
623               holding_something = true;
624               pbad_c->mode = BadGuy::HELD;
625               pbad_c->base.y-=8;
626             }
627           else if (pbad_c->mode == BadGuy::FLAT)
628             {
629               // Don't get hurt if we're kicking a flat badguy!
630             }
631           else if (pbad_c->mode == BadGuy::KICK)
632             {
633               /* Hurt if you get hit by kicked laptop: */
634               if (!invincible_timer.started())
635                 {
636                   kill(SHRINK);
637                 }
638               else
639                 {
640                    pbad_c->dying = DYING_FALLING;
641                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
642                    World::current()->add_score(pbad_c->base.x - scroll_x,
643                                                pbad_c->base.y,
644                                                25 * player_status.score_multiplier);
645                 }
646             }
647           else
648             {
649               if (!invincible_timer.started())
650                 {
651                   kill(SHRINK);
652                 }
653               else
654                 {
655                   pbad_c->kill_me(25);
656                 }
657             }
658           player_status.score_multiplier++;
659         }
660       break;
661     default:
662       break;
663     }
664
665 }
666
667 /* Kill Player! */
668
669 void
670 Player::kill(HurtMode mode)
671 {
672   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
673
674   physic.set_velocity_x(0);
675
676   if (mode == SHRINK && size == BIG)
677     {
678       if (got_coffee)
679         {
680           got_coffee = false;
681         }
682       else
683         {
684           size = SMALL;
685           base.height = 32;
686           duck = false;
687         }
688       safe_timer.start(TUX_SAFE_TIME);
689     }
690   else
691     {
692       physic.enable_gravity(true);
693       physic.set_acceleration(0, 0);
694       physic.set_velocity(0, 7);
695       dying = DYING_SQUISHED;
696     }
697 }
698
699 void
700 Player::is_dying()
701 {
702   remove_powerups();
703   dying = DYING_NOT;
704 }
705
706 bool Player::is_dead()
707 {
708   if(base.y > screen->h)
709     return true;
710   else
711     return false;
712 }
713
714 /* Remove Tux's power ups */
715 void
716 Player::remove_powerups()
717 {
718   got_coffee = false;
719   size = SMALL;
720   base.height = 32;
721 }
722
723 void
724 Player::keep_in_bounds()
725 {
726   Level* plevel = World::current()->get_level();
727
728   /* Keep tux in bounds: */
729   if (base.x < 0)
730     { // Lock Tux to the size of the level, so that he doesn't fall of
731       // on the left side
732       base.x = 0;
733     }
734   else if (base.x < scroll_x)
735     { 
736       base.x = scroll_x;
737     }
738
739   /* Keep in-bounds, vertically: */
740   if (base.y > screen->h)
741     {
742       kill(KILL);
743     }
744
745   int scroll_threshold = screen->w/2 - 80;
746   if (debug_mode)
747     {
748       scroll_x += screen->w/2;
749       // Backscrolling for debug mode
750       if (scroll_x < base.x - 80)
751         scroll_x = base.x - 80;
752       else if (scroll_x > base.x + 80)
753         scroll_x = base.x + 80;
754       scroll_x -= screen->w/2;
755
756       if(scroll_x < 0)
757         scroll_x = 0;
758     }
759   else
760     {
761       if (base.x > scroll_threshold + scroll_x
762           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
763         {
764           // FIXME: Scrolling needs to be handled by a seperate View
765           // class, doing it as a player huck is ugly
766           
767           // Scroll the screen in past center:
768           scroll_x = base.x - scroll_threshold;
769           
770           // Lock the scrolling to the levelsize, so that we don't
771           // scroll over the right border
772           if (scroll_x > 32 * plevel->width - screen->w)
773             scroll_x = 32 * plevel->width - screen->w;
774         }
775     }
776 }
777
778 // EOF //
779