2fbacae127cbcab6114ec999f3c75a56afde663a
[supertux.git] / src / badguy / badguy.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include "badguy.hpp"
23
24 #include "audio/sound_manager.hpp"
25 #include "game_session.hpp"
26 #include "level.hpp"
27 #include "log.hpp"
28 #include "main.hpp"
29 #include "object/bullet.hpp"
30 #include "object/camera.hpp"
31 #include "object/particles.hpp"
32 #include "object/player.hpp"
33 #include "object/tilemap.hpp"
34 #include "random_generator.hpp"
35 #include "sector.hpp"
36 #include "statistics.hpp"
37 #include "tile.hpp"
38
39 #include <math.h>
40
41 static const float SQUISH_TIME = 2;
42   
43 static const float X_OFFSCREEN_DISTANCE = 1600;
44 static const float Y_OFFSCREEN_DISTANCE = 1200;
45
46 BadGuy::BadGuy(const Vector& pos, const std::string& sprite_name, int layer)
47   : MovingSprite(pos, sprite_name, layer, COLGROUP_DISABLED), countMe(true), is_initialized(false),
48     dir(LEFT), start_dir(AUTO), frozen(false), ignited(false),
49     state(STATE_INIT), on_ground_flag(false), colgroup_active(COLGROUP_MOVING)
50 {
51   start_position = bbox.p1;
52
53   sound_manager->preload("sounds/squish.wav");
54   sound_manager->preload("sounds/fall.wav");
55
56   dir = (start_dir == AUTO) ? LEFT : start_dir;
57 }
58
59 BadGuy::BadGuy(const Vector& pos, Direction direction, const std::string& sprite_name, int layer)
60   : MovingSprite(pos, sprite_name, layer, COLGROUP_DISABLED), countMe(true), is_initialized(false), 
61     dir(direction), start_dir(direction), frozen(false), ignited(false),
62     state(STATE_INIT), on_ground_flag(false), colgroup_active(COLGROUP_MOVING)
63 {
64   start_position = bbox.p1;
65
66   sound_manager->preload("sounds/squish.wav");
67   sound_manager->preload("sounds/fall.wav");
68
69   dir = (start_dir == AUTO) ? LEFT : start_dir;
70 }
71
72 BadGuy::BadGuy(const lisp::Lisp& reader, const std::string& sprite_name, int layer)
73   : MovingSprite(reader, sprite_name, layer, COLGROUP_DISABLED), countMe(true), is_initialized(false), dir(LEFT), start_dir(AUTO), frozen(false), ignited(false), state(STATE_INIT), on_ground_flag(false), colgroup_active(COLGROUP_MOVING)
74 {
75   start_position = bbox.p1;
76
77   std::string dir_str = "auto";
78   reader.get("direction", dir_str);
79   start_dir = str2dir( dir_str );
80   dir = start_dir;
81
82   reader.get("dead-script", dead_script);
83
84   sound_manager->preload("sounds/squish.wav");
85   sound_manager->preload("sounds/fall.wav");
86
87   dir = (start_dir == AUTO) ? LEFT : start_dir;
88 }
89
90 void
91 BadGuy::draw(DrawingContext& context)
92 {
93   if(!sprite)
94     return;
95   if(state == STATE_INIT || state == STATE_INACTIVE)
96     return;
97   if(state == STATE_FALLING) {
98     DrawingEffect old_effect = context.get_drawing_effect();
99     context.set_drawing_effect((DrawingEffect) (old_effect | VERTICAL_FLIP));
100     sprite->draw(context, get_pos(), layer);
101     context.set_drawing_effect(old_effect);
102   } else {
103     sprite->draw(context, get_pos(), layer);
104   }
105 }
106
107 void
108 BadGuy::update(float elapsed_time)
109 {
110   if(!Sector::current()->inside(bbox)) {
111     is_active_flag = false;
112     remove_me();
113     return;
114   }
115   if ((state != STATE_INACTIVE) && is_offscreen()) {
116     if (state == STATE_ACTIVE) deactivate();
117     set_state(STATE_INACTIVE);
118   }
119
120   switch(state) {
121     case STATE_ACTIVE:
122       is_active_flag = true;
123       active_update(elapsed_time);
124       break;
125     case STATE_INIT:
126     case STATE_INACTIVE:
127       is_active_flag = false;
128       inactive_update(elapsed_time);
129       try_activate();
130       break;
131     case STATE_SQUISHED:
132       is_active_flag = false;
133       if(state_timer.check()) {
134         remove_me();
135         break;
136       }
137       movement = physic.get_movement(elapsed_time);
138       break;
139     case STATE_FALLING:
140       is_active_flag = false;
141       movement = physic.get_movement(elapsed_time);
142       break;
143   }
144
145   on_ground_flag = false;
146 }
147
148 Direction
149 BadGuy::str2dir( std::string dir_str )
150 {
151   if( dir_str == "auto" )
152     return AUTO;
153   if( dir_str == "left" )
154     return LEFT;
155   if( dir_str == "right" )
156     return RIGHT;
157
158   //default to "auto"
159   log_warning << "Badguy::str2dir: unknown direction \"" << dir_str << "\"" << std::endl;;
160   return AUTO;
161 }
162
163 void
164 BadGuy::initialize()
165 {
166 }
167
168 void
169 BadGuy::activate()
170 {
171 }
172
173 void
174 BadGuy::deactivate()
175 {
176 }
177
178 void
179 BadGuy::write(lisp::Writer& )
180 {
181   log_warning << "tried to write out a generic badguy" << std::endl;
182 }
183
184 void
185 BadGuy::active_update(float elapsed_time)
186 {
187   movement = physic.get_movement(elapsed_time);
188 }
189
190 void
191 BadGuy::inactive_update(float )
192 {
193 }
194
195 void
196 BadGuy::collision_tile(uint32_t tile_attributes)
197 {
198   if(tile_attributes & Tile::HURTS) {
199     if (tile_attributes & Tile::FIRE) {
200       if (is_flammable()) ignite();
201     }
202     else if (tile_attributes & Tile::ICE) {
203       if (is_freezable()) freeze();
204     }
205     else {
206       kill_fall();
207     }
208   }
209 }
210
211 HitResponse
212 BadGuy::collision(GameObject& other, const CollisionHit& hit)
213 {
214   if (!is_active()) return ABORT_MOVE;
215
216   BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
217   if(badguy && badguy->is_active() && badguy->get_group() == COLGROUP_MOVING) {
218
219     // hit from above?
220     if (badguy->get_bbox().p2.y < (bbox.p1.y + 16)) {
221       if(collision_squished(*badguy)) {
222         return ABORT_MOVE;
223       }
224     }
225
226     return collision_badguy(*badguy, hit);
227   }
228
229   Player* player = dynamic_cast<Player*> (&other);
230   if(player) {
231
232     // hit from above?
233     if (player->get_bbox().p2.y < (bbox.p1.y + 16)) {
234       if(collision_squished(*player)) {
235         return ABORT_MOVE;
236       }
237     }
238
239     return collision_player(*player, hit);
240   }
241
242   Bullet* bullet = dynamic_cast<Bullet*> (&other);
243   if(bullet)
244     return collision_bullet(*bullet, hit);
245
246   return FORCE_MOVE;
247 }
248
249 void
250 BadGuy::collision_solid(const CollisionHit& hit)
251 {
252   physic.set_velocity_x(0);
253   physic.set_velocity_y(0);
254   update_on_ground_flag(hit);
255 }
256
257 HitResponse
258 BadGuy::collision_player(Player& player, const CollisionHit& )
259 {
260   if(player.is_invincible()) {
261     kill_fall();
262     return ABORT_MOVE;
263   }
264
265   if(frozen)
266     unfreeze();
267   player.kill(false);
268   return FORCE_MOVE;
269 }
270
271 HitResponse
272 BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
273 {
274   return FORCE_MOVE;
275 }
276
277 bool
278 BadGuy::collision_squished(GameObject& )
279 {
280   return false;
281 }
282
283 HitResponse
284 BadGuy::collision_bullet(Bullet& bullet, const CollisionHit& hit)
285 {
286   if (is_frozen()) {
287     if(bullet.get_type() == FIRE_BONUS) {
288       // fire bullet thaws frozen badguys
289       unfreeze();
290       bullet.remove_me();
291       return ABORT_MOVE;
292     } else {
293       // other bullets ricochet
294       bullet.ricochet(*this, hit);
295       return FORCE_MOVE;
296     }
297   }
298   else if (is_ignited()) {
299     if(bullet.get_type() == ICE_BONUS) {
300       // ice bullets extinguish ignited badguys
301       extinguish();
302       bullet.remove_me();
303       return ABORT_MOVE;
304     } else {
305       // other bullets are absorbed by ignited badguys
306       bullet.remove_me();
307       return FORCE_MOVE;
308     }
309   }
310   else if(bullet.get_type() == FIRE_BONUS && is_flammable()) {
311     // fire bullets ignite flammable badguys
312     ignite();
313     bullet.remove_me();
314     return ABORT_MOVE;
315   }
316   else if(bullet.get_type() == ICE_BONUS && is_freezable()) {
317     // ice bullets freeze freezable badguys
318     freeze();
319     bullet.remove_me();
320     return ABORT_MOVE;
321   }
322   else {
323     // in all other cases, bullets ricochet
324     bullet.ricochet(*this, hit);
325     return FORCE_MOVE;
326   }
327 }
328
329 void
330 BadGuy::kill_squished(GameObject& object)
331 {
332   sound_manager->play("sounds/squish.wav", get_pos());
333   physic.enable_gravity(true);
334   physic.set_velocity_x(0);
335   physic.set_velocity_y(0);
336   set_state(STATE_SQUISHED);
337   set_group(COLGROUP_MOVING_ONLY_STATIC);
338   Player* player = dynamic_cast<Player*>(&object);
339   if (player) {
340     player->bounce(*this);
341   }
342
343   // start dead-script
344   run_dead_script();
345 }
346
347 void
348 BadGuy::kill_fall()
349 {
350   sound_manager->play("sounds/fall.wav", get_pos());
351   physic.set_velocity_y(0);
352   physic.set_acceleration_y(0);
353   physic.enable_gravity(true);
354   set_state(STATE_FALLING);
355
356   // start dead-script
357   run_dead_script();
358 }
359
360 void
361 BadGuy::run_dead_script()
362 {
363   if (countMe)
364      Sector::current()->get_level()->stats.badguys++;
365
366   countMe = false;
367    
368    // start dead-script
369   if(dead_script != "") {
370     std::istringstream stream(dead_script);
371     Sector::current()->run_script(stream, "dead-script");
372   }
373 }
374
375 void
376 BadGuy::set_state(State state)
377 {
378   if(this->state == state)
379     return;
380
381   State laststate = this->state;
382   this->state = state;
383   switch(state) {
384     case STATE_SQUISHED:
385       state_timer.start(SQUISH_TIME);
386       break;
387     case STATE_ACTIVE:
388       set_group(colgroup_active);
389       //bbox.set_pos(start_position);
390       break;
391     case STATE_INACTIVE:
392       // was the badguy dead anyway?
393       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
394         remove_me();
395       }
396       set_group(COLGROUP_DISABLED);
397       break;
398     case STATE_FALLING:
399       set_group(COLGROUP_DISABLED);
400       break;
401     default:
402       break;
403   }
404 }
405
406 bool
407 BadGuy::is_offscreen()
408 {
409   Player* player = get_nearest_player();
410   if (!player) return false;
411   Vector dist = player->get_bbox().get_middle() - get_bbox().get_middle();
412   if ((dist.x <= X_OFFSCREEN_DISTANCE+32) && (dist.y <= Y_OFFSCREEN_DISTANCE+32)) {
413     return false;
414   }
415   return true;
416 }
417
418 void
419 BadGuy::try_activate()
420 {
421   // In SuperTux 0.1.x, Badguys were activated when Tux<->Badguy center distance was approx. <= ~668px
422   // This doesn't work for wide-screen monitors which give us a virt. res. of approx. 1066px x 600px
423   Player* player = get_nearest_player();
424   if (!player) return;
425   Vector dist = player->get_bbox().get_middle() - get_bbox().get_middle();
426   if ((fabsf(dist.x) <= X_OFFSCREEN_DISTANCE) && (fabsf(dist.y) <= Y_OFFSCREEN_DISTANCE)) {
427     set_state(STATE_ACTIVE);
428     if (!is_initialized) {
429
430       // if starting direction was set to AUTO, this is our chance to re-orient the badguy
431       if (start_dir == AUTO) {
432         Player* player = get_nearest_player();
433         if (player && (player->get_bbox().p1.x > get_bbox().p2.x)) {
434           dir = RIGHT;
435         } else {
436           dir = LEFT;
437         }
438       }
439
440       initialize();
441       is_initialized = true;
442     }
443     activate();
444   }
445 }
446
447 bool
448 BadGuy::might_fall(int height)
449 {
450   // make sure we check for at least a 1-pixel fall
451   assert(height > 0);
452
453   float x1;
454   float x2;
455   float y1 = bbox.p2.y + 1;
456   float y2 = bbox.p2.y + 1 + height;
457   if (dir == LEFT) {
458     x1 = bbox.p1.x - 1;
459     x2 = bbox.p1.x - 1;
460   } else {
461     x1 = bbox.p2.x + 1;
462     x2 = bbox.p2.x + 1;
463   }
464   return Sector::current()->is_free_of_statics(Rect(x1, y1, x2, y2));
465 }
466
467 Player*
468 BadGuy::get_nearest_player()
469 {
470   // FIXME: does not really return nearest player
471
472   std::vector<Player*> players = Sector::current()->get_players();
473   for (std::vector<Player*>::iterator playerIter = players.begin(); playerIter != players.end(); ++playerIter) {
474     Player* player = *playerIter;
475     if (player->is_dying() || player->is_dead()) continue;
476     return player;
477   }
478
479   return 0;
480 }
481
482 void
483 BadGuy::update_on_ground_flag(const CollisionHit& hit)
484 {
485   if (hit.bottom) {
486     on_ground_flag = true;
487     floor_normal = hit.slope_normal;
488   }
489 }
490
491 bool
492 BadGuy::on_ground()
493 {
494   return on_ground_flag;
495 }
496
497 bool
498 BadGuy::is_active()
499 {
500   return is_active_flag;
501 }
502
503 Vector
504 BadGuy::get_floor_normal()
505 {
506   return floor_normal;
507 }
508
509 void
510 BadGuy::freeze()
511 {
512   set_group(COLGROUP_MOVING_STATIC);
513   frozen = true;
514 }
515
516 void
517 BadGuy::unfreeze()
518 {
519   set_group(colgroup_active);
520   frozen = false;
521 }
522
523 bool
524 BadGuy::is_freezable() const
525 {
526   return false;
527 }
528
529 bool
530 BadGuy::is_frozen() const
531 {
532   return frozen;
533 }
534
535 void
536 BadGuy::ignite()
537 {
538   kill_fall();
539 }
540
541 void
542 BadGuy::extinguish()
543 {
544 }
545
546 bool
547 BadGuy::is_flammable() const
548 {
549   return true;
550 }
551
552 bool
553 BadGuy::is_ignited() const
554 {
555   return ignited;
556 }
557   
558 void 
559 BadGuy::set_colgroup_active(CollisionGroup group)
560 {
561   this->colgroup_active = group;
562   if (state == STATE_ACTIVE) set_group(group); 
563 }
564