1 | // SuperTux - Boss "Yeti" |
2 | // Copyright (C) 2005 Matthias Braun <matze@braunis.de> |
3 | // Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de> |
4 | // |
5 | // This program is free software: you can redistribute it and/or modify |
6 | // it under the terms of the GNU General Public License as published by |
7 | // the Free Software Foundation, either version 3 of the License, or |
8 | // (at your option) any later version. |
9 | // |
10 | // This program is distributed in the hope that it will be useful, |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | // GNU General Public License for more details. |
14 | // |
15 | // You should have received a copy of the GNU General Public License |
16 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | |
18 | #include "badguy/yeti.hpp" |
19 | |
20 | #include <math.h> |
21 | |
22 | #include "audio/sound_manager.hpp" |
23 | #include "badguy/yeti_stalactite.hpp" |
24 | #include "math/random.hpp" |
25 | #include "object/camera.hpp" |
26 | #include "object/player.hpp" |
27 | #include "sprite/sprite.hpp" |
28 | #include "supertux/sector.hpp" |
29 | #include "util/reader_mapping.hpp" |
30 | #include "video/surface.hpp" |
31 | |
32 | namespace { |
33 | const float JUMP_DOWN_VX = 250; /**< horizontal speed while jumping off the dais */ |
34 | const float JUMP_DOWN_VY = -250; /**< vertical speed while jumping off the dais */ |
35 | |
36 | const float RUN_VX = 350; /**< horizontal speed while running */ |
37 | |
38 | const float JUMP_UP_VX = 350; /**< horizontal speed while jumping on the dais */ |
39 | const float JUMP_UP_VY = -700; /**< vertical speed while jumping on the dais */ |
40 | |
41 | const float STOMP_VY = -300; /** vertical speed while stomping on the dais */ |
42 | |
43 | const float RUN_DISTANCE = 1060; /** Distance between the x-coordinates of left and right end positions */ |
44 | const float JUMP_SPACE = 448; /** Distance between jump position and stand position */ |
45 | const float STOMP_WAIT = .5; /**< time we stay on the dais before jumping again */ |
46 | const float SAFE_TIME = .5; /**< the time we are safe when tux just hit us */ |
47 | const int INITIAL_HITPOINTS = 5; /**< number of hits we can take */ |
48 | |
49 | const float YETI_SQUISH_TIME = 3; |
50 | |
51 | const float SNOW_EXPLOSIONS_FREQUENCY = 8; /**< number of snowball explosions per second */ |
52 | const int SNOW_EXPLOSIONS_COUNT = 5; /**< number of snowballs per explosion */ |
53 | const float SNOW_EXPLOSIONS_VX = 150; /**< Speed of snowballs */ |
54 | const float SNOW_EXPLOSIONS_VY = -200; /**< Speed of snowballs */ |
55 | } |
56 | |
57 | Yeti::Yeti(const ReaderMapping& reader) : |
58 | BadGuy(reader, "images/creatures/yeti/yeti.sprite" ), |
59 | state(), |
60 | state_timer(), |
61 | safe_timer(), |
62 | stomp_count(), |
63 | hit_points(), |
64 | hud_head(), |
65 | left_stand_x(), |
66 | right_stand_x(), |
67 | left_jump_x(), |
68 | right_jump_x(), |
69 | fixed_pos(), |
70 | hud_icon() |
71 | { |
72 | reader.get("lives" , hit_points, INITIAL_HITPOINTS); |
73 | m_countMe = true; |
74 | SoundManager::current()->preload("sounds/yeti_gna.wav" ); |
75 | SoundManager::current()->preload("sounds/yeti_roar.wav" ); |
76 | |
77 | reader.get("hud-icon" , hud_icon, "images/creatures/yeti/hudlife.png" ); |
78 | hud_head = Surface::from_file(hud_icon); |
79 | |
80 | initialize(); |
81 | |
82 | reader.get("fixed-pos" , fixed_pos, false); |
83 | if (fixed_pos) { |
84 | left_stand_x = 80; |
85 | right_stand_x = 1140; |
86 | left_jump_x = 528; |
87 | right_jump_x = 692; |
88 | } else { |
89 | recalculate_pos(); |
90 | } |
91 | } |
92 | |
93 | void |
94 | Yeti::initialize() |
95 | { |
96 | m_dir = Direction::RIGHT; |
97 | jump_down(); |
98 | } |
99 | |
100 | void |
101 | Yeti::recalculate_pos() |
102 | { |
103 | if (m_dir == Direction::RIGHT) { |
104 | left_stand_x = m_col.m_bbox.get_left(); |
105 | right_stand_x = left_stand_x + RUN_DISTANCE; |
106 | } else { |
107 | right_stand_x = m_col.m_bbox.get_left(); |
108 | left_stand_x = right_stand_x - RUN_DISTANCE; |
109 | } |
110 | |
111 | left_jump_x = left_stand_x + JUMP_SPACE; |
112 | right_jump_x = right_stand_x - JUMP_SPACE; |
113 | } |
114 | |
115 | void |
116 | Yeti::draw(DrawingContext& context) |
117 | { |
118 | // we blink when we are safe |
119 | if (safe_timer.started() && size_t(g_game_time * 40) % 2) |
120 | return; |
121 | |
122 | draw_hit_points(context); |
123 | |
124 | BadGuy::draw(context); |
125 | } |
126 | |
127 | void |
128 | Yeti::draw_hit_points(DrawingContext& context) |
129 | { |
130 | if (hud_head) |
131 | { |
132 | context.push_transform(); |
133 | context.set_translation(Vector(0, 0)); |
134 | |
135 | for (int i = 0; i < hit_points; ++i) |
136 | { |
137 | context.color().draw_surface(hud_head, Vector(BORDER_X + (static_cast<float>(i * hud_head->get_width())), BORDER_Y + 1), LAYER_FOREGROUND1); |
138 | } |
139 | |
140 | context.pop_transform(); |
141 | } |
142 | } |
143 | |
144 | void |
145 | Yeti::active_update(float dt_sec) |
146 | { |
147 | switch (state) { |
148 | case JUMP_DOWN: |
149 | m_physic.set_velocity_x((m_dir==Direction::RIGHT)?+JUMP_DOWN_VX:-JUMP_DOWN_VX); |
150 | break; |
151 | case RUN: |
152 | m_physic.set_velocity_x((m_dir==Direction::RIGHT)?+RUN_VX:-RUN_VX); |
153 | if (((m_dir == Direction::RIGHT) && (get_pos().x >= right_jump_x)) || ((m_dir == Direction::LEFT) && (get_pos().x <= left_jump_x))) jump_up(); |
154 | break; |
155 | case JUMP_UP: |
156 | m_physic.set_velocity_x((m_dir==Direction::RIGHT)?+JUMP_UP_VX:-JUMP_UP_VX); |
157 | if (((m_dir == Direction::RIGHT) && (get_pos().x >= right_stand_x)) || ((m_dir == Direction::LEFT) && (get_pos().x <= left_stand_x))) be_angry(); |
158 | break; |
159 | case BE_ANGRY: |
160 | if (state_timer.check() && on_ground()) { |
161 | m_physic.set_velocity_y(STOMP_VY); |
162 | m_sprite->set_action((m_dir==Direction::RIGHT)?"stomp-right" :"stomp-left" ); |
163 | SoundManager::current()->play("sounds/yeti_gna.wav" ); |
164 | } |
165 | break; |
166 | case SQUISHED: |
167 | { |
168 | Direction newdir = (int(state_timer.get_timeleft() * SNOW_EXPLOSIONS_FREQUENCY) % 2) ? Direction::LEFT : Direction::RIGHT; |
169 | if (m_dir != newdir && m_dir == Direction::RIGHT) { |
170 | SoundManager::current()->play("sounds/stomp.wav" ); |
171 | add_snow_explosions(); |
172 | Sector::get().get_camera().shake(.05f, 0, 5); |
173 | } |
174 | m_dir = newdir; |
175 | m_sprite->set_action((m_dir==Direction::RIGHT)?"jump-right" :"jump-left" ); |
176 | } |
177 | if (state_timer.check()) { |
178 | BadGuy::kill_fall(); |
179 | state = FALLING; |
180 | m_physic.set_velocity_y(JUMP_UP_VY / 2); // Move up a bit before falling |
181 | // Add some extra explosions |
182 | for (int i = 0; i < 10; i++) { |
183 | add_snow_explosions(); |
184 | } |
185 | run_dead_script(); |
186 | } |
187 | break; |
188 | case FALLING: |
189 | break; |
190 | } |
191 | |
192 | m_col.m_movement = m_physic.get_movement(dt_sec); |
193 | } |
194 | |
195 | void |
196 | Yeti::jump_down() |
197 | { |
198 | m_sprite->set_action((m_dir==Direction::RIGHT)?"jump-right" :"jump-left" ); |
199 | m_physic.set_velocity_x((m_dir==Direction::RIGHT)?(+JUMP_DOWN_VX):(-JUMP_DOWN_VX)); |
200 | m_physic.set_velocity_y(JUMP_DOWN_VY); |
201 | state = JUMP_DOWN; |
202 | } |
203 | |
204 | void |
205 | Yeti::run() |
206 | { |
207 | m_sprite->set_action((m_dir==Direction::RIGHT)?"walking-right" :"walking-left" ); |
208 | m_physic.set_velocity_x((m_dir==Direction::RIGHT)?(+RUN_VX):(-RUN_VX)); |
209 | m_physic.set_velocity_y(0); |
210 | state = RUN; |
211 | } |
212 | |
213 | void |
214 | Yeti::jump_up() |
215 | { |
216 | m_sprite->set_action((m_dir==Direction::RIGHT)?"jump-right" :"jump-left" ); |
217 | m_physic.set_velocity_x((m_dir==Direction::RIGHT)?(+JUMP_UP_VX):(-JUMP_UP_VX)); |
218 | m_physic.set_velocity_y(JUMP_UP_VY); |
219 | state = JUMP_UP; |
220 | } |
221 | |
222 | void |
223 | Yeti::be_angry() |
224 | { |
225 | //turn around |
226 | m_dir = (m_dir==Direction::RIGHT) ? Direction::LEFT : Direction::RIGHT; |
227 | |
228 | m_sprite->set_action((m_dir==Direction::RIGHT) ? "stand-right" : "stand-left" ); |
229 | m_physic.set_velocity_x(0); |
230 | stomp_count = 0; |
231 | state = BE_ANGRY; |
232 | state_timer.start(STOMP_WAIT); |
233 | } |
234 | |
235 | bool |
236 | Yeti::collision_squished(GameObject& object) |
237 | { |
238 | kill_squished(object); |
239 | |
240 | return true; |
241 | } |
242 | |
243 | void |
244 | Yeti::kill_squished(GameObject& object) |
245 | { |
246 | auto player = dynamic_cast<Player*>(&object); |
247 | if (player) { |
248 | player->bounce(*this); |
249 | take_hit(*player); |
250 | } |
251 | } |
252 | |
253 | void Yeti::take_hit(Player& ) |
254 | { |
255 | if (safe_timer.started()) |
256 | return; |
257 | |
258 | SoundManager::current()->play("sounds/yeti_roar.wav" ); |
259 | hit_points--; |
260 | |
261 | if (hit_points <= 0) { |
262 | // We're dead |
263 | m_physic.set_velocity_x(((m_dir==Direction::RIGHT)?+RUN_VX:-RUN_VX)/5); |
264 | m_physic.set_velocity_y(0); |
265 | |
266 | // Set the badguy layer to be above the foremost, so that |
267 | // this does not reveal secret tilemaps: |
268 | m_layer = Sector::get().get_foremost_layer() + 1; |
269 | state = SQUISHED; |
270 | state_timer.start(YETI_SQUISH_TIME); |
271 | set_colgroup_active(COLGROUP_MOVING_ONLY_STATIC); |
272 | //sprite->set_action("dead"); // This sprite does not look very good |
273 | } |
274 | else { |
275 | safe_timer.start(SAFE_TIME); |
276 | } |
277 | } |
278 | |
279 | void |
280 | Yeti::kill_fall() |
281 | { |
282 | // shooting bullets or being invincible won't work :) |
283 | } |
284 | |
285 | void |
286 | Yeti::drop_stalactite() |
287 | { |
288 | // make a stalactite falling down and shake camera a bit |
289 | Sector::get().get_camera().shake(.1f, 0, 10); |
290 | |
291 | auto player = get_nearest_player(); |
292 | if (!player) return; |
293 | |
294 | for (auto& stalactite : Sector::get().get_objects_by_type<YetiStalactite>()) |
295 | { |
296 | if (stalactite.is_hanging()) { |
297 | if (hit_points >= 3) { |
298 | // drop stalactites within 3 of player, going out with each jump |
299 | float distancex = fabsf(stalactite.get_bbox().get_middle().x - player->get_bbox().get_middle().x); |
300 | if (distancex < static_cast<float>(stomp_count) * 32.0f) { |
301 | stalactite.start_shaking(); |
302 | } |
303 | } |
304 | else { /* if (hitpoints < 3) */ |
305 | // drop every 3rd pair of stalactites |
306 | if ((((static_cast<int>(stalactite.get_pos().x) + 16) / 64) % 3) == (stomp_count % 3)) { |
307 | stalactite.start_shaking(); |
308 | } |
309 | } |
310 | } |
311 | } |
312 | } |
313 | |
314 | void |
315 | Yeti::collision_solid(const CollisionHit& hit) |
316 | { |
317 | update_on_ground_flag(hit); |
318 | if (hit.top || hit.bottom) { |
319 | // hit floor or roof |
320 | m_physic.set_velocity_y(0); |
321 | switch (state) { |
322 | case JUMP_DOWN: |
323 | run(); |
324 | break; |
325 | case RUN: |
326 | break; |
327 | case JUMP_UP: |
328 | break; |
329 | case BE_ANGRY: |
330 | // we just landed |
331 | if (!state_timer.started()) { |
332 | m_sprite->set_action((m_dir==Direction::RIGHT)?"stand-right" :"stand-left" ); |
333 | stomp_count++; |
334 | drop_stalactite(); |
335 | |
336 | // go to other side after 3 jumps |
337 | if (stomp_count == 3) { |
338 | jump_down(); |
339 | } else { |
340 | // jump again |
341 | state_timer.start(STOMP_WAIT); |
342 | } |
343 | } |
344 | break; |
345 | case SQUISHED: |
346 | break; |
347 | case FALLING: |
348 | break; |
349 | } |
350 | } else if (hit.left || hit.right) { |
351 | // hit wall |
352 | if(state != SQUISHED && state != FALLING) |
353 | jump_up(); |
354 | } |
355 | } |
356 | |
357 | bool |
358 | Yeti::is_flammable() const |
359 | { |
360 | return false; |
361 | } |
362 | |
363 | ObjectSettings |
364 | Yeti::get_settings() |
365 | { |
366 | ObjectSettings result = BadGuy::get_settings(); |
367 | |
368 | result.add_text("hud-icon" , &hud_icon, "hud-icon" , std::string("images/creatures/yeti/hudlife.png" ), OPTION_HIDDEN); |
369 | result.add_bool(_("Fixed position" ), &fixed_pos, "fixed-pos" , false); |
370 | result.add_int(_("Lives" ), &hit_points, "lives" , 5); |
371 | |
372 | return result; |
373 | } |
374 | |
375 | void |
376 | Yeti::add_snow_explosions() |
377 | { |
378 | for (int i = 0; i < SNOW_EXPLOSIONS_COUNT; i++) { |
379 | Vector pos = get_pos(), velocity; |
380 | velocity.x = SNOW_EXPLOSIONS_VX * graphicsRandom.randf(0.5f, 2.0f) * (graphicsRandom.rand(2) ? 1.0f : -1.0f); |
381 | velocity.y = SNOW_EXPLOSIONS_VY * graphicsRandom.randf(0.5f, 2.0f); |
382 | pos.x += static_cast<float>(m_sprite->get_width()) / 2.0f; |
383 | pos.x += static_cast<float>(m_sprite->get_width()) * graphicsRandom.randf(0.3f, 0.5f) * ((velocity.x > 0) ? 1.0f : -1.0f); |
384 | pos.y += static_cast<float>(m_sprite->get_height()) * graphicsRandom.randf(-0.3f, 0.3f); |
385 | velocity.x += m_physic.get_velocity_x(); |
386 | Sector::get().add<SnowExplosionParticle>(pos, velocity); |
387 | } |
388 | } |
389 | |
390 | Yeti::SnowExplosionParticle::SnowExplosionParticle(const Vector& pos, const Vector& velocity) |
391 | : BadGuy(pos, (velocity.x > 0) ? Direction::RIGHT : Direction::LEFT, "images/objects/bullets/icebullet.sprite" ) |
392 | { |
393 | m_physic.set_velocity_x(velocity.x); |
394 | m_physic.set_velocity_y(velocity.y); |
395 | m_physic.enable_gravity(true); |
396 | set_state(STATE_FALLING); |
397 | m_layer = Sector::get().get_foremost_layer() + 1; |
398 | } |
399 | |
400 | /* EOF */ |
401 | |