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