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