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