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