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