- some wingling tweaks
[supertux.git] / src / badguy.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 //  Copyright (C) 2004 Matthias Braun <matze@braunis.de>
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU General Public License
10 //  as published by the Free Software Foundation; either version 2
11 //  of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 // 
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21 //  02111-1307, USA.
22
23 #include <iostream>
24 #include <math.h>
25
26 #include "globals.h"
27 #include "defines.h"
28 #include "badguy.h"
29 #include "scene.h"
30 #include "screen.h"
31 #include "world.h"
32 #include "tile.h"
33 #include "resources.h"
34 #include "sprite_manager.h"
35 #include "gameloop.h"
36 #include "display_manager.h"
37 #include "lispwriter.h"
38 #include "camera.h"
39
40 Sprite* img_mriceblock_flat_left;
41 Sprite* img_mriceblock_flat_right;
42 Sprite* img_mriceblock_falling_left;
43 Sprite* img_mriceblock_falling_right;
44 Sprite* img_mriceblock_left;
45 Sprite* img_mriceblock_right;
46 Sprite* img_jumpy_left_up;
47 Sprite* img_jumpy_left_down;
48 Sprite* img_jumpy_left_middle;
49 Sprite* img_jumpy_left_iced;
50 Sprite* img_mrbomb_left;
51 Sprite* img_mrbomb_right;
52 Sprite* img_mrbomb_iced_left;
53 Sprite* img_mrbomb_iced_right;
54 Sprite* img_mrbomb_ticking_left;
55 Sprite* img_mrbomb_ticking_right;
56 Sprite* img_mrbomb_explosion;
57 Sprite* img_stalactite;
58 Sprite* img_stalactite_broken;
59 Sprite* img_flame;
60 Sprite* img_fish;
61 Sprite* img_fish_down;
62 Sprite* img_fish_iced;
63 Sprite* img_fish_iced_down;
64 Sprite* img_bouncingsnowball_left;
65 Sprite* img_bouncingsnowball_right;
66 Sprite* img_bouncingsnowball_squished;
67 Sprite* img_flyingsnowball;
68 Sprite* img_flyingsnowball_squished;
69 Sprite* img_spiky_left;
70 Sprite* img_spiky_right;
71 Sprite* img_spiky_iced_left;
72 Sprite* img_spiky_iced_right;
73 Sprite* img_snowball_left;
74 Sprite* img_snowball_right;
75 Sprite* img_snowball_squished_left;
76 Sprite* img_snowball_squished_right;
77 Sprite* img_wingling_left;
78
79 #define BADGUY_WALK_SPEED .8f
80 #define WINGLING_FLY_SPEED 1.6f
81
82 BadGuyKind  badguykind_from_string(const std::string& str)
83 {
84   if (str == "money" || str == "jumpy") // was money in old maps
85     return BAD_JUMPY;
86   else if (str == "laptop" || str == "mriceblock") // was laptop in old maps
87     return BAD_MRICEBLOCK;
88   else if (str == "mrbomb")
89     return BAD_MRBOMB;
90   else if (str == "stalactite")
91     return BAD_STALACTITE;
92   else if (str == "flame")
93     return BAD_FLAME;
94   else if (str == "fish")
95     return BAD_FISH;
96   else if (str == "bouncingsnowball")
97     return BAD_BOUNCINGSNOWBALL;
98   else if (str == "flyingsnowball")
99     return BAD_FLYINGSNOWBALL;
100   else if (str == "spiky")
101     return BAD_SPIKY;
102   else if (str == "snowball" || str == "bsod") // was bsod in old maps
103     return BAD_SNOWBALL;
104   else if (str == "wingling")
105     return BAD_WINGLING;
106   else
107     {
108       printf("Couldn't convert badguy: '%s'\n", str.c_str());
109       return BAD_SNOWBALL;
110     }
111 }
112
113 std::string badguykind_to_string(BadGuyKind kind)
114 {
115   switch(kind)
116     {
117     case BAD_JUMPY:
118       return "jumpy";
119       break;
120     case BAD_MRICEBLOCK:
121       return "mriceblock";
122       break;
123     case BAD_MRBOMB:
124       return "mrbomb";
125       break;
126     case BAD_STALACTITE:
127       return "stalactite";
128       break;
129     case BAD_FLAME:
130       return "flame";
131       break;
132     case BAD_FISH:
133       return "fish";
134       break;
135     case BAD_BOUNCINGSNOWBALL:
136       return "bouncingsnowball";
137       break;
138     case BAD_FLYINGSNOWBALL:
139       return "flyingsnowball";
140       break;
141     case BAD_SPIKY:
142       return "spiky";
143       break;
144     case BAD_SNOWBALL:
145       return "snowball";
146       break;
147     case BAD_WINGLING:
148       return "wingling";
149       break;
150     default:
151       return "snowball";
152     }
153 }
154
155 BadGuy::BadGuy(DisplayManager& display_manager, BadGuyKind kind_,
156     LispReader& lispreader)
157   : removable(false), squishcount(0)
158 {
159   display_manager.add_drawable(this, LAYER_OBJECTS);
160
161   lispreader.read_float("x", &start_position.x);
162   lispreader.read_float("y", &start_position.y);
163
164   kind     = kind_;
165
166   stay_on_platform = false;
167   lispreader.read_bool("stay-on-platform", &stay_on_platform);  
168
169   init();
170 }
171
172 BadGuy::BadGuy(DisplayManager& display_manager, BadGuyKind kind_,
173     float x, float y)
174 {
175   display_manager.add_drawable(this, LAYER_OBJECTS);
176
177   start_position.x = x;
178   start_position.y = y;
179   stay_on_platform = false;
180
181   kind     = kind_;
182   
183   init();
184 }
185
186 BadGuy::~BadGuy()
187 {
188 }
189
190 void
191 BadGuy::init()
192 {
193   base.x = 0;
194   base.y = 0;
195   base.width  = 0;
196   base.height = 0;
197   
198   mode     = NORMAL;
199   dying    = DYING_NOT;
200   old_base = base;
201   dir      = LEFT;
202   seen     = false;
203   animation_offset = 0;
204   target.x = target.y = -1;
205   sprite_left = sprite_right = 0;
206   physic.reset();
207   frozen_timer.init(true);
208   timer.init(true);
209
210   // if we're in a solid tile at start correct that now
211   if(kind != BAD_FLAME && kind != BAD_FISH && collision_object_map(base)) 
212     {
213       std::cout << "Warning: badguy started in wall: kind: " << badguykind_to_string(kind) 
214                 << " pos: (" << base.x << ", " << base.y << ")" << std::endl;
215       while(collision_object_map(base))
216         --base.y;
217     }
218
219   if(World::current()->camera) {
220     Vector scroll = World::current()->camera->get_translation();
221
222     if(start_position.x > scroll.x - X_OFFSCREEN_DISTANCE &&
223         start_position.x < scroll.x + screen->w + X_OFFSCREEN_DISTANCE &&
224         start_position.y > scroll.y - Y_OFFSCREEN_DISTANCE &&
225         start_position.y < scroll.y + screen->h + Y_OFFSCREEN_DISTANCE) {
226       activate(LEFT);
227     }
228   }
229 }
230
231 void
232 BadGuy::write(LispWriter& writer)
233 {
234   writer.start_list(badguykind_to_string(kind));
235
236   writer.write_float("x", base.x);
237   writer.write_float("y", base.y);
238   writer.write_bool("stay-on-platform", stay_on_platform);  
239
240   writer.end_list(badguykind_to_string(kind));
241 }
242
243 void
244 BadGuy::activate(Direction activation_dir)
245 {
246   mode     = NORMAL;
247   animation_offset = 0;
248   target.x = target.y = -1;
249   physic.reset();
250   frozen_timer.init(true);
251   timer.init(true);
252
253   dir = activation_dir;
254   float dirsign = activation_dir == LEFT ? -1 : 1;
255   
256   if(kind == BAD_MRBOMB) {
257     physic.set_velocity(dirsign * BADGUY_WALK_SPEED, 0);
258     set_sprite(img_mrbomb_left, img_mrbomb_right);
259   } else if (kind == BAD_MRICEBLOCK) {
260     physic.set_velocity(dirsign * BADGUY_WALK_SPEED, 0);
261     set_sprite(img_mriceblock_left, img_mriceblock_right);
262   } else if(kind == BAD_JUMPY) {
263     set_sprite(img_jumpy_left_up, img_jumpy_left_up);
264   } else if(kind == BAD_BOMB) {
265     set_sprite(img_mrbomb_ticking_left, img_mrbomb_ticking_right);
266     // hack so that the bomb doesn't hurt until it expldes...           
267     dying = DYING_SQUISHED;
268   } else if(kind == BAD_FLAME) {
269     angle = 0;
270     physic.enable_gravity(false);
271     set_sprite(img_flame, img_flame);
272   } else if(kind == BAD_BOUNCINGSNOWBALL) {
273     physic.set_velocity(dirsign * 1.3, 0);
274     set_sprite(img_bouncingsnowball_left, img_bouncingsnowball_right);
275   } else if(kind == BAD_STALACTITE) {
276     physic.enable_gravity(false);
277     set_sprite(img_stalactite, img_stalactite);
278   } else if(kind == BAD_FISH) {
279     set_sprite(img_fish, img_fish);
280     physic.enable_gravity(true);
281   } else if(kind == BAD_FLYINGSNOWBALL) {
282     set_sprite(img_flyingsnowball, img_flyingsnowball);
283     physic.enable_gravity(false);
284   } else if(kind == BAD_SPIKY) {
285     physic.set_velocity(dirsign * BADGUY_WALK_SPEED, 0);
286     set_sprite(img_spiky_left, img_spiky_right);
287   } else if(kind == BAD_SNOWBALL) {
288     physic.set_velocity(dirsign * BADGUY_WALK_SPEED, 0);
289     set_sprite(img_snowball_left, img_snowball_right);
290   } else if(kind == BAD_WINGLING) {
291     physic.set_velocity(dirsign * WINGLING_FLY_SPEED, 0);
292     physic.enable_gravity(false);
293     set_sprite(img_wingling_left, img_wingling_left);
294   }
295
296   base.x = start_position.x;
297   base.y = start_position.y;  
298   old_base = base;
299   seen = true;
300 }
301
302 void
303 BadGuy::action_mriceblock(double elapsed_time)
304 {
305   Player& tux = *World::current()->get_tux();
306
307   if(mode != HELD)
308     fall();
309   
310   /* Move left/right: */
311   if (mode != HELD)
312     {
313       // move
314       physic.apply(elapsed_time, base.x, base.y);
315       if (dying != DYING_FALLING)
316         collision_swept_object_map(&old_base,&base);
317     }
318   else if (mode == HELD)
319     { /* FIXME: The pbad object shouldn't know about pplayer objects. */
320       /* If we're holding the iceblock */
321       dir = tux.dir;
322       if(dir==RIGHT)
323         {
324           base.x = tux.base.x + 16;
325           base.y = tux.base.y + tux.base.height/1.5 - base.height;
326         }
327       else /* facing left */
328         {
329           base.x = tux.base.x - 16;
330           base.y = tux.base.y + tux.base.height/1.5 - base.height;
331         }
332       if(collision_object_map(base))
333         {
334           base.x = tux.base.x;
335           base.y = tux.base.y + tux.base.height/1.5 - base.height;
336         }
337
338       if(tux.input.fire != DOWN) /* SHOOT! */
339         {
340           if(dir == LEFT)
341             base.x -= 24;
342           else
343             base.x += 24;
344           old_base = base;
345
346           mode=KICK;
347           tux.kick_timer.start(KICKING_TIME);
348           set_sprite(img_mriceblock_flat_left, img_mriceblock_flat_right);
349           physic.set_velocity_x((dir == LEFT) ? -3.5 : 3.5);
350           play_sound(sounds[SND_KICK],SOUND_CENTER_SPEAKER);
351         }
352     }
353
354   if (!dying)
355     {
356       int changed = dir;
357       check_horizontal_bump();
358       if(mode == KICK && changed != dir)
359         {
360           float scroll_x = World::current()->camera->get_translation().x;
361           
362           /* handle stereo sound (number 10 should be tweaked...)*/
363           if (base.x < scroll_x + screen->w/2 - 10)
364             play_sound(sounds[SND_RICOCHET], SOUND_LEFT_SPEAKER);
365           else if (base.x > scroll_x + screen->w/2 + 10)
366             play_sound(sounds[SND_RICOCHET], SOUND_RIGHT_SPEAKER);
367           else
368             play_sound(sounds[SND_RICOCHET], SOUND_CENTER_SPEAKER);
369         }
370     }
371
372   /* Handle mode timer: */
373   if (mode == FLAT)
374     {
375       if(!timer.check())
376         {
377           mode = NORMAL;
378           set_sprite(img_mriceblock_left, img_mriceblock_right);
379           physic.set_velocity( (dir == LEFT) ? -.8 : .8, 0);
380         }
381     }
382 }
383
384 void
385 BadGuy::check_horizontal_bump(bool checkcliff)
386 {
387     float halfheight = base.height / 2;
388     if (dir == LEFT && issolid( base.x, (int) base.y + halfheight))
389     {
390         if (kind == BAD_MRICEBLOCK && mode == KICK)
391             World::current()->trybreakbrick(base.x, (int) base.y + halfheight, false);
392             
393         dir = RIGHT;
394         physic.set_velocity(-physic.get_velocity_x(), physic.get_velocity_y());
395         return;
396     }
397     if (dir == RIGHT && issolid( base.x + base.width, (int)base.y + halfheight))
398     {
399         if (kind == BAD_MRICEBLOCK && mode == KICK)
400             World::current()->trybreakbrick(base.x + base.width, (int) base.y + halfheight, false);
401             
402         dir = LEFT;
403         physic.set_velocity(-physic.get_velocity_x(), physic.get_velocity_y());
404         return;
405     }
406
407     // don't check for cliffs when we're falling
408     if(!checkcliff)
409         return;
410     if(!issolid(base.x + base.width/2, base.y + base.height))
411         return;
412     
413     if(dir == LEFT && !issolid(base.x, (int) base.y + base.height + halfheight))
414     {
415         dir = RIGHT;
416         physic.set_velocity(-physic.get_velocity_x(), physic.get_velocity_y());
417         return;
418     }
419     if(dir == RIGHT && !issolid(base.x + base.width,
420                 (int) base.y + base.height + halfheight))
421     {
422         dir = LEFT;
423         physic.set_velocity(-physic.get_velocity_x(), physic.get_velocity_y());
424         return;
425     }
426 }
427
428 void
429 BadGuy::fall()
430 {
431   /* Fall if we get off the ground: */
432   if (dying != DYING_FALLING)
433     {
434       if (!issolid(base.x+base.width/2, base.y + base.height))
435         {
436           // not solid below us? enable gravity
437           physic.enable_gravity(true);
438         }
439       else
440         {
441           /* Land: */
442           if (physic.get_velocity_y() < 0)
443             {
444               base.y = int((base.y + base.height)/32) * 32 - base.height;
445               physic.set_velocity_y(0);
446             }
447           // no gravity anymore please
448           physic.enable_gravity(false);
449
450           if (stay_on_platform && mode == NORMAL)
451             {
452               if (!issolid(base.x + ((dir == LEFT) ? 0 : base.width),
453                            base.y + base.height))
454                 {
455                   if (dir == LEFT)
456                   {
457                     dir = RIGHT;
458                     physic.set_velocity_x(fabsf(physic.get_velocity_x()));
459                   } 
460                   else
461                   {
462                     dir = LEFT;
463                     physic.set_velocity_x(-fabsf(physic.get_velocity_x()));
464                   }
465                 }
466             }
467         }
468     }
469   else
470     {
471       physic.enable_gravity(true);
472     }
473 }
474
475 void
476 BadGuy::action_jumpy(double elapsed_time)
477 {
478   if(frozen_timer.check())
479     {
480     set_sprite(img_jumpy_left_iced, img_jumpy_left_iced);
481     return;
482     }
483
484   const float vy = physic.get_velocity_y();
485
486   // XXX: These tests *should* use location from ground, not velocity
487   if (fabsf(vy) > 5.6f)
488     set_sprite(img_jumpy_left_down, img_jumpy_left_down);
489   else if (fabsf(vy) > 5.3f)
490     set_sprite(img_jumpy_left_middle, img_jumpy_left_middle);
491   else
492     set_sprite(img_jumpy_left_up, img_jumpy_left_up);
493
494   Player& tux = *World::current()->get_tux();
495
496   static const float JUMPV = 6;
497     
498   fall();
499   // jump when on ground
500   if(dying == DYING_NOT && issolid(base.x, base.y+32))
501     {
502       physic.set_velocity_y(JUMPV);
503       physic.enable_gravity(true);
504
505       mode = JUMPY_JUMP;
506     }
507   else if(mode == JUMPY_JUMP)
508     {
509       mode = NORMAL;
510     }
511
512   // set direction based on tux
513   if(tux.base.x > base.x)
514     dir = RIGHT;
515   else
516     dir = LEFT;
517
518   // move
519   physic.apply(elapsed_time, base.x, base.y);
520   if(dying == DYING_NOT)
521     collision_swept_object_map(&old_base, &base);
522 }
523
524 void
525 BadGuy::action_mrbomb(double elapsed_time)
526 {
527   if(frozen_timer.check())
528     {
529     set_sprite(img_mrbomb_iced_left, img_mrbomb_iced_right);
530     return;
531     }
532
533   if (dying == DYING_NOT)
534     check_horizontal_bump(true);
535
536   fall();
537
538   physic.apply(elapsed_time, base.x, base.y);
539   if (dying != DYING_FALLING)
540     collision_swept_object_map(&old_base,&base); 
541 }
542
543 void
544 BadGuy::action_bomb(double elapsed_time)
545 {
546   static const int TICKINGTIME = 1000;
547   static const int EXPLODETIME = 1000;
548     
549   fall();
550
551   if(mode == NORMAL) {
552     mode = BOMB_TICKING;
553     timer.start(TICKINGTIME);
554   } else if(!timer.check()) {
555     if(mode == BOMB_TICKING) {
556       mode = BOMB_EXPLODE;
557       set_sprite(img_mrbomb_explosion, img_mrbomb_explosion);
558       dying = DYING_NOT; // now the bomb hurts
559       timer.start(EXPLODETIME);
560
561       float scroll_x = World::current()->camera->get_translation().x;                 
562
563       /* play explosion sound */  // FIXME: is the stereo all right? maybe we should use player cordinates...
564       if (base.x < scroll_x + screen->w/2 - 10)
565         play_sound(sounds[SND_EXPLODE], SOUND_LEFT_SPEAKER);
566       else if (base.x > scroll_x + screen->w/2 + 10)
567         play_sound(sounds[SND_EXPLODE], SOUND_RIGHT_SPEAKER);
568       else
569         play_sound(sounds[SND_EXPLODE], SOUND_CENTER_SPEAKER);
570
571     } else if(mode == BOMB_EXPLODE) {
572       remove_me();
573       return;
574     }
575   }
576
577   // move
578   physic.apply(elapsed_time, base.x, base.y);                 
579   collision_swept_object_map(&old_base,&base);
580 }
581
582 void
583 BadGuy::action_stalactite(double elapsed_time)
584 {
585   Player& tux = *World::current()->get_tux();
586
587   static const int SHAKETIME = 800;
588   static const int RANGE = 40;
589     
590   if(mode == NORMAL) {
591     // start shaking when tux is below the stalactite and at least 40 pixels
592     // near
593     if(tux.base.x + 32 > base.x - RANGE && tux.base.x < base.x + 32 + RANGE
594             && tux.base.y + tux.base.height > base.y) {
595       timer.start(SHAKETIME);
596       mode = STALACTITE_SHAKING;
597     }
598   } if(mode == STALACTITE_SHAKING) {
599     base.x = old_base.x + (rand() % 6) - 3; // TODO this could be done nicer...
600     if(!timer.check()) {
601       mode = STALACTITE_FALL;
602     }
603   } else if(mode == STALACTITE_FALL) {
604     fall();
605     /* Destroy if we collides with land */
606     if(issolid(base.x+base.width/2, base.y+base.height))
607     {
608       timer.start(2000);
609       dying = DYING_SQUISHED;
610       mode = FLAT;
611       set_sprite(img_stalactite_broken, img_stalactite_broken);
612     }
613   } else if(mode == FLAT) {
614     fall();
615   }
616
617   // move
618   physic.apply(elapsed_time, base.x, base.y);
619
620   if(dying == DYING_SQUISHED && !timer.check())
621     remove_me();
622 }
623
624 void
625 BadGuy::action_flame(double elapsed_time)
626 {
627     static const float radius = 100;
628     static const float speed = 0.02;
629     base.x = old_base.x + cos(angle) * radius;
630     base.y = old_base.y + sin(angle) * radius;
631
632     angle = fmodf(angle + elapsed_time * speed, 2*M_PI);
633 }
634
635 void
636 BadGuy::action_fish(double elapsed_time)
637 {
638   if(frozen_timer.check())
639     {
640     if(physic.get_velocity_y() < 0)
641       set_sprite(img_fish_iced_down, img_fish_iced_down);
642     else
643       set_sprite(img_fish_iced, img_fish_iced);
644
645     return;
646     }
647
648   static const float JUMPV = 6;
649   static const int WAITTIME = 1000;
650     
651   // go in wait mode when back in water
652   if(dying == DYING_NOT && gettile(base.x, base.y+ base.height)->water
653         && physic.get_velocity_y() <= 0 && mode == NORMAL)
654     {
655       mode = FISH_WAIT;
656       set_sprite(0, 0);
657       physic.set_velocity(0, 0);
658       physic.enable_gravity(false);
659       timer.start(WAITTIME);
660     }
661   else if(mode == FISH_WAIT && !timer.check())
662     {
663       // jump again
664       set_sprite(img_fish, img_fish);
665       mode = NORMAL;
666       physic.set_velocity(0, JUMPV);
667       physic.enable_gravity(true);
668     }
669
670   physic.apply(elapsed_time, base.x, base.y);
671   if(dying == DYING_NOT)
672     collision_swept_object_map(&old_base, &base);
673
674   if(physic.get_velocity_y() < 0)
675     set_sprite(img_fish_down, img_fish_down);
676 }
677
678 void
679 BadGuy::action_bouncingsnowball(double elapsed_time)
680 {
681   static const float JUMPV = 4.5;
682     
683   fall();
684
685   // jump when on ground
686   if(dying == DYING_NOT && issolid(base.x, base.y+32))
687     {
688       physic.set_velocity_y(JUMPV);
689       physic.enable_gravity(true);
690     }                                                     
691   else
692     {
693       mode = NORMAL;
694     }
695
696   // check for right/left collisions
697   check_horizontal_bump();
698
699   physic.apply(elapsed_time, base.x, base.y);
700   if(dying == DYING_NOT)
701     collision_swept_object_map(&old_base, &base);
702
703   // Handle dying timer:
704   if (dying == DYING_SQUISHED && !timer.check())
705     remove_me();
706 }
707
708 void
709 BadGuy::action_flyingsnowball(double elapsed_time)
710 {
711   static const float FLYINGSPEED = 1;
712   static const int DIRCHANGETIME = 1000;
713     
714   // go into flyup mode if none specified yet
715   if(dying == DYING_NOT && mode == NORMAL) {
716     mode = FLY_UP;
717     physic.set_velocity_y(FLYINGSPEED);
718     timer.start(DIRCHANGETIME/2);
719   }
720
721   if(dying == DYING_NOT && !timer.check()) {
722     if(mode == FLY_UP) {
723       mode = FLY_DOWN;
724       physic.set_velocity_y(-FLYINGSPEED);
725     } else if(mode == FLY_DOWN) {
726       mode = FLY_UP;
727       physic.set_velocity_y(FLYINGSPEED);
728     }
729     timer.start(DIRCHANGETIME);
730   }
731
732   if(dying != DYING_NOT)
733     physic.enable_gravity(true);
734
735   physic.apply(elapsed_time, base.x, base.y);
736   if(dying == DYING_NOT || dying == DYING_SQUISHED)
737     collision_swept_object_map(&old_base, &base);
738
739   // Handle dying timer:
740   if (dying == DYING_SQUISHED && !timer.check())
741     remove_me();
742 }
743
744 void
745 BadGuy::action_spiky(double elapsed_time)
746 {
747   if(frozen_timer.check())
748     {
749     set_sprite(img_spiky_iced_left, img_spiky_iced_right);
750     return;
751     }
752
753   if (dying == DYING_NOT)
754     check_horizontal_bump();
755
756   fall();
757 #if 0
758   // jump when we're about to fall
759   if (physic.get_velocity_y() == 0 && 
760           !issolid(base.x+base.width/2, base.y + base.height)) {
761     physic.enable_gravity(true);
762     physic.set_velocity_y(2);
763   }
764 #endif
765
766   physic.apply(elapsed_time, base.x, base.y);
767   if (dying != DYING_FALLING)
768     collision_swept_object_map(&old_base,&base);   
769 }
770
771 void
772 BadGuy::action_snowball(double elapsed_time)
773 {
774   if (dying == DYING_NOT)
775     check_horizontal_bump();
776
777   fall();
778
779   physic.apply(elapsed_time, base.x, base.y);
780   if (dying != DYING_FALLING)
781     collision_swept_object_map(&old_base,&base);
782
783   // Handle dying timer:
784   if (dying == DYING_SQUISHED && !timer.check())
785     remove_me();                                  
786 }
787
788 void
789 BadGuy::action_wingling(double elapsed_time)
790 {
791   if (dying != DYING_NOT)
792     physic.enable_gravity(true);
793   else
794   {
795     Player& tux = *World::current()->get_tux();
796     int dirsign = physic.get_velocity_x() < 0 ? -1 : 1;
797
798     if (fabsf(tux.base.x - base.x) < 150 && base.y < tux.base.y && tux.dying == DYING_NOT)
799     {
800       if (target.x < 0 && target.y < 0)
801       {
802         target.x = tux.base.x;
803         target.y = tux.base.y;
804         physic.set_velocity(dirsign * 1.5f, -2.25f);
805       }
806     }
807     else if (base.y >= target.y - 16)
808       physic.set_velocity(dirsign * WINGLING_FLY_SPEED, 0);
809   }
810
811   physic.apply(elapsed_time, base.x, base.y);
812
813
814   // Handle dying timer:
815   if (dying == DYING_SQUISHED && !timer.check())
816     remove_me();
817
818   // TODO: Winglings should be removed after flying off the screen
819 }
820
821
822 void
823 BadGuy::action(float elapsed_time)
824 {
825   float scroll_x = World::current()->camera->get_translation().x;
826   float scroll_y = World::current()->camera->get_translation().y;
827   
828   // BadGuy fall below the ground
829   if (base.y > World::current()->get_level()->height * 32) {
830     remove_me();
831     return;
832   }
833
834   // Kill us if we landed on spikes
835   if (dying == DYING_NOT
836       && (kind != BAD_STALACTITE && kind != BAD_FLAME && kind != BAD_BOMB)
837       && (isspike(base.x, base.y) || isspike(base.x + base.width, base.y)
838       ||  isspike(base.x, base.y + base.height)
839       ||  isspike(base.x + base.width, base.y + base.height)))
840       {
841          physic.set_velocity_y(3);
842          kill_me(0);
843       }
844
845   if(!seen) {
846     /* activate badguys if they're just inside the offscreen_distance around the
847      * screen. Don't activate them inside the screen, since that might have the
848      * effect of badguys suddenly popping up from nowhere
849      */
850     if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
851         start_position.x < scroll_x - base.width)
852       activate(RIGHT);
853     else if(start_position.x > scroll_y - Y_OFFSCREEN_DISTANCE &&
854         start_position.y < scroll_y - base.height)
855       activate(LEFT);
856     else if(start_position.x > scroll_x + screen->w &&
857         start_position.x < scroll_x + screen->w + X_OFFSCREEN_DISTANCE)
858       activate(LEFT);
859     else if(start_position.y > scroll_y + screen->h &&
860         start_position.y < scroll_y + screen->h + Y_OFFSCREEN_DISTANCE)
861       activate(LEFT);
862   } else {
863     if(base.x + base.width < scroll_x - X_OFFSCREEN_DISTANCE*4
864       || base.x > scroll_x + screen->w + X_OFFSCREEN_DISTANCE*4
865       || base.y + base.height < scroll_y - Y_OFFSCREEN_DISTANCE*4
866       || base.y > scroll_y + screen->h + Y_OFFSCREEN_DISTANCE*4) {
867       seen = false;
868       if(dying != DYING_NOT)
869         remove_me();
870     }
871   }
872   
873   if(!seen)
874     return;
875   
876   switch (kind)
877     {
878     case BAD_MRICEBLOCK:
879       action_mriceblock(elapsed_time);
880       break;
881   
882     case BAD_JUMPY:
883       action_jumpy(elapsed_time);
884       break;
885
886     case BAD_MRBOMB:
887       action_mrbomb(elapsed_time);
888       break;
889     
890     case BAD_BOMB:
891       action_bomb(elapsed_time);
892       break;
893
894     case BAD_STALACTITE:
895       action_stalactite(elapsed_time);
896       break;
897
898     case BAD_FLAME:
899       action_flame(elapsed_time);
900       break;
901
902     case BAD_FISH:
903       action_fish(elapsed_time);
904       break;
905
906     case BAD_BOUNCINGSNOWBALL:
907       action_bouncingsnowball(elapsed_time);
908       break;
909
910     case BAD_FLYINGSNOWBALL:
911       action_flyingsnowball(elapsed_time);
912       break;
913
914     case BAD_SPIKY:
915       action_spiky(elapsed_time);
916       break;
917
918     case BAD_SNOWBALL:
919       action_snowball(elapsed_time);
920       break;
921
922     case BAD_WINGLING:
923       action_wingling(elapsed_time);
924       break;
925
926     default:
927       break;
928     }
929 }
930
931 void
932 BadGuy::draw(Camera& viewport, int)
933 {
934   float scroll_x = viewport.get_translation().x;
935   float scroll_y = viewport.get_translation().y;
936
937   // Don't try to draw stuff that is outside of the screen
938   if(base.x <= scroll_x - base.width || base.x >= scroll_x + screen->w)
939     return;
940   
941   if(sprite_left == 0 || sprite_right == 0)
942     {
943       return;
944     }
945
946   Sprite* sprite = (dir == LEFT) ? sprite_left : sprite_right;
947   sprite->draw(viewport.world2screen(Vector(base.x, base.y)));
948
949   if (debug_mode)
950     fillrect(base.x - scroll_x, base.y - scroll_y, base.width, base.height, 75,0,75, 150);
951 }
952
953 void
954 BadGuy::set_sprite(Sprite* left, Sprite* right) 
955 {
956   if (1)
957     {
958       base.width = 32;
959       base.height = 32;
960     }
961   else
962     {
963       // FIXME: Using the image size for the physics and collision is
964       // a bad idea, since images should always overlap there physical
965       // representation
966       if(left != 0) {
967         if(base.width == 0 && base.height == 0) {
968           base.width  = left->get_width();
969           base.height = left->get_height();
970         } else if(base.width != left->get_width() || base.height != left->get_height()) {
971           base.x -= (left->get_width() - base.width) / 2;
972           base.y -= left->get_height() - base.height;
973           base.width = left->get_width();
974           base.height = left->get_height();
975           old_base = base;
976         }
977       } else {
978         base.width = base.height = 0;
979       }
980     }
981
982   animation_offset = 0;
983   sprite_left  = left;
984   sprite_right = right;
985 }
986
987 void
988 BadGuy::bump()
989 {
990   // these can't be bumped
991   if(kind == BAD_FLAME || kind == BAD_BOMB || kind == BAD_FISH
992       || kind == BAD_FLYINGSNOWBALL)
993     return;
994
995   physic.set_velocity_y(3);
996   kill_me(25);
997 }
998
999 void
1000 BadGuy::make_player_jump(Player* player)
1001 {
1002   player->physic.set_velocity_y(2);
1003   player->base.y = base.y - player->base.height - 2;
1004 }
1005
1006 void
1007 BadGuy::squish_me(Player* player)
1008 {
1009   make_player_jump(player);
1010     
1011   World::current()->add_score(Vector(base.x, base.y),
1012                               50 * player_status.score_multiplier);
1013   play_sound(sounds[SND_SQUISH], SOUND_CENTER_SPEAKER);
1014   player_status.score_multiplier++;
1015
1016   dying = DYING_SQUISHED;
1017   timer.start(2000);
1018   physic.set_velocity(0, 0);
1019 }
1020
1021 void
1022 BadGuy::squish(Player* player)
1023 {
1024   static const int MAX_ICEBLOCK_SQUICHES = 10;
1025     
1026   if(kind == BAD_MRBOMB) {
1027     // mrbomb transforms into a bomb now
1028     explode();
1029     
1030     make_player_jump(player);
1031     World::current()->add_score(Vector(base.x, base.y),
1032                                 50 * player_status.score_multiplier);
1033     play_sound(sounds[SND_SQUISH], SOUND_CENTER_SPEAKER);
1034     player_status.score_multiplier++;
1035     return;
1036
1037   } else if (kind == BAD_MRICEBLOCK) {
1038     if (mode == NORMAL || mode == KICK)
1039       {
1040         /* Flatten! */
1041         play_sound(sounds[SND_STOMP], SOUND_CENTER_SPEAKER);
1042         mode = FLAT;
1043         set_sprite(img_mriceblock_flat_left, img_mriceblock_flat_right);
1044         physic.set_velocity_x(0);
1045
1046         timer.start(4000);
1047       } else if (mode == FLAT) {
1048         /* Kick! */
1049         play_sound(sounds[SND_KICK], SOUND_CENTER_SPEAKER);
1050
1051         if (player->base.x < base.x + (base.width/2)) {
1052           physic.set_velocity_x(5);
1053           dir = RIGHT;
1054         } else {
1055           physic.set_velocity_x(-5);
1056           dir = LEFT;
1057         }
1058
1059         mode = KICK;
1060         player->kick_timer.start(KICKING_TIME);
1061         set_sprite(img_mriceblock_flat_left, img_mriceblock_flat_right);
1062       }
1063
1064     make_player_jump(player);
1065
1066     player_status.score_multiplier++;
1067
1068     // check for maximum number of squishes
1069     squishcount++;
1070     if(squishcount >= MAX_ICEBLOCK_SQUICHES) {
1071       kill_me(50);
1072       return;
1073     }
1074     
1075     return;
1076   } else if(kind == BAD_FISH) {
1077     // fish can only be killed when falling down
1078     if(physic.get_velocity_y() >= 0)
1079       return;
1080       
1081     make_player_jump(player);
1082               
1083     World::current()->add_score(Vector(base.x, base.y),
1084                                 25 * player_status.score_multiplier);
1085     player_status.score_multiplier++;
1086      
1087     // simply remove the fish...
1088     remove_me();
1089     return;
1090   } else if(kind == BAD_BOUNCINGSNOWBALL) {
1091     squish_me(player);
1092     set_sprite(img_bouncingsnowball_squished,img_bouncingsnowball_squished);
1093     return;
1094   } else if(kind == BAD_FLYINGSNOWBALL) {
1095     squish_me(player);
1096     set_sprite(img_flyingsnowball_squished,img_flyingsnowball_squished);
1097     return;
1098   } else if(kind == BAD_SNOWBALL) {
1099     squish_me(player);
1100     set_sprite(img_snowball_squished_left, img_snowball_squished_right);
1101     return;
1102   } else if(kind == BAD_WINGLING) {
1103     squish_me(player);
1104     set_sprite(img_wingling_left, img_wingling_left);
1105   }
1106     
1107   
1108 }
1109
1110 void
1111 BadGuy::kill_me(int score)
1112 {
1113   if(kind == BAD_BOMB)
1114     return;
1115
1116   dying = DYING_FALLING;
1117   if(kind == BAD_MRICEBLOCK) {
1118     set_sprite(img_mriceblock_falling_left, img_mriceblock_falling_right);
1119     if(mode == HELD) {
1120       mode = NORMAL;
1121       Player& tux = *World::current()->get_tux();  
1122       tux.holding_something = false;
1123     }
1124   }
1125
1126   physic.enable_gravity(true);
1127
1128   /* Gain some points: */
1129   if (score != 0)
1130     World::current()->add_score(Vector(base.x, base.y),
1131                                 score * player_status.score_multiplier);
1132
1133   /* Play death sound: */
1134   play_sound(sounds[SND_FALL], SOUND_CENTER_SPEAKER);
1135 }
1136
1137 void
1138 BadGuy::explode()
1139 {
1140   World::current()->add_bad_guy(base.x, base.y, BAD_BOMB);
1141   remove_me();
1142 }
1143
1144 void
1145 BadGuy::collision(const MovingObject&, int)
1146 {
1147   // later
1148 }
1149
1150 void
1151 BadGuy::collision(void *p_c_object, int c_object, CollisionType type)
1152 {
1153   BadGuy* pbad_c    = NULL;
1154   Bullet* pbullet_c = NULL;
1155
1156   if(type == COLLISION_BUMP) {
1157     bump();
1158     return;
1159   }
1160
1161   if(type == COLLISION_SQUISH) {
1162     Player* player = static_cast<Player*>(p_c_object);
1163     squish(player);
1164     return;
1165   }
1166
1167   /* COLLISION_NORMAL */
1168   switch (c_object)
1169     {
1170     case CO_BULLET:
1171       pbullet_c = (Bullet*) p_c_object;
1172
1173       if(pbullet_c->kind == FIRE_BULLET)
1174         {
1175         if (kind != BAD_BOMB && kind != BAD_STALACTITE && kind != BAD_FLAME)
1176           kill_me(10);
1177         }
1178       else if(pbullet_c->kind == ICE_BULLET)
1179         {
1180         //if(kind == BAD_FLAME)
1181         //  kill_me(10);
1182         //else
1183           frozen_timer.start(FROZEN_TIME);
1184         }
1185       break;
1186
1187     case CO_BADGUY:
1188       pbad_c = (BadGuy*) p_c_object;
1189
1190
1191       /* If we're a kicked mriceblock, kill [almost] any badguys we hit */
1192       if(kind == BAD_MRICEBLOCK && mode == KICK &&
1193          kind != BAD_FLAME && kind != BAD_BOMB && kind != BAD_STALACTITE)
1194         {
1195           pbad_c->kill_me(25);
1196         }
1197
1198       // a held mriceblock kills the enemy too but falls to ground then
1199       else if(kind == BAD_MRICEBLOCK && mode == HELD)
1200         {
1201           pbad_c->kill_me(25);
1202           kill_me(0);
1203         }
1204
1205       /* Kill badguys that run into exploding bomb */
1206       else if (kind == BAD_BOMB && dying == DYING_NOT)
1207       {
1208         if (pbad_c->kind == BAD_MRBOMB)
1209         {
1210           // mrbomb transforms into a bomb now
1211           pbad_c->explode();
1212           return;
1213         }
1214         else if (pbad_c->kind != BAD_MRBOMB)
1215         {
1216           pbad_c->kill_me(50);
1217         }
1218       }
1219
1220       /* Kill any badguys that get hit by stalactite */
1221       else if (kind == BAD_STALACTITE && dying == DYING_NOT)
1222       {
1223         if (pbad_c->kind == BAD_MRBOMB)
1224         {
1225           // mrbomb transforms into a bomb now
1226           pbad_c->explode();
1227           return;
1228         }
1229         else
1230           pbad_c->kill_me(50);
1231       }
1232
1233       /* When enemies run into eachother, make them change directions */
1234       else
1235       {
1236         // Wingling doesn't interact with other badguys
1237         if (pbad_c->kind == BAD_WINGLING || kind == BAD_WINGLING)
1238           break;
1239
1240         // Jumpy, fish, flame, stalactites, wingling are exceptions
1241         if (pbad_c->kind == BAD_JUMPY || pbad_c->kind == BAD_FLAME
1242             || pbad_c->kind == BAD_STALACTITE || pbad_c->kind == BAD_FISH)
1243           break;
1244
1245         // Bounce off of other badguy if we land on top of him
1246         if (base.y + base.height < pbad_c->base.y + pbad_c->base.height)
1247         {
1248           if (pbad_c->dir == LEFT)
1249           {
1250             dir = RIGHT;
1251             physic.set_velocity(fabsf(physic.get_velocity_x()), 2);
1252           }
1253           else if (pbad_c->dir == RIGHT)
1254           {
1255             dir = LEFT;
1256             physic.set_velocity(-fabsf(physic.get_velocity_x()), 2);
1257           }
1258
1259           break;
1260         }
1261         else if (base.y + base.height > pbad_c->base.y + pbad_c->base.height)
1262           break;
1263
1264         if (pbad_c->kind != BAD_FLAME)
1265           {
1266             if (dir == LEFT)
1267             {
1268               dir = RIGHT;
1269               physic.set_velocity_x(fabsf(physic.get_velocity_x()));
1270
1271               // in case badguys get "jammed"
1272               if (physic.get_velocity_x() != 0)
1273                 base.x = pbad_c->base.x + pbad_c->base.width;
1274             }
1275             else if (dir == RIGHT)
1276             {
1277               dir = LEFT;
1278               physic.set_velocity_x(-fabsf(physic.get_velocity_x()));
1279             }
1280
1281           }
1282       }
1283       
1284       break;
1285
1286     case CO_PLAYER:
1287       Player* player = static_cast<Player*>(p_c_object);
1288       /* Get kicked if were flat */
1289       if (mode == FLAT && !dying)
1290       {
1291         play_sound(sounds[SND_KICK], SOUND_CENTER_SPEAKER);
1292
1293         // Hit from left side
1294         if (player->base.x < base.x) {
1295           physic.set_velocity_x(5);
1296           dir = RIGHT;
1297         }
1298         // Hit from right side
1299         else {
1300           physic.set_velocity_x(-5);
1301           dir = LEFT;
1302         }
1303
1304         mode = KICK;
1305         player->kick_timer.start(KICKING_TIME);
1306         set_sprite(img_mriceblock_flat_left, img_mriceblock_flat_right);
1307       }
1308       break;
1309
1310     }
1311 }
1312
1313
1314 //---------------------------------------------------------------------------
1315
1316 void load_badguy_gfx()
1317 {
1318   img_mriceblock_flat_left = sprite_manager->load("mriceblock-flat-left");
1319   img_mriceblock_flat_right = sprite_manager->load("mriceblock-flat-right");
1320   img_mriceblock_falling_left = sprite_manager->load("mriceblock-falling-left");
1321   img_mriceblock_falling_right = sprite_manager->load("mriceblock-falling-right");
1322   img_mriceblock_left = sprite_manager->load("mriceblock-left");
1323   img_mriceblock_right = sprite_manager->load("mriceblock-right");
1324   img_jumpy_left_up = sprite_manager->load("jumpy-left-up");
1325   img_jumpy_left_down = sprite_manager->load("jumpy-left-down");
1326   img_jumpy_left_middle = sprite_manager->load("jumpy-left-middle");
1327   img_jumpy_left_iced = sprite_manager->load("jumpy-left-iced");
1328   img_mrbomb_left = sprite_manager->load("mrbomb-left");
1329   img_mrbomb_right = sprite_manager->load("mrbomb-right");
1330   img_mrbomb_iced_left = sprite_manager->load("mrbomb-iced-left");
1331   img_mrbomb_iced_right = sprite_manager->load("mrbomb-iced-right");
1332   img_mrbomb_ticking_left = sprite_manager->load("mrbomb-ticking-left");
1333   img_mrbomb_ticking_right = sprite_manager->load("mrbomb-ticking-right");
1334   img_mrbomb_explosion = sprite_manager->load("mrbomb-explosion");
1335   img_stalactite = sprite_manager->load("stalactite");
1336   img_stalactite_broken = sprite_manager->load("stalactite-broken");
1337   img_flame = sprite_manager->load("flame");
1338   img_fish = sprite_manager->load("fish");
1339   img_fish_down = sprite_manager->load("fish-down");
1340   img_fish_iced = sprite_manager->load("fish-iced");
1341   img_fish_iced_down = sprite_manager->load("fish-iced-down");
1342   img_bouncingsnowball_left = sprite_manager->load("bouncingsnowball-left");
1343   img_bouncingsnowball_right = sprite_manager->load("bouncingsnowball-right");
1344   img_bouncingsnowball_squished = sprite_manager->load("bouncingsnowball-squished");
1345   img_flyingsnowball = sprite_manager->load("flyingsnowball");
1346   img_flyingsnowball_squished = sprite_manager->load("flyingsnowball-squished");
1347   img_spiky_left = sprite_manager->load("spiky-left");
1348   img_spiky_right = sprite_manager->load("spiky-right");
1349   img_spiky_iced_left = sprite_manager->load("spiky-iced-left");
1350   img_spiky_iced_right = sprite_manager->load("spiky-iced-right");
1351   img_snowball_left = sprite_manager->load("snowball-left");
1352   img_snowball_right = sprite_manager->load("snowball-right");
1353   img_snowball_squished_left = sprite_manager->load("snowball-squished-left");
1354   img_snowball_squished_right = sprite_manager->load("snowball-squished-right");
1355   img_wingling_left = sprite_manager->load("wingling-left");
1356 }
1357
1358 void free_badguy_gfx()
1359 {
1360 }
1361
1362 // EOF //