use proper enum
[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 #include "object/camera.hpp"
24 #include "object/tilemap.hpp"
25 #include "tile.hpp"
26 #include "statistics.hpp"
27 #include "game_session.hpp"
28 #include "log.hpp"
29 #include "level.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()
36   : countMe(true), sprite(0), dir(LEFT), state(STATE_INIT)
37 {
38   set_group(COLGROUP_DISABLED);
39 }
40
41 BadGuy::~BadGuy()
42 {
43   delete sprite;
44 }
45
46 void
47 BadGuy::draw(DrawingContext& context)
48 {
49   if(!sprite)
50     return;
51   if(state == STATE_INIT || state == STATE_INACTIVE)
52     return;
53   if(state == STATE_FALLING) {
54     DrawingEffect old_effect = context.get_drawing_effect();
55     context.set_drawing_effect((DrawingEffect) (old_effect | VERTICAL_FLIP));
56     sprite->draw(context, get_pos(), LAYER_OBJECTS);
57     context.set_drawing_effect(old_effect);
58   } else {
59     sprite->draw(context, get_pos(), LAYER_OBJECTS);
60   }
61 }
62
63 void
64 BadGuy::update(float elapsed_time)
65 {
66   if(!Sector::current()->inside(bbox)) {
67     remove_me();
68     return;
69   }
70   if(is_offscreen()) {
71     set_state(STATE_INACTIVE);
72   }
73   
74   switch(state) {
75     case STATE_ACTIVE:
76       active_update(elapsed_time);
77       break;
78     case STATE_INIT:
79     case STATE_INACTIVE:
80       inactive_update(elapsed_time);
81       try_activate();
82       break;
83     case STATE_SQUISHED:
84       if(state_timer.check()) {
85         remove_me();
86         break;
87       }
88       movement = physic.get_movement(elapsed_time);
89       break;
90     case STATE_FALLING:
91       movement = physic.get_movement(elapsed_time);
92       break;
93   }
94 }
95
96 void
97 BadGuy::activate()
98 {
99 }
100
101 void
102 BadGuy::deactivate()
103 {
104 }
105
106 void
107 BadGuy::save(lisp::Writer& )
108 {
109         log_warning << "tried to write out a generic badguy" << std::endl;
110 }
111
112 void
113 BadGuy::active_update(float elapsed_time)
114 {
115   movement = physic.get_movement(elapsed_time);
116 }
117
118 void
119 BadGuy::inactive_update(float )
120 {
121 }
122
123 void
124 BadGuy::collision_tile(uint32_t tile_attributes)
125 {
126   if(tile_attributes & Tile::HURTS)
127     kill_fall();
128 }
129
130 HitResponse
131 BadGuy::collision(GameObject& other, const CollisionHit& hit)
132 {
133   switch(state) {
134     case STATE_INIT:
135     case STATE_INACTIVE:
136       return ABORT_MOVE;
137     case STATE_ACTIVE: {
138       if(other.get_flags() & FLAG_SOLID)
139         return collision_solid(other, hit);
140
141       BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
142       if(badguy && badguy->state == STATE_ACTIVE)
143         return collision_badguy(*badguy, hit);
144
145       Player* player = dynamic_cast<Player*> (&other);
146       if(player)
147         return collision_player(*player, hit);
148
149       return FORCE_MOVE;
150     }
151     case STATE_SQUISHED:
152       if(other.get_flags() & FLAG_SOLID)
153         return CONTINUE;
154       return FORCE_MOVE;
155     case STATE_FALLING:
156       return FORCE_MOVE;
157   }
158
159   return ABORT_MOVE;
160 }
161
162 HitResponse
163 BadGuy::collision_solid(GameObject& , const CollisionHit& )
164 {
165   return FORCE_MOVE;
166 }
167
168 HitResponse
169 BadGuy::collision_player(Player& player, const CollisionHit& )
170 {
171   if(player.is_invincible()) {
172     kill_fall();
173     return ABORT_MOVE;
174   }
175
176   /*
177   printf("PlayerHit: GT %3.1f PM: %3.1f %3.1f BM: %3.1f %3.1f Hit: %3.1f %3.1f\n",
178           game_time,
179           player.get_movement().x, player.get_movement().y,
180           get_movement().x, get_movement().y,
181           hit.normal.x, hit.normal.y);
182   */
183
184   // hit from above?
185   if(player.get_movement().y /*- get_movement().y*/ > 0 
186           && player.get_bbox().p2.y <
187       (get_bbox().p1.y + get_bbox().p2.y) / 2) {
188     // if it's not possible to squish us, then this will hurt
189     if(collision_squished(player))
190       return ABORT_MOVE;
191   }
192
193   player.kill(Player::SHRINK);
194   return FORCE_MOVE;
195 }
196
197 HitResponse
198 BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
199 {
200   return FORCE_MOVE;
201 }
202
203 bool
204 BadGuy::collision_squished(Player& )
205 {
206   return false;
207 }
208
209 void
210 BadGuy::kill_squished(Player& player)
211 {
212   sound_manager->play("sounds/squish.wav", get_pos());
213   physic.enable_gravity(true);
214   physic.set_velocity_x(0);
215   physic.set_velocity_y(0);
216   set_state(STATE_SQUISHED);
217   set_group(COLGROUP_MOVING_ONLY_STATIC);
218   if (countMe) Sector::current()->get_level()->stats.badguys++;
219   player.bounce(*this);
220 }
221
222 void
223 BadGuy::kill_fall()
224 {
225   sound_manager->play("sounds/fall.wav", get_pos());
226   if (countMe) Sector::current()->get_level()->stats.badguys++;
227   physic.set_velocity_y(0);
228   physic.enable_gravity(true);
229   set_state(STATE_FALLING);
230 }
231
232 void
233 BadGuy::set_state(State state)
234 {
235   if(this->state == state)
236     return;
237
238   State laststate = this->state;
239   this->state = state;
240   switch(state) {
241     case STATE_SQUISHED:
242       state_timer.start(SQUISH_TIME);
243       break;
244     case STATE_ACTIVE:
245       set_group(COLGROUP_MOVING);
246       bbox.set_pos(start_position);
247       break;
248     case STATE_INACTIVE:
249       // was the badguy dead anyway?
250       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
251         remove_me();
252       }
253       set_group(COLGROUP_DISABLED);
254       break;
255     case STATE_FALLING:
256       set_group(COLGROUP_DISABLED);
257       break;
258     default:
259       break;
260   }
261 }
262
263 bool
264 BadGuy::is_offscreen()
265 {
266   float scroll_x = Sector::current()->camera->get_translation().x;
267   float scroll_y = Sector::current()->camera->get_translation().y;
268      
269   if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE
270       || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE
271       || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE
272       || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE)
273     return true;
274
275   return false;
276 }
277
278 void
279 BadGuy::try_activate()
280 {
281   float scroll_x = Sector::current()->camera->get_translation().x;
282   float scroll_y = Sector::current()->camera->get_translation().y;
283
284   /* Activate badguys if they're just around the screen to avoid
285    * the effect of having badguys suddenly popping up from nowhere.
286    */
287   if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
288       start_position.x < scroll_x - bbox.get_width() &&
289       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
290       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
291     dir = RIGHT;
292     set_state(STATE_ACTIVE);
293     activate();
294   } else if (start_position.x > scroll_x &&
295       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
296       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
297       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
298     dir = LEFT;
299     set_state(STATE_ACTIVE);
300     activate();
301   } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
302       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
303       ((start_position.y > scroll_y &&
304         start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) ||
305        (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
306         start_position.y < scroll_y))) {
307     dir = start_position.x < scroll_x ? RIGHT : LEFT;
308     set_state(STATE_ACTIVE);
309     activate();
310   } else if(state == STATE_INIT
311       && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE
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   } 
319 }
320
321 bool
322 BadGuy::may_fall_off_platform()
323 {
324   int tile_x, tile_y;
325   // First, let's say the badguy moves 32 units in the
326   // direction it's heading, so do some voodoo maths magic
327   // to determine its future position.
328   Vector pos;
329   if (dir == LEFT)
330     pos = Vector(bbox.p1.x - 32.f, bbox.p2.y);
331   else
332     pos = Vector(bbox.p2.x, bbox.p2.y);
333
334   // Now, snap the badguy's X coordinate to the 32x32/cell grid.
335   if (dir == LEFT) // use the ceiling
336     tile_x = (int)ceilf(pos.x/32.0f);
337   else // use the floor
338     tile_x = (int)floorf(pos.x/32.0f);
339
340   // We might be falling down, so use the ceiling to round upward and
341   // get the lower position. (Positive Y goes downward.)
342   tile_y = (int)ceilf(pos.y/32.0f);
343
344 #if defined(DEBUG_STAY_ON_PLATFORM)
345   // Draw!
346   GameSession::current()->context->draw_filled_rect(Vector(tile_x*32.0f, tile_y*32.0f), Vector(32.f, 32.f), Color(1.f, 0.f, 0.f), 999);
347 #endif
348
349   // Now, if the badguy intersects with a tile, he won't fall off.
350   // If he doesn't intersect, he probably will.
351   // Note that the tile's Y coordinate is offset by +1 from the object's Y.
352   if (Sector::current()->solids->get_tile(tile_x, tile_y)->getAttributes()
353        & Tile::SOLID)
354   {
355     // It's a solid tile. Good.
356     return false;
357   }
358
359   // Watch out there buddy, you might take a sticky end!
360   return true;
361 }
362
363 bool
364 BadGuy::might_fall(int height)
365 {
366   // make sure we check for at least a 1-pixel fall
367   assert(height > 0);
368
369   float x1;
370   float x2;
371   float y1 = bbox.p2.y + 1;
372   float y2 = bbox.p2.y + 1 + height;
373   if (dir == LEFT) {
374     x1 = bbox.p1.x - 1;
375     x2 = bbox.p1.x - 1;
376   } else {
377     x1 = bbox.p2.x + 1;
378     x2 = bbox.p2.x + 1;
379   }
380   return Sector::current()->is_free_space(Rect(x1, y1, x2, y2));
381 }
382
383 Player* 
384 BadGuy::get_nearest_player()
385 {
386   // FIXME: does not really return nearest player
387
388   std::vector<Player*> players = Sector::current()->get_players();
389   for (std::vector<Player*>::iterator playerIter = players.begin(); playerIter != players.end(); ++playerIter) {
390     Player* player = *playerIter;
391     return player;
392   }
393
394   return 0;
395 }