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