gave the Yeti hitpoints and Matze's angry roar :)
[supertux.git] / src / badguy / badguy.cpp
1 #include <config.h>
2
3 #include "badguy.h"
4 #include "object/camera.h"
5
6 static const float SQUISH_TIME = 2;
7 static const float X_OFFSCREEN_DISTANCE = 1600;
8 static const float Y_OFFSCREEN_DISTANCE = 1200;
9
10 BadGuy::BadGuy()
11   : sprite(0), dir(LEFT), state(STATE_INIT)
12 {
13   //TODO: Count fireball hits separately so you can make badguys need more fireballs than jumps
14   hitpoints = 1;
15 }
16
17 BadGuy::~BadGuy()
18 {
19   delete sprite;
20 }
21
22 void
23 BadGuy::draw(DrawingContext& context)
24 {
25   if(!sprite)
26     return;
27   if(state == STATE_INIT || state == STATE_INACTIVE)
28     return;
29   if(state == STATE_FALLING) {
30     sprite->draw(context, get_pos(), LAYER_OBJECTS, VERTICAL_FLIP);
31   } else {
32     sprite->draw(context, get_pos(), LAYER_OBJECTS);
33   }
34 }
35
36 void
37 BadGuy::action(float elapsed_time)
38 {
39   if(!Sector::current()->inside(bbox)) {
40     remove_me();
41     return;
42   }
43   if(is_offscreen()) {
44     set_state(STATE_INACTIVE);
45   }
46   
47   switch(state) {
48     case STATE_ACTIVE:
49       active_action(elapsed_time);
50       break;
51     case STATE_INIT:
52     case STATE_INACTIVE:
53       inactive_action(elapsed_time);
54       try_activate();
55       break;
56     case STATE_SQUISHED:
57       if(state_timer.check()) {
58         remove_me();
59         break;
60       }
61       movement = physic.get_movement(elapsed_time);
62       break;
63     case STATE_FALLING:
64       movement = physic.get_movement(elapsed_time);
65       break;
66   }
67 }
68
69 void
70 BadGuy::activate()
71 {
72 }
73
74 void
75 BadGuy::deactivate()
76 {
77 }
78
79 void
80 BadGuy::active_action(float elapsed_time)
81 {
82   movement = physic.get_movement(elapsed_time);
83 }
84
85 void
86 BadGuy::inactive_action(float )
87 {
88 }
89
90 HitResponse
91 BadGuy::collision(GameObject& other, const CollisionHit& hit)
92 {
93   switch(state) {
94     case STATE_INIT:
95     case STATE_INACTIVE:
96       return ABORT_MOVE;
97     case STATE_ACTIVE: {
98       if(other.get_flags() & FLAG_SOLID)
99         return collision_solid(other, hit);
100
101       BadGuy* badguy = dynamic_cast<BadGuy*> (&other);
102       if(badguy && badguy->state == STATE_ACTIVE)
103         return collision_badguy(*badguy, hit);
104
105       Player* player = dynamic_cast<Player*> (&other);
106       if(player)
107         return collision_player(*player, hit);
108
109       return FORCE_MOVE;
110     }
111     case STATE_SQUISHED:
112       if(other.get_flags() & FLAG_SOLID)
113         return CONTINUE;
114       return FORCE_MOVE;
115     case STATE_FALLING:
116       return FORCE_MOVE;
117   }
118
119   return ABORT_MOVE;
120 }
121
122 HitResponse
123 BadGuy::collision_solid(GameObject& , const CollisionHit& )
124 {
125   return FORCE_MOVE;
126 }
127
128 HitResponse
129 BadGuy::collision_player(Player& player, const CollisionHit& hit)
130 {
131   if(player.is_invincible()) {
132     kill_fall();
133     return ABORT_MOVE;
134   }
135   if(hit.normal.y > .9) {
136     //TODO: fix inaccuracy (tux sometimes dies even if badguy was hit)
137     //      give badguys some invincible time (prevent them from being hit multiple times)
138     hitpoints--;
139     if(collision_squished(player))
140       return ABORT_MOVE;
141     else if (hitpoints <= 0) {
142       player.kill(Player::SHRINK);
143       return FORCE_MOVE;
144     }
145   }
146   player.kill(Player::SHRINK);
147   return FORCE_MOVE;
148 }
149
150 HitResponse
151 BadGuy::collision_badguy(BadGuy& , const CollisionHit& )
152 {
153   return FORCE_MOVE;
154 }
155
156 bool
157 BadGuy::collision_squished(Player& )
158 {
159   return false;
160 }
161
162 void
163 BadGuy::kill_squished(Player& player)
164 {
165   SoundManager::get()->play_sound(IDToSound(SND_SQUISH), get_pos(),
166       player.get_pos());
167   physic.enable_gravity(true);
168   physic.set_velocity_x(0);
169   physic.set_velocity_y(0);
170   set_state(STATE_SQUISHED);
171   player.bounce(*this);
172 }
173
174 void
175 BadGuy::kill_fall()
176 {
177   hitpoints--;
178   if (hitpoints <= 0) {
179     SoundManager::get()->play_sound(IDToSound(SND_FALL), this,
180        Sector::current()->player->get_pos());
181     physic.set_velocity_y(0);
182     physic.enable_gravity(true);
183     set_state(STATE_FALLING);
184   }
185 }
186
187 void
188 BadGuy::set_state(State state)
189 {
190   if(this->state == state)
191     return;
192
193   State laststate = this->state;
194   this->state = state;
195   switch(state) {
196     case STATE_SQUISHED:
197       state_timer.start(SQUISH_TIME);
198       break;
199     case STATE_ACTIVE:
200       flags &= ~FLAG_NO_COLLDET;
201       bbox.set_pos(start_position);
202       break;
203     case STATE_INACTIVE:
204       // was the badguy dead anyway?
205       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
206         remove_me();
207       }
208       flags |= FLAG_NO_COLLDET;
209       break;
210     case STATE_FALLING:
211       flags |= FLAG_NO_COLLDET;
212       break;
213     default:
214       break;
215   }
216 }
217
218 bool
219 BadGuy::is_offscreen()
220 {
221   float scroll_x = Sector::current()->camera->get_translation().x;
222   float scroll_y = Sector::current()->camera->get_translation().y;
223      
224   if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE
225       || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE
226       || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE
227       || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE)
228     return true;
229
230   return false;
231 }
232
233 void
234 BadGuy::try_activate()
235 {
236   float scroll_x = Sector::current()->camera->get_translation().x;
237   float scroll_y = Sector::current()->camera->get_translation().y;
238
239   /* Activate badguys if they're just around the screen to avoid
240    * the effect of having badguys suddenly popping up from nowhere.
241    */
242   if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
243       start_position.x < scroll_x - bbox.get_width() &&
244       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
245       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
246     dir = RIGHT;
247     set_state(STATE_ACTIVE);
248     activate();
249   } else if (start_position.x > scroll_x &&
250       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
251       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
252       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
253     dir = LEFT;
254     set_state(STATE_ACTIVE);
255     activate();
256   } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
257       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
258       ((start_position.y > scroll_y &&
259         start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) ||
260        (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
261         start_position.y < scroll_y))) {
262     dir = start_position.x < scroll_x ? RIGHT : LEFT;
263     set_state(STATE_ACTIVE);
264     activate();
265   } else if(state == STATE_INIT
266       && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE
267       && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE
268       && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE
269       && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
270     dir = LEFT;
271     set_state(STATE_ACTIVE);
272     activate();
273   } 
274 }