try to fix badguys walking on water
[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 }
36
37 BadGuy::~BadGuy()
38 {
39   delete sprite;
40 }
41
42 void
43 BadGuy::draw(DrawingContext& context)
44 {
45   if(!sprite)
46     return;
47   if(state == STATE_INIT || state == STATE_INACTIVE)
48     return;
49   if(state == STATE_FALLING) {
50     uint32_t old_effect = context.get_drawing_effect();
51     context.set_drawing_effect(old_effect | VERTICAL_FLIP);
52     sprite->draw(context, get_pos(), LAYER_OBJECTS);
53     context.set_drawing_effect(old_effect);
54   } else {
55     sprite->draw(context, get_pos(), LAYER_OBJECTS);
56   }
57 }
58
59 void
60 BadGuy::update(float elapsed_time)
61 {
62   if(!Sector::current()->inside(bbox)) {
63     remove_me();
64     return;
65   }
66   if(is_offscreen()) {
67     set_state(STATE_INACTIVE);
68   }
69   
70   switch(state) {
71     case STATE_ACTIVE:
72       active_update(elapsed_time);
73       break;
74     case STATE_INIT:
75     case STATE_INACTIVE:
76       inactive_update(elapsed_time);
77       try_activate();
78       break;
79     case STATE_SQUISHED:
80       if(state_timer.check()) {
81         remove_me();
82         break;
83       }
84       movement = physic.get_movement(elapsed_time);
85       break;
86     case STATE_FALLING:
87       movement = physic.get_movement(elapsed_time);
88       break;
89   }
90 }
91
92 void
93 BadGuy::activate()
94 {
95 }
96
97 void
98 BadGuy::deactivate()
99 {
100 }
101
102 void
103 BadGuy::active_update(float elapsed_time)
104 {
105   movement = physic.get_movement(elapsed_time);
106 }
107
108 void
109 BadGuy::inactive_update(float )
110 {
111 }
112
113 HitResponse
114 BadGuy::collision(GameObject& other, const CollisionHit& hit)
115 {
116   switch(state) {
117     case STATE_INIT:
118     case STATE_INACTIVE:
119       return ABORT_MOVE;
120     case STATE_ACTIVE: {
121       TileMap* tilemap = dynamic_cast<TileMap*> (&other);
122       if(tilemap != 0) {
123         const TilemapCollisionHit* thit 
124           = static_cast<const TilemapCollisionHit*> (&hit);
125         if(thit->tileflags & Tile::SPIKE)
126           kill_fall();
127         if(thit->tileflags & Tile::SOLID)
128           return collision_solid(other, hit);
129         return FORCE_MOVE;
130       }
131
132       BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
133       if(badguy && badguy->state == STATE_ACTIVE)
134         return collision_badguy(*badguy, hit);
135
136       Player* player = dynamic_cast<Player*> (&other);
137       if(player)
138         return collision_player(*player, hit);
139
140       return FORCE_MOVE;
141     }
142     case STATE_SQUISHED:
143       if(other.get_flags() & FLAG_SOLID)
144         return CONTINUE;
145       return FORCE_MOVE;
146     case STATE_FALLING:
147       return FORCE_MOVE;
148   }
149
150   return ABORT_MOVE;
151 }
152
153 HitResponse
154 BadGuy::collision_solid(GameObject& , const CollisionHit& )
155 {
156   return FORCE_MOVE;
157 }
158
159 HitResponse
160 BadGuy::collision_player(Player& player, const CollisionHit& )
161 {
162   if(player.is_invincible()) {
163     kill_fall();
164     return ABORT_MOVE;
165   }
166   // hit from above?
167   if(player.get_movement().y - get_movement().y > 0 && player.get_bbox().p2.y <
168       (get_bbox().p1.y + get_bbox().p2.y) / 2) {
169     // if it's not is it possible to squish us, then this will hurt
170     if(!collision_squished(player))
171       player.kill(Player::SHRINK);
172
173     return FORCE_MOVE;
174   }
175   player.kill(Player::SHRINK);
176   return FORCE_MOVE;
177 }
178
179 HitResponse
180 BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
181 {
182   return FORCE_MOVE;
183 }
184
185 bool
186 BadGuy::collision_squished(Player& )
187 {
188   return false;
189 }
190
191 void
192 BadGuy::kill_squished(Player& player)
193 {
194   sound_manager->play("sounds/squish.wav", get_pos());
195   physic.enable_gravity(true);
196   physic.set_velocity_x(0);
197   physic.set_velocity_y(0);
198   set_state(STATE_SQUISHED);
199   global_stats.add_points(BADGUYS_KILLED_STAT, 1);
200   player.bounce(*this);
201 }
202
203 void
204 BadGuy::kill_fall()
205 {
206   sound_manager->play("sounds/fall.wav", get_pos());
207   global_stats.add_points(BADGUYS_KILLED_STAT, 1);
208   physic.set_velocity_y(0);
209   physic.enable_gravity(true);
210   set_state(STATE_FALLING);
211 }
212
213 void
214 BadGuy::set_state(State state)
215 {
216   if(this->state == state)
217     return;
218
219   State laststate = this->state;
220   this->state = state;
221   switch(state) {
222     case STATE_SQUISHED:
223       state_timer.start(SQUISH_TIME);
224       break;
225     case STATE_ACTIVE:
226       flags &= ~FLAG_NO_COLLDET;
227       bbox.set_pos(start_position);
228       break;
229     case STATE_INACTIVE:
230       // was the badguy dead anyway?
231       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
232         remove_me();
233       }
234       flags |= FLAG_NO_COLLDET;
235       break;
236     case STATE_FALLING:
237       flags |= FLAG_NO_COLLDET;
238       break;
239     default:
240       break;
241   }
242 }
243
244 bool
245 BadGuy::is_offscreen()
246 {
247   float scroll_x = Sector::current()->camera->get_translation().x;
248   float scroll_y = Sector::current()->camera->get_translation().y;
249      
250   if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE
251       || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE
252       || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE
253       || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE)
254     return true;
255
256   return false;
257 }
258
259 void
260 BadGuy::try_activate()
261 {
262   float scroll_x = Sector::current()->camera->get_translation().x;
263   float scroll_y = Sector::current()->camera->get_translation().y;
264
265   /* Activate badguys if they're just around the screen to avoid
266    * the effect of having badguys suddenly popping up from nowhere.
267    */
268   if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
269       start_position.x < scroll_x - bbox.get_width() &&
270       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
271       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
272     dir = RIGHT;
273     set_state(STATE_ACTIVE);
274     activate();
275   } else if (start_position.x > scroll_x &&
276       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
277       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
278       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
279     dir = LEFT;
280     set_state(STATE_ACTIVE);
281     activate();
282   } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
283       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
284       ((start_position.y > scroll_y &&
285         start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) ||
286        (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
287         start_position.y < scroll_y))) {
288     dir = start_position.x < scroll_x ? RIGHT : LEFT;
289     set_state(STATE_ACTIVE);
290     activate();
291   } else if(state == STATE_INIT
292       && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE
293       && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE
294       && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE
295       && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
296     dir = LEFT;
297     set_state(STATE_ACTIVE);
298     activate();
299   } 
300 }