- bullet tweaks
[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)
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 (skidding_timer.started())
512             {
513               if (dir == RIGHT)
514                 sprite->skid_right->draw(base.x - scroll_x, base.y);
515               else
516                 sprite->skid_left->draw(base.x - scroll_x, base.y); 
517             }
518           else
519             {
520               if (duck)
521                 {
522                   if (dir == RIGHT)
523                     sprite->duck_right->draw(base.x - scroll_x, base.y);
524                   else 
525                     sprite->duck_left->draw(base.x - scroll_x, base.y);
526                 }                    
527               else if (physic.get_velocity_y() != 0)
528                 {
529                   if (dir == RIGHT)
530                     sprite->jump_right->draw(base.x - scroll_x, base.y);
531                   else
532                     sprite->jump_left->draw(base.x - scroll_x, base.y);                   
533                 }
534               else
535                 {
536                   if (fabsf(physic.get_velocity_x()) < 1.0f) // standing
537                     {
538                       if (dir == RIGHT)
539                         sprite->stand_right->draw( base.x - scroll_x, base.y);
540                       else
541                         sprite->stand_left->draw( base.x - scroll_x, base.y);
542                     }
543                   else // moving
544                     {
545                       if (dir == RIGHT)
546                         sprite->walk_right->draw(base.x - scroll_x, base.y);
547                       else
548                         sprite->walk_left->draw(base.x - scroll_x, base.y);
549                     }
550                 }
551             }
552           
553           // Draw arm overlay graphics when Tux is holding something
554           if (holding_something && physic.get_velocity_y() == 0)
555             {
556               if (dir == RIGHT)
557                 sprite->grab_right->draw(base.x - scroll_x, base.y);
558               else
559                 sprite->grab_left->draw(base.x - scroll_x, base.y);
560             }
561
562           // Draw blinking star overlay
563           if (invincible_timer.started())
564             {
565               if (size == SMALL || duck)
566                 smalltux_star->draw(base.x - scroll_x, base.y);
567               else
568                 largetux_star->draw(base.x - scroll_x, base.y);
569             }
570         }
571     }     
572   
573   if (debug_mode)
574     fillrect(base.x - scroll_x, base.y, 
575              base.width, base.height, 75,75,75, 150);
576 }
577
578 void
579 Player::collision(void* p_c_object, int c_object)
580 {
581   BadGuy* pbad_c = NULL;
582
583   switch (c_object)
584     {
585     case CO_BADGUY:
586       pbad_c = (BadGuy*) p_c_object;
587
588      /* Hurt player if he touches a badguy */
589       if (!pbad_c->dying && !dying &&
590           !safe_timer.started() &&
591           pbad_c->mode != BadGuy::HELD)
592         {
593           if (pbad_c->mode == BadGuy::FLAT && input.fire == DOWN)
594             {
595               holding_something = true;
596               pbad_c->mode = BadGuy::HELD;
597               pbad_c->base.y-=8;
598             }
599           else if (pbad_c->mode == BadGuy::FLAT)
600             {
601               // Don't get hurt if we're kicking a flat badguy!
602             }
603           else if (pbad_c->mode == BadGuy::KICK)
604             {
605               /* Hurt if you get hit by kicked laptop: */
606               if (!invincible_timer.started())
607                 {
608                   kill(SHRINK);
609                 }
610               else
611                 {
612                    pbad_c->dying = DYING_FALLING;
613                    play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
614                    World::current()->add_score(pbad_c->base.x - scroll_x,
615                                                pbad_c->base.y,
616                                                25 * player_status.score_multiplier);
617                 }
618             }
619           else
620             {
621               if (!invincible_timer.started())
622                 {
623                   kill(SHRINK);
624                 }
625               else
626                 {
627                   pbad_c->kill_me();
628                 }
629             }
630           player_status.score_multiplier++;
631         }
632       break;
633     default:
634       break;
635     }
636
637 }
638
639 /* Kill Player! */
640
641 void
642 Player::kill(HurtMode mode)
643 {
644   play_sound(sounds[SND_HURT], SOUND_CENTER_SPEAKER);
645
646   physic.set_velocity_x(0);
647
648   if (mode == SHRINK && size == BIG)
649     {
650       if (got_coffee)
651         {
652           got_coffee = false;
653         }
654       else
655         {
656           size = SMALL;
657           base.height = 32;
658           duck = false;
659         }
660       safe_timer.start(TUX_SAFE_TIME);
661     }
662   else
663     {
664       physic.enable_gravity(true);
665       physic.set_acceleration(0, 0);
666       physic.set_velocity(0, 7);
667       dying = DYING_SQUISHED;
668     }
669 }
670
671 void
672 Player::is_dying()
673 {
674   remove_powerups();
675   dying = DYING_NOT;
676 }
677
678 bool Player::is_dead()
679 {
680   if(base.y > screen->h)
681     return true;
682   else
683     return false;
684 }
685
686 /* Remove Tux's power ups */
687 void
688 Player::remove_powerups()
689 {
690   got_coffee = false;
691   size = SMALL;
692   base.height = 32;
693 }
694
695 void
696 Player::keep_in_bounds()
697 {
698   Level* plevel = World::current()->get_level();
699
700   /* Keep tux in bounds: */
701   if (base.x < 0)
702     { // Lock Tux to the size of the level, so that he doesn't fall of
703       // on the left side
704       base.x = 0;
705     }
706   else if (base.x < scroll_x)
707     { 
708       base.x = scroll_x;
709     }
710
711   /* Keep in-bounds, vertically: */
712   if (base.y > screen->h)
713     {
714       kill(KILL);
715     }
716
717   int scroll_threshold = screen->w/2 - 80;
718   if (debug_mode)
719     {
720       scroll_x += screen->w/2;
721       // Backscrolling for debug mode
722       if (scroll_x < base.x - 80)
723         scroll_x = base.x - 80;
724       else if (scroll_x > base.x + 80)
725         scroll_x = base.x + 80;
726       scroll_x -= screen->w/2;
727
728       if(scroll_x < 0)
729         scroll_x = 0;
730     }
731   else
732     {
733       if (base.x > scroll_threshold + scroll_x
734           && scroll_x < ((World::current()->get_level()->width * 32) - screen->w))
735         {
736           // FIXME: Scrolling needs to be handled by a seperate View
737           // class, doing it as a player huck is ugly
738           
739           // Scroll the screen in past center:
740           scroll_x = base.x - scroll_threshold;
741           
742           // Lock the scrolling to the levelsize, so that we don't
743           // scroll over the right border
744           if (scroll_x > 32 * plevel->width - screen->w)
745             scroll_x = 32 * plevel->width - screen->w;
746         }
747     }
748 }
749
750 // EOF //
751