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