844fbfb383171fcf70f089c98e0a395375ae5b05
[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
21 #include <config.h>
22
23 #include "badguy.h"
24 #include "object/camera.h"
25
26 static const float SQUISH_TIME = 2;
27 static const float X_OFFSCREEN_DISTANCE = 1600;
28 static const float Y_OFFSCREEN_DISTANCE = 1200;
29
30 BadGuy::BadGuy()
31   : sprite(0), dir(LEFT), state(STATE_INIT)
32 {
33   //Set hitpoints and bullet hitpoints
34   hitpoints = 1;
35   bullet_hitpoints = 1;
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     uint32_t old_effect = context.get_drawing_effect();
52     context.set_drawing_effect(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::action(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_action(elapsed_time);
74       break;
75     case STATE_INIT:
76     case STATE_INACTIVE:
77       inactive_action(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::active_action(float elapsed_time)
105 {
106   movement = physic.get_movement(elapsed_time);
107 }
108
109 void
110 BadGuy::inactive_action(float )
111 {
112 }
113
114 HitResponse
115 BadGuy::collision(GameObject& other, const CollisionHit& hit)
116 {
117   switch(state) {
118     case STATE_INIT:
119     case STATE_INACTIVE:
120       return ABORT_MOVE;
121     case STATE_ACTIVE: {
122       if(other.get_flags() & FLAG_SOLID)
123         return collision_solid(other, hit);
124
125       BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
126       if(badguy && badguy->state == STATE_ACTIVE)
127         return collision_badguy(*badguy, hit);
128
129       Player* player = dynamic_cast<Player*> (&other);
130       if(player)
131         return collision_player(*player, hit);
132
133       return FORCE_MOVE;
134     }
135     case STATE_SQUISHED:
136       if(other.get_flags() & FLAG_SOLID)
137         return CONTINUE;
138       return FORCE_MOVE;
139     case STATE_FALLING:
140       return FORCE_MOVE;
141   }
142
143   return ABORT_MOVE;
144 }
145
146 HitResponse
147 BadGuy::collision_solid(GameObject& , const CollisionHit& )
148 {
149   return FORCE_MOVE;
150 }
151
152 HitResponse
153 BadGuy::collision_player(Player& player, const CollisionHit& hit)
154 {
155   if(player.is_invincible()) {
156     kill_fall();
157     return ABORT_MOVE;
158   }
159   if(hit.normal.y > .9) {
160     //TODO: fix inaccuracy (tux sometimes dies even if badguy was hit)
161     //      give badguys some invincible time (prevent them from being hit multiple times)
162     hitpoints--;
163     bullet_hitpoints--;
164     if(collision_squished(player))
165       return ABORT_MOVE;
166     else if (hitpoints <= 0) {
167       bullet_hitpoints = 0;
168       player.kill(Player::SHRINK);
169       return FORCE_MOVE;
170     }
171   }
172   player.kill(Player::SHRINK);
173   return FORCE_MOVE;
174 }
175
176 HitResponse
177 BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
178 {
179   return FORCE_MOVE;
180 }
181
182 bool
183 BadGuy::collision_squished(Player& )
184 {
185   return false;
186 }
187
188 void
189 BadGuy::kill_squished(Player& player)
190 {
191   sound_manager->play_sound("squish", get_pos(), player.get_pos());
192   physic.enable_gravity(true);
193   physic.set_velocity_x(0);
194   physic.set_velocity_y(0);
195   set_state(STATE_SQUISHED);
196   player.bounce(*this);
197 }
198
199 void
200 BadGuy::kill_fall()
201 {
202   bullet_hitpoints--;
203   if (bullet_hitpoints <= 0) {
204     hitpoints = 0;
205     sound_manager->play_sound("fall", this,
206                               Sector::current()->player->get_pos());
207     physic.set_velocity_y(0);
208     physic.enable_gravity(true);
209     set_state(STATE_FALLING);
210   }
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 }