* Updated Czech translation
[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     DrawingEffect old_effect = context.get_drawing_effect();
51     context.set_drawing_effect((DrawingEffect) (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::save(lisp::Writer& )
104 {
105         std::cout << "Warning: tried to write out a generic badguy." << std::endl;
106 }
107
108 void
109 BadGuy::active_update(float elapsed_time)
110 {
111   movement = physic.get_movement(elapsed_time);
112 }
113
114 void
115 BadGuy::inactive_update(float )
116 {
117 }
118
119 HitResponse
120 BadGuy::collision(GameObject& other, const CollisionHit& hit)
121 {
122   switch(state) {
123     case STATE_INIT:
124     case STATE_INACTIVE:
125       return ABORT_MOVE;
126     case STATE_ACTIVE: {
127       TileMap* tilemap = dynamic_cast<TileMap*> (&other);
128       if(tilemap != 0) {
129         const TilemapCollisionHit* thit 
130           = static_cast<const TilemapCollisionHit*> (&hit);
131         if(thit->tileflags & Tile::SPIKE)
132           kill_fall();
133         if(thit->tileflags & Tile::SOLID)
134           return collision_solid(other, hit);
135         return FORCE_MOVE;
136       }
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 is it 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   global_stats.add_points(BADGUYS_KILLED_STAT, 1);
206   player.bounce(*this);
207 }
208
209 void
210 BadGuy::kill_fall()
211 {
212   sound_manager->play("sounds/fall.wav", get_pos());
213   global_stats.add_points(BADGUYS_KILLED_STAT, 1);
214   physic.set_velocity_y(0);
215   physic.enable_gravity(true);
216   set_state(STATE_FALLING);
217 }
218
219 void
220 BadGuy::set_state(State state)
221 {
222   if(this->state == state)
223     return;
224
225   State laststate = this->state;
226   this->state = state;
227   switch(state) {
228     case STATE_SQUISHED:
229       state_timer.start(SQUISH_TIME);
230       break;
231     case STATE_ACTIVE:
232       flags &= ~FLAG_NO_COLLDET;
233       bbox.set_pos(start_position);
234       break;
235     case STATE_INACTIVE:
236       // was the badguy dead anyway?
237       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
238         remove_me();
239       }
240       flags |= FLAG_NO_COLLDET;
241       break;
242     case STATE_FALLING:
243       flags |= FLAG_NO_COLLDET;
244       break;
245     default:
246       break;
247   }
248 }
249
250 bool
251 BadGuy::is_offscreen()
252 {
253   float scroll_x = Sector::current()->camera->get_translation().x;
254   float scroll_y = Sector::current()->camera->get_translation().y;
255      
256   if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE
257       || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE
258       || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE
259       || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE)
260     return true;
261
262   return false;
263 }
264
265 void
266 BadGuy::try_activate()
267 {
268   float scroll_x = Sector::current()->camera->get_translation().x;
269   float scroll_y = Sector::current()->camera->get_translation().y;
270
271   /* Activate badguys if they're just around the screen to avoid
272    * the effect of having badguys suddenly popping up from nowhere.
273    */
274   if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
275       start_position.x < scroll_x - bbox.get_width() &&
276       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
277       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
278     dir = RIGHT;
279     set_state(STATE_ACTIVE);
280     activate();
281   } else if (start_position.x > scroll_x &&
282       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
283       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
284       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
285     dir = LEFT;
286     set_state(STATE_ACTIVE);
287     activate();
288   } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
289       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
290       ((start_position.y > scroll_y &&
291         start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) ||
292        (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
293         start_position.y < scroll_y))) {
294     dir = start_position.x < scroll_x ? RIGHT : LEFT;
295     set_state(STATE_ACTIVE);
296     activate();
297   } else if(state == STATE_INIT
298       && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE
299       && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE
300       && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE
301       && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
302     dir = LEFT;
303     set_state(STATE_ACTIVE);
304     activate();
305   } 
306 }