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