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