-Started to move stuff from library back to main game
[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   std::cout << "KILL_FALL - HITPOINTS: " << hitpoints << ", BULLET HP: " << bullet_hitpoints << std::endl;
209 }
210
211 void
212 BadGuy::set_state(State state)
213 {
214   if(this->state == state)
215     return;
216
217   State laststate = this->state;
218   this->state = state;
219   switch(state) {
220     case STATE_SQUISHED:
221       state_timer.start(SQUISH_TIME);
222       break;
223     case STATE_ACTIVE:
224       flags &= ~FLAG_NO_COLLDET;
225       bbox.set_pos(start_position);
226       break;
227     case STATE_INACTIVE:
228       // was the badguy dead anyway?
229       if(laststate == STATE_SQUISHED || laststate == STATE_FALLING) {
230         remove_me();
231       }
232       flags |= FLAG_NO_COLLDET;
233       break;
234     case STATE_FALLING:
235       flags |= FLAG_NO_COLLDET;
236       break;
237     default:
238       break;
239   }
240 }
241
242 bool
243 BadGuy::is_offscreen()
244 {
245   float scroll_x = Sector::current()->camera->get_translation().x;
246   float scroll_y = Sector::current()->camera->get_translation().y;
247      
248   if(bbox.p2.x < scroll_x - X_OFFSCREEN_DISTANCE
249       || bbox.p1.x > scroll_x + X_OFFSCREEN_DISTANCE
250       || bbox.p2.y < scroll_y - Y_OFFSCREEN_DISTANCE
251       || bbox.p1.y > scroll_y + Y_OFFSCREEN_DISTANCE)
252     return true;
253
254   return false;
255 }
256
257 void
258 BadGuy::try_activate()
259 {
260   float scroll_x = Sector::current()->camera->get_translation().x;
261   float scroll_y = Sector::current()->camera->get_translation().y;
262
263   /* Activate badguys if they're just around the screen to avoid
264    * the effect of having badguys suddenly popping up from nowhere.
265    */
266   if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
267       start_position.x < scroll_x - bbox.get_width() &&
268       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
269       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
270     dir = RIGHT;
271     set_state(STATE_ACTIVE);
272     activate();
273   } else if (start_position.x > scroll_x &&
274       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
275       start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
276       start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
277     dir = LEFT;
278     set_state(STATE_ACTIVE);
279     activate();
280   } else if (start_position.x > scroll_x - X_OFFSCREEN_DISTANCE &&
281       start_position.x < scroll_x + X_OFFSCREEN_DISTANCE &&
282       ((start_position.y > scroll_y &&
283         start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) ||
284        (start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE &&
285         start_position.y < scroll_y))) {
286     dir = start_position.x < scroll_x ? RIGHT : LEFT;
287     set_state(STATE_ACTIVE);
288     activate();
289   } else if(state == STATE_INIT
290       && start_position.x > scroll_x - X_OFFSCREEN_DISTANCE
291       && start_position.x < scroll_x + X_OFFSCREEN_DISTANCE
292       && start_position.y > scroll_y - Y_OFFSCREEN_DISTANCE
293       && start_position.y < scroll_y + Y_OFFSCREEN_DISTANCE) {
294     dir = LEFT;
295     set_state(STATE_ACTIVE);
296     activate();
297   } 
298 }