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