void BadGuy::try_activate() { // Don't activate if player is dying auto player = get_nearest_player(); if (!player) return; if (!is_offscreen()) { set_state(STATE_ACTIVE); if (!m_is_initialized) { // if starting direction was set to AUTO, this is our chance to re-orient the badguy if (m_start_dir == Direction::AUTO) { auto player_ = get_nearest_player(); if (player_ && (player_->get_bbox().get_left() > m_col.m_bbox.get_right())) { m_dir = Direction::RIGHT; } else { m_dir = Direction::LEFT; } } initialize(); m_is_initialized = true; } activate(); } }
void Kugelblitz::try_activate() { // Much smaller offscreen distances to pop out of nowhere and surprise Tux float X_OFFSCREEN_DISTANCE = 400; float Y_OFFSCREEN_DISTANCE = 600; Player* player = get_nearest_player(); if (!player) return; Vector dist = player->get_bbox().get_middle() - get_bbox().get_middle(); if ((fabsf(dist.x) <= X_OFFSCREEN_DISTANCE) && (fabsf(dist.y) <= Y_OFFSCREEN_DISTANCE)) { set_state(STATE_ACTIVE); if (!is_initialized) { // if starting direction was set to AUTO, this is our chance to re-orient the badguy if (start_dir == AUTO) { Player* player = get_nearest_player(); if (player && (player->get_bbox().p1.x > get_bbox().p2.x)) { dir = RIGHT; } else { dir = LEFT; } } initialize(); is_initialized = true; } activate(); } }
void BadGuy::try_activate() { // Don't activate if player is dying Player* player = get_nearest_player(); if (!player) return; if (!is_offscreen()) { set_state(STATE_ACTIVE); if (!is_initialized) { // if starting direction was set to AUTO, this is our chance to re-orient the badguy if (start_dir == AUTO) { Player* player_ = get_nearest_player(); if (player_ && (player_->get_bbox().p1.x > get_bbox().p2.x)) { dir = RIGHT; } else { dir = LEFT; } } initialize(); is_initialized = true; } activate(); } }
void Plant::active_update(float elapsed_time) { BadGuy::active_update(elapsed_time); if(state == PLANT_SLEEPING) { auto player = get_nearest_player(); if (player) { Rectf pb = player->get_bbox(); bool inReach_left = (pb.p2.x >= bbox.p2.x-((dir == LEFT) ? 256 : 0)); bool inReach_right = (pb.p1.x <= bbox.p1.x+((dir == RIGHT) ? 256 : 0)); bool inReach_top = (pb.p2.y >= bbox.p2.y); bool inReach_bottom = (pb.p1.y <= bbox.p1.y); if (inReach_left && inReach_right && inReach_top && inReach_bottom) { // wake up sprite->set_action(dir == LEFT ? "waking-left" : "waking-right"); if(!timer.started()) timer.start(WAKE_TIME); state = PLANT_WAKING; } } } if(state == PLANT_WAKING) { if(timer.check()) { // start walking sprite->set_action(dir == LEFT ? "left" : "right"); physic.set_velocity_x(dir == LEFT ? -PLANT_SPEED : PLANT_SPEED); state = PLANT_WALKING; } } }
void Yeti::drop_stalactite() { // make a stalactite falling down and shake camera a bit Sector::current()->camera->shake(.1f, 0, 10); auto player = get_nearest_player(); if (!player) return; Sector* sector = Sector::current(); for(const auto& obj : sector->gameobjects) { auto stalactite = dynamic_cast<YetiStalactite*>(obj.get()); if(stalactite && stalactite->is_hanging()) { if (hit_points >= 3) { // drop stalactites within 3 of player, going out with each jump float distancex = fabsf(stalactite->get_bbox().get_middle().x - player->get_bbox().get_middle().x); if(distancex < stomp_count*32) { stalactite->start_shaking(); } } else { /* if (hitpoints < 3) */ // drop every 3rd pair of stalactites if(((((int)stalactite->get_pos().x + 16) / 64) % 3) == (stomp_count % 3)) { stalactite->start_shaking(); } } } /* if(stalactite && stalactite->is_hanging()) */ } }
void WillOWisp::active_update(float elapsed_time) { Player* player = get_nearest_player(); if (!player) return; Vector p1 = bbox.get_middle(); Vector p2 = player->get_bbox().get_middle(); Vector dist = (p2 - p1); switch(mystate) { case STATE_STOPPED: break; case STATE_IDLE: if (dist.norm() <= track_range) { mystate = STATE_TRACKING; } break; case STATE_TRACKING: if (dist.norm() > vanish_range) { vanish(); } else if (dist.norm() >= 1) { Vector dir_ = dist.unit(); movement = dir_ * elapsed_time * flyspeed; } else { /* We somehow landed right on top of the player without colliding. * Sit tight and avoid a division by zero. */ } sound_source->set_position(get_pos()); break; case STATE_WARPING: if(sprite->animation_done()) { remove_me(); } case STATE_VANISHING: { Vector dir_ = dist.unit(); movement = dir_ * elapsed_time * flyspeed; if(sprite->animation_done()) { remove_me(); } break; } case STATE_PATHMOVING: case STATE_PATHMOVING_TRACK: if(walker.get() == NULL) return; movement = walker->advance(elapsed_time) - get_pos(); if(mystate == STATE_PATHMOVING_TRACK && dist.norm() <= track_range) { mystate = STATE_TRACKING; } break; default: assert(false); } }
void Toad::set_state(ToadState newState) { if (newState == IDLE) { m_physic.set_velocity_x(0); m_physic.set_velocity_y(0); if (!m_frozen) m_sprite->set_action(m_dir == Direction::LEFT ? "idle-left" : "idle-right"); recover_timer.start(TOAD_RECOVER_TIME); } else if (newState == JUMPING) { m_sprite->set_action(m_dir == Direction::LEFT ? "jumping-left" : "jumping-right"); m_physic.set_velocity_x(m_dir == Direction::LEFT ? -HORIZONTAL_SPEED : HORIZONTAL_SPEED); m_physic.set_velocity_y(VERTICAL_SPEED); SoundManager::current()->play( HOP_SOUND, get_pos()); } else if (newState == FALLING) { Player* player = get_nearest_player(); // face player if (player && (player->get_bbox().get_right() < m_col.m_bbox.get_left()) && (m_dir == Direction::RIGHT)) m_dir = Direction::LEFT; if (player && (player->get_bbox().get_left() > m_col.m_bbox.get_right()) && (m_dir == Direction::LEFT)) m_dir = Direction::RIGHT; m_sprite->set_action(m_dir == Direction::LEFT ? "idle-left" : "idle-right"); } state = newState; }
void Jumpy::active_update(float dt_sec) { BadGuy::active_update(dt_sec); if (m_frozen) return; auto player = get_nearest_player(); if (player) { m_dir = (player->get_pos().x > get_pos().x) ? Direction::RIGHT : Direction::LEFT; } if (!groundhit_pos_set) { m_sprite->set_action(m_dir == Direction::LEFT ? "left-middle" : "right-middle"); return; } if ( get_pos().y < (pos_groundhit.y - JUMPY_MID_TOLERANCE ) ) m_sprite->set_action(m_dir == Direction::LEFT ? "left-up" : "right-up"); else if ( get_pos().y >= (pos_groundhit.y - JUMPY_MID_TOLERANCE) && get_pos().y < (pos_groundhit.y - JUMPY_LOW_TOLERANCE) ) m_sprite->set_action(m_dir == Direction::LEFT ? "left-middle" : "right-middle"); else m_sprite->set_action(m_dir == Direction::LEFT ? "left-down" : "right-down"); }
void SpiderMite::active_update(float elapsed_time) { if(frozen) { BadGuy::active_update(elapsed_time); return; } if(timer.check()) { if(mode == FLY_UP) { mode = FLY_DOWN; physic.set_velocity_y(-MOVE_SPEED); } else if(mode == FLY_DOWN) { mode = FLY_UP; physic.set_velocity_y(MOVE_SPEED); } timer.start(FLYTIME); } movement=physic.get_movement(elapsed_time); Player* player = get_nearest_player(); if (player) { dir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT; sprite->set_action(dir == LEFT ? "left" : "right"); } }
void InfoBlock::update(float delta) { Block::update(delta); if (delta == 0) return; // hide message if player is too far away if (dest_pct > 0) { Player* player = get_nearest_player(); if (player) { Vector p1 = bbox.get_middle(); Vector p2 = player->get_bbox().get_middle(); Vector dist = (p2 - p1); float d = dist.norm(); if (d > 128) dest_pct = 0; } } // handle soft fade-in and fade-out if (shown_pct != dest_pct) { if (dest_pct > shown_pct) shown_pct = std::min(shown_pct + 2*delta, dest_pct); if (dest_pct < shown_pct) shown_pct = std::max(shown_pct - 2*delta, dest_pct); } }
void Toad::set_state(ToadState newState) { if (newState == IDLE) { physic.set_velocity_x(0); physic.set_velocity_y(0); if (!frozen) sprite->set_action(dir == LEFT ? "idle-left" : "idle-right"); recover_timer.start(TOAD_RECOVER_TIME); } else if (newState == JUMPING) { sprite->set_action(dir == LEFT ? "jumping-left" : "jumping-right"); physic.set_velocity_x(dir == LEFT ? -HORIZONTAL_SPEED : HORIZONTAL_SPEED); physic.set_velocity_y(VERTICAL_SPEED); SoundManager::current()->play( HOP_SOUND, get_pos()); } else if (newState == FALLING) { Player* player = get_nearest_player(); // face player if (player && (player->get_bbox().p2.x < get_bbox().p1.x) && (dir == RIGHT)) dir = LEFT; if (player && (player->get_bbox().p1.x > get_bbox().p2.x) && (dir == LEFT)) dir = RIGHT; sprite->set_action(dir == LEFT ? "idle-left" : "idle-right"); } state = newState; }
void Dispenser::active_update(float ) { if (dispense_timer.check()) { // auto always shoots in Tux's direction if( autotarget ){ if( sprite->animation_done()) { sprite->set_action(dir == LEFT ? "working-left" : "working-right"); swivel = false; } Player* player = get_nearest_player(); if( player && !swivel ){ Direction targetdir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT; if( dir != targetdir ){ // no target: swivel cannon swivel = true; dir = targetdir; sprite->set_action(dir == LEFT ? "swivel-left" : "swivel-right", 1); } else { // tux in sight: shoot launch_badguy(); } } } else { launch_badguy(); } } }
void Stalactite::active_update(float elapsed_time) { if(state == STALACTITE_HANGING) { auto player = get_nearest_player(); if (player && !player->get_ghost_mode()) { if(player->get_bbox().p2.x > bbox.p1.x - SHAKE_RANGE_X && player->get_bbox().p1.x < bbox.p2.x + SHAKE_RANGE_X && player->get_bbox().p2.y > bbox.p1.y && player->get_bbox().p1.y < bbox.p2.y + SHAKE_RANGE_Y && Sector::current()->can_see_player(bbox.get_middle())) { timer.start(SHAKE_TIME); state = STALACTITE_SHAKING; SoundManager::current()->play("sounds/cracking.wav", get_pos()); } } } else if(state == STALACTITE_SHAKING) { shake_delta = Vector(static_cast<float>(graphicsRandom.rand(-3, 3)), 0.0f); if(timer.check()) { state = STALACTITE_FALLING; physic.enable_gravity(true); set_colgroup_active(COLGROUP_MOVING); } } else if(state == STALACTITE_FALLING) { movement = physic.get_movement(elapsed_time); } }
bool BadGuy::is_offscreen() { Player* player = get_nearest_player(); if (!player) return false; Vector dist = player->get_bbox().get_middle() - get_bbox().get_middle(); // In SuperTux 0.1.x, Badguys were activated when Tux<->Badguy center distance was approx. <= ~668px // This doesn't work for wide-screen monitors which give us a virt. res. of approx. 1066px x 600px if ((fabsf(dist.x) <= X_OFFSCREEN_DISTANCE) && (fabsf(dist.y) <= Y_OFFSCREEN_DISTANCE)) { return false; } return true; }
/** linear prediction of player and badguy positions to decide if we should enter the DIVING state */ bool Zeekling::should_we_dive() { if (m_frozen) return false; const auto player = get_nearest_player(); if (player && last_player && (player == last_player)) { // get positions, calculate movement const Vector& player_pos = player->get_pos(); const Vector player_mov = (player_pos - last_player_pos); const Vector self_pos = m_col.m_bbox.p1(); const Vector self_mov = (self_pos - last_self_pos); // new vertical speed to test with float vy = 2*fabsf(self_mov.x); // do not dive if we are not above the player float height = player_pos.y - self_pos.y; if (height <= 0) return false; // do not dive if we are too far above the player if (height > 512) return false; // do not dive if we would not descend faster than the player float relSpeed = vy - player_mov.y; if (relSpeed <= 0) return false; // guess number of frames to descend to same height as player float estFrames = height / relSpeed; // guess where the player would be at this time float estPx = (player_pos.x + (estFrames * player_mov.x)); // guess where we would be at this time float estBx = (self_pos.x + (estFrames * self_mov.x)); // near misses are OK, too if (fabsf(estPx - estBx) < 8) return true; } // update last player tracked, as well as our positions last_player = player; if (player) { last_player_pos = player->get_pos(); last_self_pos = m_col.m_bbox.p1(); } return false; }
void Dispenser::activate() { if( broken ){ return; } if( autotarget && !swivel ){ // auto cannon sprite might be wrong Player* player = get_nearest_player(); if( player ){ dir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT; sprite->set_action(dir == LEFT ? "working-left" : "working-right"); } } dispense_timer.start(cycle, true); launch_badguy(); }
void Dispenser::activate() { if (m_broken){ return; } if (m_autotarget && !m_swivel){ // auto cannon sprite might be wrong auto* player = get_nearest_player(); if (player) { m_dir = (player->get_pos().x > get_pos().x) ? Direction::RIGHT : Direction::LEFT; m_sprite->set_action(m_dir == Direction::LEFT ? "working-left" : "working-right"); } } m_dispense_timer.start(m_cycle, true); launch_badguy(); }
void Haywire::active_update(float elapsed_time) { if (is_exploding) { ticking->set_position(get_pos()); grunting->set_position(get_pos()); if (elapsed_time >= time_until_explosion) { kill_fall (); return; } else time_until_explosion -= elapsed_time; } if (is_stunned) { if (time_stunned > elapsed_time) { time_stunned -= elapsed_time; } else { /* if (time_stunned <= elapsed_time) */ time_stunned = 0.f; is_stunned = false; } } if (is_exploding) { auto p = get_nearest_player (); float target_velocity = 0.f; if (p && time_stunned == 0.f) { /* Player is on the right */ if (p->get_pos ().x > get_pos ().x) target_velocity = walk_speed; else /* player in on the left */ target_velocity = (-1.f) * walk_speed; } /* if (player) */ WalkingBadguy::active_update(elapsed_time, target_velocity); } else { WalkingBadguy::active_update(elapsed_time); } }
bool BadGuy::is_offscreen() const { Vector dist; if (Editor::is_active()) { Camera& cam = Sector::get().get_camera(); dist = cam.get_center() - m_col.m_bbox.get_middle(); } auto player = get_nearest_player(); if (!player) return false; if (!Editor::is_active()) { dist = player->get_bbox().get_middle() - m_col.m_bbox.get_middle(); } // In SuperTux 0.1.x, Badguys were activated when Tux<->Badguy center distance was approx. <= ~668px // This doesn't work for wide-screen monitors which give us a virt. res. of approx. 1066px x 600px if ((fabsf(dist.x) <= X_OFFSCREEN_DISTANCE) && (fabsf(dist.y) <= Y_OFFSCREEN_DISTANCE)) { return false; } return true; }
void WillOWisp::active_update(float elapsed_time) { Player* player = get_nearest_player(); if (!player) return; Vector p1 = this->get_pos() + (this->get_bbox().p2 - this->get_bbox().p1) / 2; Vector p2 = player->get_pos() + (player->get_bbox().p2 - player->get_bbox().p1) / 2; Vector dist = (p2 - p1); if (mystate == STATE_IDLE) { if (dist.norm() <= TRACK_RANGE) { mystate = STATE_TRACKING; } } if (mystate == STATE_TRACKING) { if (dist.norm() <= VANISH_RANGE) { Vector dir = dist.unit(); movement = dir*elapsed_time*FLYSPEED; } else { mystate = STATE_VANISHING; sprite->set_action("vanishing", 1); } soundSource->set_position(get_pos()); } if (mystate == STATE_WARPING) { if(sprite->animation_done()) { remove_me(); } } if (mystate == STATE_VANISHING) { if(sprite->animation_done()) { remove_me(); } } }
void AngryStone::active_update(float elapsed_time) { BadGuy::active_update(elapsed_time); if (frozen) { return; } switch (state) { case IDLE: { MovingObject* player = get_nearest_player(); if(player) { MovingObject* badguy = this; const Vector playerPos = player->get_pos(); const Vector badguyPos = badguy->get_pos(); float dx = (playerPos.x - badguyPos.x); float dy = (playerPos.y - badguyPos.y); float playerHeight = player->get_bbox().get_height(); float badguyHeight = badguy->get_bbox().get_height(); float playerWidth = player->get_bbox().get_width(); float badguyWidth = badguy->get_bbox().get_width(); if ((dx > -playerWidth) && (dx < badguyWidth)) { if (dy > 0) { attackDirection.x = 0; attackDirection.y = 1; } else { attackDirection.x = 0; attackDirection.y = -1; } if ((attackDirection.x != oldWallDirection.x) || (attackDirection.y != oldWallDirection.y)) { sprite->set_action("charging"); timer.start(CHARGE_TIME); state = CHARGING; } } else if ((dy > -playerHeight) && (dy < badguyHeight)) { if (dx > 0) { attackDirection.x = 1; attackDirection.y = 0; } else { attackDirection.x = -1; attackDirection.y = 0; } if ((attackDirection.x != oldWallDirection.x) || (attackDirection.y != oldWallDirection.y)) { sprite->set_action("charging"); timer.start(CHARGE_TIME); state = CHARGING; } } } } break; case CHARGING: { if (timer.check()) { sprite->set_action("attacking"); timer.start(ATTACK_TIME); state = ATTACKING; physic.enable_gravity(false); physic.set_velocity_x(CHARGE_SPEED * attackDirection.x); physic.set_velocity_y(CHARGE_SPEED * attackDirection.y); oldWallDirection.x = 0; oldWallDirection.y = 0; } } break; case ATTACKING: { if (timer.check()) { timer.start(RECOVER_TIME); state = RECOVERING; sprite->set_action("idle"); physic.enable_gravity(true); physic.set_velocity_x(0); physic.set_velocity_y(0); } } break; case RECOVERING: { if (timer.check()) { state = IDLE; sprite->set_action("idle"); physic.enable_gravity(true); physic.set_velocity_x(0); physic.set_velocity_y(0); } } break; } }
void Dispenser::launch_badguy() { if (badguys.empty()) return; if (frozen) return; //FIXME: Does is_offscreen() work right here? if (!is_offscreen() && !Editor::is_active()) { Direction launchdir = dir; if( !autotarget && start_dir == AUTO ){ Player* player = get_nearest_player(); if( player ){ launchdir = (player->get_pos().x > get_pos().x) ? RIGHT : LEFT; } } if (badguys.size() > 1) { if (random) { next_badguy = gameRandom.rand(badguys.size()); } else { next_badguy++; if (next_badguy >= badguys.size()) next_badguy = 0; } } std::string badguy = badguys[next_badguy]; if(badguy == "random") { log_warning << "random is outdated; use a list of badguys to select from." << std::endl; return; } if(badguy == "goldbomb") { log_warning << "goldbomb is not allowed to be dispensed" << std::endl; return; } try { GameObjectPtr game_object; Vector spawnpoint; Rectf object_bbox; /* Need to allocate the badguy first to figure out its bounding box. */ game_object = ObjectFactory::instance().create(badguy, get_pos(), launchdir); if (game_object == NULL) throw std::runtime_error("Creating " + badguy + " object failed."); BadGuy& bad_guy = dynamic_cast<BadGuy&>(*game_object); object_bbox = bad_guy.get_bbox(); switch (type) { case DT_DROPPER: spawnpoint = get_anchor_pos (bbox, ANCHOR_BOTTOM); spawnpoint.x -= 0.5 * object_bbox.get_width(); break; case DT_ROCKETLAUNCHER: case DT_CANNON: spawnpoint = get_pos(); /* top-left corner of the cannon */ if (launchdir == LEFT) spawnpoint.x -= object_bbox.get_width() + 1; else spawnpoint.x += bbox.get_width() + 1; break; case DT_POINT: spawnpoint = bbox.p1; default: break; } /* Now we set the real spawn position */ bad_guy.set_pos(spawnpoint); /* We don't want to count dispensed badguys in level stats */ bad_guy.countMe = false; Sector::current()->add_object(game_object); } catch(const std::exception& e) { log_warning << "Error dispensing badguy: " << e.what() << std::endl; return; } } }
void Yeti::kill_fall() { // shooting bullets or being invincible won't work :) take_hit(*get_nearest_player()); // FIXME: debug only(?) }
Player* get_nearest_player (const Rectf& pos) const { return (get_nearest_player (get_anchor_pos (pos, ANCHOR_MIDDLE))); }
void Dispenser::launch_badguy() { if (m_badguys.empty()) return; if (m_frozen) return; if (m_limit_dispensed_badguys && m_current_badguys >= m_max_concurrent_badguys) return; //FIXME: Does is_offscreen() work right here? if (!is_offscreen() && !Editor::is_active()) { Direction launchdir = m_dir; if ( !m_autotarget && m_start_dir == Direction::AUTO ){ Player* player = get_nearest_player(); if ( player ){ launchdir = (player->get_pos().x > get_pos().x) ? Direction::RIGHT : Direction::LEFT; } } if (m_badguys.size() > 1) { if (m_random) { m_next_badguy = static_cast<unsigned int>(gameRandom.rand(static_cast<int>(m_badguys.size()))); } else { m_next_badguy++; if (m_next_badguy >= m_badguys.size()) m_next_badguy = 0; } } std::string badguy = m_badguys[m_next_badguy]; if (badguy == "random") { log_warning << "random is outdated; use a list of badguys to select from." << std::endl; return; } if (badguy == "goldbomb") { log_warning << "goldbomb is not allowed to be dispensed" << std::endl; return; } try { /* Need to allocate the badguy first to figure out its bounding box. */ auto game_object = GameObjectFactory::instance().create(badguy, get_pos(), launchdir); if (game_object == nullptr) throw std::runtime_error("Creating " + badguy + " object failed."); auto& bad_guy = dynamic_cast<BadGuy&>(*game_object); Rectf object_bbox = bad_guy.get_bbox(); Vector spawnpoint; switch (m_type) { case DispenserType::DROPPER: spawnpoint = get_anchor_pos (m_col.m_bbox, ANCHOR_BOTTOM); spawnpoint.x -= 0.5f * object_bbox.get_width(); break; case DispenserType::ROCKETLAUNCHER: case DispenserType::CANNON: spawnpoint = get_pos(); /* top-left corner of the cannon */ if (launchdir == Direction::LEFT) spawnpoint.x -= object_bbox.get_width() + 1; else spawnpoint.x += m_col.m_bbox.get_width() + 1; break; case DispenserType::POINT: spawnpoint = m_col.m_bbox.p1(); break; default: break; } /* Now we set the real spawn position */ bad_guy.set_pos(spawnpoint); /* We don't want to count dispensed badguys in level stats */ bad_guy.m_countMe = false; /* Set reference to dispenser in badguy itself */ if (m_limit_dispensed_badguys) { bad_guy.set_parent_dispenser(this); m_current_badguys++; } Sector::get().add_object(std::move(game_object)); } catch(const std::exception& e) { log_warning << "Error dispensing badguy: " << e.what() << std::endl; return; } } }
void GhostTree::active_update(float elapsed_time) { (void) elapsed_time; if (mystate == STATE_IDLE) { if(colorchange_timer.check()) { SoundManager::current()->play("sounds/tree_howling.ogg", get_pos()); suck_timer.start(3); treecolor = (treecolor + 1) % 3; Color col; switch(treecolor) { case 0: col = Color(1, 0, 0); break; case 1: col = Color(0, 1, 0); break; case 2: col = Color(0, 0, 1); break; case 3: col = Color(1, 1, 0); break; case 4: col = Color(1, 0, 1); break; case 5: col = Color(0, 1, 1); break; default: assert(false); } glow_sprite->set_color(col); } if(suck_timer.check()) { Color col = glow_sprite->get_color(); SoundManager::current()->play("sounds/tree_suck.ogg", get_pos()); for(auto iter = willowisps.begin(); iter != willowisps.end(); ++iter) { TreeWillOWisp& willo = **iter; if(willo.get_color() == col) { willo.start_sucking(get_bbox().get_middle() + SUCK_TARGET_OFFSET + Vector(gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD), gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD))); } } mystate = STATE_SUCKING; } if(willowisp_timer.check()) { if(willowisps.size() < WILLOWISP_COUNT) { Vector pos = Vector(bbox.get_width() / 2, bbox.get_height() / 2 + willo_spawn_y + WILLOWISP_TOP_OFFSET); auto willowisp = std::make_shared<TreeWillOWisp>(this, pos, 200 + willo_radius, willo_speed); Sector::current()->add_object(willowisp); willowisps.push_back(willowisp); willo_spawn_y -= 40; if(willo_spawn_y < -160) willo_spawn_y = 0; willo_radius += 20; if(willo_radius > 120) willo_radius = 0; if(willo_speed == 1.8f) { willo_speed = 1.5f; } else { willo_speed = 1.8f; } do { willo_color = (willo_color + 1) % 3; } while(willo_color == treecolor); switch(willo_color) { case 0: willowisp->set_color(Color(1, 0, 0)); break; case 1: willowisp->set_color(Color(0, 1, 0)); break; case 2: willowisp->set_color(Color(0, 0, 1)); break; case 3: willowisp->set_color(Color(1, 1, 0)); break; case 4: willowisp->set_color(Color(1, 0, 1)); break; case 5: willowisp->set_color(Color(0, 1, 1)); break; default: assert(false); } } } if(root_timer.check()) { /* TODO indicate root with an animation */ Player* player = get_nearest_player(); if (player) { auto root = std::make_shared<Root>(Vector(player->get_bbox().get_left(), get_bbox().get_bottom()+ROOT_TOP_OFFSET)); Sector::current()->add_object(root); } } } else if (mystate == STATE_SWALLOWING) { if (suck_lantern) { // suck in lantern assert (suck_lantern); Vector pos = suck_lantern->get_pos(); Vector delta = get_bbox().get_middle() + SUCK_TARGET_OFFSET - pos; Vector dir_ = delta.unit(); if (delta.norm() < 1) { dir_ = delta; suck_lantern->ungrab(*this, RIGHT); suck_lantern->remove_me(); suck_lantern = 0; sprite->set_action("swallow", 1); } else { pos += dir_; suck_lantern->grab(*this, pos, RIGHT); } } else { // wait until lantern is swallowed if (sprite->animation_done()) { if (is_color_deadly(suck_lantern_color)) { die(); } else { sprite->set_action("default"); mystate = STATE_IDLE; spawn_lantern(); } } } } }