| 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 | |