mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'holychowders/prevent_undesired_movement_on_agent_interaction' into 'master'
Agent Perception: Improve Awareness System See merge request veloren/veloren!3263
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@ target
|
|||||||
|
|
||||||
**/.nvimrc
|
**/.nvimrc
|
||||||
**/*.vi
|
**/*.vi
|
||||||
|
**/*.vim
|
||||||
**/*.swp
|
**/*.swp
|
||||||
**/*tags
|
**/*tags
|
||||||
|
|
||||||
|
@ -24,9 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed bug that would sometimes cause taking a screenshot to panic because a buffer was mapped a the wrong time.
|
- Fixed bug that would sometimes cause taking a screenshot to panic because a buffer was mapped at the wrong time.
|
||||||
- Players can no longer push waypoints around
|
- Players can no longer push waypoints around
|
||||||
- Sites will now also be placed near the edge of the map
|
- Sites will now also be placed near the edge of the map
|
||||||
|
- Fix a bug causing NPCs to jitter on interaction and randomly run away.
|
||||||
|
|
||||||
## [0.12.0] - 2022-02-19
|
## [0.12.0] - 2022-02-19
|
||||||
|
|
||||||
|
@ -347,13 +347,13 @@ impl Sound {
|
|||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum SoundKind {
|
pub enum SoundKind {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
Utterance(UtteranceKind, Body),
|
||||||
Movement,
|
Movement,
|
||||||
Melee,
|
Melee,
|
||||||
Projectile,
|
Projectile,
|
||||||
Explosion,
|
Explosion,
|
||||||
Beam,
|
Beam,
|
||||||
Shockwave,
|
Shockwave,
|
||||||
Utterance(UtteranceKind, Body),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -100,13 +100,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
if rng.gen_bool(0.005) {
|
if rng.gen_bool(0.005) {
|
||||||
server_events.push(ServerEvent::Sound {
|
server_events.push(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Beam, pos.0, 7.0, time),
|
sound: Sound::new(SoundKind::Beam, pos.0, 13.0, time),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If beam segment is out of time emit destroy event but still continue since it
|
// If beam segment is out of time emit destroy event but still continue since it
|
||||||
// may have traveled and produced effects a bit before reaching its
|
// may have traveled and produced effects a bit before reaching its end point
|
||||||
// end point
|
|
||||||
if end_time < time {
|
if end_time < time {
|
||||||
server_events.push(ServerEvent::Delete(entity));
|
server_events.push(ServerEvent::Delete(entity));
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
server_emitter.emit(ServerEvent::Sound {
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Melee, pos.0, 3.0, read_data.time.0),
|
sound: Sound::new(SoundKind::Melee, pos.0, 2.0, read_data.time.0),
|
||||||
});
|
});
|
||||||
melee_attack.applied = true;
|
melee_attack.applied = true;
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
|
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
|
||||||
server_emitter.emit(ServerEvent::Sound {
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Projectile, pos.0, 2.0, read_data.time.0),
|
sound: Sound::new(SoundKind::Projectile, pos.0, 4.0, read_data.time.0),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
if rng.gen_bool(0.05) {
|
if rng.gen_bool(0.05) {
|
||||||
server_emitter.emit(ServerEvent::Sound {
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Shockwave, pos.0, 16.0, time),
|
sound: Sound::new(SoundKind::Shockwave, pos.0, 40.0, time),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,7 +597,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
.retrieve_entity_internal(uid.into())
|
.retrieve_entity_internal(uid.into())
|
||||||
});
|
});
|
||||||
|
|
||||||
let explosion_volume = 2.5 * explosion.radius;
|
let explosion_volume = 6.25 * explosion.radius;
|
||||||
let mut emitter = server_eventbus.emitter();
|
let mut emitter = server_eventbus.emitter();
|
||||||
emitter.emit(ServerEvent::Sound {
|
emitter.emit(ServerEvent::Sound {
|
||||||
sound: Sound::new(SoundKind::Explosion, pos, explosion_volume, time.0),
|
sound: Sound::new(SoundKind::Explosion, pos, explosion_volume, time.0),
|
||||||
|
@ -7,10 +7,9 @@ use crate::{
|
|||||||
rtsim::RtSim,
|
rtsim::RtSim,
|
||||||
sys::agent::{
|
sys::agent::{
|
||||||
consts::{
|
consts::{
|
||||||
AVG_FOLLOW_DIST, AWARENESS_INVESTIGATE_THRESHOLD, DAMAGE_MEMORY_DURATION,
|
AVG_FOLLOW_DIST, DAMAGE_MEMORY_DURATION, DEFAULT_ATTACK_RANGE, FLEE_DURATION,
|
||||||
DEFAULT_ATTACK_RANGE, FLEE_DURATION, HEALING_ITEM_THRESHOLD,
|
HEALING_ITEM_THRESHOLD, IDLE_HEALING_ITEM_THRESHOLD, MAX_FLEE_DIST, MAX_FOLLOW_DIST,
|
||||||
IDLE_HEALING_ITEM_THRESHOLD, MAX_FLEE_DIST, MAX_FOLLOW_DIST, PARTIAL_PATH_DIST,
|
PARTIAL_PATH_DIST, RETARGETING_THRESHOLD_SECONDS, SEPARATION_BIAS, SEPARATION_DIST,
|
||||||
RETARGETING_THRESHOLD_SECONDS, SEPARATION_BIAS, SEPARATION_DIST,
|
|
||||||
},
|
},
|
||||||
data::{AgentData, AttackData, Path, ReadData, Tactic, TargetData},
|
data::{AgentData, AttackData, Path, ReadData, Tactic, TargetData},
|
||||||
util::{
|
util::{
|
||||||
@ -502,8 +501,8 @@ impl<'a> AgentData<'a> {
|
|||||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||||
rng: &mut impl Rng,
|
rng: &mut impl Rng,
|
||||||
) {
|
) {
|
||||||
agent.decrement_awareness(read_data.dt.0);
|
// TODO: Awareness currently doesn't influence anything.
|
||||||
agent.forget_old_sounds(read_data.time.0);
|
//agent.decrement_awareness(read_data.dt.0);
|
||||||
|
|
||||||
let small_chance = rng.gen_bool(0.1);
|
let small_chance = rng.gen_bool(0.1);
|
||||||
// Set owner if no target
|
// Set owner if no target
|
||||||
@ -524,7 +523,6 @@ impl<'a> AgentData<'a> {
|
|||||||
match sound {
|
match sound {
|
||||||
Some(AgentEvent::ServerSound(sound)) => {
|
Some(AgentEvent::ServerSound(sound)) => {
|
||||||
agent.sounds_heard.push(sound);
|
agent.sounds_heard.push(sound);
|
||||||
agent.awareness += sound.vol;
|
|
||||||
},
|
},
|
||||||
Some(AgentEvent::Hurt) => {
|
Some(AgentEvent::Hurt) => {
|
||||||
// Hurt utterances at random upon receiving damage
|
// Hurt utterances at random upon receiving damage
|
||||||
@ -572,10 +570,8 @@ impl<'a> AgentData<'a> {
|
|||||||
|
|
||||||
if rng.gen::<f32>() < 0.1 {
|
if rng.gen::<f32>() < 0.1 {
|
||||||
self.choose_target(agent, controller, read_data, event_emitter);
|
self.choose_target(agent, controller, read_data, event_emitter);
|
||||||
} else if agent.awareness > AWARENESS_INVESTIGATE_THRESHOLD {
|
|
||||||
self.handle_elevated_awareness(agent, controller, read_data, rng);
|
|
||||||
} else {
|
} else {
|
||||||
self.idle(agent, controller, read_data, rng);
|
self.handle_sounds_heard(agent, controller, read_data, rng);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -637,8 +633,7 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
let aggro_on = *aggro_on;
|
let aggro_on = *aggro_on;
|
||||||
|
|
||||||
let should_flee = self.damage.min(1.0) < agent.psyche.flee_health;
|
if self.below_flee_health(agent) {
|
||||||
if should_flee {
|
|
||||||
let has_opportunity_to_flee = agent.action_state.timer < FLEE_DURATION;
|
let has_opportunity_to_flee = agent.action_state.timer < FLEE_DURATION;
|
||||||
let within_flee_distance = dist_sqrd < MAX_FLEE_DIST.powi(2);
|
let within_flee_distance = dist_sqrd < MAX_FLEE_DIST.powi(2);
|
||||||
|
|
||||||
@ -1949,8 +1944,8 @@ impl<'a> AgentData<'a> {
|
|||||||
angle_xy,
|
angle_xy,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Match on tactic. Each tactic has different controls
|
// Match on tactic. Each tactic has different controls depending on the distance
|
||||||
// depending on the distance from the agent to the target
|
// from the agent to the target.
|
||||||
match tactic {
|
match tactic {
|
||||||
Tactic::SimpleMelee => {
|
Tactic::SimpleMelee => {
|
||||||
self.handle_simple_melee(agent, controller, &attack_data, tgt_data, read_data, rng)
|
self.handle_simple_melee(agent, controller, &attack_data, tgt_data, read_data, rng)
|
||||||
@ -2172,56 +2167,56 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_elevated_awareness(
|
fn handle_sounds_heard(
|
||||||
&self,
|
&self,
|
||||||
agent: &mut Agent,
|
agent: &mut Agent,
|
||||||
controller: &mut Controller,
|
controller: &mut Controller,
|
||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
rng: &mut impl Rng,
|
rng: &mut impl Rng,
|
||||||
) {
|
) {
|
||||||
|
agent.forget_old_sounds(read_data.time.0);
|
||||||
|
|
||||||
if is_invulnerable(*self.entity, read_data) {
|
if is_invulnerable(*self.entity, read_data) {
|
||||||
self.idle(agent, controller, read_data, rng);
|
self.idle(agent, controller, read_data, rng);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sound) = agent.sounds_heard.last() {
|
if let Some(sound) = agent.sounds_heard.last() {
|
||||||
if let Some(agent_stats) = read_data.stats.get(*self.entity) {
|
let sound_pos = Pos(sound.pos);
|
||||||
let sound_pos = Pos(sound.pos);
|
let dist_sqrd = self.pos.0.distance_squared(sound_pos.0);
|
||||||
let dist_sqrd = self.pos.0.distance_squared(sound_pos.0);
|
// NOTE: There is an implicit distance requirement given that sound volume
|
||||||
|
// disipates as it travels, but we will not want to flee if a sound is super
|
||||||
|
// loud but heard from a great distance, regardless of how loud it was.
|
||||||
|
// `is_close` is this limiter.
|
||||||
|
let is_close = dist_sqrd < 35.0_f32.powi(2);
|
||||||
|
|
||||||
// FIXME: We need to be able to change the name of a guard without breaking this
|
let sound_was_loud = sound.vol >= 10.0;
|
||||||
// logic The `Mark` enum from common::agent could be used to
|
let sound_was_threatening = sound_was_loud
|
||||||
// match with `agent::Mark::Guard`
|
|| matches!(sound.kind, SoundKind::Utterance(UtteranceKind::Scream, _));
|
||||||
let is_village_guard = agent_stats.name == *"Guard".to_string();
|
|
||||||
let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
|
|
||||||
|
|
||||||
if is_enemy {
|
let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
|
||||||
let far_enough = dist_sqrd > 10.0_f32.powi(2);
|
// FIXME: We need to be able to change the name of a guard without breaking this
|
||||||
|
// logic. The `Mark` enum from common::agent could be used to match with
|
||||||
|
// `agent::Mark::Guard`
|
||||||
|
let is_village_guard = read_data
|
||||||
|
.stats
|
||||||
|
.get(*self.entity)
|
||||||
|
.map_or(false, |stats| stats.name == *"Guard".to_string());
|
||||||
|
let follows_threatening_sounds = is_enemy || is_village_guard;
|
||||||
|
|
||||||
if far_enough {
|
// TODO: Awareness currently doesn't influence anything.
|
||||||
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
//agent.awareness += 0.5 * sound.vol;
|
||||||
} else {
|
|
||||||
// TODO: Change this to a search action instead of idle
|
if sound_was_threatening && is_close {
|
||||||
self.idle(agent, controller, read_data, rng);
|
if !self.below_flee_health(agent) && follows_threatening_sounds {
|
||||||
}
|
|
||||||
} else if is_village_guard {
|
|
||||||
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
||||||
} else if !is_village_guard {
|
} else if self.below_flee_health(agent) || !follows_threatening_sounds {
|
||||||
let flee_health = agent.psyche.flee_health;
|
self.flee(agent, controller, &read_data.terrain, &sound_pos);
|
||||||
let close_enough = dist_sqrd < 35.0_f32.powi(2);
|
|
||||||
let sound_was_loud = sound.vol >= 10.0;
|
|
||||||
|
|
||||||
if close_enough
|
|
||||||
&& (flee_health <= 0.7 || (flee_health <= 0.5 && sound_was_loud))
|
|
||||||
{
|
|
||||||
self.flee(agent, controller, &read_data.terrain, &sound_pos);
|
|
||||||
} else {
|
|
||||||
self.idle(agent, controller, read_data, rng);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: Change this to a search action instead of idle
|
|
||||||
self.idle(agent, controller, read_data, rng);
|
self.idle(agent, controller, read_data, rng);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.idle(agent, controller, read_data, rng);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2377,7 +2372,7 @@ impl<'a> AgentData<'a> {
|
|||||||
sound: Sound::new(
|
sound: Sound::new(
|
||||||
SoundKind::Utterance(UtteranceKind::Scream, *body),
|
SoundKind::Utterance(UtteranceKind::Scream, *body),
|
||||||
self.pos.0,
|
self.pos.0,
|
||||||
100.0,
|
13.0,
|
||||||
time,
|
time,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@ -2420,4 +2415,8 @@ impl<'a> AgentData<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn below_flee_health(&self, agent: &Agent) -> bool {
|
||||||
|
self.damage.min(1.0) < agent.psyche.flee_health
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,3 @@ pub const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
|
|||||||
pub const HEALING_ITEM_THRESHOLD: f32 = 0.5;
|
pub const HEALING_ITEM_THRESHOLD: f32 = 0.5;
|
||||||
pub const IDLE_HEALING_ITEM_THRESHOLD: f32 = 0.999;
|
pub const IDLE_HEALING_ITEM_THRESHOLD: f32 = 0.999;
|
||||||
pub const DEFAULT_ATTACK_RANGE: f32 = 2.0;
|
pub const DEFAULT_ATTACK_RANGE: f32 = 2.0;
|
||||||
pub const AWARENESS_INVESTIGATE_THRESHOLD: f32 = 1.0;
|
|
||||||
|
Reference in New Issue
Block a user