6b4db82690bcc8ab5440b9e3932abe03ab2cd180
[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     {
188       base_type target = base;
189
190       collision_swept_object_map(&old_base, &base);
191
192       // Don't accelerate Tux if he is running against a wall
193       if (target.x != base.x)
194         {
195           physic.set_velocity_x(0);
196         }
197
198       // special exception for cases where we're stuck under tiles after
199       // being ducked. In this case we drift out
200       if(!duck && on_ground() && old_base.x == base.x && old_base.y == base.y
201          && collision_object_map(&base)) {
202         base.x += frame_ratio * WALK_SPEED * (dir ? 1 : -1);
203         previous_base = old_base = base;
204       }
205       keep_in_bounds();
206     }
207
208   if (dying == DYING_NOT)
209     {
210       /* Land: */
211
212
213       if( !on_ground())
214         {
215           physic.enable_gravity(true);
216           if(under_solid())
217             {
218               // fall down
219               physic.set_velocity_y(0);
220               jumped_in_solid = true;
221             }
222         }
223       else
224         {
225           /* Land: */
226           if (physic.get_velocity_y() < 0)
227             {
228               base.y = (int)(((int)base.y / 32) * 32);
229               physic.set_velocity_y(0);
230             }
231
232           physic.enable_gravity(false);
233           /* Reset score multiplier (for multi-hits): */
234           player_status.score_multiplier = 1;
235         }
236
237       if(jumped_in_solid)
238         {
239           if (isbrick(base.x, base.y) ||
240               isfullbox(base.x, base.y))
241             {
242               World::current()->trygrabdistro(base.x, base.y - 32,BOUNCE);
243               World::current()->trybumpbadguy(base.x, base.y - 64);
244
245               World::current()->trybreakbrick(base.x, base.y, size == SMALL);
246
247               bumpbrick(base.x, base.y);
248               World::current()->tryemptybox(base.x, base.y, RIGHT);
249             }
250
251           if (isbrick(base.x+ 31, base.y) ||
252               isfullbox(base.x+ 31, base.y))
253             {
254               World::current()->trygrabdistro(base.x+ 31, base.y - 32,BOUNCE);
255               World::current()->trybumpbadguy(base.x+ 31, base.y - 64);
256
257               if(size == BIG)
258                 World::current()->trybreakbrick(base.x+ 31, base.y, size == SMALL);
259
260               bumpbrick(base.x+ 31, base.y);
261               World::current()->tryemptybox(base.x+ 31, base.y, LEFT);
262             }
263         }
264
265       grabdistros();
266
267       if (jumped_in_solid)
268         {
269           ++base.y;
270           ++old_base.y;
271           if(on_ground())
272             {
273               /* Make sure jumping is off. */
274               jumping = false;
275             }
276         }
277     }
278
279   /* ---- DONE HANDLING TUX! --- */
280
281   // check some timers
282   skidding_timer.check();
283   invincible_timer.check();
284   safe_timer.check();
285 }
286
287 bool
288 Player::on_ground()
289 {
290   return ( issolid(base.x + base.width / 2, base.y + base.height) ||
291            issolid(base.x + 1, base.y + base.height) ||
292            issolid(base.x + base.width - 1, base.y + base.height)  );
293 }
294
295 bool
296 Player::under_solid()
297 {
298   return ( issolid(base.x + base.width / 2, base.y) ||
299            issolid(base.x + 1, base.y) ||
300            issolid(base.x + base.width - 1, base.y)  );
301 }
302
303 void
304 Player::handle_horizontal_input()
305 {
306   float vx = physic.get_velocity_x();
307   float vy = physic.get_velocity_y();
308   float ax = physic.get_acceleration_x();
309   float ay = physic.get_acceleration_y();
310
311   float dirsign = 0;
312   if(input.left == DOWN && input.right == UP && (!duck || physic.get_velocity_y() != 0)) {
313       dir = LEFT;
314       dirsign = -1;
315   } else if(input.left == UP && input.right == DOWN && (!duck || physic.get_velocity_y() != 0)) {
316       dir = RIGHT;
317       dirsign = 1;
318   }
319
320   if (input.fire == UP) {
321       ax = dirsign * WALK_ACCELERATION_X;
322       // limit speed
323       if(vx >= MAX_WALK_XM && dirsign > 0) {
324         vx = MAX_WALK_XM;
325         ax = 0;
326       } else if(vx <= -MAX_WALK_XM && dirsign < 0) {
327         vx = -MAX_WALK_XM;
328         ax = 0;
329       }
330   } else {
331       ax = dirsign * RUN_ACCELERATION_X;
332       // limit speed
333       if(vx >= MAX_RUN_XM && dirsign > 0) {
334         vx = MAX_RUN_XM;
335         ax = 0;
336       } else if(vx <= -MAX_RUN_XM && dirsign < 0) {
337         vx = -MAX_RUN_XM;
338         ax = 0;
339       }
340   }
341
342   // we can reach WALK_SPEED without any acceleration
343   if(dirsign != 0 && fabs(vx) < WALK_SPEED) {
344     vx = dirsign * WALK_SPEED;
345   }
346
347   // changing directions?
348   if(on_ground() && ((vx < 0 && dirsign >0) || (vx>0 && dirsign<0))) {
349       if(fabs(vx)>SKID_XM && !skidding_timer.check()) {
350           skidding_timer.start(SKID_TIME);
351           play_sound(sounds[SND_SKID], SOUND_CENTER_SPEAKER);
352           ax *= 2.5;
353       } else {
354           ax *= 2;
355       }
356   }
357
358   // we get slower when not pressing any keys
359   if(dirsign == 0) {
360       if(fabs(vx) < WALK_SPEED) {
361           vx = 0;
362           ax = 0;
363       } else if(vx < 0) {
364           ax = WALK_ACCELERATION_X * 1.5;
365       } else {
366           ax = WALK_ACCELERATION_X * -1.5;
367       }
368   }
369
370   // if we're on ice slow down acceleration or deceleration
371   if (isice(base.x, base.y + base.height))
372   {
373     /* the acceleration/deceleration rate on ice is inversely proportional to
374      * the current velocity.
375      */
376
377     // increasing 1 will increase acceleration/deceleration rate
378     // decreasing 1 will decrease acceleration/deceleration rate
379     //  must stay above zero, though
380     if (ax != 0) ax *= 1 / fabs(vx);
381   }
382
383   physic.set_velocity(vx, vy);
384   physic.set_acceleration(ax, ay);
385 }
386
387 void
388 Player::handle_vertical_input()
389 {
390   if(input.up == DOWN)
391     {
392       if (on_ground())
393         {
394           // jump
395           if (physic.get_velocity_x() > MAX_WALK_XM)
396             physic.set_velocity_y(5.8);
397           else
398             physic.set_velocity_y(5.2);
399
400           --base.y;
401           jumping = true;
402           if (size == SMALL)
403             play_sound(sounds[SND_JUMP], SOUND_CENTER_SPEAKER);
404           else
405             play_sound(sounds[SND_BIGJUMP], SOUND_CENTER_SPEAKER);
406         }
407     }
408   else if(input.up == UP && jumping)
409     {
410       jumping = false;
411       if(physic.get_velocity_y() > 0) {
412         physic.set_velocity_y(0);
413       }
414     }
415 }
416
417 void
418 Player::handle_input()
419 {
420   /* Handle horizontal movement: */
421     handle_horizontal_input();
422
423   /* Jump/jumping? */
424
425   if ( input.up == DOWN || (input.up == UP && jumping))
426     {
427       handle_vertical_input();
428     }
429
430   /* Shoot! */
431
432   if (input.fire == DOWN && input.old_fire == UP && got_coffee)
433     {
434       World::current()->add_bullet(base.x, base.y, physic.get_velocity_x(), dir);
435     }
436
437   /* tux animations: */
438   if(!frame_timer.check())
439     {
440       frame_timer.start(25);
441       if (input.right == UP && input.left == UP)
442         {
443           frame_main = 1;
444           frame_ = 1;
445         }
446       else
447         {
448           if ((input.fire == DOWN && (global_frame_counter % 2) == 0) ||
449               (global_frame_counter % 4) == 0)
450             frame_main = (frame_main + 1) % 4;
451
452           frame_ = frame_main;
453
454           if (frame_ == 3)
455             frame_ = 1;
456         }
457     }
458
459   /* Duck! */
460   if (input.down == DOWN && size == BIG && !duck && physic.get_velocity_y() == 0)
461     {
462       duck = true;
463       base.height = 32;                             
464       base.y += 32;
465       // changing base size confuses collision otherwise
466       old_base = previous_base = base;
467     }
468   else if(input.down == UP && size == BIG && duck && physic.get_velocity_y() == 0)
469     {
470       duck = false;
471       base.y -= 32;
472       base.height = 64;
473       old_base = previous_base = base;
474     }
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 (size == SMALL)
514         {
515           if (invincible_timer.started())
516             {
517               /* Draw cape: */
518
519               if (dir == RIGHT)
520                 cape_right[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
521               else
522                 cape_left[global_frame_counter % 2]->draw(base.x- scroll_x, base.y);
523             }
524
525
526           if (!got_coffee)
527             {
528               if (physic.get_velocity_y() != 0)
529                 {
530                   if (dir == RIGHT)
531                     smalltux_jump_right->draw( base.x - scroll_x, base.y - 10);
532                   else
533                     smalltux_jump_left->draw( base.x - scroll_x, base.y - 10);                   
534                 }
535               else
536                 {
537                   if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
538                     {
539                       if (dir == RIGHT)
540                         smalltux_stand_right->draw( base.x - scroll_x, base.y - 9);
541                       else
542                         smalltux_stand_left->draw( base.x - scroll_x, base.y - 9);
543                     }
544                   else // moving
545                     {
546                       if (dir == RIGHT)
547                         tux_right[(global_frame_counter/2) % tux_right.size()]->draw(base.x - scroll_x, base.y - 9);
548                       else
549                         tux_left[(global_frame_counter/2) % tux_left.size()]->draw(base.x - scroll_x, base.y - 9);
550                     }
551                 }
552             }
553           else
554             {
555               /* Tux got coffee! */
556
557               if (dir == RIGHT)
558                 {
559                   firetux_right[frame_]->draw( base.x- scroll_x, base.y);
560                 }
561               else
562                 {
563                   firetux_left[frame_]->draw( base.x- scroll_x, base.y);
564                 }
565             }
566         }
567       else
568         {
569           if (invincible_timer.started())
570             {
571               float capex = base.x + (base.width - bigcape_right[0]->w) / 2;
572               capex -= scroll_x;
573               float capey = base.y + (base.height - bigcape_right[0]->h) / 2;
574                 
575               /* Draw cape (just not in ducked mode since that looks silly): */
576               if (dir == RIGHT)
577                 bigcape_right[global_frame_counter % 2]->draw(capex, capey);
578               else
579                 bigcape_left[global_frame_counter % 2]->draw(capex, capey);
580             }
581
582           if (!got_coffee)
583             {
584               if (!duck)
585                 {
586                   if (!skidding_timer.started())
587                     {
588                       if (physic.get_velocity_y() == 0)
589                         {
590                           if (dir == RIGHT)
591                             bigtux_right->draw(base.x - scroll_x, base.y);
592                           else
593                             bigtux_left->draw(base.x - scroll_x, base.y);
594                         }
595                       else
596                         {
597                           if (dir == RIGHT)
598                             bigtux_right_jump->draw(base.x - scroll_x, base.y);
599                           else
600                             bigtux_left_jump->draw(base.x - scroll_x, base.y);
601                         }
602                     }
603                   else
604                     {
605                       if (dir == RIGHT)
606                         skidtux_right->draw(base.x - scroll_x - 8, base.y);
607                       else
608                         skidtux_left->draw(base.x - scroll_x - 8, base.y);
609                     }
610                 }
611               else
612                 {
613                   if (dir == RIGHT)
614                     ducktux_right->draw(base.x - scroll_x, base.y);
615                   else
616                     ducktux_left->draw(base.x - scroll_x, base.y);
617                 }
618             }
619           else
620             {
621               /* Tux has coffee! */
622               if (!duck)
623                 {
624                   if (!skidding_timer.started())
625                     {
626                       if (!jumping || physic.get_velocity_y() > 0)
627                         {
628                           if (dir == RIGHT)
629                             bigfiretux_right[frame_]->draw(base.x- scroll_x - 8, base.y);
630                           else
631                             bigfiretux_left[frame_]->draw(base.x- scroll_x - 8, base.y);
632                         }
633                       else
634                         {
635                           if (dir == RIGHT)
636                             bigfiretux_right_jump->draw(base.x- scroll_x - 8, base.y);
637                           else
638                             bigfiretux_left_jump->draw(base.x- scroll_x - 8, base.y);
639                         }
640                     }
641                   else
642                     {
643                       if (dir == RIGHT)
644                         skidfiretux_right->draw(base.x- scroll_x - 8, base.y);
645                       else
646                         skidfiretux_left->draw(base.x- scroll_x - 8, base.y);
647                     }
648                 }
649               else
650                 {
651                   if (dir == RIGHT)
652                     duckfiretux_right->draw( base.x- scroll_x - 8, base.y - 16);
653                   else
654                     duckfiretux_left->draw( base.x- scroll_x - 8, base.y - 16);
655                 }
656             }
657         }
658     }
659
660   if (debug_mode)
661     fillrect(base.x - scroll_x, base.y, 32, 32, 75,75,75, 150);
662 }
663
664 void
665 Player::collision(void* p_c_object, int c_object)
666 {
667   BadGuy* pbad_c = NULL;
668
669   switch (c_object)
670     {
671     case CO_BADGUY:
672       pbad_c = (BadGuy*) p_c_object;
673
674      /* Hurt player if he touches a badguy */
675       if (!pbad_c->dying && !dying &&
676           !safe_timer.started() &&
677           pbad_c->mode != BadGuy::HELD)
678         {
679           if (pbad_c->mode == BadGuy::FLAT && input.fire == DOWN)
680             {
681               pbad_c->mode = BadGuy::HELD;
682               pbad_c->base.y-=8;
683             }
684           else if (pbad_c->mode == BadGuy::FLAT)
685             {
686               // Don't get hurt if we're kicking a flat badguy!
687             }
688           else if (pbad_c->mode == BadGuy::KICK)
689             {
690               /* Hurt if you get hit by kicked laptop: */
691               if (!invincible_timer.started())
692                 {
693                   kill(SHRINK);
694                 }
695               else
696                 {
697                    pbad_c->dying = DYING_FALLING;
698                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
699                    World::current()->add_score(pbad_c->base.x - scroll_x,
700                                                pbad_c->base.y,
701                                                25 * player_status.score_multiplier);
702                 }
703             }
704           else
705             {
706               if (!invincible_timer.started())
707                 {
708                   kill(SHRINK);
709                 }
710               else
711                 {
712                   pbad_c->kill_me();
713                 }
714             }
715           player_status.score_multiplier++;
716         }
717       break;
718     default:
719       break;
720     }
721
722 }
723
724 /* Kill Player! */
725
726 void
727 Player::kill(int mode)
728 {
729   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
730
731   physic.set_velocity(0, physic.get_velocity_y());
732
733   if (mode == SHRINK && size == BIG)
734     {
735       if (got_coffee)
736         got_coffee = false;
737
738       size = SMALL;
739       base.height = 32;
740       duck = false;
741
742       safe_timer.start(TUX_SAFE_TIME);
743     }
744   else
745     {
746       physic.enable_gravity(true);
747       physic.set_acceleration(0, 0);
748       physic.set_velocity(0, 7);
749       dying = DYING_SQUISHED;
750     }
751 }
752
753 void
754 Player::is_dying()
755 {
756   remove_powerups();
757   dying = DYING_NOT;
758 }
759
760 bool Player::is_dead()
761 {
762   if(base.y > screen->h)
763     return true;
764   else
765     return false;
766 }
767
768 /* Remove Tux's power ups */
769 void
770 Player::remove_powerups()
771 {
772   got_coffee = false;
773   size = SMALL;
774   base.height = 32;
775 }
776
777 void
778 Player::keep_in_bounds()
779 {
780   Level* plevel = World::current()->get_level();
781
782   /* Keep tux in bounds: */
783   if (base.x < 0)
784     { // Lock Tux to the size of the level, so that he doesn't fall of
785       // on the left side
786       base.x = 0;
787     }
788   else if (base.x < scroll_x)
789     { 
790       base.x = scroll_x;
791     }
792
793   /* Keep in-bounds, vertically: */
794   if (base.y > screen->h)
795     {
796       kill(KILL);
797     }
798
799   int scroll_threshold = screen->w/2 - 80;
800   if (debug_mode)
801     {
802       scroll_x += screen->w/2;
803       // Backscrolling for debug mode
804       if (scroll_x < base.x - 80)
805         scroll_x = base.x - 80;
806       else if (scroll_x > base.x + 80)
807         scroll_x = base.x + 80;
808       scroll_x -= screen->w/2;
809
810       if(scroll_x < 0)
811         scroll_x = 0;
812     }
813   else
814     {
815       if (base.x > scroll_threshold + scroll_x
816           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
817         {
818           // FIXME: Scrolling needs to be handled by a seperate View
819           // class, doing it as a player huck is ugly
820           
821           // Scroll the screen in past center:
822           scroll_x = base.x - scroll_threshold;
823           
824           // Lock the scrolling to the levelsize, so that we don't
825           // scroll over the right border
826           if (scroll_x > 32 * plevel->width - screen->w)
827             scroll_x = 32 * plevel->width - screen->w;
828         }
829     }
830 }
831
832 // EOF //
833