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