Okay, I guess this is it. The "stay on platform" flag is back. Featuring floorf and...
[supertux.git] / src / badguy / badguy.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2005 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
19 //  02111-1307, USA.
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
28 static const float SQUISH_TIME = 2;
29 static const float X_OFFSCREEN_DISTANCE = 1600;
30 static const float Y_OFFSCREEN_DISTANCE = 1200;
31
32 BadGuy::BadGuy()
33   : countMe(true), sprite(0), dir(LEFT), state(STATE_INIT)
34 {
35   set_group(COLGROUP_DISABLED);
36 }
37
38 BadGuy::~BadGuy()
39 {
40   delete sprite;
41 }
42
43 void
44 BadGuy::draw(DrawingContext& context)
45 {
46   if(!sprite)
47     return;
48   if(state == STATE_INIT || state == STATE_INACTIVE)
49     return;
50   if(state == STATE_FALLING) {
51     DrawingEffect old_effect = context.get_drawing_effect();
52     context.set_drawing_effect((DrawingEffect) (old_effect | VERTICAL_FLIP));
53     sprite->draw(context, get_pos(), LAYER_OBJECTS);
54     context.set_drawing_effect(old_effect);
55   } else {
56     sprite->draw(context, get_pos(), LAYER_OBJECTS);
57   }
58 }
59
60 void
61 BadGuy::update(float elapsed_time)
62 {
63   if(!Sector::current()->inside(bbox)) {
64     remove_me();
65     return;
66   }
67   if(is_offscreen()) {
68     set_state(STATE_INACTIVE);
69   }
70   
71   switch(state) {
72     case STATE_ACTIVE:
73       active_update(elapsed_time);
74       break;
75     case STATE_INIT:
76     case STATE_INACTIVE:
77       inactive_update(elapsed_time);
78       try_activate();
79       break;
80     case STATE_SQUISHED:
81       if(state_timer.check()) {
82         remove_me();
83         break;
84       }
85       movement = physic.get_movement(elapsed_time);
86       break;
87     case STATE_FALLING:
88       movement = physic.get_movement(elapsed_time);
89       break;
90   }
91 }
92
93 void
94 BadGuy::activate()
95 {
96 }
97
98 void
99 BadGuy::deactivate()
100 {
101 }
102
103 void
104 BadGuy::save(lisp::Writer& )
105 {
106         std::cout << "Warning: tried to write out a generic badguy." << std::endl;
107 }
108
109 void
110 BadGuy::active_update(float elapsed_time)
111 {
112   movement = physic.get_movement(elapsed_time);
113 }
114
115 void
116 BadGuy::inactive_update(float )
117 {
118 }
119
120 void
121 BadGuy::collision_tile(uint32_t tile_attributes)
122 {
123   if(tile_attributes & Tile::HURTS)
124     kill_fall();
125 }
126
127 HitResponse
128 BadGuy::collision(GameObject& other, const CollisionHit& hit)
129 {
130   switch(state) {
131     case STATE_INIT:
132     case STATE_INACTIVE:
133       return ABORT_MOVE;
134     case STATE_ACTIVE: {
135       if(other.get_flags() & FLAG_SOLID)
136         return collision_solid(other, hit);
137
138       BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
139       if(badguy && badguy->state == STATE_ACTIVE)
140         return collision_badguy(*badguy, hit);
141
142       Player* player = dynamic_cast<Player*> (&other);
143       if(player)
144         return collision_player(*player, hit);
145
146       return FORCE_MOVE;
147     }
148     case STATE_SQUISHED:
149       if(other.get_flags() & FLAG_SOLID)
150         return CONTINUE;
151       return FORCE_MOVE;
152     case STATE_FALLING:
153       return FORCE_MOVE;
154   }
155
156   return ABORT_MOVE;
157 }
158
159 HitResponse
160 BadGuy::collision_solid(GameObject& , const CollisionHit& )
161 {
162   return FORCE_MOVE;
163 }
164
165 HitResponse
166 BadGuy::collision_player(Player& player, const CollisionHit& )
167 {
168   if(player.is_invincible()) {
169     kill_fall();
170     return ABORT_MOVE;
171   }
172   // hit from above?
173   if(player.get_movement().y - get_movement().y > 0 && player.get_bbox().p2.y <
174       (get_bbox().p1.y + get_bbox().p2.y) / 2) {
175     // if it's not possible to squish us, then this will hurt
176     if(!collision_squished(player))
177       player.kill(Player::SHRINK);
178
179     return FORCE_MOVE;
180   }
181   player.kill(Player::SHRINK);
182   return FORCE_MOVE;
183 }
184
185 HitResponse
186 BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
187 {
188   return FORCE_MOVE;
189 }
190
191 bool
192 BadGuy::collision_squished(Player& )
193 {
194   return false;
195 }
196
197 void
198 BadGuy::kill_squished(Player& player)
199 {
200   sound_manager->play("sounds/squish.wav", get_pos());
201   physic.enable_gravity(true);
202   physic.set_velocity_x(0);
203   physic.set_velocity_y(0);
204   set_state(STATE_SQUISHED);
205   set_group(COLGROUP_MOVING_ONLY_STATIC);
206   global_stats.add_points(BADGUYS_KILLED_STAT, 1);
207   player.bounce(*this);
208 }
209
210 void
211 BadGuy::kill_fall()
212 {
213   sound_manager->play("sounds/fall.wav", get_pos());
214   global_stats.add_points(BADGUYS_KILLED_STAT, 1);
215   physic.set_velocity_y(0);
216   physic.enable_gravity(true);
217   set_state(STATE_FALLING);
218 }
219
220 void
221 BadGuy::set_state(State state)
222 {
223   if(this->state == state)
224     return;
225
226   State laststate = this->state;
227   this->state = state;
228   switch(state) {
229     case STATE_SQUISHED:
230       state_timer.start(SQUISH_TIME);
231       break;
232     case STATE_ACTIVE:
233       set_group(COLGROUP_MOVING);
234       bbox.set_pos(start_position);
235       break;
236     case STATE_INACTIVE:
237       // was the badguy dead anyway?
238       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
239         remove_me();
240       }
241       set_group(COLGROUP_DISABLED);
242       break;
243     case STATE_FALLING:
244       set_group(COLGROUP_DISABLED);
245       break;
246     default:
247       break;
248   }
249 }
250
251 bool
252 BadGuy::is_offscreen()
253 {
254   float scroll_x = Sector::current()->camera->get_translation().x;
255   float scroll_y = Sector::current()->camera->get_translation().y;
256      
257   if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE
258       || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE
259       || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE
260       || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE)
261     return true;
262
263   return false;
264 }
265
266 void
267 BadGuy::try_activate()
268 {
269   float scroll_x = Sector::current()->camera->get_translation().x;
270   float scroll_y = Sector::current()->camera->get_translation().y;
271
272   /* Activate badguys if they're just around the screen to avoid
273    * the effect of having badguys suddenly popping up from nowhere.
274    */
275   if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
276       start_position.x < scroll_x - bbox.get_width() &&
277       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
278       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
279     dir = RIGHT;
280     set_state(STATE_ACTIVE);
281     activate();
282   } else if (start_position.x > scroll_x &&
283       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
284       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
285       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
286     dir = LEFT;
287     set_state(STATE_ACTIVE);
288     activate();
289   } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
290       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
291       ((start_position.y > scroll_y &&
292         start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) ||
293        (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
294         start_position.y < scroll_y))) {
295     dir = start_position.x < scroll_x ? RIGHT : LEFT;
296     set_state(STATE_ACTIVE);
297     activate();
298   } else if(state == STATE_INIT
299       && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE
300       && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE
301       && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE
302       && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
303     dir = LEFT;
304     set_state(STATE_ACTIVE);
305     activate();
306   } 
307 }
308
309 bool
310 BadGuy::may_fall_off_platform()
311 {
312   int tile_x, tile_y;
313   // First, let's say the badguy moves once its width in the
314   // direction it's heading.
315   Vector pos = get_pos();
316   pos.x += (dir == LEFT ? -bbox.get_width() : bbox.get_width());
317
318   // Now, snap the badguy's X coordinate to the 32x32/cell grid.
319   if (dir == LEFT) // use the ceiling
320     tile_x = (int)ceilf(pos.x/32.0f);
321   else // use the floor
322     tile_x = (int)floorf(pos.x/32.0f);
323
324   // We might be falling down, so use the ceiling to round upward and
325   // get the lower position. (Positive Y goes downward.)
326   tile_y = (int)ceilf(pos.y/32.0f);
327
328   // Now, if the badguy intersects with a tile, he won't fall off.
329   // If he doesn't intersect, he probably will.
330   if (Sector::current()->solids->get_tile(tile_x, tile_y)->getAttributes() & FLAG_SOLID)
331   {
332     // It's a solid tile. Good.
333     return false;
334   }
335
336   // Watch out there buddy, you might take a sticky end!
337   return true;
338 }