Badguys now get their deactivate() method called prior to being deactivated
[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
21 #include "badguy.hpp"
22 #include "object/camera.hpp"
23 #include "object/tilemap.hpp"
24 #include "tile.hpp"
25 #include "statistics.hpp"
26 #include "game_session.hpp"
27 #include "log.hpp"
28 #include "level.hpp"
29 #include "object/bullet.hpp"
30
31 static const float SQUISH_TIME = 2;
32 static const float X_OFFSCREEN_DISTANCE = 1600;
33 static const float Y_OFFSCREEN_DISTANCE = 1200;
34
35 BadGuy::BadGuy(const Vector& pos, const std::string& sprite_name, int layer)
36   : MovingSprite(pos, sprite_name, layer, COLGROUP_DISABLED), countMe(true), dir(LEFT), state(STATE_INIT) 
37 {
38   start_position = bbox.p1;
39
40   sound_manager->preload("sounds/squish.wav");
41   sound_manager->preload("sounds/fall.wav");
42 }
43
44 BadGuy::BadGuy(const lisp::Lisp& reader, const std::string& sprite_name, int layer)
45   : MovingSprite(reader, sprite_name, layer, COLGROUP_DISABLED), countMe(true), dir(LEFT), state(STATE_INIT) 
46 {
47   start_position = bbox.p1;
48
49   sound_manager->preload("sounds/squish.wav");
50   sound_manager->preload("sounds/fall.wav");
51 }
52
53 void
54 BadGuy::draw(DrawingContext& context)
55 {
56   if(!sprite)
57     return;
58   if(state == STATE_INIT || state == STATE_INACTIVE)
59     return;
60   if(state == STATE_FALLING) {
61     DrawingEffect old_effect = context.get_drawing_effect();
62     context.set_drawing_effect((DrawingEffect) (old_effect | VERTICAL_FLIP));
63     sprite->draw(context, get_pos(), layer);
64     context.set_drawing_effect(old_effect);
65   } else {
66     sprite->draw(context, get_pos(), layer);
67   }
68 }
69
70 void
71 BadGuy::update(float elapsed_time)
72 {
73   if(!Sector::current()->inside(bbox)) {
74     remove_me();
75     return;
76   }
77   if(is_offscreen()) {
78     if (state == STATE_ACTIVE) deactivate();
79     set_state(STATE_INACTIVE);
80   }
81   
82   switch(state) {
83     case STATE_ACTIVE:
84       active_update(elapsed_time);
85       break;
86     case STATE_INIT:
87     case STATE_INACTIVE:
88       inactive_update(elapsed_time);
89       try_activate();
90       break;
91     case STATE_SQUISHED:
92       if(state_timer.check()) {
93         remove_me();
94         break;
95       }
96       movement = physic.get_movement(elapsed_time);
97       break;
98     case STATE_FALLING:
99       movement = physic.get_movement(elapsed_time);
100       break;
101   }
102 }
103
104 void
105 BadGuy::activate()
106 {
107 }
108
109 void
110 BadGuy::deactivate()
111 {
112 }
113
114 void
115 BadGuy::save(lisp::Writer& )
116 {
117         log_warning << "tried to write out a generic badguy" << std::endl;
118 }
119
120 void
121 BadGuy::active_update(float elapsed_time)
122 {
123   movement = physic.get_movement(elapsed_time);
124 }
125
126 void
127 BadGuy::inactive_update(float )
128 {
129 }
130
131 void
132 BadGuy::collision_tile(uint32_t tile_attributes)
133 {
134   if(tile_attributes & Tile::HURTS)
135     kill_fall();
136 }
137
138 HitResponse
139 BadGuy::collision(GameObject& other, const CollisionHit& hit)
140 {
141   switch(state) {
142     case STATE_INIT:
143     case STATE_INACTIVE:
144       return ABORT_MOVE;
145     case STATE_ACTIVE: {
146       if(other.get_flags() & FLAG_SOLID)
147         return collision_solid(other, hit);
148
149       BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
150       if(badguy && badguy->state == STATE_ACTIVE && badguy->get_group() == COLGROUP_MOVING)
151         return collision_badguy(*badguy, hit);
152
153       Player* player = dynamic_cast<Player*> (&other);
154       if(player)
155         return collision_player(*player, hit);
156
157       Bullet* bullet = dynamic_cast<Bullet*> (&other);
158       if(bullet)
159         return collision_bullet(*bullet, hit);
160
161       return FORCE_MOVE;
162     }
163     case STATE_SQUISHED:
164       if(other.get_flags() & FLAG_SOLID)
165         return CONTINUE;
166       return FORCE_MOVE;
167     case STATE_FALLING:
168       return FORCE_MOVE;
169   }
170
171   return ABORT_MOVE;
172 }
173
174 HitResponse
175 BadGuy::collision_solid(GameObject& , const CollisionHit& )
176 {
177   return FORCE_MOVE;
178 }
179
180 HitResponse
181 BadGuy::collision_player(Player& player, const CollisionHit& )
182 {
183   /*
184   printf("PlayerHit: GT %3.1f PM: %3.1f %3.1f BM: %3.1f %3.1f Hit: %3.1f %3.1f\n",
185           game_time,
186           player.get_movement().x, player.get_movement().y,
187           get_movement().x, get_movement().y,
188           hit.normal.x, hit.normal.y);
189   */
190
191   // hit from above?
192   if(player.get_bbox().p2.y < (bbox.p1.y + 16)) {
193     // if it's not possible to squish us, then this will hurt
194     if(collision_squished(player))
195       return ABORT_MOVE;
196   }
197
198   if(player.is_invincible()) {
199     kill_fall();
200     return ABORT_MOVE;
201   }
202
203   player.kill(false);
204   return FORCE_MOVE;
205 }
206
207 HitResponse
208 BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
209 {
210   return FORCE_MOVE;
211 }
212
213 bool
214 BadGuy::collision_squished(Player& )
215 {
216   return false;
217 }
218
219 HitResponse
220 BadGuy::collision_bullet(Bullet& , const CollisionHit& )
221 {
222   kill_fall();
223   return ABORT_MOVE;
224 }
225
226 void
227 BadGuy::kill_squished(Player& player)
228 {
229   sound_manager->play("sounds/squish.wav", get_pos());
230   physic.enable_gravity(true);
231   physic.set_velocity_x(0);
232   physic.set_velocity_y(0);
233   set_state(STATE_SQUISHED);
234   set_group(COLGROUP_MOVING_ONLY_STATIC);
235   if (countMe) Sector::current()->get_level()->stats.badguys++;
236   player.bounce(*this);
237 }
238
239 void
240 BadGuy::kill_fall()
241 {
242   sound_manager->play("sounds/fall.wav", get_pos());
243   if (countMe) Sector::current()->get_level()->stats.badguys++;
244   physic.set_velocity_y(0);
245   physic.enable_gravity(true);
246   set_state(STATE_FALLING);
247 }
248
249 void
250 BadGuy::set_state(State state)
251 {
252   if(this->state == state)
253     return;
254
255   State laststate = this->state;
256   this->state = state;
257   switch(state) {
258     case STATE_SQUISHED:
259       state_timer.start(SQUISH_TIME);
260       break;
261     case STATE_ACTIVE:
262       set_group(COLGROUP_MOVING);
263       bbox.set_pos(start_position);
264       break;
265     case STATE_INACTIVE:
266       // was the badguy dead anyway?
267       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
268         remove_me();
269       }
270       set_group(COLGROUP_DISABLED);
271       break;
272     case STATE_FALLING:
273       set_group(COLGROUP_DISABLED);
274       break;
275     default:
276       break;
277   }
278 }
279
280 bool
281 BadGuy::is_offscreen()
282 {
283   float scroll_x = Sector::current()->camera->get_translation().x;
284   float scroll_y = Sector::current()->camera->get_translation().y;
285      
286   if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE
287       || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE
288       || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE
289       || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE)
290     return true;
291
292   return false;
293 }
294
295 void
296 BadGuy::try_activate()
297 {
298   float scroll_x = Sector::current()->camera->get_translation().x;
299   float scroll_y = Sector::current()->camera->get_translation().y;
300
301   /* Activate badguys if they're just around the screen to avoid
302    * the effect of having badguys suddenly popping up from nowhere.
303    */
304   if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
305       start_position.x < scroll_x - bbox.get_width() &&
306       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
307       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
308     dir = RIGHT;
309     set_state(STATE_ACTIVE);
310     activate();
311   } else if (start_position.x > scroll_x &&
312       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
313       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
314       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
315     dir = LEFT;
316     set_state(STATE_ACTIVE);
317     activate();
318   } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
319       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
320       ((start_position.y > scroll_y &&
321         start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) ||
322        (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
323         start_position.y < scroll_y))) {
324     dir = start_position.x < scroll_x ? RIGHT : LEFT;
325     set_state(STATE_ACTIVE);
326     activate();
327   } else if(state == STATE_INIT
328       && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE
329       && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE
330       && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE
331       && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
332     dir = LEFT;
333     set_state(STATE_ACTIVE);
334     activate();
335   } 
336 }
337
338 bool
339 BadGuy::might_fall(int height)
340 {
341   // make sure we check for at least a 1-pixel fall
342   assert(height > 0);
343
344   float x1;
345   float x2;
346   float y1 = bbox.p2.y + 1;
347   float y2 = bbox.p2.y + 1 + height;
348   if (dir == LEFT) {
349     x1 = bbox.p1.x - 1;
350     x2 = bbox.p1.x - 1;
351   } else {
352     x1 = bbox.p2.x + 1;
353     x2 = bbox.p2.x + 1;
354   }
355   return Sector::current()->is_free_space(Rect(x1, y1, x2, y2));
356 }
357
358 Player* 
359 BadGuy::get_nearest_player()
360 {
361   // FIXME: does not really return nearest player
362
363   std::vector<Player*> players = Sector::current()->get_players();
364   for (std::vector<Player*>::iterator playerIter = players.begin(); playerIter != players.end(); ++playerIter) {
365     Player* player = *playerIter;
366     return player;
367   }
368
369   return 0;
370 }