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