Make Badguy activation dependent of Player position, not currently-visible screen
[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.enable_gravity(true);
350   set_state(STATE_FALLING);
351
352   // start dead-script
353   if(dead_script != "") {
354     std::istringstream stream(dead_script);
355     Sector::current()->run_script(stream, "dead-script");
356   }
357 }
358
359 void
360 BadGuy::run_dead_script()
361 {
362    if (countMe)
363      Sector::current()->get_level()->stats.badguys++;
364    
365    // start dead-script
366   if(dead_script != "") {
367     std::istringstream stream(dead_script);
368     Sector::current()->run_script(stream, "dead-script");
369   }
370 }
371
372 void
373 BadGuy::set_state(State state)
374 {
375   if(this->state == state)
376     return;
377
378   State laststate = this->state;
379   this->state = state;
380   switch(state) {
381     case STATE_SQUISHED:
382       state_timer.start(SQUISH_TIME);
383       break;
384     case STATE_ACTIVE:
385       set_group(colgroup_active);
386       //bbox.set_pos(start_position);
387       break;
388     case STATE_INACTIVE:
389       // was the badguy dead anyway?
390       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
391         remove_me();
392       }
393       set_group(COLGROUP_DISABLED);
394       break;
395     case STATE_FALLING:
396       set_group(COLGROUP_DISABLED);
397       break;
398     default:
399       break;
400   }
401 }
402
403 bool
404 BadGuy::is_offscreen()
405 {
406   Player* player = get_nearest_player();
407   if (!player) return false;
408   Vector dist = player->get_bbox().get_middle() - get_bbox().get_middle();
409   if ((dist.x <= X_OFFSCREEN_DISTANCE+32) && (dist.y <= Y_OFFSCREEN_DISTANCE+32)) {
410     return false;
411   }
412   return true;
413 }
414
415 void
416 BadGuy::try_activate()
417 {
418   // In SuperTux 0.1.x, Badguys were activated when Tux<->Badguy center distance was approx. <= ~668px
419   // This doesn't work for wide-screen monitors which give us a virt. res. of approx. 1066px x 600px
420   Player* player = get_nearest_player();
421   if (!player) return;
422   Vector dist = player->get_bbox().get_middle() - get_bbox().get_middle();
423   if ((fabsf(dist.x) <= X_OFFSCREEN_DISTANCE) && (fabsf(dist.y) <= Y_OFFSCREEN_DISTANCE)) {
424     set_state(STATE_ACTIVE);
425     if (!is_initialized) {
426
427       // if starting direction was set to AUTO, this is our chance to re-orient the badguy
428       if (start_dir == AUTO) {
429         Player* player = get_nearest_player();
430         if (player && (player->get_bbox().p1.x > get_bbox().p2.x)) {
431           dir = RIGHT;
432         } else {
433           dir = LEFT;
434         }
435       }
436
437       initialize();
438       is_initialized = true;
439     }
440     activate();
441   }
442 }
443
444 bool
445 BadGuy::might_fall(int height)
446 {
447   // make sure we check for at least a 1-pixel fall
448   assert(height > 0);
449
450   float x1;
451   float x2;
452   float y1 = bbox.p2.y + 1;
453   float y2 = bbox.p2.y + 1 + height;
454   if (dir == LEFT) {
455     x1 = bbox.p1.x - 1;
456     x2 = bbox.p1.x - 1;
457   } else {
458     x1 = bbox.p2.x + 1;
459     x2 = bbox.p2.x + 1;
460   }
461   return Sector::current()->is_free_of_statics(Rect(x1, y1, x2, y2));
462 }
463
464 Player*
465 BadGuy::get_nearest_player()
466 {
467   // FIXME: does not really return nearest player
468
469   std::vector<Player*> players = Sector::current()->get_players();
470   for (std::vector<Player*>::iterator playerIter = players.begin(); playerIter != players.end(); ++playerIter) {
471     Player* player = *playerIter;
472     if (player->is_dying() || player->is_dead()) continue;
473     return player;
474   }
475
476   return 0;
477 }
478
479 void
480 BadGuy::update_on_ground_flag(const CollisionHit& hit)
481 {
482   if (hit.bottom) {
483     on_ground_flag = true;
484     floor_normal = hit.slope_normal;
485   }
486 }
487
488 bool
489 BadGuy::on_ground()
490 {
491   return on_ground_flag;
492 }
493
494 bool
495 BadGuy::is_active()
496 {
497   return is_active_flag;
498 }
499
500 Vector
501 BadGuy::get_floor_normal()
502 {
503   return floor_normal;
504 }
505
506 void
507 BadGuy::freeze()
508 {
509   set_group(COLGROUP_MOVING_STATIC);
510   frozen = true;
511 }
512
513 void
514 BadGuy::unfreeze()
515 {
516   set_group(colgroup_active);
517   frozen = false;
518 }
519
520 bool
521 BadGuy::is_freezable() const
522 {
523   return false;
524 }
525
526 bool
527 BadGuy::is_frozen() const
528 {
529   return frozen;
530 }
531
532 void
533 BadGuy::ignite()
534 {
535   kill_fall();
536 }
537
538 void
539 BadGuy::extinguish()
540 {
541 }
542
543 bool
544 BadGuy::is_flammable() const
545 {
546   return true;
547 }
548
549 bool
550 BadGuy::is_ignited() const
551 {
552   return ignited;
553 }
554   
555 void 
556 BadGuy::set_colgroup_active(CollisionGroup group)
557 {
558   this->colgroup_active = group;
559   if (state == STATE_ACTIVE) set_group(group); 
560 }
561