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