Mr. Ice Block: Enforce 100ms delay between squish and kick (closes issue 238); kill...
[supertux.git] / src / badguy / mriceblock.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
20 #include <config.h>
21
22 #include "mriceblock.hpp"
23 #include "object/block.hpp"
24
25 namespace {
26   const float KICKSPEED = 500;
27   const int MAXSQUISHES = 10;
28   const float NOKICK_TIME = 0.1f;
29 }
30
31 MrIceBlock::MrIceBlock(const lisp::Lisp& reader)
32   : WalkingBadguy(reader, "images/creatures/mr_iceblock/mr_iceblock.sprite", "left", "right"), ice_state(ICESTATE_NORMAL), squishcount(0)
33 {
34   walk_speed = 80;
35   max_drop_height = 600;
36   sound_manager->preload("sounds/iceblock_bump.wav");
37   sound_manager->preload("sounds/stomp.wav");
38   sound_manager->preload("sounds/kick.wav");
39 }
40
41 MrIceBlock::MrIceBlock(const Vector& pos, Direction d)
42   : WalkingBadguy(pos, d, "images/creatures/mr_iceblock/mr_iceblock.sprite", "left", "right"), ice_state(ICESTATE_NORMAL), squishcount(0)
43 {
44   walk_speed = 80;
45   max_drop_height = 600;
46   sound_manager->preload("sounds/iceblock_bump.wav");
47   sound_manager->preload("sounds/stomp.wav");
48   sound_manager->preload("sounds/kick.wav");
49 }
50
51 void
52 MrIceBlock::write(lisp::Writer& writer)
53 {
54   writer.start_list("mriceblock");
55   WalkingBadguy::write(writer);
56   writer.end_list("mriceblock");
57 }
58
59 void
60 MrIceBlock::activate()
61 {
62   WalkingBadguy::activate();
63   set_state(ICESTATE_NORMAL);
64 }
65
66 void
67 MrIceBlock::active_update(float elapsed_time)
68 {
69   if(ice_state == ICESTATE_GRABBED)
70     return;
71
72   if(ice_state == ICESTATE_FLAT && flat_timer.check()) {
73     set_state(ICESTATE_NORMAL);
74   }
75
76   if (ice_state == ICESTATE_NORMAL)
77   {
78     WalkingBadguy::active_update(elapsed_time);
79     return;
80   }
81
82   BadGuy::active_update(elapsed_time);
83 }
84
85 bool
86 MrIceBlock::can_break(){
87     return ice_state == ICESTATE_KICKED;
88 }
89
90 void
91 MrIceBlock::collision_solid(const CollisionHit& hit)
92 {
93   update_on_ground_flag(hit);
94
95   if(hit.top || hit.bottom) { // floor or roof
96     physic.set_velocity_y(0);
97     return;
98   }
99
100   // hit left or right
101   switch(ice_state) {
102     case ICESTATE_NORMAL:
103       WalkingBadguy::collision_solid(hit);
104       break;
105     case ICESTATE_KICKED: {
106       if(hit.right && dir == RIGHT) {
107         dir = LEFT;
108         sound_manager->play("sounds/iceblock_bump.wav", get_pos());
109         if(++squishcount >= MAXSQUISHES) { kill_fall(); break; }
110         physic.set_velocity_x(-KICKSPEED);
111       } else if(hit.left && dir == LEFT) {
112         dir = RIGHT;
113         sound_manager->play("sounds/iceblock_bump.wav", get_pos());
114         if(++squishcount >= MAXSQUISHES) { kill_fall(); break; }
115         physic.set_velocity_x(KICKSPEED);
116       }
117       sprite->set_action(dir == LEFT ? "flat-left" : "flat-right");
118       break;
119     }
120     case ICESTATE_FLAT:
121       physic.set_velocity_x(0);
122       break;
123     case ICESTATE_GRABBED:
124       break;
125   }
126 }
127
128 HitResponse
129 MrIceBlock::collision(GameObject& object, const CollisionHit& hit)
130 {
131   if(ice_state == ICESTATE_GRABBED)
132     return FORCE_MOVE;
133
134   return BadGuy::collision(object, hit);
135 }
136
137 HitResponse
138 MrIceBlock::collision_player(Player& player, const CollisionHit& hit)
139 {
140   if(ice_state == ICESTATE_GRABBED)
141     return FORCE_MOVE;
142
143   if(dir == UP) {
144     return FORCE_MOVE;
145   }
146
147   // handle kicks from left or right side
148   if(ice_state == ICESTATE_FLAT && get_state() == STATE_ACTIVE) {
149     if(hit.left) {
150       dir = RIGHT;
151       player.kick();
152       set_state(ICESTATE_KICKED);
153       return FORCE_MOVE;
154     } else if(hit.right) {
155       dir = LEFT;
156       player.kick();
157       set_state(ICESTATE_KICKED);
158       return FORCE_MOVE;
159     }
160   }
161
162   return BadGuy::collision_player(player, hit);
163 }
164
165 HitResponse
166 MrIceBlock::collision_badguy(BadGuy& badguy, const CollisionHit& hit)
167 {
168   switch(ice_state) {
169     case ICESTATE_NORMAL:
170       return WalkingBadguy::collision_badguy(badguy, hit);
171     case ICESTATE_FLAT:
172       return FORCE_MOVE;
173     case ICESTATE_KICKED:
174       badguy.kill_fall();
175       return FORCE_MOVE;
176     default:
177       assert(false);
178   }
179
180   return ABORT_MOVE;
181 }
182
183 bool
184 MrIceBlock::collision_squished(GameObject& object)
185 {
186   switch(ice_state) {
187     case ICESTATE_KICKED:
188     case ICESTATE_NORMAL:
189       squishcount++;
190       if(squishcount >= MAXSQUISHES) {
191         kill_fall();
192         return true;
193       }
194
195       set_state(ICESTATE_FLAT);
196       nokick_timer.start(NOKICK_TIME);
197       break;
198     case ICESTATE_FLAT:
199       {
200         MovingObject* movingobject = dynamic_cast<MovingObject*>(&object);
201         if (movingobject && (movingobject->get_pos().x < get_pos().x)) {
202           dir = RIGHT;
203         } else {
204           dir = LEFT;
205         }
206       }
207       if (nokick_timer.check()) set_state(ICESTATE_KICKED);
208       break;
209     case ICESTATE_GRABBED:
210       assert(false);
211       break;
212   }
213
214   Player* player = dynamic_cast<Player*>(&object);
215   if (player) player->bounce(*this);
216   return true;
217 }
218
219 void
220 MrIceBlock::set_state(IceState state)
221 {
222   if(ice_state == state)
223     return;
224
225   switch(state) {
226     case ICESTATE_NORMAL:
227       WalkingBadguy::activate();
228       break;
229     case ICESTATE_FLAT:
230       if(dir == UP) {
231         physic.set_velocity_y(-KICKSPEED);
232         bbox.set_size(34, 31.8f);
233       } else {
234         sound_manager->play("sounds/stomp.wav", get_pos());
235         physic.set_velocity_x(0);
236         physic.set_velocity_y(0);
237
238         sprite->set_action(dir == LEFT ? "flat-left" : "flat-right");
239       }
240       flat_timer.start(4);
241       break;
242     case ICESTATE_KICKED:
243       sound_manager->play("sounds/kick.wav", get_pos());
244
245       physic.set_velocity_x(dir == LEFT ? -KICKSPEED : KICKSPEED);
246       sprite->set_action(dir == LEFT ? "flat-left" : "flat-right");
247       // we should slide above 1 block holes now...
248       bbox.set_size(34, 31.8f);
249       break;
250     case ICESTATE_GRABBED:
251       flat_timer.stop();
252       break;
253     default:
254       assert(false);
255   }
256   ice_state = state;
257 }
258
259 void
260 MrIceBlock::grab(MovingObject&, const Vector& pos, Direction dir)
261 {
262   movement = pos - get_pos();
263   this->dir = dir;
264   sprite->set_action(dir == LEFT ? "flat-left" : "flat-right");
265   set_state(ICESTATE_GRABBED);
266   set_group(COLGROUP_DISABLED);
267 }
268
269 void
270 MrIceBlock::ungrab(MovingObject& , Direction dir)
271 {
272   this->dir = dir;
273   set_state(dir == UP ? ICESTATE_FLAT : ICESTATE_KICKED);
274   set_group(COLGROUP_MOVING);
275 }
276
277 bool
278 MrIceBlock::is_portable() const
279 {
280   return ice_state == ICESTATE_FLAT;
281 }
282
283 IMPLEMENT_FACTORY(MrIceBlock, "mriceblock")