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