Intro file should be the one to handle the intro sequence, even if it is only one...
[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
96   physic.reset();
97 }
98
99 int
100 Player::key_event(SDLKey key, int state)
101 {
102   if(key == keymap.right)
103     {
104       input.right = state;
105       return true;
106     }
107   else if(key == keymap.left)
108     {
109       input.left = state;
110       return true;
111     }
112   else if(key == keymap.jump)
113     {
114       input.up = state;
115       return true;
116     }
117   else if(key == keymap.duck)
118     {
119       input.down = state;
120       return true;
121     }
122   else if(key == keymap.fire)
123     {
124       if (state == UP)
125         input.old_fire = UP;
126       input.fire = state;
127       return true;
128     }
129   else
130     return false;
131 }
132
133 void
134 Player::level_begin()
135 {
136   base.x  = 100;
137   base.y  = 170;
138   base.xm = 0;
139   base.ym = 0;
140   previous_base = old_base = base;
141   duck = false;
142
143   dying = DYING_NOT;
144
145   player_input_init(&input);
146
147   invincible_timer.init(true);
148   skidding_timer.init(true);
149   safe_timer.init(true);
150   frame_timer.init(true);
151
152   physic.reset();
153 }
154
155 void
156 Player::action(double frame_ratio)
157 {
158   bool jumped_in_solid = false;
159
160   if (input.fire == UP)
161     holding_something = false;
162
163   /* Move tux: */
164   previous_base = base;
165
166   /* --- HANDLE TUX! --- */
167   if(dying == DYING_NOT)
168     handle_input();
169
170   physic.apply(frame_ratio, base.x, base.y);
171
172   if(dying == DYING_NOT) 
173     {
174       base_type target = base;
175
176       collision_swept_object_map(&old_base, &base);
177
178       // Don't accelerate Tux if he is running against a wall
179       if (target.x != base.x)
180         {
181           physic.set_velocity_x(0);
182         }
183
184       // special exception for cases where we're stuck under tiles after
185       // being ducked. In this case we drift out
186       if(!duck && on_ground() && old_base.x == base.x && old_base.y == base.y
187          && collision_object_map(base))
188         {
189           base.x += frame_ratio * WALK_SPEED * (dir ? 1 : -1);
190           previous_base = old_base = base;
191         }
192       keep_in_bounds();
193
194       // Land:
195       if (!on_ground())
196         {
197           physic.enable_gravity(true);
198           if(under_solid())
199             {
200               // fall down
201               physic.set_velocity_y(0);
202               jumped_in_solid = true;
203             }
204         }
205       else
206         {
207           /* Land: */
208           if (physic.get_velocity_y() < 0)
209             {
210               base.y = (int)(((int)base.y / 32) * 32);
211               physic.set_velocity_y(0);
212             }
213
214           physic.enable_gravity(false);
215           /* Reset score multiplier (for multi-hits): */
216           player_status.score_multiplier = 1;
217         }
218
219       if(jumped_in_solid)
220         {
221           if (isbrick(base.x, base.y) ||
222               isfullbox(base.x, base.y))
223             {
224               World::current()->trygrabdistro(base.x, base.y - 32,BOUNCE);
225               World::current()->trybumpbadguy(base.x, base.y - 64);
226
227               World::current()->trybreakbrick(base.x, base.y, size == SMALL);
228
229               bumpbrick(base.x, base.y);
230               World::current()->tryemptybox(base.x, base.y, RIGHT);
231             }
232
233           if (isbrick(base.x+ 31, base.y) ||
234               isfullbox(base.x+ 31, base.y))
235             {
236               World::current()->trygrabdistro(base.x+ 31, base.y - 32,BOUNCE);
237               World::current()->trybumpbadguy(base.x+ 31, base.y - 64);
238
239               if(size == BIG)
240                 World::current()->trybreakbrick(base.x+ 31, base.y, size == SMALL);
241
242               bumpbrick(base.x+ 31, base.y);
243               World::current()->tryemptybox(base.x+ 31, base.y, LEFT);
244             }
245         }
246
247       grabdistros();
248
249       if (jumped_in_solid)
250         {
251           ++base.y;
252           ++old_base.y;
253           if(on_ground())
254             {
255               /* Make sure jumping is off. */
256               jumping = false;
257             }
258         }
259     }
260
261   /* ---- DONE HANDLING TUX! --- */
262
263   // check some timers
264   skidding_timer.check();
265   invincible_timer.check();
266   safe_timer.check();
267 }
268
269 bool
270 Player::on_ground()
271 {
272   return ( issolid(base.x + base.width / 2, base.y + base.height) ||
273            issolid(base.x + 1, base.y + base.height) ||
274            issolid(base.x + base.width - 1, base.y + base.height)  );
275 }
276
277 bool
278 Player::under_solid()
279 {
280   return ( issolid(base.x + base.width / 2, base.y) ||
281            issolid(base.x + 1, base.y) ||
282            issolid(base.x + base.width - 1, base.y)  );
283 }
284
285 void
286 Player::handle_horizontal_input()
287 {
288   float vx = physic.get_velocity_x();
289   float vy = physic.get_velocity_y();
290   float ax = physic.get_acceleration_x();
291   float ay = physic.get_acceleration_y();
292
293   float dirsign = 0;
294   if(input.left == DOWN && input.right == UP && (!duck || physic.get_velocity_y() != 0)) {
295       dir = LEFT;
296       dirsign = -1;
297   } else if(input.left == UP && input.right == DOWN && (!duck || physic.get_velocity_y() != 0)) {
298       dir = RIGHT;
299       dirsign = 1;
300   }
301
302   if (input.fire == UP) {
303       ax = dirsign * WALK_ACCELERATION_X;
304       // limit speed
305       if(vx >= MAX_WALK_XM && dirsign > 0) {
306         vx = MAX_WALK_XM;
307         ax = 0;
308       } else if(vx <= -MAX_WALK_XM && dirsign < 0) {
309         vx = -MAX_WALK_XM;
310         ax = 0;
311       }
312   } else {
313       ax = dirsign * RUN_ACCELERATION_X;
314       // limit speed
315       if(vx >= MAX_RUN_XM && dirsign > 0) {
316         vx = MAX_RUN_XM;
317         ax = 0;
318       } else if(vx <= -MAX_RUN_XM && dirsign < 0) {
319         vx = -MAX_RUN_XM;
320         ax = 0;
321       }
322   }
323
324   // we can reach WALK_SPEED without any acceleration
325   if(dirsign != 0 && fabs(vx) < WALK_SPEED) {
326     vx = dirsign * WALK_SPEED;
327   }
328
329   // changing directions?
330   if(on_ground() && ((vx < 0 && dirsign >0) || (vx>0 && dirsign<0))) {
331       if(fabs(vx)>SKID_XM && !skidding_timer.check()) {
332           skidding_timer.start(SKID_TIME);
333           play_sound(sounds[SND_SKID], SOUND_CENTER_SPEAKER);
334           ax *= 2.5;
335       } else {
336           ax *= 2;
337       }
338   }
339
340   // we get slower when not pressing any keys
341   if(dirsign == 0) {
342       if(fabs(vx) < WALK_SPEED) {
343           vx = 0;
344           ax = 0;
345       } else if(vx < 0) {
346           ax = WALK_ACCELERATION_X * 1.5;
347       } else {
348           ax = WALK_ACCELERATION_X * -1.5;
349       }
350   }
351
352   // if we're on ice slow down acceleration or deceleration
353   if (isice(base.x, base.y + base.height))
354   {
355     /* the acceleration/deceleration rate on ice is inversely proportional to
356      * the current velocity.
357      */
358
359     // increasing 1 will increase acceleration/deceleration rate
360     // decreasing 1 will decrease acceleration/deceleration rate
361     //  must stay above zero, though
362     if (ax != 0) ax *= 1 / fabs(vx);
363   }
364
365   physic.set_velocity(vx, vy);
366   physic.set_acceleration(ax, ay);
367 }
368
369 void
370 Player::handle_vertical_input()
371 {
372   if(input.up == DOWN)
373     {
374       if (on_ground())
375         {
376           // jump higher if we are running
377           if (physic.get_velocity_x() > MAX_WALK_XM)
378             physic.set_velocity_y(5.8);
379           else
380             physic.set_velocity_y(5.2);
381
382           --base.y;
383           jumping = true;
384           if (size == SMALL)
385             play_sound(sounds[SND_JUMP], SOUND_CENTER_SPEAKER);
386           else
387             play_sound(sounds[SND_BIGJUMP], SOUND_CENTER_SPEAKER);
388         }
389     }
390   else if(input.up == UP && jumping)
391     {
392       jumping = false;
393       if(physic.get_velocity_y() > 0) {
394         physic.set_velocity_y(0);
395       }
396     }
397 }
398
399 void
400 Player::handle_input()
401 {
402   /* Handle horizontal movement: */
403     handle_horizontal_input();
404
405   /* Jump/jumping? */
406
407   if ( input.up == DOWN || (input.up == UP && jumping))
408     {
409       handle_vertical_input();
410     }
411
412   /* Shoot! */
413
414   if (input.fire == DOWN && input.old_fire == UP && got_coffee)
415     {
416       World::current()->add_bullet(base.x, base.y, physic.get_velocity_x(), dir);
417       input.old_fire = DOWN;
418     }
419
420   /* tux animations: */
421   if(!frame_timer.check())
422     {
423       frame_timer.start(25);
424       if (input.right == UP && input.left == UP)
425         {
426           frame_main = 1;
427           frame_ = 1;
428         }
429       else
430         {
431           if ((input.fire == DOWN && (global_frame_counter % 2) == 0) ||
432               (global_frame_counter % 4) == 0)
433             frame_main = (frame_main + 1) % 4;
434
435           frame_ = frame_main;
436
437           if (frame_ == 3)
438             frame_ = 1;
439         }
440     }
441
442   /* Duck! */
443   if (input.down == DOWN && size == BIG && !duck && physic.get_velocity_y() == 0 && on_ground())
444     {
445       duck = true;
446       base.height = 32;                             
447       base.y += 32;
448       // changing base size confuses collision otherwise
449       old_base = previous_base = base;
450     }
451   else if(input.down == UP && size == BIG && duck && physic.get_velocity_y() == 0 && on_ground())
452     {
453       duck = false;
454       base.y -= 32;
455       base.height = 64;
456       old_base = previous_base = base;
457     }
458 }
459
460 void
461 Player::grabdistros()
462 {
463   /* Grab distros: */
464   if (!dying)
465     {
466       World::current()->trygrabdistro(base.x, base.y, NO_BOUNCE);
467       World::current()->trygrabdistro(base.x+ 31, base.y, NO_BOUNCE);
468
469       World::current()->trygrabdistro(base.x, base.y + base.height, NO_BOUNCE);
470       World::current()->trygrabdistro(base.x+ 31, base.y + base.height, NO_BOUNCE);
471
472       if(size == BIG)
473         {
474           World::current()->trygrabdistro(base.x, base.y + base.height / 2, NO_BOUNCE);
475           World::current()->trygrabdistro(base.x+ 31, base.y + base.height / 2, NO_BOUNCE);
476         }
477
478     }
479
480   /* Enough distros for a One-up? */
481   if (player_status.distros >= DISTROS_LIFEUP)
482     {
483       player_status.distros = player_status.distros - DISTROS_LIFEUP;
484       if(player_status.lives < MAX_LIVES)
485         ++player_status.lives;
486       /*We want to hear the sound even, if MAX_LIVES is reached*/
487       play_sound(sounds[SND_LIFEUP], SOUND_CENTER_SPEAKER);
488     }
489 }
490
491 void
492 Player::draw()
493 {
494   if (!safe_timer.started() || (global_frame_counter % 2) == 0)
495     {
496       if (dying == DYING_SQUISHED)
497         {
498           smalltux_gameover->draw(base.x - scroll_x, base.y);
499         }
500       else
501         {
502           PlayerSprite* sprite;
503           
504           if (size == SMALL)
505             sprite = &smalltux;
506           else if (got_coffee)
507             sprite = &firetux;
508           else
509             sprite = &largetux;
510           
511           if (duck)
512             {
513               if (dir == RIGHT)
514                 sprite->duck_right->draw(base.x - scroll_x, base.y);
515               else 
516                 sprite->duck_left->draw(base.x - scroll_x, base.y);
517             }                    
518           else if (skidding_timer.started())
519             {
520               if (dir == RIGHT)
521                 sprite->skid_right->draw(base.x - scroll_x, base.y);
522               else
523                 sprite->skid_left->draw(base.x - scroll_x, base.y); 
524             }
525           else if (physic.get_velocity_y() != 0)
526             {
527               if (dir == RIGHT)
528                 sprite->jump_right->draw(base.x - scroll_x, base.y);
529               else
530                 sprite->jump_left->draw(base.x - scroll_x, base.y);                   
531             }
532           else
533             {
534               if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
535                 {
536                   if (dir == RIGHT)
537                     sprite->stand_right->draw( base.x - scroll_x, base.y);
538                   else
539                     sprite->stand_left->draw( base.x - scroll_x, base.y);
540                 }
541               else // moving
542                 {
543                   if (dir == RIGHT)
544                     sprite->walk_right->draw(base.x - scroll_x, base.y);
545                   else
546                     sprite->walk_left->draw(base.x - scroll_x, base.y);
547                 }
548             }
549                       
550           // Draw arm overlay graphics when Tux is holding something
551           if (holding_something && physic.get_velocity_y() == 0)
552             {
553               if (dir == RIGHT)
554                 sprite->grab_right->draw(base.x - scroll_x, base.y);
555               else
556                 sprite->grab_left->draw(base.x - scroll_x, base.y);
557             }
558
559           // Draw blinking star overlay
560           if (invincible_timer.started())
561             {
562               if (size == SMALL || duck)
563                 smalltux_star->draw(base.x - scroll_x, base.y);
564               else
565                 largetux_star->draw(base.x - scroll_x, base.y);
566             }
567         }
568     }     
569   
570   if (debug_mode)
571     fillrect(base.x - scroll_x, base.y, 
572              base.width, base.height, 75,75,75, 150);
573 }
574
575 void
576 Player::collision(void* p_c_object, int c_object)
577 {
578   BadGuy* pbad_c = NULL;
579
580   switch (c_object)
581     {
582     case CO_BADGUY:
583       pbad_c = (BadGuy*) p_c_object;
584
585      /* Hurt player if he touches a badguy */
586       if (!pbad_c->dying && !dying &&
587           !safe_timer.started() &&
588           pbad_c->mode != BadGuy::HELD)
589         {
590           if (pbad_c->mode == BadGuy::FLAT && input.fire == DOWN)
591             {
592               holding_something = true;
593               pbad_c->mode = BadGuy::HELD;
594               pbad_c->base.y-=8;
595             }
596           else if (pbad_c->mode == BadGuy::FLAT)
597             {
598               // Don't get hurt if we're kicking a flat badguy!
599             }
600           else if (pbad_c->mode == BadGuy::KICK)
601             {
602               /* Hurt if you get hit by kicked laptop: */
603               if (!invincible_timer.started())
604                 {
605                   kill(SHRINK);
606                 }
607               else
608                 {
609                    pbad_c->dying = DYING_FALLING;
610                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
611                    World::current()->add_score(pbad_c->base.x - scroll_x,
612                                                pbad_c->base.y,
613                                                25 * player_status.score_multiplier);
614                 }
615             }
616           else
617             {
618               if (!invincible_timer.started())
619                 {
620                   kill(SHRINK);
621                 }
622               else
623                 {
624                   pbad_c->kill_me();
625                 }
626             }
627           player_status.score_multiplier++;
628         }
629       break;
630     default:
631       break;
632     }
633
634 }
635
636 /* Kill Player! */
637
638 void
639 Player::kill(HurtMode mode)
640 {
641   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
642
643   physic.set_velocity_x(0);
644
645   if (mode == SHRINK && size == BIG)
646     {
647       if (got_coffee)
648         {
649           got_coffee = false;
650         }
651       else
652         {
653           size = SMALL;
654           base.height = 32;
655           duck = false;
656         }
657       safe_timer.start(TUX_SAFE_TIME);
658     }
659   else
660     {
661       physic.enable_gravity(true);
662       physic.set_acceleration(0, 0);
663       physic.set_velocity(0, 7);
664       dying = DYING_SQUISHED;
665     }
666 }
667
668 void
669 Player::is_dying()
670 {
671   remove_powerups();
672   dying = DYING_NOT;
673 }
674
675 bool Player::is_dead()
676 {
677   if(base.y > screen->h)
678     return true;
679   else
680     return false;
681 }
682
683 /* Remove Tux's power ups */
684 void
685 Player::remove_powerups()
686 {
687   got_coffee = false;
688   size = SMALL;
689   base.height = 32;
690 }
691
692 void
693 Player::keep_in_bounds()
694 {
695   Level* plevel = World::current()->get_level();
696
697   /* Keep tux in bounds: */
698   if (base.x < 0)
699     { // Lock Tux to the size of the level, so that he doesn't fall of
700       // on the left side
701       base.x = 0;
702     }
703   else if (base.x < scroll_x)
704     { 
705       base.x = scroll_x;
706     }
707
708   /* Keep in-bounds, vertically: */
709   if (base.y > screen->h)
710     {
711       kill(KILL);
712     }
713
714   int scroll_threshold = screen->w/2 - 80;
715   if (debug_mode)
716     {
717       scroll_x += screen->w/2;
718       // Backscrolling for debug mode
719       if (scroll_x < base.x - 80)
720         scroll_x = base.x - 80;
721       else if (scroll_x > base.x + 80)
722         scroll_x = base.x + 80;
723       scroll_x -= screen->w/2;
724
725       if(scroll_x < 0)
726         scroll_x = 0;
727     }
728   else
729     {
730       if (base.x > scroll_threshold + scroll_x
731           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
732         {
733           // FIXME: Scrolling needs to be handled by a seperate View
734           // class, doing it as a player huck is ugly
735           
736           // Scroll the screen in past center:
737           scroll_x = base.x - scroll_threshold;
738           
739           // Lock the scrolling to the levelsize, so that we don't
740           // scroll over the right border
741           if (scroll_x > 32 * plevel->width - screen->w)
742             scroll_x = 32 * plevel->width - screen->w;
743         }
744     }
745 }
746
747 // EOF //
748