1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "badguy/mriceblock.hpp"
18
19#include <math.h>
20
21#include "audio/sound_manager.hpp"
22#include "object/player.hpp"
23#include "sprite/sprite.hpp"
24
25namespace {
26const float KICKSPEED = 500;
27const int MAXSQUISHES = 10;
28const float NOKICK_TIME = 0.1f;
29}
30
31MrIceBlock::MrIceBlock(const ReaderMapping& reader) :
32 WalkingBadguy(reader, "images/creatures/mr_iceblock/mr_iceblock.sprite", "left", "right"),
33 ice_state(ICESTATE_NORMAL),
34 nokick_timer(),
35 flat_timer(),
36 squishcount(0)
37{
38 walk_speed = 80;
39 max_drop_height = 600;
40 SoundManager::current()->preload("sounds/iceblock_bump.wav");
41 SoundManager::current()->preload("sounds/stomp.wav");
42 SoundManager::current()->preload("sounds/kick.wav");
43}
44
45void
46MrIceBlock::initialize()
47{
48 WalkingBadguy::initialize();
49 set_state(ICESTATE_NORMAL);
50}
51
52void
53MrIceBlock::active_update(float dt_sec)
54{
55 if (ice_state == ICESTATE_GRABBED)
56 return;
57
58 if (ice_state == ICESTATE_FLAT && flat_timer.check()) {
59 set_state(ICESTATE_WAKING);
60 }
61
62 if (ice_state == ICESTATE_WAKING && m_sprite->animation_done()) {
63 set_state(ICESTATE_NORMAL);
64 }
65
66 if (ice_state == ICESTATE_NORMAL)
67 {
68 WalkingBadguy::active_update(dt_sec);
69 return;
70 }
71
72 BadGuy::active_update(dt_sec);
73}
74
75bool
76MrIceBlock::can_break() const {
77 return ice_state == ICESTATE_KICKED;
78}
79
80void
81MrIceBlock::collision_solid(const CollisionHit& hit)
82{
83 update_on_ground_flag(hit);
84
85 if (hit.top || hit.bottom) { // floor or roof
86 m_physic.set_velocity_y(0);
87 }
88
89 // hit left or right
90 switch (ice_state) {
91 case ICESTATE_NORMAL:
92 WalkingBadguy::collision_solid(hit);
93 break;
94 case ICESTATE_KICKED: {
95 if ((hit.right && m_dir == Direction::RIGHT) || (hit.left && m_dir == Direction::LEFT)) {
96 m_dir = (m_dir == Direction::LEFT) ? Direction::RIGHT : Direction::LEFT;
97 SoundManager::current()->play("sounds/iceblock_bump.wav", get_pos());
98 m_physic.set_velocity_x(-m_physic.get_velocity_x()*.975f);
99 }
100 set_action(m_dir == Direction::LEFT ? "flat-left" : "flat-right", /* loops = */ -1);
101 if (fabsf(m_physic.get_velocity_x()) < walk_speed * 1.5f)
102 set_state(ICESTATE_NORMAL);
103 break;
104 }
105 case ICESTATE_FLAT:
106 case ICESTATE_WAKING:
107 m_physic.set_velocity_x(0);
108 break;
109 case ICESTATE_GRABBED:
110 break;
111 }
112}
113
114HitResponse
115MrIceBlock::collision(GameObject& object, const CollisionHit& hit)
116{
117 if (ice_state == ICESTATE_GRABBED)
118 return FORCE_MOVE;
119
120 return BadGuy::collision(object, hit);
121}
122
123HitResponse
124MrIceBlock::collision_player(Player& player, const CollisionHit& hit)
125{
126 // handle kicks from left or right side
127 if (ice_state == ICESTATE_FLAT && get_state() == STATE_ACTIVE) {
128 if (hit.left) {
129 m_dir = Direction::RIGHT;
130 player.kick();
131 set_state(ICESTATE_KICKED);
132 return FORCE_MOVE;
133 } else if (hit.right) {
134 m_dir = Direction::LEFT;
135 player.kick();
136 set_state(ICESTATE_KICKED);
137 return FORCE_MOVE;
138 }
139 }
140
141 return BadGuy::collision_player(player, hit);
142}
143
144HitResponse
145MrIceBlock::collision_badguy(BadGuy& badguy, const CollisionHit& hit)
146{
147 switch (ice_state) {
148 case ICESTATE_NORMAL:
149 return WalkingBadguy::collision_badguy(badguy, hit);
150 case ICESTATE_FLAT:
151 case ICESTATE_WAKING:
152 return FORCE_MOVE;
153 case ICESTATE_KICKED:
154 badguy.kill_fall();
155 return FORCE_MOVE;
156 default:
157 assert(false);
158 }
159
160 return ABORT_MOVE;
161}
162
163bool
164MrIceBlock::collision_squished(GameObject& object)
165{
166 Player* player = dynamic_cast<Player*>(&object);
167 if (player && (player->m_does_buttjump || player->is_invincible())) {
168 player->bounce(*this);
169 kill_fall();
170 return true;
171 }
172
173 switch (ice_state)
174 {
175 case ICESTATE_KICKED:
176 {
177 auto badguy = dynamic_cast<BadGuy*>(&object);
178 if (badguy) {
179 badguy->kill_fall();
180 break;
181 }
182 }
183 BOOST_FALLTHROUGH;
184
185 case ICESTATE_NORMAL:
186 {
187 squishcount++;
188 if (squishcount >= MAXSQUISHES) {
189 kill_fall();
190 return true;
191 }
192 }
193
194 set_state(ICESTATE_FLAT);
195 nokick_timer.start(NOKICK_TIME);
196 break;
197
198 case ICESTATE_FLAT:
199 case ICESTATE_WAKING:
200 {
201 auto movingobject = dynamic_cast<MovingObject*>(&object);
202 if (movingobject && (movingobject->get_pos().x < get_pos().x)) {
203 m_dir = Direction::RIGHT;
204 } else {
205 m_dir = Direction::LEFT;
206 }
207 }
208 if (nokick_timer.check()) set_state(ICESTATE_KICKED);
209 break;
210
211 case ICESTATE_GRABBED:
212 assert(false);
213 break;
214 }
215
216 if (player) player->bounce(*this);
217 return true;
218}
219
220void
221MrIceBlock::set_state(IceState state_, bool up)
222{
223 if (ice_state == state_)
224 return;
225
226 switch (state_) {
227 case ICESTATE_NORMAL:
228 set_action(m_dir == Direction::LEFT ? "left" : "right", /* loops = */ -1);
229 WalkingBadguy::initialize();
230 break;
231 case ICESTATE_FLAT:
232 if (up) {
233 m_physic.set_velocity_y(-KICKSPEED);
234 } else {
235 SoundManager::current()->play("sounds/stomp.wav", get_pos());
236 m_physic.set_velocity_x(0);
237 m_physic.set_velocity_y(0);
238 }
239 set_action(m_dir == Direction::LEFT ? "flat-left" : "flat-right", /* loops = */ -1);
240 flat_timer.start(4);
241 break;
242 case ICESTATE_KICKED:
243 SoundManager::current()->play("sounds/kick.wav", get_pos());
244
245 m_physic.set_velocity_x(m_dir == Direction::LEFT ? -KICKSPEED : KICKSPEED);
246 set_action(m_dir == Direction::LEFT ? "flat-left" : "flat-right", /* loops = */ -1);
247 // we should slide above 1 block holes now...
248 m_col.m_bbox.set_size(34, 31.8f);
249 break;
250 case ICESTATE_GRABBED:
251 flat_timer.stop();
252 break;
253 case ICESTATE_WAKING:
254 m_sprite->set_action(m_dir == Direction::LEFT ? "waking-left" : "waking-right",
255 /* loops = */ 1);
256 break;
257 default:
258 assert(false);
259 }
260 ice_state = state_;
261}
262
263void
264MrIceBlock::grab(MovingObject& object, const Vector& pos, Direction dir_)
265{
266 Portable::grab(object, pos, dir_);
267 m_col.m_movement = pos - get_pos();
268 m_dir = dir_;
269 set_action(dir_ == Direction::LEFT ? "flat-left" : "flat-right", /* loops = */ -1);
270 set_state(ICESTATE_GRABBED);
271 set_colgroup_active(COLGROUP_DISABLED);
272}
273
274void
275MrIceBlock::ungrab(MovingObject& object, Direction dir_)
276{
277 if (dir_ == Direction::UP) {
278 set_state(ICESTATE_FLAT, true);
279 } else {
280 m_dir = dir_;
281 set_state(ICESTATE_KICKED);
282 }
283 set_colgroup_active(COLGROUP_MOVING);
284 Portable::ungrab(object, dir_);
285}
286
287bool
288MrIceBlock::is_portable() const
289{
290 return ice_state == ICESTATE_FLAT;
291}
292
293void
294MrIceBlock::ignite() {
295 set_state(ICESTATE_NORMAL);
296 BadGuy::ignite();
297}
298
299/* EOF */
300