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