bool CRSFTelemetry::send_attitude() { vehicle_attitude_s vehicle_attitude; if (orb_copy(ORB_ID(vehicle_attitude), _vehicle_attitude_sub, &vehicle_attitude) != 0) { return false; } matrix::Eulerf attitude = matrix::Quatf(vehicle_attitude.q); int16_t pitch = attitude(1) * 1e4f; int16_t roll = attitude(0) * 1e4f; int16_t yaw = attitude(2) * 1e4f; return crsf_send_telemetry_attitude(_uart_fd, pitch, roll, yaw); }
Creature::Attitude monster::attitude_to( const Creature &other ) const { const auto m = dynamic_cast<const monster *>( &other ); const auto p = dynamic_cast<const player *>( &other ); if( m != nullptr ) { if( friendly != 0 && friendly != 0 ) { // Currently friendly means "friendly to the player" (on same side as player), // so if both monsters are friendly (towards the player), they are friendly towards // each other. return A_FRIENDLY; } // For now monsters are neutral (not hostile!) to all other monsters. return A_NEUTRAL; } else if( p != nullptr ) { switch( attitude( const_cast<player *>( p ) ) ) { case MATT_FRIEND: return A_FRIENDLY; case MATT_FPASSIVE: case MATT_FLEE: case MATT_IGNORE: case MATT_FOLLOW: case MATT_ZLAVE: return A_NEUTRAL; case MATT_ATTACK: return A_HOSTILE; case MATT_NULL: case NUM_MONSTER_ATTITUDES: break; } } // Should not happen!, creature should be either player or monster return A_NEUTRAL; }
PyObject *PyLink::getRotation() { boost::python::list retval; const hrp::Matrix33 R = attitude(); Matrix33ToPyList(R, retval); return boost::python::incref(retval.ptr()); }
bool monster::can_move_to(game *g, int x, int y) { if (g->m.move_cost(x, y) == 0 && (!has_flag(MF_DESTROYS) || !g->m.is_destructable(x, y)) && ((!has_flag(MF_AQUATIC) && !has_flag(MF_SWIMS)) || !g->m.has_flag(swimmable, x, y))) return false; if (has_flag(MF_DIGS) && !g->m.has_flag(diggable, x, y)) return false; if (has_flag(MF_AQUATIC) && !g->m.has_flag(swimmable, x, y)) return false; // various animal behaviours if (has_flag(MF_ANIMAL)) { // don't enter sharp terrain unless tiny, or attacking if (g->m.has_flag(sharp, x, y) && !(attitude(&(g->u)) == MATT_ATTACK || type->size == MS_TINY)) return false; // don't enter open pits ever unless tiny or can fly if (!(type->size == MS_TINY || has_flag(MF_FLIES)) && (g->m.ter(x, y) == t_pit || g->m.ter(x, y) == t_pit_spiked)) return false; // don't enter lava ever if (g->m.ter(x, y) == t_lava) return false; // don't enter fire or electricity ever if (g->m.field_at(x, y).findField(fd_fire) || g->m.field_at(x, y).findField(fd_electricity)) return false; } return true; }
/* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; Make sure that non-smashing monsters won't "teleport" through windows Injure monsters if they're gonna be walking through pits or whatevs */ bool monster::will_reach(int x, int y) { monster_attitude att = attitude(&(g->u)); if (att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND) return false; if (has_flag(MF_DIGS)) return false; if (has_flag(MF_IMMOBILE) && (posx() != x || posy() != y)) return false; std::vector<point> path = g->m.route(posx(), posy(), x, y, has_flag(MF_BASHES)); if (path.size() == 0) return false; if (has_flag(MF_SMELLS) && g->scent(posx(), posy()) > 0 && g->scent(x, y) > g->scent(posx(), posy())) return true; if (can_hear() && wandf > 0 && rl_dist(wandx, wandy, x, y) <= 2 && rl_dist(posx(), posy(), wandx, wandy) <= wandf) return true; int t; if (can_see() && g->m.sees(posx(), posy(), x, y, g->light_level(), t)) return true; return false; }
void monster::get_Attitude(nc_color &color, std::string &text) { switch (attitude(&(g->u))) { case MATT_FRIEND: color = h_white; text = _("Friendly "); break; case MATT_FPASSIVE: color = h_white; text = _("Passive "); break; case MATT_FLEE: color = c_green; text = _("Fleeing! "); break; case MATT_IGNORE: color = c_ltgray; text = _("Ignoring "); break; case MATT_FOLLOW: color = c_yellow; text = _("Tracking "); break; case MATT_ATTACK: color = c_red; text = _("Hostile! "); break; default: color = h_red; text = "BUG: Behavior unnamed. (monster.cpp:get_Attitude)"; break; } }
/* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; Make sure that non-smashing monsters won't "teleport" through windows Injure monsters if they're gonna be walking through pits or whatevs */ bool monster::will_reach(game *g, int x, int y) { monster_attitude att = attitude(&(g->u)); if (att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND) return false; if (has_flag(MF_DIGS)) return false; if (has_flag(MF_IMMOBILE) && (posx != x || posy != y)) return false; if (has_flag(MF_SMELLS) && g->scent(posx, posy) > 0 && g->scent(x, y) > g->scent(posx, posy)) return true; if (can_hear() && wandf > 0 && rl_dist(wandx, wandy, x, y) <= 2 && rl_dist(posx, posy, wandx, wandy) <= wandf) return true; int t; if (can_see() && g->m.sees(posx, posy, x, y, g->light_level(), t)) return true; return false; }
/* Runs the kinematics model on the state vector and returns a vector with the derivative of each components (except for the accelerations, which must be calculated directly using a dynamics model). Contents are as follows: - Rate of change in position (3-vector, m/s, NED frame) - Rate of change in linear velocity (3-vector, m/s^2, NED frame) - Rate of change in attitude (quaternion (x, y, z, w), 1/s, body frame) - Rate of change in angular velocity (3-vector, rad/s^2, body frame) */ const StateVectorDerivative State::model(ControlVector c, DynamicsModel *d) { StateVectorDerivative output; AccelerationVector a = d->evaluate(*this, c); /* Calculate change in position. */ output.segment<3>(0) << velocity(); /* Calculate change in velocity. */ Quaternionr attitude_q = Quaternionr(attitude()); output.segment<3>(3) = attitude_q.conjugate() * a.segment<3>(0); /* Calculate change in attitude. */ Eigen::Matrix<real_t, 4, 1> omega_q; omega_q << angular_velocity(), 0; attitude_q = Quaternionr(omega_q).conjugate() * attitude_q; output.segment<4>(6) << attitude_q.vec(), attitude_q.w(); output.segment<4>(6) *= 0.5; /* Calculate change in angular velocity (just angular acceleration). */ output.segment<3>(10) = a.segment<3>(3); return output; }
bool monster::is_fleeing(player &u) { if (has_effect("run")) return true; monster_attitude att = attitude(&u); return (att == MATT_FLEE || (att == MATT_FOLLOW && rl_dist(_posx, _posy, u.posx, u.posy) <= 4)); }
bool monster::can_move_to( const tripoint &p ) const { const bool can_climb = has_flag( MF_CLIMBS ) || has_flag( MF_FLIES ); if( g->m.move_cost( p ) == 0 && !( can_climb && g->m.has_flag( "CLIMBABLE", p ) ) ) { return false; } if( !can_submerge() && g->m.has_flag( TFLAG_DEEP_WATER, p ) ) { return false; } if( has_flag( MF_DIGS ) && !g->m.has_flag( "DIGGABLE", p ) ) { return false; } if( has_flag( MF_AQUATIC ) && !g->m.has_flag( "SWIMMABLE", p ) ) { return false; } if( has_flag( MF_SUNDEATH ) && g->is_in_sunlight( p ) ) { return false; } // various animal behaviours if( has_flag( MF_ANIMAL ) ) { // don't enter sharp terrain unless tiny, or attacking if( g->m.has_flag( "SHARP", p ) && !( attitude( &( g->u ) ) == MATT_ATTACK || type->size == MS_TINY || has_flag( MF_FLIES )) ) { return false; } // Don't enter open pits ever unless tiny, can fly or climb well if( !( type->size == MS_TINY || can_climb ) && ( g->m.ter( p ) == t_pit || g->m.ter( p ) == t_pit_spiked || g->m.ter( p ) == t_pit_glass ) ) { return false; } // don't enter lava ever if( g->m.ter( p ) == t_lava ) { return false; } // don't enter fire or electricity ever const field &local_field = g->m.field_at( p ); if( local_field.findField( fd_fire ) || local_field.findField( fd_electricity ) ) { return false; } if( g->m.has_flag( TFLAG_NO_FLOOR, p ) && !has_flag( MF_FLIES ) ) { return false; } } return true; }
mf_attitude monfaction::attitude( const mfaction_id &other ) const { const auto &found = attitude_map.find( other ); if( found != attitude_map.end() ) { return found->second; } const auto base = other.obj().base_faction; if( other != base ) { return attitude( base ); } // Shouldn't happen debugmsg( "Invalid faction relations (no relation found): %s -> %s", id.c_str(), other.obj().id.c_str() ); return MFA_FRIENDLY; }
bool monster::can_move_to(int x, int y) { if (g->m.move_cost(x, y) == 0 && (!has_flag(MF_DESTROYS) || !g->m.is_destructable(x, y))) { return false; } if (!can_submerge() && g->m.has_flag(TFLAG_DEEP_WATER, x, y)) { return false; } if (has_flag(MF_DIGS) && !g->m.has_flag("DIGGABLE", x, y)) { return false; } if (has_flag(MF_AQUATIC) && !g->m.has_flag("SWIMMABLE", x, y)) { return false; } if (has_flag(MF_SUNDEATH) && g->is_in_sunlight(x, y)) { return false; } // various animal behaviours if (has_flag(MF_ANIMAL)) { // don't enter sharp terrain unless tiny, or attacking if (g->m.has_flag("SHARP", x, y) && !(attitude(&(g->u)) == MATT_ATTACK || type->size == MS_TINY)) return false; // don't enter open pits ever unless tiny or can fly if (!(type->size == MS_TINY || has_flag(MF_FLIES)) && (g->m.ter(x, y) == t_pit || g->m.ter(x, y) == t_pit_spiked)) return false; // don't enter lava ever if (g->m.ter(x, y) == t_lava) return false; // don't enter fire or electricity ever field &local_field = g->m.field_at(x, y); if (local_field.findField(fd_fire) || local_field.findField(fd_electricity)) return false; } return true; }
Creature::Attitude monster::attitude_to( const Creature &other ) const { const auto m = dynamic_cast<const monster *>( &other ); const auto p = dynamic_cast<const player *>( &other ); if( m != nullptr ) { if( m == this ) { return A_FRIENDLY; } auto faction_att = faction->attitude( m->faction ); if( ( friendly != 0 && m->friendly != 0 ) || ( friendly == 0 && m->friendly == 0 && faction_att == MFA_FRIENDLY ) ) { // Friendly (to player) monsters are friendly to each other // Unfriendly monsters go by faction attitude return A_FRIENDLY; } else if( ( friendly == 0 && m->friendly == 0 && faction_att == MFA_NEUTRAL ) || morale < 0 || anger < 10 ) { // Stuff that won't attack is neutral to everything return A_NEUTRAL; } else { return A_HOSTILE; } } else if( p != nullptr ) { switch( attitude( const_cast<player *>( p ) ) ) { case MATT_FRIEND: return A_FRIENDLY; case MATT_FPASSIVE: case MATT_FLEE: case MATT_IGNORE: case MATT_FOLLOW: case MATT_ZLAVE: return A_NEUTRAL; case MATT_ATTACK: return A_HOSTILE; case MATT_NULL: case NUM_MONSTER_ATTITUDES: break; } } // Should not happen!, creature should be either player or monster return A_NEUTRAL; }
/* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; Make sure that non-smashing monsters won't "teleport" through windows Injure monsters if they're gonna be walking through pits or whatevs */ bool monster::will_reach( int x, int y ) { monster_attitude att = attitude( &( g->u ) ); if( att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND && att != MATT_ZLAVE ) { return false; } if( has_flag( MF_DIGS ) || has_flag( MF_AQUATIC ) ) { return false; } if( has_flag( MF_IMMOBILE ) && ( posx() != x || posy() != y ) ) { return false; } std::vector<tripoint> path = g->m.route( pos(), tripoint(x, y, posz()), 0, 100 ); if( path.empty() ) { return false; } if( has_flag( MF_SMELLS ) && g->scent( pos3() ) > 0 && g->scent( { x, y, posz() } ) > g->scent( pos3() ) ) { return true; } if( can_hear() && wandf > 0 && rl_dist( wander_pos.x, wander_pos.y, x, y ) <= 2 && rl_dist( posx(), posy(), wander_pos.x, wander_pos.y ) <= wandf ) { return true; } int t; if( can_see() && g->m.sees( posx(), posy(), x, y, g->light_level(), t ) ) { return true; } return false; }
/* will_reach() is used for determining whether we'll get to stairs (and * potentially other locations of interest). It is generally permissive. * TODO: Pathfinding; Make sure that non-smashing monsters won't "teleport" through windows Injure monsters if they're gonna be walking through pits or whatevs */ bool monster::will_reach( int x, int y ) { monster_attitude att = attitude( &( g->u ) ); if( att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND && att != MATT_ZLAVE ) { return false; } if( has_flag( MF_DIGS ) || has_flag( MF_AQUATIC ) ) { return false; } if( has_flag( MF_IMMOBILE ) && ( posx() != x || posy() != y ) ) { return false; } auto path = g->m.route( pos(), tripoint( x, y, posz() ), get_pathfinding_settings() ); if( path.empty() ) { return false; } if( has_flag( MF_SMELLS ) && g->scent.get( pos() ) > 0 && g->scent.get( { x, y, posz() } ) > g->scent.get( pos() ) ) { return true; } if( can_hear() && wandf > 0 && rl_dist( wander_pos.x, wander_pos.y, x, y ) <= 2 && rl_dist( posx(), posy(), wander_pos.x, wander_pos.y ) <= wandf ) { return true; } if( can_see() && sees( tripoint( x, y, posz() ) ) ) { return true; } return false; }
// 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::plan(const mfactions &factions) { // Bots are more intelligent than most living stuff bool electronic = has_flag( MF_ELECTRONIC ); Creature *target = nullptr; // 8.6f is rating for tank drone 60 tiles away, moose 16 or boomer 33 float dist = !electronic ? 1000 : 8.6f; int bresenham_slope = 0; int selected_slope = 0; bool fleeing = false; bool docile = has_flag( MF_VERMIN ) || ( friendly != 0 && has_effect( "docile" ) ); bool angers_hostile_weak = type->anger.find( MTRIG_HOSTILE_WEAK ) != type->anger.end(); int angers_hostile_near = ( type->anger.find( MTRIG_HOSTILE_CLOSE ) != type->anger.end() ) ? 5 : 0; int fears_hostile_near = ( type->fear.find( MTRIG_HOSTILE_CLOSE ) != type->fear.end() ) ? 5 : 0; bool group_morale = has_flag( MF_GROUP_MORALE ) && morale < type->morale; bool swarms = has_flag( MF_SWARMS ); auto mood = attitude(); // If we can see the player, move toward them or flee. if( friendly == 0 && sees( g->u, bresenham_slope ) ) { dist = rate_target( g->u, bresenham_slope, dist, electronic ); fleeing = fleeing || is_fleeing( g->u ); target = &g->u; selected_slope = bresenham_slope; if( dist <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } else if( friendly != 0 && !docile ) { // Target unfriendly monsters, only if we aren't interacting with the player. for( int i = 0, numz = g->num_zombies(); i < numz; i++ ) { monster &tmp = g->zombie( i ); if( tmp.friendly == 0 ) { float rating = rate_target( tmp, bresenham_slope, dist, electronic ); if( rating < dist ) { target = &tmp; dist = rating; selected_slope = bresenham_slope; } } } } if( !docile ) { for( size_t i = 0; i < g->active_npc.size(); i++ ) { npc *me = g->active_npc[i]; float rating = rate_target( *me, bresenham_slope, dist, electronic ); bool fleeing_from = is_fleeing( *me ); // Switch targets if closer and hostile or scarier than current target if( ( rating < dist && fleeing ) || ( rating < dist && attitude( me ) == MATT_ATTACK ) || ( !fleeing && fleeing_from ) ) { target = me; dist = rating; selected_slope = bresenham_slope; } fleeing = fleeing || fleeing_from; if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } } fleeing = fleeing || ( mood == MATT_FLEE ); if( friendly == 0 && !docile ) { for( const auto &fac : factions ) { auto faction_att = faction->attitude( fac.first ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { continue; } for( int i : fac.second ) { // mon indices monster &mon = g->zombie( i ); float rating = rate_target( mon, bresenham_slope, dist, electronic ); if( rating < dist ) { target = &mon; dist = rating; selected_slope = bresenham_slope; } if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } } } // Friendly monsters here // Avoid for hordes of same-faction stuff or it could get expensive const monfaction *actual_faction = friendly == 0 ? faction : GetMFact( "player" ); auto const &myfaction_iter = factions.find( actual_faction ); if( myfaction_iter == factions.end() ) { DebugLog( D_ERROR, D_GAME ) << disp_name() << " tried to find faction " << ( friendly == 0 ? faction->name : "player" ) << " which wasn't loaded in game::monmove"; swarms = false; group_morale = false; } swarms = swarms && target == nullptr; // Only swarm if we have no target if( group_morale || swarms ) { auto const &myfaction = myfaction_iter->second; for( int i : myfaction ) { monster &mon = g->zombie( i ); float rating = rate_target( mon, bresenham_slope, dist, electronic ); if( group_morale && rating <= 10 ) { morale += 10 - rating; } if( swarms ) { if( rating < 5 ) { // Too crowded here wandx = posx() * rng( 1, 3 ) - mon.posx(); wandy = posy() * rng( 1, 3 ) - mon.posy(); wandf = 2; target = nullptr; // Swarm to the furthest ally you can see } else if( rating < INT_MAX && rating > dist && wandf <= 0 ) { target = &mon; dist = rating; selected_slope = bresenham_slope; } } } } if( target != nullptr ) { if( one_in( 2 ) ) { // Random for the diversity of the trajectory ++selected_slope; } else { --selected_slope; } point dest = target->pos(); auto att_to_target = attitude_to( *target ); if( att_to_target == Attitude::A_HOSTILE && !fleeing ) { set_dest( dest.x, dest.y, selected_slope ); } else if( fleeing ) { set_dest( posx() * 2 - dest.x, posy() * 2 - dest.y, selected_slope ); } if( angers_hostile_weak && att_to_target != Attitude::A_FRIENDLY ) { int hp_per = target->hp_percentage(); if( hp_per <= 70 ) { anger += 10 - int( hp_per / 10 ); } } } else if( friendly > 0 && one_in(3)) { // Grow restless with no targets friendly--; } else if( friendly < 0 && sees( g->u, bresenham_slope ) ) { if( rl_dist( pos(), g->u.pos() ) > 2 ) { set_dest(g->u.posx(), g->u.posy(), bresenham_slope); } else { plans.clear(); } } // If we're not adjacent to the start of our plan path, don't act on it. // This is to catch when we had pre-existing invalid plans and // made it through the function without changing them. if( !plans.empty() && square_dist(pos().x, pos().y, plans.front().x, plans.front().y ) > 1 ) { plans.clear(); } }
void monster::print_info(game *g, WINDOW* w) { // First line of w is the border; the next two are terrain info, and after that // is a blank line. w is 13 characters tall, and we can't use the last one // because it's a border as well; so we have lines 4 through 11. // w is also 48 characters wide - 2 characters for border = 46 characters for us mvwprintz(w, 6, 1, c_white, "%s ", type->name.c_str()); switch (attitude(&(g->u))) { case MATT_FRIEND: wprintz(w, h_white, "Friendly! "); break; case MATT_FLEE: wprintz(w, c_green, "Fleeing! "); break; case MATT_IGNORE: wprintz(w, c_ltgray, "Ignoring "); break; case MATT_FOLLOW: wprintz(w, c_yellow, "Tracking "); break; case MATT_ATTACK: wprintz(w, c_red, "Hostile! "); break; default: wprintz(w, h_red, "BUG: Behavior unnamed "); break; } if (has_effect(ME_DOWNED)) wprintz(w, h_white, "On ground"); else if (has_effect(ME_STUNNED)) wprintz(w, h_white, "Stunned"); else if (has_effect(ME_BEARTRAP)) wprintz(w, h_white, "Trapped"); std::string damage_info; nc_color col; if (hp == type->hp) { damage_info = "It is uninjured"; col = c_green; } else if (hp >= type->hp * .8) { damage_info = "It is lightly injured"; col = c_ltgreen; } else if (hp >= type->hp * .6) { damage_info = "It is moderately injured"; col = c_yellow; } else if (hp >= type->hp * .3) { damage_info = "It is heavily injured"; col = c_yellow; } else if (hp >= type->hp * .1) { damage_info = "It is severly injured"; col = c_ltred; } else { damage_info = "it is nearly dead"; col = c_red; } mvwprintz(w, 7, 1, col, damage_info.c_str()); std::string tmp = type->description; std::string out; size_t pos; int line = 8; do { pos = tmp.find_first_of('\n'); out = tmp.substr(0, pos); mvwprintz(w, line, 1, c_white, out.c_str()); tmp = tmp.substr(pos + 1); line++; } while (pos != std::string::npos && line < 12); }
void monster::plan(const std::vector<int> &friendlies) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc = 0; int stc = 0; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0, numz = g->num_zombies(); i < numz; i++) { monster *tmp = &(g->zombie(i)); if (tmp->friendly == 0) { int d = rl_dist(posx(), posy(), tmp->posx(), tmp->posy()); if (d < dist && g->m.sees(posx(), posy(), tmp->posx(), tmp->posy(), sightrange, tc)) { closest = i; dist = d; stc = tc; } } } if (has_effect("docile")) { closest = -1; } if (closest >= 0) { set_dest(g->zombie(closest).posx(), g->zombie(closest).posy(), stc); } else if (friendly > 0 && one_in(3)) { // Grow restless with no targets friendly--; } else if (friendly < 0 && sees_player( tc ) ) { if (rl_dist(posx(), posy(), g->u.posx, g->u.posy) > 2) { set_dest(g->u.posx, g->u.posy, tc); } else { plans.clear(); } } return; } // If we can see, and we can see a character, move toward them or flee. if (can_see() && sees_player( tc ) ) { dist = rl_dist(posx(), posy(), g->u.posx, g->u.posy); if (is_fleeing(g->u)) { // Wander away. fleeing = true; set_dest(posx() * 2 - g->u.posx, posy() * 2 - g->u.posy, tc); } else { // Chase the player. closest = -2; stc = tc; } } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx(), posy(), me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx(), posy(), me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; set_dest(posx() * 2 - me->posx, posy() * 2 - me->posy, tc); \ } else { closest = i; stc = tc; } dist = medist; } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; if (can_see()) { for (int f = 0, numf = friendlies.size(); f < numf; f++) { const int i = friendlies[f]; monster *mon = &(g->zombie(i)); int mondist = rl_dist(posx(), posy(), mon->posx(), mon->posy()); if (mondist < dist && g->m.sees(posx(), posy(), mon->posx(), mon->posy(), sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx() * 2 - mon->posx(); wandy = posy() * 2 - mon->posy(); wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (closest == -2) { if (one_in(2)) {//random for the diversity of the trajectory ++stc; } else { --stc; } set_dest(g->u.posx, g->u.posy, stc); } else if (closest <= -3) set_dest(g->zombie(-3 - closest).posx(), g->zombie(-3 - closest).posy(), stc); else if (closest >= 0) set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } }
void monster::plan(game *g) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc, stc; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0; i < g->z.size(); i++) { monster *tmp = &(g->z[i]); if (tmp->friendly == 0 && rl_dist(posx, posy, tmp->posx, tmp->posy) < dist && g->m.sees(posx, posy, tmp->posx, tmp->posy, sightrange, tc)) { closest = i; dist = rl_dist(posx, posy, tmp->posx, tmp->posy); stc = tc; } } if (has_effect(ME_DOCILE)) closest = -1; if (closest >= 0) set_dest(g->z[closest].posx, g->z[closest].posy, stc); else if (friendly > 0 && one_in(3)) // Grow restless with no targets friendly--; else if (friendly < 0 && g->sees_u(posx, posy, tc)) { if (rl_dist(posx, posy, g->u.posx, g->u.posy) > 2) set_dest(g->u.posx, g->u.posy, tc); else plans.clear(); } return; } if (is_fleeing(g->u) && can_see() && g->sees_u(posx, posy, tc)) { fleeing = true; wandx = posx * 2 - g->u.posx; wandy = posy * 2 - g->u.posy; wandf = 40; dist = rl_dist(posx, posy, g->u.posx, g->u.posy); } // If we can see, and we can see a character, start moving towards them if (!is_fleeing(g->u) && can_see() && g->sees_u(posx, posy, tc)) { dist = rl_dist(posx, posy, g->u.posx, g->u.posy); closest = -2; stc = tc; } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx, posy, me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx, posy, me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; wandx = posx * 2 - me->posx; wandy = posy * 2 - me->posy; wandf = 40; dist = medist; } else if (can_see() && g->m.sees(posx, posy, me->posx, me->posy, sightrange, tc)) { dist = rl_dist(posx, posy, me->posx, me->posy); closest = i; stc = tc; } } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; for (int i = 0; i < g->z.size(); i++) { monster *mon = &(g->z[i]); int mondist = rl_dist(posx, posy, mon->posx, mon->posy); if (mon->friendly != 0 && mondist < dist && can_see() && g->m.sees(posx, posy, mon->posx, mon->posy, sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx * 2 - mon->posx; wandy = posy * 2 - mon->posy; wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (!fleeing) { if (closest == -2) set_dest(g->u.posx, g->u.posy, stc); else if (closest <= -3) set_dest(g->z[-3 - closest].posx, g->z[-3 - closest].posy, stc); else if (closest >= 0) set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } }
// 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(); 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)) { g->add_msg(_("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)) { g->add_msg(_("The %s seems a little healthier."), name().c_str()); } hp += 10; if(hp > type->hp) { hp = type->hp; } } } // If this critter dies in sunlight, check & assess damage. if (g->is_in_sunlight(posx(), posy()) && has_flag(MF_SUNDEATH)) { g->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.size() > 0 ? 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.size() > 0) { 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.size() > 0 && (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.size() == 0)) || !moved) { stumble(moved); } }
void camera::update_view_matrix() const { view = attitude() * glm::translate(glm::mat4(), -position); view_dirty = false; }
bool monster::can_move_to( const tripoint &p ) const { const bool can_climb = has_flag( MF_CLIMBS ) || has_flag( MF_FLIES ); if( g->m.impassable( p ) && !( can_climb && g->m.has_flag( "CLIMBABLE", p ) ) ) { return false; } if( !can_submerge() && g->m.has_flag( TFLAG_DEEP_WATER, p ) ) { return false; } if( has_flag( MF_DIGS ) && !g->m.has_flag( "DIGGABLE", p ) ) { return false; } if( has_flag( MF_AQUATIC ) && !g->m.has_flag( "SWIMMABLE", p ) ) { return false; } if( has_flag( MF_SUNDEATH ) && g->is_in_sunlight( p ) ) { return false; } // Various avoiding behaviors if( has_flag( MF_AVOID_DANGER_1 ) || has_flag( MF_AVOID_DANGER_2 ) ) { const ter_id target = g->m.ter( p ); // Don't enter lava ever if( target == t_lava ) { return false; } // Don't ever throw ourselves off cliffs if( !g->m.has_floor( p ) && !has_flag( MF_FLIES ) ) { return false; } // Don't enter open pits ever unless tiny, can fly or climb well if( !( type->size == MS_TINY || can_climb ) && ( target == t_pit || target == t_pit_spiked || target == t_pit_glass ) ) { return false; } // The following behaviors are overridden when attacking if( attitude( &( g->u ) ) != MATT_ATTACK ) { if( g->m.has_flag( "SHARP", p ) && !( type->size == MS_TINY || has_flag( MF_FLIES ) ) ) { return false; } } const field &target_field = g->m.field_at( p ); // Differently handled behaviors if( has_flag( MF_AVOID_DANGER_2 ) ) { const trap &target_trap = g->m.tr_at( p ); // Don't enter any dangerous fields if( is_dangerous_fields( target_field ) ) { return false; } // Don't step on any traps (if we can see) if( has_flag( MF_SEES ) && !target_trap.is_benign() && g->m.has_floor( p ) ) { return false; } } else if( has_flag( MF_AVOID_DANGER_1 ) ) { // Don't enter fire or electricity ever (other dangerous fields are fine though) if( target_field.findField( fd_fire ) || target_field.findField( fd_electricity ) ) { return false; } } } return true; }
glm::vec3 camera::right() const { glm::vec4 right = glm::inverse(attitude()) * glm::vec4(1, 0, 0, 1); return glm::vec3(right); }
// 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 ) ) { 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()); for( auto &elem : g->m.i_at(posx(), posy()) ) { 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 && !has_effect("pacified") && !is_hallucination() ) { 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; // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); 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; } int mondex = !plans.empty() ? g->mon_at( plans[0].x, plans[0].y ) : -1; auto mon_att = mondex != -1 ? attitude_to( g->zombie( mondex ) ) : A_HOSTILE; if( !plans.empty() && ( mon_att == A_HOSTILE || 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 ) || has_flag( MF_BORES ) ) && 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); } }
int monster::print_info(game *g, WINDOW* w, int vStart, int vLines, int column) { // First line of w is the border; the next two are terrain info, and after that // is a blank line. w is 13 characters tall, and we can't use the last one // because it's a border as well; so we have lines 4 through 11. // w is also 48 characters wide - 2 characters for border = 46 characters for us // vStart added because 'help' text in targeting win makes helpful info hard to find // at a glance. const int vEnd = vStart + vLines; mvwprintz(w, vStart++, column, c_white, "%s ", type->name.c_str()); switch (attitude(&(g->u))) { case MATT_FRIEND: wprintz(w, h_white, _("Friendly! ")); break; case MATT_FLEE: wprintz(w, c_green, _("Fleeing! ")); break; case MATT_IGNORE: wprintz(w, c_ltgray, _("Ignoring ")); break; case MATT_FOLLOW: wprintz(w, c_yellow, _("Tracking ")); break; case MATT_ATTACK: wprintz(w, c_red, _("Hostile! ")); break; default: wprintz(w, h_red, "BUG: Behavior unnamed. (monster.cpp:print_info)"); break; } if (has_effect(ME_DOWNED)) wprintz(w, h_white, _("On ground")); else if (has_effect(ME_STUNNED)) wprintz(w, h_white, _("Stunned")); else if (has_effect(ME_BEARTRAP)) wprintz(w, h_white, _("Trapped")); std::string damage_info; nc_color col; if (hp >= type->hp) { damage_info = _("It is uninjured"); col = c_green; } else if (hp >= type->hp * .8) { damage_info = _("It is lightly injured"); col = c_ltgreen; } else if (hp >= type->hp * .6) { damage_info = _("It is moderately injured"); col = c_yellow; } else if (hp >= type->hp * .3) { damage_info = _("It is heavily injured"); col = c_yellow; } else if (hp >= type->hp * .1) { damage_info = _("It is severly injured"); col = c_ltred; } else { damage_info = _("it is nearly dead"); col = c_red; } mvwprintz(w, vStart++, column, col, damage_info.c_str()); std::vector<std::string> lines = foldstring(type->description, getmaxx(w) - 1 - column); int numlines = lines.size(); for (int i = 0; i < numlines && vStart <= vEnd; i++) mvwprintz(w, vStart++, column, c_white, lines[i].c_str()); return vStart; }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move(game *g) { // 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(g); return; } // First, use the special attack, if we can! if (sp_timeout > 0) { sp_timeout--; } if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; if(!is_hallucination()) { (ma.*type->sp_attack)(g, this); } } if (moves < 0) return; if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect(ME_STUNNED)) { stumble(g, false); moves = 0; return; } if (has_effect(ME_DOWNED)) { moves = 0; return; } if (has_effect(ME_BOULDERING)){ moves -= 20; if (moves < 0) { return; } } if (friendly != 0) { if (friendly > 0) friendly--; friendly_move(g); return; } bool moved = false; point next; int mondex = (plans.size() > 0 ? 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.size() > 0) { 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(g, false); return; } if (plans.size() > 0 && !is_fleeing(g->u) && (mondex == -1 || g->zombie(mondex).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(g, 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(g); 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(g); 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(g, 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.size() == 0)) || !moved) stumble(g, moved); }
glm::vec3 camera::forward() const { glm::vec4 forward = glm::inverse(attitude()) * glm::vec4(0, 0, -1, 1); return glm::vec3(forward); }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move(game *g) { // 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--; // First, use the special attack, if we can! if (sp_timeout > 0) sp_timeout--; if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; (ma.*type->sp_attack)(g, this); } if (moves < 0) return; if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect(ME_STUNNED)) { stumble(g, false); moves = 0; return; } if (has_effect(ME_DOWNED)) { moves = 0; return; } if (has_effect(ME_BOULDERING)){ moves -= 20; if (moves < 0) moves = 0; return; } if (friendly != 0) { if (friendly > 0) friendly--; friendly_move(g); return; } bool moved = false; point next; int mondex = (plans.size() > 0 ? 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.size() > 0) { 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(g, false); return; } if (plans.size() > 0 && !is_fleeing(g->u) && (mondex == -1 || g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(g, 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. point tmp = scent_move(g); if (tmp.x != -1) { next = tmp; moved = true; } } if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound point tmp = sound_move(g); 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 mondex = g->mon_at(next.x, next.y); int npcdex = g->npc_at(next.x, next.y); if (next.x == g->u.posx && next.y == g->u.posy && type->melee_dice > 0) hit_player(g, g->u); else if (mondex != -1 && g->z[mondex].type->species == species_hallu) { g->kill_mon(mondex); moves -= 100; } else if (mondex != -1 && type->melee_dice > 0 && this != &(g->z[mondex]) && (g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON))) hit_monster(g, mondex); else if (npcdex != -1 && type->melee_dice > 0) hit_player(g, *g->active_npc[npcdex]); else if ((!can_move_to(g, next.x, next.y) || one_in(3)) && g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(next.x, next.y, bashskill, bashsound); g->sound(next.x, next.y, 18, bashsound); moves -= 100; } else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) { g->m.destroy(g, next.x, next.y, true); moves -= 250; } else if (can_move_to(g, next.x, next.y) && g->is_empty(next.x, next.y)) move_to(g, next.x, next.y); else moves -= 100; } 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.size() == 0)) || !moved) stumble(g, moved); }
glm::vec3 camera::up() const { glm::vec4 up = glm::inverse(attitude()) * glm::vec4(0, 1, 0, 1); return glm::vec3(up); }