int monster::move_to(int x, int y, bool force) { // Make sure that we can move there, unless force is true. if(!force) if(!g->is_empty(x, y) || !can_move_to(x, y)) { return 0; } if (has_effect("beartrap")) { moves = 0; return 0; } if (!plans.empty()) { plans.erase(plans.begin()); } if (!force) { moves -= calc_movecost(posx(), posy(), x, y); } //Check for moving into/out of water bool was_water = g->m.is_divable(posx(), posy()); bool will_be_water = g->m.is_divable(x, y); if(was_water && !will_be_water && g->u_see(x, y)) { //Use more dramatic messages for swimming monsters add_msg(m_warning, _("A %s %s from the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("leaps") : _("emerges"), g->m.tername(posx(), posy()).c_str()); } else if(!was_water && will_be_water && g->u_see(x, y)) { add_msg(m_warning, _("A %s %s into the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("dives") : _("sinks"), g->m.tername(x, y).c_str()); } setpos(x, y); footsteps(x, y); if(is_hallucination()) { //Hallucinations don't do any of the stuff after this point return 1; } if (type->size != MS_TINY && g->m.has_flag("SHARP", posx(), posy()) && !one_in(4)) { hurt(rng(2, 3)); } if (type->size != MS_TINY && g->m.has_flag("ROUGH", posx(), posy()) && one_in(6)) { hurt(rng(1, 2)); } if (!digging() && !has_flag(MF_FLIES) && g->m.tr_at(posx(), posy()) != tr_null) { // Monster stepped on a trap! trap* tr = traplist[g->m.tr_at(posx(), posy())]; if (dice(3, type->sk_dodge + 1) < dice(3, tr->get_avoidance())) { tr->trigger(this, posx(), posy()); } } // Diggers turn the dirt into dirtmound if (digging()){ int factor = 0; switch (type->size) { case MS_TINY: factor = 100; break; case MS_SMALL: factor = 30; break; case MS_MEDIUM: factor = 6; break; case MS_LARGE: factor = 3; break; case MS_HUGE: factor = 1; break; } if (has_flag(MF_VERMIN)) { factor *= 100; } if (one_in(factor)) { g->m.ter_set(posx(), posy(), t_dirtmound); } } // Acid trail monsters leave... a trail of acid if (has_flag(MF_ACIDTRAIL)){ g->m.add_field(posx(), posy(), fd_acid, 3); } if (has_flag(MF_SLUDGETRAIL)) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { const int fstr = 3 - (abs(dx) + abs(dy)); if (fstr >= 2) { g->m.add_field(posx() + dx, posy() + dy, fd_sludge, fstr); } } } } if (has_flag(MF_LEAKSGAS)){ if (one_in(6)){ g->m.add_field(posx() + rng(-1,1), posy() + rng(-1, 1), fd_toxic_gas, 3); } } return 1; }
void monster::die() { if (!dead) dead = true; if (!no_extra_death_drops) { drop_items_on_death(); } if (type->difficulty >= 30 && get_killer() != NULL && get_killer()->is_player()) { g->u.add_memorial_log( pgettext("memorial_male", "Killed a %s."), pgettext("memorial_female", "Killed a %s."), name().c_str()); } // If we're a queen, make nearby groups of our type start to die out if (has_flag(MF_QUEEN)) { // Do it for overmap above/below too for(int z = 0; z >= -1; --z) { for (int x = -MAPSIZE/2; x <= MAPSIZE/2; x++) { for (int y = -MAPSIZE/2; y <= MAPSIZE/2; y++) { std::vector<mongroup*> groups = g->cur_om->monsters_at(g->levx+x, g->levy+y, z); for (int i = 0; i < groups.size(); i++) { if (MonsterGroupManager::IsMonsterInGroup (groups[i]->type, (type->id))) groups[i]->dying = true; } } } } } // If we're a mission monster, update the mission if (mission_id != -1) { mission_type *misstype = g->find_mission_type(mission_id); if (misstype->goal == MGOAL_FIND_MONSTER) g->fail_mission(mission_id); if (misstype->goal == MGOAL_KILL_MONSTER) g->mission_step_complete(mission_id, 1); } // Also, perform our death function mdeath md; if(is_hallucination()) { //Hallucinations always just disappear md.disappear(this); return; } else { //Not a hallucination, go process the death effects. std::vector<void (mdeath::*)(monster *)> deathfunctions = type->dies; void (mdeath::*func)(monster *); for (int i = 0; i < deathfunctions.size(); i++) { func = deathfunctions.at(i); (md.*func)(this); } } // If our species fears seeing one of our own die, process that int anger_adjust = 0, morale_adjust = 0; if (type->has_anger_trigger(MTRIG_FRIEND_DIED)){ anger_adjust += 15; } if (type->has_fear_trigger(MTRIG_FRIEND_DIED)){ morale_adjust -= 15; } if (type->has_placate_trigger(MTRIG_FRIEND_DIED)){ anger_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { int light = g->light_level(); for (int i = 0; i < g->num_zombies(); i++) { int t = 0; if (g->m.sees(g->zombie(i).posx(), g->zombie(i).posy(), _posx, _posy, light, t)) { g->zombie(i).morale += morale_adjust; g->zombie(i).anger += anger_adjust; } } } }
bool monster::move_to( const tripoint &p, bool force, const float stagger_adjustment ) { const bool digs = digging(); const bool flies = has_flag( MF_FLIES ); const bool on_ground = !digs && !flies; const bool climbs = has_flag( MF_CLIMBS ) && g->m.has_flag( TFLAG_NO_FLOOR, p ); // Allows climbing monsters to move on terrain with movecost <= 0 Creature *critter = g->critter_at( p, is_hallucination() ); if( g->m.has_flag( "CLIMBABLE", p ) ) { if( g->m.impassable( p ) && critter == nullptr ) { if( flies ) { moves -= 100; force = true; if( g->u.sees( *this ) ) { add_msg( _( "The %1$s flies over the %2$s." ), name().c_str(), g->m.has_flag_furn( "CLIMBABLE", p ) ? g->m.furnname( p ).c_str() : g->m.tername( p ).c_str() ); } } else if( has_flag( MF_CLIMBS ) ) { moves -= 150; force = true; if( g->u.sees( *this ) ) { add_msg( _( "The %1$s climbs over the %2$s." ), name().c_str(), g->m.has_flag_furn( "CLIMBABLE", p ) ? g->m.furnname( p ).c_str() : g->m.tername( p ).c_str() ); } } } } if( critter != nullptr && !force ) { return false; } // Make sure that we can move there, unless force is true. if( !force && !can_move_to( p ) ) { return false; } if( !force ) { // This adjustment is to make it so that monster movement speed relative to the player // is consistent even if the monster stumbles, // and the same regardless of the distance measurement mode. const int cost = stagger_adjustment * ( float )( climbs ? calc_climb_cost( pos(), p ) : calc_movecost( pos(), p ) ); if( cost > 0 ) { moves -= cost; } else { return false; } } //Check for moving into/out of water bool was_water = g->m.is_divable( pos() ); bool will_be_water = on_ground && can_submerge() && g->m.is_divable( p ); if( was_water && !will_be_water && g->u.sees( p ) ) { //Use more dramatic messages for swimming monsters add_msg( m_warning, _( "A %1$s %2$s from the %3$s!" ), name().c_str(), has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "leaps" ) : _( "emerges" ), g->m.tername( pos() ).c_str() ); } else if( !was_water && will_be_water && g->u.sees( p ) ) { add_msg( m_warning, _( "A %1$s %2$s into the %3$s!" ), name().c_str(), has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "dives" ) : _( "sinks" ), g->m.tername( p ).c_str() ); } setpos( p ); footsteps( p ); underwater = will_be_water; if( is_hallucination() ) { //Hallucinations don't do any of the stuff after this point return true; } // TODO: Make tanks stop taking damage from rubble, because it's just silly if( type->size != MS_TINY && on_ground ) { if( g->m.has_flag( "SHARP", pos() ) && !one_in( 4 ) ) { apply_damage( nullptr, bp_torso, rng( 1, 10 ) ); } if( g->m.has_flag( "ROUGH", pos() ) && one_in( 6 ) ) { apply_damage( nullptr, bp_torso, rng( 1, 2 ) ); } } if( g->m.has_flag( "UNSTABLE", p ) && on_ground ) { add_effect( effect_bouldering, 1, num_bp, true ); } else if( has_effect( effect_bouldering ) ) { remove_effect( effect_bouldering ); } g->m.creature_on_trap( *this ); if( !will_be_water && ( has_flag( MF_DIGS ) || has_flag( MF_CAN_DIG ) ) ) { underwater = g->m.has_flag( "DIGGABLE", pos() ); } // Diggers turn the dirt into dirtmound if( digging() ) { int factor = 0; switch( type->size ) { case MS_TINY: factor = 100; break; case MS_SMALL: factor = 30; break; case MS_MEDIUM: factor = 6; break; case MS_LARGE: factor = 3; break; case MS_HUGE: factor = 1; break; } if( one_in( factor ) ) { g->m.ter_set( pos(), t_dirtmound ); } } // Acid trail monsters leave... a trail of acid if( has_flag( MF_ACIDTRAIL ) ) { g->m.add_field( pos(), fd_acid, 3, 0 ); } if( has_flag( MF_SLUDGETRAIL ) ) { for( const tripoint &sludge_p : g->m.points_in_radius( pos(), 1 ) ) { const int fstr = 3 - ( abs( sludge_p.x - posx() ) + abs( sludge_p.y - posy() ) ); if( fstr >= 2 ) { g->m.add_field( sludge_p, fd_sludge, fstr, 0 ); } } } return true; }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if (wandf > 0) { wandf--; } //Hallucinations have a chance of disappearing each turn if (is_hallucination() && one_in(25)) { dead = true; return; } // First, use the special attack, if we can! if (sp_timeout > 0) { sp_timeout--; } //If this monster has the ability to heal in combat, do it now. if (has_flag(MF_REGENERATES_50)) { if (hp < type->hp) { if (one_in(2)) { add_msg(m_warning, _("The %s is visibly regenerating!"), name().c_str()); } hp += 50; if(hp > type->hp) { hp = type->hp; } } } if (has_flag(MF_REGENERATES_10)) { if (hp < type->hp) { if (one_in(2)) { add_msg(m_warning, _("The %s seems a little healthier."), name().c_str()); } hp += 10; if(hp > type->hp) { hp = type->hp; } } } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if (has_flag(MF_ABSORBS)) { if(!g->m.i_at(posx(), posy()).empty()) { add_msg(_("The %s flows around the objects on the floor and they are quickly dissolved!"), name().c_str()); std::vector<item> items_absorbed = g->m.i_at(posx(), posy()); for( size_t i = 0; i < items_absorbed.size(); ++i ) { hp += items_absorbed.at(i).volume(); //Yeah this means it can get more HP than normal. } g->m.i_clear(posx(), posy()); } } //Monster will regen morale and agression if it is on max HP //It regens more morale and agression if is currently fleeing. if(has_flag(MF_REGENMORALE) && hp >= type->hp){ if(is_fleeing(g->u)){ morale = type->morale; anger = type->agro; } if(morale <= type->morale) morale += 1; if(anger <= type->agro) anger += 1; if(morale < 0) morale += 5; if(anger < 0) anger += 5; } // If this critter dies in sunlight, check & assess damage. if (g->is_in_sunlight(posx(), posy()) && has_flag(MF_SUNDEATH)) { add_msg(_("The %s burns horribly in the sunlight!"), name().c_str()); hp -= 100; if(hp < 0) { hp = 0 ; } } if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; if(!is_hallucination()) { (ma.*type->sp_attack)(this); } } if (moves < 0) { return; } if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect("stunned")) { stumble(false); moves = 0; return; } if (has_effect("downed")) { moves = 0; return; } if (has_effect("bouldering")) { moves -= 20; if (moves < 0) { return; } } if (friendly != 0) { if (friendly > 0) { friendly--; } friendly_move(); return; } bool moved = false; point next; int mondex = (!plans.empty() ? g->mon_at(plans[0].x, plans[0].y) : -1); monster_attitude current_attitude = attitude(); if (friendly == 0) { current_attitude = attitude(&(g->u)); } // If our plans end in a player, set our attitude to consider that player if (!plans.empty()) { if (plans.back().x == g->u.posx && plans.back().y == g->u.posy) { current_attitude = attitude(&(g->u)); } else { for (int i = 0; i < g->active_npc.size(); i++) { if (plans.back().x == g->active_npc[i]->posx && plans.back().y == g->active_npc[i]->posy) { current_attitude = attitude((g->active_npc[i])); } } } } if (current_attitude == MATT_IGNORE || (current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST)) { moves -= 100; stumble(false); return; } if (!plans.empty() && (mondex == -1 || g->zombie(mondex).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(plans[0].x, plans[0].y) || (plans[0].x == g->u.posx && plans[0].y == g->u.posy) || (g->m.has_flag("BASHABLE", plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){ // CONCRETE PLANS - Most likely based on sight next = plans[0]; moved = true; } else if (has_flag(MF_SMELLS)) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. plans.clear(); point tmp = scent_move(); if (tmp.x != -1) { next = tmp; moved = true; } } if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound plans.clear(); point tmp = wander_next(); if (tmp.x != posx() || tmp.y != posy()) { next = tmp; moved = true; } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if (moved) { // Actual effects of moving to the square we've chosen // Note: The below works because C++ in A() || B() won't call B() if A() is true int& x = next.x; int& y = next.y; // Define alias for x and y bool did_something = attack_at(x, y) || bash_at(x, y) || move_to(x, y); if(!did_something) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; } // If we're close to our target, we get focused and don't stumble if ((has_flag(MF_STUMBLES) && (plans.size() > 3 || plans.empty())) || !moved) { stumble(moved); } }
void monster::knock_back_from( const tripoint &p ) { if( p == pos() ) { return; // No effect } if( is_hallucination() ) { die( nullptr ); return; } tripoint to = pos();; if( p.x < posx() ) { to.x++; } if( p.x > posx() ) { to.x--; } if( p.y < posy() ) { to.y++; } if( p.y > posy() ) { to.y--; } bool u_see = g->u.sees( to ); // First, see if we hit another monster if( monster *const z = g->critter_at<monster>( to ) ) { apply_damage( z, bp_torso, z->type->size ); add_effect( effect_stunned, 1 ); if( type->size > 1 + z->type->size ) { z->knock_back_from( pos() ); // Chain reaction! z->apply_damage( this, bp_torso, type->size ); z->add_effect( effect_stunned, 1 ); } else if( type->size > z->type->size ) { z->apply_damage( this, bp_torso, type->size ); z->add_effect( effect_stunned, 1 ); } z->check_dead_state(); if( u_see ) { add_msg( _( "The %1$s bounces off a %2$s!" ), name().c_str(), z->name().c_str() ); } return; } if( npc *const p = g->critter_at<npc>( to ) ) { apply_damage( p, bp_torso, 3 ); add_effect( effect_stunned, 1 ); p->deal_damage( this, bp_torso, damage_instance( DT_BASH, type->size ) ); if( u_see ) { add_msg( _( "The %1$s bounces off %2$s!" ), name().c_str(), p->name.c_str() ); } p->check_dead_state(); return; } // If we're still in the function at this point, we're actually moving a tile! if( g->m.has_flag_ter( TFLAG_DEEP_WATER, to ) ) { if( g->m.has_flag( "LIQUID", to ) && can_drown() ) { die( nullptr ); if( u_see ) { add_msg( _( "The %s drowns!" ), name().c_str() ); } } else if( has_flag( MF_AQUATIC ) ) { // We swim but we're NOT in water die( nullptr ); if( u_see ) { add_msg( _( "The %s flops around and dies!" ), name().c_str() ); } } } if( g->m.impassable( to ) ) { // It's some kind of wall. apply_damage( nullptr, bp_torso, type->size ); add_effect( effect_stunned, 2 ); if( u_see ) { add_msg( _( "The %1$s bounces off a %2$s." ), name().c_str(), g->m.obstacle_name( to ).c_str() ); } } else { // It's no wall setpos( to ); } check_dead_state(); }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( TFLAG_SEALED, pos() ) && g->m.has_items( pos() ) ) { if( g->u.sees( *this ) ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); } static const auto volume_per_hp = units::from_milliliter( 250 ); for( auto &elem : g->m.i_at( pos() ) ) { hp += elem.volume() / volume_per_hp; // Yeah this means it can get more HP than normal. } g->m.i_clear( pos() ); } const bool pacified = has_effect( effect_pacified ); // First, use the special attack, if we can! // The attack may change `monster::special_attacks` (e.g. by transforming // this into another monster type). Therefor we can not iterate over it // directly and instead iterate over the map from the monster type // (properties of monster types should never change). for( const auto &sp_type : type->special_attacks ) { const std::string &special_name = sp_type.first; const auto local_iter = special_attacks.find( special_name ); if( local_iter == special_attacks.end() ) { continue; } mon_special_attack &local_attack_data = local_iter->second; if( !local_attack_data.enabled ) { continue; } if( local_attack_data.cooldown > 0 ) { local_attack_data.cooldown--; } if( local_attack_data.cooldown == 0 && !pacified && !is_hallucination() ) { if( !sp_type.second->call( *this ) ) { continue; } // `special_attacks` might have changed at this point. Sadly `reset_special` // doesn't check the attack name, so we need to do it here. if( special_attacks.count( special_name ) == 0 ) { continue; } reset_special( special_name ); } } // The monster can sometimes hang in air due to last fall being blocked const bool can_fly = has_flag( MF_FLIES ); if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } // TODO: Move this to attack_at/move_to/etc. functions bool attacking = false; if( !move_effects( attacking ) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } if( has_effect( effect_stunned ) ) { stumble(); moves = 0; return; } if( friendly > 0 ) { --friendly; } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !wander() ) { if( goal == g->u.pos() ) { current_attitude = attitude( &( g->u ) ); } else { for( auto &i : g->active_npc ) { if( goal == i->pos() ) { current_attitude = attitude( i ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && rl_dist( pos(), goal ) <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble(); return; } bool moved = false; tripoint destination; // If true, don't try to greedily avoid locally bad paths bool pathed = false; if( !wander() ) { while( !path.empty() && path.front() == pos() ) { path.erase( path.begin() ); } const auto &pf_settings = get_pathfinding_settings(); if( pf_settings.max_dist >= rl_dist( pos(), goal ) && ( path.empty() || rl_dist( pos(), path.front() ) >= 2 || path.back() != goal ) ) { // We need a new path path = g->m.route( pos(), goal, pf_settings, get_path_avoid() ); } // Try to respect old paths, even if we can't pathfind at the moment if( !path.empty() && path.back() == goal ) { destination = path.front(); moved = true; pathed = true; } else { // Straight line forward, probably because we can't pathfind (well enough) destination = goal; moved = true; } } if( !moved && has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. unset_dest(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { destination = tmp; moved = true; } } if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound unset_dest(); if( wander_pos != pos() ) { destination = wander_pos; moved = true; } } if( !g->m.has_zlevels() ) { // Otherwise weird things happen destination.z = posz(); } tripoint next_step; const bool staggers = has_flag( MF_STUMBLES ); if( moved ) { // Implement both avoiding obstacles and staggering. moved = false; float switch_chance = 0.0; const bool can_bash = bash_skill() > 0; // This is a float and using trig_dist() because that Does the Right Thing(tm) // in both circular and roguelike distance modes. const float distance_to_target = trig_dist( pos(), destination ); for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) { if( candidate.z != posz() ) { bool can_z_move = true; if( !g->m.valid_move( pos(), candidate, false, true ) ) { // Can't phase through floor can_z_move = false; } if( can_z_move && !can_fly && candidate.z > posz() && !g->m.has_floor_or_support( candidate ) ) { // Can't "jump" up a whole z-level can_z_move = false; } // Last chance - we can still do the z-level stair teleport bullshit that isn't removed yet // @todo Remove z-level stair bullshit teleport after aligning all stairs if( !can_z_move && posx() / ( SEEX * 2 ) == candidate.x / ( SEEX * 2 ) && posy() / ( SEEY * 2 ) == candidate.y / ( SEEY * 2 ) ) { const tripoint &upper = candidate.z > posz() ? candidate : pos(); const tripoint &lower = candidate.z > posz() ? pos() : candidate; if( g->m.has_flag( TFLAG_GOES_DOWN, upper ) && g->m.has_flag( TFLAG_GOES_UP, lower ) ) { can_z_move = true; } } if( !can_z_move ) { continue; } } // A flag to allow non-stumbling critters to stumble when the most direct choice is bad. bool bad_choice = false; const Creature *target = g->critter_at( candidate, is_hallucination() ); if( target != nullptr ) { const Creature::Attitude att = attitude_to( *target ); if( att == A_HOSTILE ) { // When attacking an adjacent enemy, we're direct. moved = true; next_step = candidate; break; } else if( att == A_FRIENDLY && ( target->is_player() || target->is_npc() ) ) { continue; // Friendly firing the player or an NPC is illegal for gameplay reasons } else if( !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) { // Bail out if there's a non-hostile monster in the way and we're not pushy. continue; } // Friendly fire and pushing are always bad choices - they take a lot of time bad_choice = true; } // Bail out if we can't move there and we can't bash. if( !pathed && !can_move_to( candidate ) ) { if( !can_bash ) { continue; } const int estimate = g->m.bash_rating( bash_estimate(), candidate ); if( estimate <= 0 ) { continue; } if( estimate < 5 ) { bad_choice = true; } } const float progress = distance_to_target - trig_dist( candidate, destination ); // The x2 makes the first (and most direct) path twice as likely, // since the chance of switching is 1/1, 1/4, 1/6, 1/8 switch_chance += progress * 2; // Randomly pick one of the viable squares to move to weighted by distance. if( moved == false || x_in_y( progress, switch_chance ) ) { moved = true; next_step = candidate; // If we stumble, pick a random square, otherwise take the first one, // which is the most direct path. // Except if the direct path is bad, then check others // Or if the path is given by pathfinder if( !staggers && ( !bad_choice || pathed ) ) { break; } } } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen const bool did_something = ( !pacified && attack_at( next_step ) ) || ( !pacified && bash_at( next_step ) ) || ( !pacified && push_to( next_step, 0, 0 ) ) || move_to( next_step, false, get_stagger_adjust( pos(), destination, next_step ) ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; stumble(); path.clear(); } }
void monster::melee_attack(Creature &target, bool) { mod_moves(-100); if (type->melee_dice == 0) { // We don't attack, so just return return; } add_effect("hit_by_player", 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect("run", 4); } bool u_see_me = g->u_see(this); body_part bp_hit; //int highest_hit = 0; int hitstat = type->melee_skill; int hitroll = dice(hitstat,10); damage_instance damage; if(!is_hallucination()) { if (type->melee_dice > 0) { damage.add_damage(DT_BASH, dice(type->melee_dice,type->melee_sides)); } if (type->melee_cut > 0) { damage.add_damage(DT_CUT, type->melee_cut); } } /* TODO: height-related bodypart selection //If the player is knocked down or the monster can fly, any body part is a valid target if(target.is_on_ground() || has_flag(MF_FLIES)){ highest_hit = 20; } else { switch (type->size) { case MS_TINY: highest_hit = 3; break; case MS_SMALL: highest_hit = 12; break; case MS_MEDIUM: highest_hit = 20; break; case MS_LARGE: highest_hit = 28; break; case MS_HUGE: highest_hit = 35; break; } if (digging()){ highest_hit -= 8; } if (highest_hit <= 1){ highest_hit = 2; } } if (highest_hit > 20){ highest_hit = 20; } int bp_rand = rng(0, highest_hit - 1); if (bp_rand <= 2){ bp_hit = bp_legs; } else if (bp_rand <= 10){ bp_hit = bp_torso; } else if (bp_rand <= 14){ bp_hit = bp_arms; } else if (bp_rand <= 16){ bp_hit = bp_mouth; } else if (bp_rand == 18){ bp_hit = bp_eyes; } else{ bp_hit = bp_head; } */ dealt_damage_instance dealt_dam; int hitspread = target.deal_melee_attack(this, hitroll, false, damage, dealt_dam); bp_hit = dealt_dam.bp_hit; //Hallucinations always produce messages but never actually deal damage if (is_hallucination() || dealt_dam.total_damage() > 0) { if (target.is_player()) { if (u_see_me) { g->add_msg(_("The %1$s hits your %2$s."), name().c_str(), body_part_name(bp_hit, random_side(bp_hit)).c_str()); } else { g->add_msg(_("Something hits your %s."), body_part_name(bp_hit, random_side(bp_hit)).c_str()); } } else { if (u_see_me) { g->add_msg(_("The %1$s hits %2$s's %3$s."), name().c_str(), target.disp_name().c_str(), body_part_name(bp_hit, random_side(bp_hit)).c_str()); } } } else if (hitspread < 0) { // a miss // TODO: characters practice dodge when a hit misses 'em if (target.is_player()) { if (u_see_me) { g->add_msg(_("You dodge %1$s."), disp_name().c_str()); } else { g->add_msg(_("You dodge an attack from an unseen source.")); } } else { if (u_see_me) { g->add_msg(_("The %1$s dodges %2$s's attack."), name().c_str(), target.disp_name().c_str()); } } } if (is_hallucination()) { if(one_in(7)) { die(); } return; } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; if (type->has_anger_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust += 15; } if (type->has_fear_trigger(MTRIG_FRIEND_ATTACKED)){ morale_adjust -= 15; } if (type->has_placate_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { for (int i = 0; i < g->num_zombies(); i++) { g->zombie(i).morale += morale_adjust; g->zombie(i).anger += anger_adjust; } } }
bool monster::push_to( const tripoint &p, const int boost, const size_t depth ) { if( is_hallucination() ) { // Don't let hallucinations push, not even other hallucinations return false; } if( !has_flag( MF_PUSH_MON ) || depth > 2 || has_effect( effect_pushed ) ) { return false; } // TODO: Generalize this to Creature monster *const critter = g->critter_at<monster>( p ); if( critter == nullptr || critter == this || p == pos() ) { return false; } if( !can_move_to( p ) ) { return false; } if( critter->is_hallucination() ) { // Kill the hallu, but return false so that the regular move_to is uses instead critter->die( nullptr ); return false; } // Stability roll of the pushed critter const int defend = critter->stability_roll(); // Stability roll of the pushing zed const int attack = stability_roll() + boost; if( defend > attack ) { return false; } const int movecost_from = 50 * g->m.move_cost( p ); const int movecost_attacker = std::max( movecost_from, 200 - 10 * ( attack - defend ) ); const tripoint dir = p - pos(); // Mark self as pushed to simplify recursive pushing add_effect( effect_pushed, 1 ); for( size_t i = 0; i < 6; i++ ) { const int dx = rng( -1, 1 ); const int dy = rng( -1, 1 ); if( dx == 0 && dy == 0 ) { continue; } // Pushing forward is easier than pushing aside const int direction_penalty = abs( dx - dir.x ) + abs( dy - dir.y ); if( direction_penalty > 2 ) { continue; } tripoint dest( p.x + dx, p.y + dy, p.z ); // Pushing into cars/windows etc. is harder const int movecost_penalty = g->m.move_cost( dest ) - 2; if( movecost_penalty <= -2 ) { // Can't push into unpassable terrain continue; } int roll = attack - ( defend + direction_penalty + movecost_penalty ); if( roll < 0 ) { continue; } Creature *critter_recur = g->critter_at( dest ); if( critter_recur == nullptr || critter_recur->is_hallucination() ) { // Try to push recursively monster *mon_recur = dynamic_cast< monster * >( critter_recur ); if( mon_recur == nullptr ) { continue; } if( critter->push_to( dest, roll, depth + 1 ) ) { // The tile isn't necessarily free, need to check if( g->mon_at( p ) == -1 ) { move_to( p ); } moves -= movecost_attacker; if( movecost_from > 100 ) { critter->add_effect( effect_downed, movecost_from / 100 + 1 ); } else { critter->moves -= movecost_from; } return true; } else { continue; } } critter_recur = g->critter_at( dest ); if( critter_recur != nullptr ) { if( critter_recur->is_hallucination() ) { critter_recur->die( nullptr ); } else { return false; } } critter->setpos( dest ); move_to( p ); moves -= movecost_attacker; if( movecost_from > 100 ) { critter->add_effect( effect_downed, movecost_from / 100 + 1 ); } else { critter->moves -= movecost_from; } return true; } // Try to trample over a much weaker zed (or one with worse rolls) // Don't allow trampling with boost if( boost > 0 || attack < 2 * defend ) { return false; } g->swap_critters( *critter, *this ); critter->add_effect( effect_stunned, rng( 0, 2 ) ); // Only print the message when near player or it can get spammy if( rl_dist( g->u.pos(), pos() ) < 4 && g->u.sees( *critter ) ) { add_msg( m_warning, _( "The %1$s tramples %2$s" ), name().c_str(), critter->disp_name().c_str() ); } moves -= movecost_attacker; if( movecost_from > 100 ) { critter->add_effect( effect_downed, movecost_from / 100 + 1 ); } else { critter->moves -= movecost_from; } return true; }
void monster::die(Creature* nkiller) { if( dead ) { // We are already dead, don't die again, note that monster::dead is // *only* set to true in this function! return; } dead = true; set_killer( nkiller ); if( hp < -( type->size < MS_MEDIUM ? 1.5 : 3 ) * type->hp ) { explode(); // Explode them if it was big overkill } if (!no_extra_death_drops) { drop_items_on_death(); } // TODO: should actually be class Character player *ch = dynamic_cast<player*>( get_killer() ); if( !is_hallucination() && ch != nullptr ) { if( has_flag( MF_GUILT ) || ( ch->has_trait( "PACIFIST" ) && has_flag( MF_HUMAN ) ) ) { // has guilt flag or player is pacifist && monster is humanoid mdeath::guilt(this); } // TODO: add a kill counter to npcs? if( ch->is_player() ) { g->increase_kill_count( type->id ); } if( type->difficulty >= 30 ) { ch->add_memorial_log( pgettext( "memorial_male", "Killed a %s." ), pgettext( "memorial_female", "Killed a %s." ), name().c_str() ); } } // We were tied up at the moment of death, add a short rope to inventory if ( has_effect("tied") ) { item rope_6("rope_6", 0); add_item(rope_6); } if( !is_hallucination() ) { for( const auto &it : inv ) { g->m.add_item_or_charges( posx(), posy(), it ); } } // If we're a queen, make nearby groups of our type start to die out if( !is_hallucination() && has_flag( MF_QUEEN ) ) { // The submap coordinates of this monster, monster groups coordinates are // submap coordinates. const point abssub = overmapbuffer::ms_to_sm_copy( g->m.getabs( posx(), posy() ) ); // Do it for overmap above/below too for( int z = 1; z >= -1; --z ) { for( int x = -MAPSIZE / 2; x <= MAPSIZE / 2; x++ ) { for( int y = -MAPSIZE / 2; y <= MAPSIZE / 2; y++ ) { std::vector<mongroup*> groups = overmap_buffer.groups_at( abssub.x + x, abssub.y + y, g->levz + z ); for( auto &mgp : groups ) { if( MonsterGroupManager::IsMonsterInGroup( mgp->type, type->id ) ) { mgp->dying = true; } } } } } } // If we're a mission monster, update the mission if (!is_hallucination() && mission_id != -1) { mission_type *misstype = g->find_mission_type(mission_id); if (misstype->goal == MGOAL_FIND_MONSTER) { g->fail_mission(mission_id); } if (misstype->goal == MGOAL_KILL_MONSTER) { g->mission_step_complete(mission_id, 1); } } // Also, perform our death function if(is_hallucination()) { //Hallucinations always just disappear mdeath::disappear(this); return; } //Not a hallucination, go process the death effects. for (auto const &deathfunction : type->dies) { deathfunction(this); } // If our species fears seeing one of our own die, process that int anger_adjust = 0, morale_adjust = 0; if (type->has_anger_trigger(MTRIG_FRIEND_DIED)){ anger_adjust += 15; } if (type->has_fear_trigger(MTRIG_FRIEND_DIED)){ morale_adjust -= 15; } if (type->has_placate_trigger(MTRIG_FRIEND_DIED)){ anger_adjust -= 15; } if (anger_adjust != 0 || morale_adjust != 0) { int light = g->light_level(); for (size_t i = 0; i < g->num_zombies(); i++) { monster &critter = g->zombie( i ); if( !critter.type->same_species( *type ) ) { continue; } int t = 0; if( g->m.sees( critter.pos(), pos(), light, t ) ) { critter.morale += morale_adjust; critter.anger += anger_adjust; } } } }
void monster::melee_attack(Creature &target, bool, matec_id) { mod_moves(-100); if (type->melee_dice == 0) { // We don't attack, so just return return; } add_effect("hit_by_player", 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect("run", 4); } bool u_see_me = g->u.sees(*this); body_part bp_hit; //int highest_hit = 0; damage_instance damage; if(!is_hallucination()) { if (type->melee_dice > 0) { damage.add_damage(DT_BASH, dice(type->melee_dice,type->melee_sides)); } if (type->melee_cut > 0) { damage.add_damage(DT_CUT, type->melee_cut); } } dealt_damage_instance dealt_dam; int hitspread = target.deal_melee_attack(this, hit_roll()); if (hitspread >= 0) { target.deal_melee_hit(this, hitspread, false, damage, dealt_dam); } bp_hit = dealt_dam.bp_hit; if (hitspread < 0) { // a miss // TODO: characters practice dodge when a hit misses 'em if (target.is_player()) { if (u_see_me) { add_msg(_("You dodge %s."), disp_name().c_str()); } else { add_msg(_("You dodge an attack from an unseen source.")); } } else { if (u_see_me) { add_msg(_("The %1$s dodges %2$s attack."), name().c_str(), target.disp_name(true).c_str()); } } //Hallucinations always produce messages but never actually deal damage } else if (is_hallucination() || dealt_dam.total_damage() > 0) { if (target.is_player()) { if (u_see_me) { //~ 1$s is attacker name, 2$s is bodypart name in accusative. add_msg(m_bad, _("The %1$s hits your %2$s."), name().c_str(), body_part_name_accusative(bp_hit).c_str()); } else { //~ %s is bodypart name in accusative. add_msg(m_bad, _("Something hits your %s."), body_part_name_accusative(bp_hit).c_str()); } } else { if (u_see_me) { //~ 1$s is attacker name, 2$s is target name, 3$s is bodypart name in accusative. add_msg(_("The %1$s hits %2$s %3$s."), name().c_str(), target.disp_name(true).c_str(), body_part_name_accusative(bp_hit).c_str()); } } } else { if (target.is_player()) { if (u_see_me) { //~ 1$s is attacker name, 2$s is bodypart name in accusative, 3$s is armor name add_msg(_("The %1$s hits your %2$s, but your %3$s protects you."), name().c_str(), body_part_name_accusative(bp_hit).c_str(), target.skin_name().c_str()); } else { //~ 1$s is bodypart name in accusative, 2$s is armor name. add_msg(_("Something hits your %1$s, but your %2$s protects you."), body_part_name_accusative(bp_hit).c_str(), target.skin_name().c_str()); } } else { if (u_see_me) { //~ $1s is monster name, %2$s is that monster target name, //~ $3s is target bodypart name in accusative, $4s is the monster target name, //~ 5$s is target armor name. add_msg(_("The %1$s hits %2$s %3$s but is stopped by %4$s %5$s."), name().c_str(), target.disp_name(true).c_str(), body_part_name_accusative(bp_hit).c_str(), target.disp_name(true).c_str(), target.skin_name().c_str()); } } } target.check_dead_state(); if (is_hallucination()) { if(one_in(7)) { die( nullptr ); } return; } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; if (type->has_anger_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust += 15; } if (type->has_fear_trigger(MTRIG_FRIEND_ATTACKED)){ morale_adjust -= 15; } if (type->has_placate_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { for (size_t i = 0; i < g->num_zombies(); i++) { g->zombie(i).morale += morale_adjust; g->zombie(i).anger += anger_adjust; } } }
void monster::explode() { if( is_hallucination() ) { //Can't gib hallucinations return; } if( type->has_flag( MF_NOGIB ) || type->has_flag( MF_VERMIN ) ) { return; } // Send body parts and blood all over! const itype_id meat = type->get_meat_itype(); const field_id type_blood = bloodType(); const field_id type_gib = gibType(); if( meat != "null" || type_blood != fd_null || type_gib != fd_null ) { // Only create chunks if we know what kind to make. int num_chunks = 0; switch( type->size ) { case MS_TINY: num_chunks = 1; break; case MS_SMALL: num_chunks = 2; break; case MS_MEDIUM: num_chunks = 4; break; case MS_LARGE: num_chunks = 8; break; case MS_HUGE: num_chunks = 16; break; } for( int i = 0; i < num_chunks; i++ ) { int tarx = posx() + rng( -3, 3 ), tary = posy() + rng( -3, 3 ); std::vector<point> traj = line_to( posx(), posy(), tarx, tary, 0 ); for( size_t j = 0; j < traj.size(); j++ ) { tarx = traj[j].x; tary = traj[j].y; if( one_in( 2 ) && type_blood != fd_null ) { g->m.add_field( tarx, tary, type_blood, 1 ); } else if( type_gib != fd_null ) { g->m.add_field( tarx, tary, type_gib, rng( 1, j + 1 ) ); } if( g->m.move_cost( tarx, tary ) == 0 ) { if( !g->m.bash( tarx, tary, 3 ).second ) { // Target is obstacle, not destroyed by bashing, // stop trajectory in front of it, if this is the first // point (e.g. wall adjacent to monster) , make it invalid. if( j > 0 ) { tarx = traj[j - 1].x; tary = traj[j - 1].y; } else { tarx = -1; } break; } } } if( meat != "null" && tarx != -1 ) { g->m.spawn_item( tarx, tary, meat, 1, 0, calendar::turn ); } } } }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( TFLAG_SEALED, pos() ) ) { if( !g->m.i_at( pos3() ).empty() ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); for( auto &elem : g->m.i_at( pos3() ) ) { hp += elem.volume(); // Yeah this means it can get more HP than normal. } g->m.i_clear( pos3() ); } } static const std::string pacified_string = "pacified"; const bool pacified = has_effect( pacified_string ); // First, use the special attack, if we can! for( size_t i = 0; i < sp_timeout.size(); ++i ) { if( sp_timeout[i] > 0 ) { sp_timeout[i]--; } if( sp_timeout[i] == 0 && !pacified && !is_hallucination() ) { type->sp_attack[i]( this, i ); } } // The monster can sometimes hang in air due to last fall being blocked const bool can_fly = has_flag( MF_FLIES ); if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } // TODO: Move this to attack_at/move_to/etc. functions bool attacking = false; if( !move_effects(attacking) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } static const std::string stun_string = "stunned"; if( has_effect( stun_string ) ) { stumble(); moves = 0; return; } if( friendly > 0 ) { --friendly; } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !wander() ) { if( goal == g->u.pos3() ) { current_attitude = attitude( &( g->u ) ); } else { for( auto &i : g->active_npc ) { if( goal == i->pos3() ) { current_attitude = attitude( i ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && rl_dist(pos(), goal) <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble(); return; } bool moved = false; tripoint destination; // CONCRETE PLANS - Most likely based on sight if( !wander() ) { destination = goal; moved = true; } if( !moved && has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. unset_dest(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { destination = tmp; moved = true; } } if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound unset_dest(); tripoint tmp = wander_next(); if( tmp != pos() ) { destination = tmp; moved = true; } } tripoint next_step; if( moved ) { // Implement both avoiding obstacles and staggering. moved = false; float switch_chance = 0.0; const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); // This is a float and using trig_dist() because that Does the Right Thing(tm) // in both circular and roguelike distance modes. const float distance_to_target = trig_dist( pos(), destination ); for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) { const Creature *target = g->critter_at( candidate, is_hallucination() ); // When attacking an adjacent enemy, we're direct. if( target != nullptr && attitude_to( *target ) == A_HOSTILE ) { moved = true; next_step = candidate; break; } // Bail out if we can't move there and we can't bash. if( !can_move_to( candidate ) && !(can_bash && g->m.bash_rating( bash_estimate(), candidate ) >= 0 ) ) { continue; } // Bail out if there's a non-hostile monster in the way and we're not pushy. if( target != nullptr && attitude_to( *target ) != A_HOSTILE && !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) { continue; } float progress = distance_to_target - trig_dist( candidate, destination ); switch_chance += progress; // Randomly pick one of the viable squares to move to weighted by distance. if( x_in_y( progress, switch_chance ) ) { moved = true; next_step = candidate; // If we stumble, pick a random square, otherwise take the first one, // which is the most direct path. if( !has_flag( MF_STUMBLES ) ) { break; } } } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen // move_to() uses the slope to determine some move speed scaling. const float slope = (destination.x > destination.y) ? (float)destination.y / (float)destination.x : (float)destination.x / (float)destination.y; const bool did_something = ( !pacified && attack_at( next_step ) ) || ( !pacified && bash_at( next_step ) ) || ( !pacified && push_to( next_step, 0, 0 ) ) || move_to( next_step, false, slope ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; stumble(); } }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if( wandf > 0 ) { wandf--; } //Hallucinations have a chance of disappearing each turn if( is_hallucination() && one_in( 25 ) ) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( "SEALED", pos() ) ) { if( !g->m.i_at( pos3() ).empty() ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); for( auto &elem : g->m.i_at( pos3() ) ) { hp += elem.volume(); // Yeah this means it can get more HP than normal. } g->m.i_clear( pos3() ); } } // First, use the special attack, if we can! for( size_t i = 0; i < sp_timeout.size(); ++i ) { if( sp_timeout[i] > 0 ) { sp_timeout[i]--; } if( sp_timeout[i] == 0 && !has_effect( "pacified" ) && !is_hallucination() ) { type->sp_attack[i]( this, i ); } } // The monster can sometimes hang in air due to last fall being blocked if( has_flag( MF_FLIES ) && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } bool attacking = false; if( !move_effects(attacking) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } if( has_effect( "stunned" ) ) { stumble( false ); moves = 0; return; } if( friendly != 0 ) { if( friendly > 0 ) { friendly--; } friendly_move(); return; } bool moved = false; tripoint next; // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !plans.empty() ) { if( plans.back() == g->u.pos3() ) { current_attitude = attitude( &( g->u ) ); } else { for( auto &i : g->active_npc ) { if( plans.back() == i->pos3() ) { current_attitude = attitude( i ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble( false ); return; } // Fix possibly invalid plans // Also make sure the monster won't act across z-levels when it shouldn't. // Don't do it in plan(), because the mon can still use ranged special attacks using // the plans that are not valid for travel/melee. const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); const bool can_fly = has_flag( MF_FLIES ); if( !plans.empty() && !g->m.valid_move( pos(), plans[0], can_bash, can_fly ) ) { plans.clear(); } int mondex = !plans.empty() ? g->mon_at( plans[0] ) : -1; auto mon_att = mondex != -1 ? attitude_to( g->zombie( mondex ) ) : A_HOSTILE; if( !plans.empty() && ( mon_att == A_HOSTILE || has_flag( MF_ATTACKMON ) || has_flag( MF_PUSH_MON ) ) && ( can_move_to( plans[0] ) || ( plans[0] == g->u.pos3() ) || ( ( has_flag( MF_BASHES ) || has_flag( MF_BORES ) ) && g->m.bash_rating( bash_estimate(), plans[0] ) >= 0 ) ) ) { // CONCRETE PLANS - Most likely based on sight next = plans[0]; moved = true; } else if( has_flag( MF_SMELLS ) ) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. plans.clear(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { next = tmp; moved = true; } } if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound plans.clear(); tripoint tmp = wander_next(); if( tmp != pos() ) { next = tmp; moved = true; } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if( moved ) { // Actual effects of moving to the square we've chosen // Note: The below works because C++ in A() || B() won't call B() if A() is true bool did_something = attack_at( next ) || bash_at( next ) || push_to( next, 0, 0 ) || move_to( next ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; } // If we're close to our target, we get focused and don't stumble if( ( has_flag( MF_STUMBLES ) && ( plans.size() > 3 || plans.empty() ) ) || !moved ) { stumble( moved ); } }
void monster::knock_back_from( const tripoint &p ) { if( p == pos3() ) { return; // No effect } if( is_hallucination() ) { die( nullptr ); return; } tripoint to = pos3();; if( p.x < posx() ) { to.x++; } if( p.x > posx() ) { to.x--; } if( p.y < posy() ) { to.y++; } if( p.y > posy() ) { to.y--; } bool u_see = g->u.sees( to ); // First, see if we hit another monster int mondex = g->mon_at( to ); if( mondex != -1 && g->zombie( mondex ).is_hallucination() ) { // hallucinations should not affect real monsters. If they interfer, just remove them. g->zombie( mondex ).die( this ); mondex = -1; } if( mondex != -1 ) { monster *z = &( g->zombie( mondex ) ); apply_damage( z, bp_torso, z->type->size ); add_effect( "stunned", 1 ); if( type->size > 1 + z->type->size ) { z->knock_back_from( pos3() ); // Chain reaction! z->apply_damage( this, bp_torso, type->size ); z->add_effect( "stunned", 1 ); } else if( type->size > z->type->size ) { z->apply_damage( this, bp_torso, type->size ); z->add_effect( "stunned", 1 ); } z->check_dead_state(); if( u_see ) { add_msg( _( "The %s bounces off a %s!" ), name().c_str(), z->name().c_str() ); } return; } int npcdex = g->npc_at( to ); if( npcdex != -1 ) { npc *p = g->active_npc[npcdex]; apply_damage( p, bp_torso, 3 ); add_effect( "stunned", 1 ); p->deal_damage( this, bp_torso, damage_instance( DT_BASH, type->size ) ); if( u_see ) { add_msg( _( "The %s bounces off %s!" ), name().c_str(), p->name.c_str() ); } p->check_dead_state(); return; } // If we're still in the function at this point, we're actually moving a tile! if( g->m.ter_at( to ).has_flag( TFLAG_DEEP_WATER ) ) { if( g->m.has_flag( "LIQUID", to ) && can_drown() ) { die( nullptr ); if( u_see ) { add_msg( _( "The %s drowns!" ), name().c_str() ); } } else if( has_flag( MF_AQUATIC ) ) { // We swim but we're NOT in water die( nullptr ); if( u_see ) { add_msg( _( "The %s flops around and dies!" ), name().c_str() ); } } } if( g->m.move_cost( to ) == 0 ) { // It's some kind of wall. apply_damage( nullptr, bp_torso, type->size ); add_effect( "stunned", 2 ); if( u_see ) { add_msg( _( "The %s bounces off a %s." ), name().c_str(), g->m.tername( to ).c_str() ); } } else { // It's no wall setpos( to ); } check_dead_state(); }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if (wandf > 0) { wandf--; } //Hallucinations have a chance of disappearing each turn if (is_hallucination() && one_in(25)) { die( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if (has_flag(MF_ABSORBS)) { if(!g->m.i_at(posx(), posy()).empty()) { add_msg(_("The %s flows around the objects on the floor and they are quickly dissolved!"), name().c_str()); std::vector<item> items_absorbed = g->m.i_at(posx(), posy()); for( auto &elem : items_absorbed ) { hp += elem.volume(); // Yeah this means it can get more HP than normal. } g->m.i_clear(posx(), posy()); } } // First, use the special attack, if we can! for (size_t i = 0; i < sp_timeout.size(); ++i) { if (sp_timeout[i] > 0) { sp_timeout[i]--; } if( sp_timeout[i] == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL)) && !has_effect("pacified") ) { mattack ma; if(!is_hallucination()) { (ma.*type->sp_attack[i])(this, i); } } } if (moves < 0) { return; } if (!move_effects()) { moves = 0; return; } if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect("stunned")) { stumble(false); moves = 0; return; } if (friendly != 0) { if (friendly > 0) { friendly--; } friendly_move(); return; } bool moved = false; point next; int mondex = (!plans.empty() ? g->mon_at(plans[0].x, plans[0].y) : -1); monster_attitude current_attitude = attitude(); if (friendly == 0) { current_attitude = attitude(&(g->u)); } // If our plans end in a player, set our attitude to consider that player if (!plans.empty()) { if (plans.back().x == g->u.posx && plans.back().y == g->u.posy) { current_attitude = attitude(&(g->u)); } else { for (auto &i : g->active_npc) { if (plans.back().x == i->posx && plans.back().y == i->posy) { current_attitude = attitude(i); } } } } if (current_attitude == MATT_IGNORE || (current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST)) { moves -= 100; stumble(false); return; } if( !plans.empty() && (mondex == -1 || g->zombie(mondex).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(plans[0].x, plans[0].y) || (plans[0].x == g->u.posx && plans[0].y == g->u.posy) || (has_flag(MF_BASHES) && g->m.bash_rating(bash_estimate(), plans[0].x, plans[0].y) >= 0) ) ) { // CONCRETE PLANS - Most likely based on sight next = plans[0]; moved = true; } else if (has_flag(MF_SMELLS)) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. plans.clear(); point tmp = scent_move(); if (tmp.x != -1) { next = tmp; moved = true; } } if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound plans.clear(); point tmp = wander_next(); if (tmp.x != posx() || tmp.y != posy()) { next = tmp; moved = true; } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if (moved) { // Actual effects of moving to the square we've chosen // Note: The below works because C++ in A() || B() won't call B() if A() is true int& x = next.x; int& y = next.y; // Define alias for x and y bool did_something = attack_at(x, y) || bash_at(x, y) || move_to(x, y); if(!did_something) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; } // If we're close to our target, we get focused and don't stumble if ((has_flag(MF_STUMBLES) && (plans.size() > 3 || plans.empty())) || !moved) { stumble(moved); } }