5018a617650083e19a216ac6049a7416f342fcc4
[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 std::vector<Surface*> tux_right;
33 std::vector<Surface*> tux_left;
34 Surface* smalltux_jump_left;
35 Surface* smalltux_jump_right;
36 Surface* smalltux_stand_left;
37 Surface* smalltux_stand_right;
38
39 Sprite* bigtux_right;
40 Sprite* bigtux_left;
41 Sprite* bigtux_right_jump;
42 Sprite* bigtux_left_jump;
43 Sprite* ducktux_right;
44 Sprite* ducktux_left;
45 Surface* skidtux_right;
46 Surface* skidtux_left;
47 Surface* firetux_right[3];
48 Surface* firetux_left[3];
49 Surface* bigfiretux_right[3];
50 Surface* bigfiretux_left[3];
51 Surface* bigfiretux_right_jump;
52 Surface* bigfiretux_left_jump;
53 Surface* duckfiretux_right;
54 Surface* duckfiretux_left;
55 Surface* skidfiretux_right;
56 Surface* skidfiretux_left;
57 Surface* cape_right[2];
58 Surface* cape_left[2];
59 Surface* bigcape_right[2];
60 Surface* bigcape_left[2];
61
62 PlayerKeymap keymap;
63
64 PlayerKeymap::PlayerKeymap()
65 {
66   keymap.jump  = SDLK_UP;
67   keymap.duck  = SDLK_DOWN;
68   keymap.left  = SDLK_LEFT;
69   keymap.right = SDLK_RIGHT;
70   keymap.fire  = SDLK_LCTRL;
71 }
72
73 void player_input_init(player_input_type* pplayer_input)
74 {
75   pplayer_input->down = UP;
76   pplayer_input->fire = UP;
77   pplayer_input->left = UP;
78   pplayer_input->old_fire = UP;
79   pplayer_input->right = UP;
80   pplayer_input->up = UP;
81 }
82
83 void
84 Player::init()
85 {
86   Level* plevel = World::current()->get_level();
87
88   base.width = 32;
89   base.height = 32;
90
91   size = SMALL;
92   got_coffee = false;
93
94   base.x = plevel->start_pos_x;
95   base.y = plevel->start_pos_y;
96   base.xm = 0;
97   base.ym = 0;
98   previous_base = old_base = base;
99   dir = RIGHT;
100   duck = false;
101
102   dying   = DYING_NOT;
103   jumping = false;
104
105   frame_main = 0;
106   frame_ = 0;
107   
108   player_input_init(&input);
109
110   invincible_timer.init(true);
111   skidding_timer.init(true);
112   safe_timer.init(true);
113   frame_timer.init(true);
114
115   physic.reset();
116 }
117
118 int
119 Player::key_event(SDLKey key, int state)
120 {
121   if(key == keymap.right)
122     {
123       input.right = state;
124       return true;
125     }
126   else if(key == keymap.left)
127     {
128       input.left = state;
129       return true;
130     }
131   else if(key == keymap.jump)
132     {
133       input.up = state;
134       return true;
135     }
136   else if(key == keymap.duck)
137     {
138       input.down = state;
139       return true;
140     }
141   else if(key == keymap.fire)
142     {
143       input.fire = state;
144       return true;
145     }
146   else
147     return false;
148 }
149
150 void
151 Player::level_begin()
152 {
153   base.x  = 100;
154   base.y  = 170;
155   base.xm = 0;
156   base.ym = 0;
157   previous_base = old_base = base;
158   duck = false;
159
160   dying = DYING_NOT;
161
162   player_input_init(&input);
163
164   invincible_timer.init(true);
165   skidding_timer.init(true);
166   safe_timer.init(true);
167   frame_timer.init(true);
168
169   physic.reset();
170 }
171
172 void
173 Player::action(double frame_ratio)
174 {
175   bool jumped_in_solid = false;
176
177   /* --- HANDLE TUX! --- */
178
179   if(dying == DYING_NOT)
180     handle_input();
181
182   /* Move tux: */
183   previous_base = base;
184
185   physic.apply(frame_ratio, base.x, base.y);
186   if(dying == DYING_NOT) {
187       collision_swept_object_map(&old_base, &base);
188       // special exception for cases where we're stuck under tiles after
189       // being ducked. In this case we drift out
190       if(!duck && on_ground() && old_base.x == base.x && old_base.y == base.y
191               && collision_object_map(&base)) {
192           base.x += frame_ratio * WALK_SPEED * (dir ? 1 : -1);
193           previous_base = old_base = base;
194       }
195       keep_in_bounds();
196   }
197
198   if (dying == DYING_NOT)
199     {
200       /* Land: */
201
202
203       if( !on_ground())
204         {
205           physic.enable_gravity(true);
206           if(under_solid())
207             {
208               // fall down
209               physic.set_velocity(physic.get_velocity_x(), 0);
210               jumped_in_solid = true;
211             }
212         }
213       else
214         {
215           /* Land: */
216           if (physic.get_velocity_y() < 0)
217             {
218               base.y = (int)(((int)base.y / 32) * 32);
219               physic.set_velocity(physic.get_velocity_x(), 0);
220             }
221
222           physic.enable_gravity(false);
223           /* Reset score multiplier (for multi-hits): */
224           player_status.score_multiplier = 1;
225         }
226
227       if(jumped_in_solid)
228         {
229           if (isbrick(base.x, base.y) ||
230               isfullbox(base.x, base.y))
231             {
232               World::current()->trygrabdistro(base.x, base.y - 32,BOUNCE);
233               World::current()->trybumpbadguy(base.x, base.y - 64);
234
235               World::current()->trybreakbrick(base.x, base.y, size == SMALL);
236
237               bumpbrick(base.x, base.y);
238               World::current()->tryemptybox(base.x, base.y, RIGHT);
239             }
240
241           if (isbrick(base.x+ 31, base.y) ||
242               isfullbox(base.x+ 31, base.y))
243             {
244               World::current()->trygrabdistro(base.x+ 31, base.y - 32,BOUNCE);
245               World::current()->trybumpbadguy(base.x+ 31, base.y - 64);
246
247               if(size == BIG)
248                 World::current()->trybreakbrick(base.x+ 31, base.y, size == SMALL);
249
250               bumpbrick(base.x+ 31, base.y);
251               World::current()->tryemptybox(base.x+ 31, base.y, LEFT);
252             }
253         }
254
255       grabdistros();
256
257       if (jumped_in_solid)
258         {
259           ++base.y;
260           ++old_base.y;
261           if(on_ground())
262             {
263               /* Make sure jumping is off. */
264               jumping = false;
265             }
266         }
267     }
268
269
270   /* ---- DONE HANDLING TUX! --- */
271
272   /* Handle invincibility timer: */
273   if (get_current_music() == HERRING_MUSIC && !invincible_timer.check())
274     {
275       /*
276          no, we are no more invincible
277          or we were not in invincible mode
278          but are we in hurry ?
279        */
280
281       // FIXME: Move this to gamesession
282       if (GameSession::current()->time_left.get_left() < TIME_WARNING)
283         {
284           /* yes, we are in hurry
285              stop the herring_song, prepare to play the correct
286              fast level_song !
287            */
288           set_current_music(HURRYUP_MUSIC);
289         }
290       else
291         {
292           set_current_music(LEVEL_MUSIC);
293         }
294
295       /* start playing it */
296       play_current_music();
297     }
298
299   // check some timers
300   skidding_timer.check();
301   invincible_timer.check();
302   safe_timer.check();
303 }
304
305 bool
306 Player::on_ground()
307 {
308   return ( issolid(base.x + base.width / 2, base.y + base.height) ||
309            issolid(base.x + 1, base.y + base.height) ||
310            issolid(base.x + base.width - 1, base.y + base.height)  );
311 }
312
313 bool
314 Player::under_solid()
315 {
316   return ( issolid(base.x + base.width / 2, base.y) ||
317            issolid(base.x + 1, base.y) ||
318            issolid(base.x + base.width - 1, base.y)  );
319 }
320
321 void
322 Player::handle_horizontal_input()
323 {
324   float vx = physic.get_velocity_x();
325   float vy = physic.get_velocity_y();
326   float ax = physic.get_acceleration_x();
327   float ay = physic.get_acceleration_y();
328
329   float dirsign = 0;
330   if(input.left == DOWN && input.right == UP && (!duck || physic.get_velocity_y() != 0)) {
331       dir = LEFT;
332       dirsign = -1;
333   } else if(input.left == UP && input.right == DOWN && (!duck || physic.get_velocity_y() != 0)) {
334       dir = RIGHT;
335       dirsign = 1;
336   }
337
338   if (input.fire == UP) {
339       ax = dirsign * WALK_ACCELERATION_X;
340       // limit speed
341       if(vx >= MAX_WALK_XM && dirsign > 0) {
342         vx = MAX_WALK_XM;
343         ax = 0;
344       } else if(vx <= -MAX_WALK_XM && dirsign < 0) {
345         vx = -MAX_WALK_XM;
346         ax = 0;
347       }
348   } else {
349       ax = dirsign * RUN_ACCELERATION_X;
350       // limit speed
351       if(vx >= MAX_RUN_XM && dirsign > 0) {
352         vx = MAX_RUN_XM;
353         ax = 0;
354       } else if(vx <= -MAX_RUN_XM && dirsign < 0) {
355         vx = -MAX_RUN_XM;
356         ax = 0;
357       }
358   }
359
360   // we can reach WALK_SPEED without any acceleration
361   if(dirsign != 0 && fabs(vx) < WALK_SPEED) {
362     vx = dirsign * WALK_SPEED;
363   }
364
365   // changing directions?
366   if(on_ground() && ((vx < 0 && dirsign >0) || (vx>0 && dirsign<0))) {
367       if(fabs(vx)>SKID_XM && !skidding_timer.check()) {
368           skidding_timer.start(SKID_TIME);
369           play_sound(sounds[SND_SKID], SOUND_CENTER_SPEAKER);
370           ax *= 2.5;
371       } else {
372           ax *= 2;
373       }
374   }
375
376   // we get slower when not pressing any keys
377   if(dirsign == 0) {
378       if(fabs(vx) < WALK_SPEED) {
379           vx = 0;
380           ax = 0;
381       } else if(vx < 0) {
382           ax = WALK_ACCELERATION_X * 1.5;
383       } else {
384           ax = WALK_ACCELERATION_X * -1.5;
385       }
386   }
387
388   // if we're on ice slow down acceleration or deceleration
389   if (isice(base.x, base.y + base.height))
390   {
391     /* the acceleration/deceleration rate on ice is inversely proportional to
392      * the current velocity.
393      */
394
395     // increasing 1 will increase acceleration/deceleration rate
396     // decreasing 1 will decrease acceleration/deceleration rate
397     //  must stay above zero, though
398     if (ax != 0) ax *= 1 / fabs(vx);
399   }
400
401   physic.set_velocity(vx, vy);
402   physic.set_acceleration(ax, ay);
403 }
404
405 void
406 Player::handle_vertical_input()
407 {
408   if(input.up == DOWN)
409     {
410       if (on_ground())
411         {
412           // jump
413           physic.set_velocity(physic.get_velocity_x(), 5.5);
414           --base.y;
415           jumping = true;
416           if (size == SMALL)
417             play_sound(sounds[SND_JUMP], SOUND_CENTER_SPEAKER);
418           else
419             play_sound(sounds[SND_BIGJUMP], SOUND_CENTER_SPEAKER);
420         }
421     }
422   else if(input.up == UP && jumping)
423     {
424       jumping = false;
425       if(physic.get_velocity_y() > 0) {
426         physic.set_velocity(physic.get_velocity_x(), 0);
427       }
428     }
429 }
430
431 void
432 Player::handle_input()
433 {
434   /* Handle horizontal movement: */
435     handle_horizontal_input();
436
437   /* Jump/jumping? */
438
439   if ( input.up == DOWN || (input.up == UP && jumping))
440     {
441       handle_vertical_input();
442     }
443
444   /* Shoot! */
445
446   if (input.fire == DOWN && input.old_fire == UP && got_coffee)
447     {
448       World::current()->add_bullet(base.x, base.y, physic.get_velocity_x(), dir);
449     }
450
451   /* tux animations: */
452   if(!frame_timer.check())
453     {
454       frame_timer.start(25);
455       if (input.right == UP && input.left == UP)
456         {
457           frame_main = 1;
458           frame_ = 1;
459         }
460       else
461         {
462           if ((input.fire == DOWN && (global_frame_counter % 2) == 0) ||
463               (global_frame_counter % 4) == 0)
464             frame_main = (frame_main + 1) % 4;
465
466           frame_ = frame_main;
467
468           if (frame_ == 3)
469             frame_ = 1;
470         }
471     }
472
473   /* Duck! */
474   if (input.down == DOWN && size == BIG && !duck && physic.get_velocity_y() == 0)
475     {
476       duck = true;
477       base.height = 32;                             
478       base.y += 32;
479       // changing base size confuses collision otherwise
480       old_base = previous_base = base;
481     }
482   else if(input.down == UP && size == BIG && duck && physic.get_velocity_y() == 0)
483     {
484       duck = false;
485       base.y -= 32;
486       base.height = 64;
487       old_base = previous_base = base;
488     }
489 }
490
491 void
492 Player::grabdistros()
493 {
494   /* Grab distros: */
495   if (!dying)
496     {
497       World::current()->trygrabdistro(base.x, base.y, NO_BOUNCE);
498       World::current()->trygrabdistro(base.x+ 31, base.y, NO_BOUNCE);
499
500       World::current()->trygrabdistro(base.x, base.y + base.height, NO_BOUNCE);
501       World::current()->trygrabdistro(base.x+ 31, base.y + base.height, NO_BOUNCE);
502
503       if(size == BIG)
504         {
505           World::current()->trygrabdistro(base.x, base.y + base.height / 2, NO_BOUNCE);
506           World::current()->trygrabdistro(base.x+ 31, base.y + base.height / 2, NO_BOUNCE);
507         }
508
509     }
510
511   /* Enough distros for a One-up? */
512   if (player_status.distros >= DISTROS_LIFEUP)
513     {
514       player_status.distros = player_status.distros - DISTROS_LIFEUP;
515       if(player_status.lives < MAX_LIVES)
516         ++player_status.lives;
517       /*We want to hear the sound even, if MAX_LIVES is reached*/
518       play_sound(sounds[SND_LIFEUP], SOUND_CENTER_SPEAKER);
519     }
520 }
521
522 void
523 Player::draw()
524 {
525   if (!safe_timer.started() || (global_frame_counter % 2) == 0)
526     {
527       if (size == SMALL)
528         {
529           if (invincible_timer.started())
530             {
531               /* Draw cape: */
532
533               if (dir == RIGHT)
534                 cape_right[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
535               else
536                 cape_left[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
537             }
538
539
540           if (!got_coffee)
541             {
542               if (physic.get_velocity_y() != 0)
543                 {
544                   if (dir == RIGHT)
545                     smalltux_jump_right->draw( base.x - scroll_x, base.y - 10);
546                   else
547                     smalltux_jump_left->draw( base.x - scroll_x, base.y - 10);                   
548                 }
549               else
550                 {
551                   if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
552                     {
553                       if (dir == RIGHT)
554                         smalltux_stand_right->draw( base.x - scroll_x, base.y - 9);
555                       else
556                         smalltux_stand_left->draw( base.x - scroll_x, base.y - 9);
557                     }
558                   else // moving
559                     {
560                       if (dir == RIGHT)
561                         tux_right[(global_frame_counter/2) % tux_right.size()]->draw(base.x - scroll_x, base.y - 9);
562                       else
563                         tux_left[(global_frame_counter/2) % tux_left.size()]->draw(base.x - scroll_x, base.y - 9);
564                     }
565                 }
566             }
567           else
568             {
569               /* Tux got coffee! */
570
571               if (dir == RIGHT)
572                 {
573                   firetux_right[frame_]->draw( base.x- scroll_x, base.y);
574                 }
575               else
576                 {
577                   firetux_left[frame_]->draw( base.x- scroll_x, base.y);
578                 }
579             }
580         }
581       else
582         {
583           if (invincible_timer.started())
584             {
585               float capex = base.x + (base.width - bigcape_right[0]->w) / 2;
586               capex -= scroll_x;
587               float capey = base.y + (base.height - bigcape_right[0]->h) / 2;
588                 
589               /* Draw cape (just not in ducked mode since that looks silly): */
590               if (dir == RIGHT)
591                 bigcape_right[global_frame_counter % 2]->draw(capex, capey);
592               else
593                 bigcape_left[global_frame_counter % 2]->draw(capex, capey);
594             }
595
596           if (!got_coffee)
597             {
598               if (!duck)
599                 {
600                   if (!skidding_timer.started())
601                     {
602                       if (physic.get_velocity_y() == 0)
603                         {
604                           if (dir == RIGHT)
605                             bigtux_right->draw(base.x - scroll_x, base.y);
606                           else
607                             bigtux_left->draw(base.x - scroll_x, base.y);
608                         }
609                       else
610                         {
611                           if (dir == RIGHT)
612                             bigtux_right_jump->draw(base.x - scroll_x, base.y);
613                           else
614                             bigtux_left_jump->draw(base.x - scroll_x, base.y);
615                         }
616                     }
617                   else
618                     {
619                       if (dir == RIGHT)
620                         skidtux_right->draw(base.x - scroll_x - 8, base.y);
621                       else
622                         skidtux_left->draw(base.x - scroll_x - 8, base.y);
623                     }
624                 }
625               else
626                 {
627                   if (dir == RIGHT)
628                     ducktux_right->draw(base.x - scroll_x, base.y);
629                   else
630                     ducktux_left->draw(base.x - scroll_x, base.y);
631                 }
632             }
633           else
634             {
635               /* Tux has coffee! */
636               if (!duck)
637                 {
638                   if (!skidding_timer.started())
639                     {
640                       if (!jumping || physic.get_velocity_y() > 0)
641                         {
642                           if (dir == RIGHT)
643                             bigfiretux_right[frame_]->draw(base.x- scroll_x - 8, base.y);
644                           else
645                             bigfiretux_left[frame_]->draw(base.x- scroll_x - 8, base.y);
646                         }
647                       else
648                         {
649                           if (dir == RIGHT)
650                             bigfiretux_right_jump->draw(base.x- scroll_x - 8, base.y);
651                           else
652                             bigfiretux_left_jump->draw(base.x- scroll_x - 8, base.y);
653                         }
654                     }
655                   else
656                     {
657                       if (dir == RIGHT)
658                         skidfiretux_right->draw(base.x- scroll_x - 8, base.y);
659                       else
660                         skidfiretux_left->draw(base.x- scroll_x - 8, base.y);
661                     }
662                 }
663               else
664                 {
665                   if (dir == RIGHT)
666                     duckfiretux_right->draw( base.x- scroll_x - 8, base.y - 16);
667                   else
668                     duckfiretux_left->draw( base.x- scroll_x - 8, base.y - 16);
669                 }
670             }
671         }
672     }
673
674   if (debug_mode)
675     fillrect(base.x - scroll_x, base.y, 32, 32, 75,75,75, 150);
676 }
677
678 void
679 Player::collision(void* p_c_object, int c_object)
680 {
681   BadGuy* pbad_c = NULL;
682
683   switch (c_object)
684     {
685     case CO_BADGUY:
686       pbad_c = (BadGuy*) p_c_object;
687
688      /* Hurt player if he touches a badguy */
689       if (!pbad_c->dying && !dying &&
690           !safe_timer.started() &&
691           pbad_c->mode != HELD)
692         {
693           if (pbad_c->mode == FLAT && input.fire == DOWN)
694             {
695               pbad_c->mode = HELD;
696               pbad_c->base.y-=8;
697             }
698           else if (pbad_c->mode == FLAT)
699             {
700               // Don't get hurt if we're kicking a flat badguy!
701             }
702           else if (pbad_c->mode == KICK)
703             {
704               /* Hurt if you get hit by kicked laptop: */
705               if (!invincible_timer.started())
706                 {
707                   kill(SHRINK);
708                 }
709               else
710                 {
711                    pbad_c->dying = DYING_FALLING;
712                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
713                    World::current()->add_score(pbad_c->base.x - scroll_x,
714                                                pbad_c->base.y,
715                                                25 * player_status.score_multiplier);
716                 }
717             }
718           else
719             {
720               if (!invincible_timer.started())
721                 {
722                   kill(SHRINK);
723                 }
724               else
725                 {
726                   pbad_c->kill_me();
727                 }
728             }
729           player_status.score_multiplier++;
730         }
731       break;
732     default:
733       break;
734     }
735
736 }
737
738 /* Kill Player! */
739
740 void
741 Player::kill(int mode)
742 {
743   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
744
745   physic.set_velocity(0, physic.get_velocity_y());
746
747   if (mode == SHRINK && size == BIG)
748     {
749       if (got_coffee)
750         got_coffee = false;
751
752       size = SMALL;
753       base.height = 32;
754       duck = false;
755
756       safe_timer.start(TUX_SAFE_TIME);
757     }
758   else
759     {
760       physic.enable_gravity(true);
761       physic.set_acceleration(0, 0);
762       physic.set_velocity(0, 7);
763       dying = DYING_SQUISHED;
764     }
765 }
766
767 void
768 Player::is_dying()
769 {
770   remove_powerups();
771   dying = DYING_NOT;
772 }
773
774 bool Player::is_dead()
775 {
776   if(base.y > screen->h)
777     return true;
778   else
779     return false;
780 }
781
782 /* Remove Tux's power ups */
783 void
784 Player::remove_powerups()
785 {
786   got_coffee = false;
787   size = SMALL;
788   base.height = 32;
789 }
790
791 void
792 Player::keep_in_bounds()
793 {
794   Level* plevel = World::current()->get_level();
795
796   /* Keep tux in bounds: */
797   if (base.x < 0)
798     { // Lock Tux to the size of the level, so that he doesn't fall of
799       // on the left side
800       base.x = 0;
801     }
802   else if (base.x < scroll_x)
803     { 
804       base.x = scroll_x;
805     }
806
807   /* Keep in-bounds, vertically: */
808   if (base.y > screen->h)
809     {
810       kill(KILL);
811     }
812
813   int scroll_threshold = screen->w/2 - 80;
814   if (debug_mode)
815     {
816       scroll_x += screen->w/2;
817       // Backscrolling for debug mode
818       if (scroll_x < base.x - 80)
819         scroll_x = base.x - 80;
820       else if (scroll_x > base.x + 80)
821         scroll_x = base.x + 80;
822       scroll_x -= screen->w/2;
823
824       if(scroll_x < 0)
825         scroll_x = 0;
826     }
827   else
828     {
829       if (base.x > scroll_threshold + scroll_x
830           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
831         {
832           // FIXME: Scrolling needs to be handled by a seperate View
833           // class, doing it as a player huck is ugly
834           
835           // Scroll the screen in past center:
836           scroll_x = base.x - scroll_threshold;
837           
838           // Lock the scrolling to the levelsize, so that we don't
839           // scroll over the right border
840           if (scroll_x > 32 * plevel->width - screen->w)
841             scroll_x = 32 * plevel->width - screen->w;
842         }
843     }
844 }
845
846 // EOF //
847