mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Make NPCs Aware of Sound - See Issue #913
This commit is contained in:
parent
9060e1ea71
commit
d5f3ba77d4
@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added timed bans and ban history.
|
- Added timed bans and ban history.
|
||||||
- Added non-admin moderators with limit privileges and updated the security model to reflect this.
|
- Added non-admin moderators with limit privileges and updated the security model to reflect this.
|
||||||
- Chat tabs
|
- Chat tabs
|
||||||
|
- NPC's now hear certain sounds
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ use super::dialogue::Subject;
|
|||||||
|
|
||||||
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
|
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
|
||||||
pub const TRADE_INTERACTION_TIME: f32 = 300.0;
|
pub const TRADE_INTERACTION_TIME: f32 = 300.0;
|
||||||
|
pub const MAX_LISTEN_DIST: f32 = 100.0;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
@ -245,7 +246,7 @@ pub enum AgentEvent {
|
|||||||
TradeAccepted(Uid),
|
TradeAccepted(Uid),
|
||||||
FinishedTrade(TradeResult),
|
FinishedTrade(TradeResult),
|
||||||
UpdatePendingTrade(
|
UpdatePendingTrade(
|
||||||
// this data structure is large so box it to keep AgentEvent small
|
// This data structure is large so box it to keep AgentEvent small
|
||||||
Box<(
|
Box<(
|
||||||
TradeId,
|
TradeId,
|
||||||
PendingTrade,
|
PendingTrade,
|
||||||
@ -253,7 +254,43 @@ pub enum AgentEvent {
|
|||||||
[Option<ReducedInventory>; 2],
|
[Option<ReducedInventory>; 2],
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
// Add others here
|
ServerSound(Sound),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Sound {
|
||||||
|
pub kind: SoundKind,
|
||||||
|
pub pos: Vec3<f32>,
|
||||||
|
pub vol: f32,
|
||||||
|
pub time: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sound {
|
||||||
|
pub fn new(kind: SoundKind, pos: Vec3<f32>, vol: f32, time: f64) -> Self {
|
||||||
|
Sound {
|
||||||
|
kind,
|
||||||
|
pos,
|
||||||
|
vol,
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_new_vol(mut self, new_vol: f32) -> Self {
|
||||||
|
self.vol = new_vol;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum SoundKind {
|
||||||
|
Unknown,
|
||||||
|
Movement,
|
||||||
|
Melee,
|
||||||
|
Projectile,
|
||||||
|
Explosion,
|
||||||
|
Beam,
|
||||||
|
Shockwave,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -274,6 +311,8 @@ pub struct Agent {
|
|||||||
pub inbox: VecDeque<AgentEvent>,
|
pub inbox: VecDeque<AgentEvent>,
|
||||||
pub action_state: ActionState,
|
pub action_state: ActionState,
|
||||||
pub bearing: Vec2<f32>,
|
pub bearing: Vec2<f32>,
|
||||||
|
pub sounds_heard: Vec<Sound>,
|
||||||
|
pub awareness: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -18,5 +18,8 @@ pub const IRON_DENSITY: f32 = 7870.0;
|
|||||||
pub const HUMAN_DENSITY: f32 = 990.0; // value we use to make humanoids gently float
|
pub const HUMAN_DENSITY: f32 = 990.0; // value we use to make humanoids gently float
|
||||||
// 1 thread might be used for long-running cpu intensive tasks, like chunk
|
// 1 thread might be used for long-running cpu intensive tasks, like chunk
|
||||||
// generation. having at least 2 helps not blocking in the main tick here
|
// generation. having at least 2 helps not blocking in the main tick here
|
||||||
|
|
||||||
pub const MIN_RECOMMENDED_RAYON_THREADS: usize = 2;
|
pub const MIN_RECOMMENDED_RAYON_THREADS: usize = 2;
|
||||||
pub const MIN_RECOMMENDED_TOKIO_THREADS: usize = 2;
|
pub const MIN_RECOMMENDED_TOKIO_THREADS: usize = 2;
|
||||||
|
|
||||||
|
pub const SOUND_TRAVEL_DIST_PER_VOLUME: f32 = 3.0;
|
||||||
|
@ -2,6 +2,7 @@ use crate::{
|
|||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
|
agent::Sound,
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
item::Item,
|
item::Item,
|
||||||
DisconnectReason, Ori, Pos,
|
DisconnectReason, Ori, Pos,
|
||||||
@ -175,6 +176,9 @@ pub enum ServerEvent {
|
|||||||
range: Option<f32>,
|
range: Option<f32>,
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
},
|
},
|
||||||
|
Sound {
|
||||||
|
sound: Sound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventBus<E> {
|
pub struct EventBus<E> {
|
||||||
|
@ -69,7 +69,6 @@ pub trait CharacterBehavior {
|
|||||||
ControlAction::CancelInput(input) => self.cancel_input(data, input),
|
ControlAction::CancelInput(input) => self.cancel_input(data, input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fn init(data: &JoinData) -> CharacterState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read-Only Data sent from Character Behavior System to behavior fn's
|
/// Read-Only Data sent from Character Behavior System to behavior fn's
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
|
agent::{Sound, SoundKind},
|
||||||
Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource,
|
Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource,
|
||||||
Inventory, Ori, Pos, Scale, Stats,
|
Inventory, Ori, Pos, Scale, Stats,
|
||||||
},
|
},
|
||||||
@ -13,6 +14,7 @@ use common::{
|
|||||||
GroupTarget,
|
GroupTarget,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect,
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect,
|
||||||
@ -88,6 +90,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
None => return (server_events, add_hit_entities, outcomes),
|
None => return (server_events, add_hit_entities, outcomes),
|
||||||
};
|
};
|
||||||
let end_time = creation_time + beam_segment.duration.as_secs_f64();
|
let end_time = creation_time + beam_segment.duration.as_secs_f64();
|
||||||
|
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
if rng.gen_bool(0.005) {
|
||||||
|
server_events.push(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(SoundKind::Beam, pos.0, 7.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
|
||||||
@ -151,8 +161,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let height_b = body_b.height() * scale_b;
|
let height_b = body_b.height() * scale_b;
|
||||||
|
|
||||||
// Check if it is a hit
|
// Check if it is a hit
|
||||||
let hit = entity != target
|
let hit = entity != target && !health_b.is_dead
|
||||||
&& !health_b.is_dead
|
|
||||||
// Collision shapes
|
// Collision shapes
|
||||||
&& sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.look_dir(), beam_segment.angle, pos_b.0, rad_b, height_b);
|
&& sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.look_dir(), beam_segment.angle, pos_b.0, rad_b, height_b);
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
|
agent::{Sound, SoundKind},
|
||||||
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale,
|
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale,
|
||||||
Stats,
|
Stats,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
|
resources::Time,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
util::Dir,
|
util::Dir,
|
||||||
GroupTarget,
|
GroupTarget,
|
||||||
@ -18,6 +20,7 @@ use vek::*;
|
|||||||
|
|
||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
pub struct ReadData<'a> {
|
pub struct ReadData<'a> {
|
||||||
|
time: Read<'a, Time>,
|
||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
uids: ReadStorage<'a, Uid>,
|
uids: ReadStorage<'a, Uid>,
|
||||||
positions: ReadStorage<'a, Pos>,
|
positions: ReadStorage<'a, Pos>,
|
||||||
@ -66,6 +69,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
if melee_attack.applied {
|
if melee_attack.applied {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(SoundKind::Melee, pos.0, 3.0, read_data.time.0),
|
||||||
|
});
|
||||||
melee_attack.applied = true;
|
melee_attack.applied = true;
|
||||||
|
|
||||||
// Scales
|
// Scales
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
|
agent::{Sound, SoundKind},
|
||||||
projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory,
|
projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory,
|
||||||
Ori, PhysicsState, Pos, Projectile, Stats, Vel,
|
Ori, PhysicsState, Pos, Projectile, Stats, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
resources::DeltaTime,
|
resources::{DeltaTime, Time},
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
util::Dir,
|
util::Dir,
|
||||||
GroupTarget,
|
GroupTarget,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
||||||
World, Write, WriteStorage,
|
World, Write, WriteStorage,
|
||||||
@ -21,6 +23,7 @@ use vek::*;
|
|||||||
|
|
||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
pub struct ReadData<'a> {
|
pub struct ReadData<'a> {
|
||||||
|
time: Read<'a, Time>,
|
||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
uid_allocator: Read<'a, UidAllocator>,
|
uid_allocator: Read<'a, UidAllocator>,
|
||||||
@ -59,7 +62,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
(read_data, mut orientations, mut projectiles, mut outcomes): Self::SystemData,
|
(read_data, mut orientations, mut projectiles, mut outcomes): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
let mut server_emitter = read_data.server_bus.emitter();
|
let mut server_emitter = read_data.server_bus.emitter();
|
||||||
|
|
||||||
// Attacks
|
// Attacks
|
||||||
'projectile_loop: for (entity, pos, physics, vel, mut projectile) in (
|
'projectile_loop: for (entity, pos, physics, vel, mut projectile) in (
|
||||||
&read_data.entities,
|
&read_data.entities,
|
||||||
@ -70,7 +72,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
|
||||||
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(SoundKind::Projectile, pos.0, 2.0, read_data.time.0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut projectile_vanished: bool = false;
|
let mut projectile_vanished: bool = false;
|
||||||
|
|
||||||
// Hit entity
|
// Hit entity
|
||||||
for other in physics.touch_entities.iter().copied() {
|
for other in physics.touch_entities.iter().copied() {
|
||||||
let same_group = projectile
|
let same_group = projectile
|
||||||
@ -86,16 +96,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
.and_then(|e| read_data.groups.get(e))
|
.and_then(|e| read_data.groups.get(e))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Skip if in the same group
|
||||||
let target_group = if same_group {
|
let target_group = if same_group {
|
||||||
GroupTarget::InGroup
|
GroupTarget::InGroup
|
||||||
} else {
|
} else {
|
||||||
GroupTarget::OutOfGroup
|
GroupTarget::OutOfGroup
|
||||||
};
|
};
|
||||||
|
|
||||||
if projectile.ignore_group
|
if projectile.ignore_group && same_group {
|
||||||
// Skip if in the same group
|
|
||||||
&& same_group
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +181,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
pos: pos.0,
|
pos: pos.0,
|
||||||
explosion: e,
|
explosion: e,
|
||||||
owner: projectile.owner,
|
owner: projectile.owner,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
projectile::Effect::Vanish => {
|
projectile::Effect::Vanish => {
|
||||||
server_emitter.emit(ServerEvent::Destroy {
|
server_emitter.emit(ServerEvent::Destroy {
|
||||||
@ -198,8 +206,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hit something solid
|
if physics.on_surface().is_some() {
|
||||||
if physics.on_wall.is_some() || physics.on_ground || physics.on_ceiling {
|
|
||||||
let projectile = &mut *projectile;
|
let projectile = &mut *projectile;
|
||||||
for effect in projectile.hit_solid.drain(..) {
|
for effect in projectile.hit_solid.drain(..) {
|
||||||
match effect {
|
match effect {
|
||||||
@ -208,7 +215,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
pos: pos.0,
|
pos: pos.0,
|
||||||
explosion: e,
|
explosion: e,
|
||||||
owner: projectile.owner,
|
owner: projectile.owner,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
projectile::Effect::Vanish => {
|
projectile::Effect::Vanish => {
|
||||||
server_emitter.emit(ServerEvent::Destroy {
|
server_emitter.emit(ServerEvent::Destroy {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackSource, AttackerInfo, TargetInfo},
|
combat::{AttackSource, AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
|
agent::{Sound, SoundKind},
|
||||||
Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori,
|
Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori,
|
||||||
PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
|
PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
|
||||||
},
|
},
|
||||||
@ -12,6 +13,7 @@ use common::{
|
|||||||
GroupTarget,
|
GroupTarget,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
||||||
World, Write, WriteStorage,
|
World, Write, WriteStorage,
|
||||||
@ -83,9 +85,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let end_time = creation_time + shockwave.duration.as_secs_f64();
|
let end_time = creation_time + shockwave.duration.as_secs_f64();
|
||||||
|
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
if rng.gen_bool(0.05) {
|
||||||
|
server_emitter.emit(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(SoundKind::Shockwave, pos.0, 16.0, time),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If shockwave is out of time emit destroy event but still continue since it
|
// If shockwave is out of time emit destroy event but still continue since it
|
||||||
// may have traveled and produced effects a bit before reaching it's
|
// may have traveled and produced effects a bit before reaching it's end point
|
||||||
// end point
|
|
||||||
if time > end_time {
|
if time > end_time {
|
||||||
server_emitter.emit(ServerEvent::Destroy {
|
server_emitter.emit(ServerEvent::Destroy {
|
||||||
entity,
|
entity,
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
client::Client,
|
client::Client,
|
||||||
comp::{
|
comp::{
|
||||||
biped_large, quadruped_low, quadruped_medium, quadruped_small, skills::SkillGroupKind,
|
agent::{Sound, SoundKind},
|
||||||
|
biped_large, quadruped_low, quadruped_medium, quadruped_small,
|
||||||
|
skills::SkillGroupKind,
|
||||||
theropod, PhysicsState,
|
theropod, PhysicsState,
|
||||||
},
|
},
|
||||||
rtsim::RtSim,
|
rtsim::RtSim,
|
||||||
@ -476,19 +478,22 @@ pub fn handle_delete(server: &mut Server, entity: EcsEntity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>) {
|
pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>) {
|
||||||
let state = &server.state;
|
let ecs = server.state.ecs();
|
||||||
|
|
||||||
if vel.z <= -30.0 {
|
if vel.z <= -30.0 {
|
||||||
let mass = state
|
let mass = ecs
|
||||||
.ecs()
|
|
||||||
.read_storage::<comp::Mass>()
|
.read_storage::<comp::Mass>()
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let inventories = state.ecs().read_storage::<Inventory>();
|
let impact_energy = mass.0 * vel.z.powi(2) / 2.0;
|
||||||
let falldmg = mass.0 * vel.z.powi(2) / 200.0;
|
let falldmg = impact_energy / 100.0;
|
||||||
let stats = state.ecs().read_storage::<Stats>();
|
|
||||||
|
let inventories = ecs.read_storage::<Inventory>();
|
||||||
|
let stats = ecs.read_storage::<Stats>();
|
||||||
|
|
||||||
// Handle health change
|
// Handle health change
|
||||||
if let Some(mut health) = state.ecs().write_storage::<comp::Health>().get_mut(entity) {
|
if let Some(mut health) = ecs.write_storage::<comp::Health>().get_mut(entity) {
|
||||||
let damage = Damage {
|
let damage = Damage {
|
||||||
source: DamageSource::Falling,
|
source: DamageSource::Falling,
|
||||||
kind: DamageKind::Crushing,
|
kind: DamageKind::Crushing,
|
||||||
@ -503,7 +508,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
|
|||||||
health.change_by(change);
|
health.change_by(change);
|
||||||
}
|
}
|
||||||
// Handle poise change
|
// Handle poise change
|
||||||
if let Some(mut poise) = state.ecs().write_storage::<comp::Poise>().get_mut(entity) {
|
if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) {
|
||||||
let poise_damage = PoiseChange {
|
let poise_damage = PoiseChange {
|
||||||
amount: -(mass.0 * vel.magnitude_squared() / 1500.0) as i32,
|
amount: -(mass.0 * vel.magnitude_squared() / 1500.0) as i32,
|
||||||
source: PoiseSource::Falling,
|
source: PoiseSource::Falling,
|
||||||
@ -557,6 +562,13 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
|||||||
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, owner: Option<Uid>) {
|
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, owner: Option<Uid>) {
|
||||||
// Go through all other entities
|
// Go through all other entities
|
||||||
let ecs = &server.state.ecs();
|
let ecs = &server.state.ecs();
|
||||||
|
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
|
||||||
|
let time = ecs.read_resource::<Time>();
|
||||||
|
|
||||||
|
let explosion_volume = 2.5 * explosion.radius;
|
||||||
|
server_eventbus.emit_now(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(SoundKind::Explosion, pos, explosion_volume, time.0),
|
||||||
|
});
|
||||||
|
|
||||||
// Add an outcome
|
// Add an outcome
|
||||||
// Uses radius as outcome power for now
|
// Uses radius as outcome power for now
|
||||||
@ -751,8 +763,6 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
char_state: char_state_b_maybe,
|
char_state: char_state_b_maybe,
|
||||||
};
|
};
|
||||||
|
|
||||||
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
|
|
||||||
|
|
||||||
attack.apply_attack(
|
attack.apply_attack(
|
||||||
target_group,
|
target_group,
|
||||||
attacker_info,
|
attacker_info,
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
use specs::{world::WorldExt, Builder, Entity as EcsEntity};
|
use specs::{world::WorldExt, Builder, Entity as EcsEntity, Join};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self, agent::AgentEvent, dialogue::Subject, inventory::slot::EquipSlot, item, slot::Slot,
|
self,
|
||||||
tool::ToolKind, Inventory, Pos,
|
agent::{AgentEvent, Sound, MAX_LISTEN_DIST},
|
||||||
|
dialogue::Subject,
|
||||||
|
inventory::slot::EquipSlot,
|
||||||
|
item,
|
||||||
|
slot::Slot,
|
||||||
|
tool::ToolKind,
|
||||||
|
Inventory, Pos,
|
||||||
},
|
},
|
||||||
consts::MAX_MOUNT_RANGE,
|
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
@ -72,7 +78,7 @@ pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_en
|
|||||||
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
|
if let Some(interactor_uid) = state.ecs().uid_from_entity(interactor) {
|
||||||
agent
|
agent
|
||||||
.inbox
|
.inbox
|
||||||
.push_front(AgentEvent::Talk(interactor_uid, Subject::Regular));
|
.push_back(AgentEvent::Talk(interactor_uid, Subject::Regular));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,8 +109,7 @@ pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity)
|
|||||||
state.ecs().uid_from_entity(mountee),
|
state.ecs().uid_from_entity(mountee),
|
||||||
) {
|
) {
|
||||||
// We know the entities must exist to be able to look up their UIDs, so these
|
// We know the entities must exist to be able to look up their UIDs, so these
|
||||||
// are guaranteed to work; hence we can ignore possible errors
|
// are guaranteed to work; hence we can ignore possible errors here.
|
||||||
// here.
|
|
||||||
state.write_component_ignore_entity_dead(
|
state.write_component_ignore_entity_dead(
|
||||||
mountee,
|
mountee,
|
||||||
comp::MountState::MountedBy(mounter_uid),
|
comp::MountState::MountedBy(mounter_uid),
|
||||||
@ -301,3 +306,27 @@ pub fn handle_mine_block(server: &mut Server, pos: Vec3<i32>, tool: Option<ToolK
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_sound(server: &mut Server, sound: &Sound) {
|
||||||
|
let ecs = &server.state.ecs();
|
||||||
|
let positions = &ecs.read_storage::<comp::Pos>();
|
||||||
|
let agents = &mut ecs.write_storage::<comp::Agent>();
|
||||||
|
|
||||||
|
for (agent, agent_pos) in (agents, positions).join() {
|
||||||
|
// TODO: Use pathfinding for more dropoff around obstacles
|
||||||
|
let agent_dist_sqrd = agent_pos.0.distance_squared(sound.pos);
|
||||||
|
let sound_travel_dist_sqrd = (sound.vol * SOUND_TRAVEL_DIST_PER_VOLUME).powi(2);
|
||||||
|
|
||||||
|
let vol_dropoff = agent_dist_sqrd / sound_travel_dist_sqrd * sound.vol;
|
||||||
|
let propagated_sound = sound.with_new_vol(sound.vol - vol_dropoff);
|
||||||
|
|
||||||
|
let can_hear_sound = propagated_sound.vol > 0.00;
|
||||||
|
let should_hear_sound = agent_dist_sqrd < MAX_LISTEN_DIST.powi(2);
|
||||||
|
|
||||||
|
if can_hear_sound && should_hear_sound {
|
||||||
|
agent
|
||||||
|
.inbox
|
||||||
|
.push_back(AgentEvent::ServerSound(propagated_sound));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -132,7 +132,7 @@ pub fn handle_invite(
|
|||||||
} else if let Some(agent) = agents.get_mut(invitee) {
|
} else if let Some(agent) = agents.get_mut(invitee) {
|
||||||
if send_invite() {
|
if send_invite() {
|
||||||
if let Some(inviter) = uids.get(inviter) {
|
if let Some(inviter) = uids.get(inviter) {
|
||||||
agent.inbox.push_front(AgentEvent::TradeInvite(*inviter));
|
agent.inbox.push_back(AgentEvent::TradeInvite(*inviter));
|
||||||
invite_sent = true;
|
invite_sent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,7 +221,7 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
|
|||||||
if let Some(agent) = agents.get_mut(inviter) {
|
if let Some(agent) = agents.get_mut(inviter) {
|
||||||
agent
|
agent
|
||||||
.inbox
|
.inbox
|
||||||
.push_front(AgentEvent::TradeAccepted(invitee_uid));
|
.push_back(AgentEvent::TradeAccepted(invitee_uid));
|
||||||
}
|
}
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let pricing = agents
|
let pricing = agents
|
||||||
|
@ -14,7 +14,7 @@ use group_manip::handle_group;
|
|||||||
use information::handle_site_info;
|
use information::handle_site_info;
|
||||||
use interaction::{
|
use interaction::{
|
||||||
handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction, handle_possess,
|
handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction, handle_possess,
|
||||||
handle_unmount,
|
handle_sound, handle_unmount,
|
||||||
};
|
};
|
||||||
use inventory_manip::handle_inventory;
|
use inventory_manip::handle_inventory;
|
||||||
use invite::{handle_invite, handle_invite_response};
|
use invite::{handle_invite, handle_invite_response};
|
||||||
@ -110,7 +110,6 @@ impl Server {
|
|||||||
},
|
},
|
||||||
ServerEvent::InitiateInvite(interactor, target, kind) => {
|
ServerEvent::InitiateInvite(interactor, target, kind) => {
|
||||||
handle_invite(self, interactor, target, kind)
|
handle_invite(self, interactor, target, kind)
|
||||||
//handle_initiate_trade(self, interactor, target)
|
|
||||||
},
|
},
|
||||||
ServerEvent::InviteResponse(entity, response) => {
|
ServerEvent::InviteResponse(entity, response) => {
|
||||||
handle_invite_response(self, entity, response)
|
handle_invite_response(self, entity, response)
|
||||||
@ -217,10 +216,11 @@ impl Server {
|
|||||||
ServerEvent::CreateSafezone { range, pos } => {
|
ServerEvent::CreateSafezone { range, pos } => {
|
||||||
self.state.create_safezone(range, pos).build();
|
self.state.create_safezone(range, pos).build();
|
||||||
},
|
},
|
||||||
|
ServerEvent::Sound { sound } => handle_sound(self, &sound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate requested chunks.
|
// Generate requested chunks
|
||||||
for (entity, key) in requested_chunks {
|
for (entity, key) in requested_chunks {
|
||||||
self.generate_chunk(entity, key);
|
self.generate_chunk(entity, key);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ fn notify_agent_simple(
|
|||||||
event: AgentEvent,
|
event: AgentEvent,
|
||||||
) {
|
) {
|
||||||
if let Some(agent) = agents.get_mut(entity) {
|
if let Some(agent) = agents.get_mut(entity) {
|
||||||
agent.inbox.push_front(event);
|
agent.inbox.push_back(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ fn notify_agent_prices(
|
|||||||
// Box<(tid, pend, _, inventories)>) = event {
|
// Box<(tid, pend, _, inventories)>) = event {
|
||||||
agent
|
agent
|
||||||
.inbox
|
.inbox
|
||||||
.push_front(AgentEvent::UpdatePendingTrade(Box::new((
|
.push_back(AgentEvent::UpdatePendingTrade(Box::new((
|
||||||
// Prefer using this Agent's price data, but use the counterparty's price
|
// Prefer using this Agent's price data, but use the counterparty's price
|
||||||
// data if we don't have price data
|
// data if we don't have price data
|
||||||
boxval.0,
|
boxval.0,
|
||||||
|
@ -2,7 +2,9 @@ use crate::rtsim::{Entity as RtSimData, RtSim};
|
|||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
agent::{AgentEvent, Target, DEFAULT_INTERACTION_TIME, TRADE_INTERACTION_TIME},
|
agent::{
|
||||||
|
AgentEvent, Target, DEFAULT_INTERACTION_TIME, MAX_LISTEN_DIST, TRADE_INTERACTION_TIME,
|
||||||
|
},
|
||||||
buff::{BuffKind, Buffs},
|
buff::{BuffKind, Buffs},
|
||||||
compass::{Direction, Distance},
|
compass::{Direction, Distance},
|
||||||
dialogue::{MoodContext, MoodState, Subject},
|
dialogue::{MoodContext, MoodState, Subject},
|
||||||
@ -150,7 +152,6 @@ const FLEE_DURATION: f32 = 3.0;
|
|||||||
const MAX_FOLLOW_DIST: f32 = 12.0;
|
const MAX_FOLLOW_DIST: f32 = 12.0;
|
||||||
const MAX_CHASE_DIST: f32 = 250.0;
|
const MAX_CHASE_DIST: f32 = 250.0;
|
||||||
const MAX_FLEE_DIST: f32 = 20.0;
|
const MAX_FLEE_DIST: f32 = 20.0;
|
||||||
const LISTEN_DIST: f32 = 16.0;
|
|
||||||
const SEARCH_DIST: f32 = 48.0;
|
const SEARCH_DIST: f32 = 48.0;
|
||||||
const SIGHT_DIST: f32 = 80.0;
|
const SIGHT_DIST: f32 = 80.0;
|
||||||
const SNEAK_COEFFICIENT: f32 = 0.25;
|
const SNEAK_COEFFICIENT: f32 = 0.25;
|
||||||
@ -158,6 +159,9 @@ const AVG_FOLLOW_DIST: f32 = 6.0;
|
|||||||
const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
|
const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
|
||||||
const HEALING_ITEM_THRESHOLD: f32 = 0.5;
|
const HEALING_ITEM_THRESHOLD: f32 = 0.5;
|
||||||
const DEFAULT_ATTACK_RANGE: f32 = 2.0;
|
const DEFAULT_ATTACK_RANGE: f32 = 2.0;
|
||||||
|
const AWARENESS_INVESTIGATE_THRESHOLD: f32 = 1.0;
|
||||||
|
const AWARENESS_DECREMENT_CONSTANT: f32 = 0.07;
|
||||||
|
const SECONDS_BEFORE_FORGET_SOUNDS: f64 = 180.0;
|
||||||
|
|
||||||
/// This system will allow NPCs to modify their controller
|
/// This system will allow NPCs to modify their controller
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -479,8 +483,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
{
|
{
|
||||||
if let Some(tgt_pos) = read_data.positions.get(attacker) {
|
if let Some(tgt_pos) = read_data.positions.get(attacker) {
|
||||||
// If the target is dead or in a safezone, remove the
|
// If the target is dead or in a safezone, remove the
|
||||||
// target
|
// target and idle.
|
||||||
// and idle.
|
|
||||||
if should_stop_attacking(
|
if should_stop_attacking(
|
||||||
read_data.healths.get(attacker),
|
read_data.healths.get(attacker),
|
||||||
read_data.buffs.get(attacker),
|
read_data.buffs.get(attacker),
|
||||||
@ -585,6 +588,9 @@ impl<'a> AgentData<'a> {
|
|||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||||
) {
|
) {
|
||||||
|
decrement_awareness(agent);
|
||||||
|
forget_old_sounds(agent, read_data);
|
||||||
|
|
||||||
// Set owner if no target
|
// Set owner if no target
|
||||||
if agent.target.is_none() && thread_rng().gen_bool(0.1) {
|
if agent.target.is_none() && thread_rng().gen_bool(0.1) {
|
||||||
if let Some(Alignment::Owned(owner)) = self.alignment {
|
if let Some(Alignment::Owned(owner)) = self.alignment {
|
||||||
@ -599,7 +605,12 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
// Interact if incoming messages
|
// Interact if incoming messages
|
||||||
if !agent.inbox.is_empty() {
|
if !agent.inbox.is_empty() {
|
||||||
agent.action_state.timer = 0.1;
|
if !matches!(agent.inbox.front(), Some(AgentEvent::ServerSound(_))) {
|
||||||
|
agent.action_state.timer = 0.1;
|
||||||
|
} else if let Some(AgentEvent::ServerSound(sound)) = agent.inbox.pop_front() {
|
||||||
|
agent.sounds_heard.push(sound);
|
||||||
|
agent.awareness += sound.vol;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if agent.action_state.timer > 0.0 {
|
if agent.action_state.timer > 0.0 {
|
||||||
if agent.action_state.timer
|
if agent.action_state.timer
|
||||||
@ -618,6 +629,8 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
} else if thread_rng().gen::<f32>() < 0.1 {
|
} else if thread_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);
|
||||||
} else {
|
} else {
|
||||||
self.idle(agent, controller, &read_data);
|
self.idle(agent, controller, &read_data);
|
||||||
}
|
}
|
||||||
@ -654,13 +667,8 @@ impl<'a> AgentData<'a> {
|
|||||||
agent.action_state.timer = 0.01;
|
agent.action_state.timer = 0.01;
|
||||||
} else if agent.action_state.timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST
|
} else if agent.action_state.timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST
|
||||||
{
|
{
|
||||||
self.flee(
|
self.flee(agent, controller, &read_data.terrain, tgt_pos);
|
||||||
agent,
|
agent.action_state.timer += read_data.dt.0;
|
||||||
controller,
|
|
||||||
&read_data.terrain,
|
|
||||||
tgt_pos,
|
|
||||||
&read_data.dt,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
agent.action_state.timer = 0.0;
|
agent.action_state.timer = 0.0;
|
||||||
agent.target = None;
|
agent.target = None;
|
||||||
@ -680,12 +688,10 @@ impl<'a> AgentData<'a> {
|
|||||||
event_emitter
|
event_emitter
|
||||||
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
agent.target = None;
|
agent.target = None;
|
||||||
// Choose a new target every 10 seconds, but only for
|
// Choose a new target every 10 seconds, but only for
|
||||||
// enemies
|
// enemies
|
||||||
// TODO: This should be more
|
// TODO: This should be more principled. Consider factoring
|
||||||
// principled. Consider factoring
|
|
||||||
// health, combat rating, wielded weapon, etc, into the
|
// health, combat rating, wielded weapon, etc, into the
|
||||||
// decision to change target.
|
// decision to change target.
|
||||||
} else if read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS
|
} else if read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS
|
||||||
@ -951,7 +957,8 @@ impl<'a> AgentData<'a> {
|
|||||||
// .push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
// .push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
||||||
// }
|
// }
|
||||||
agent.action_state.timer += read_data.dt.0;
|
agent.action_state.timer += read_data.dt.0;
|
||||||
let msg = agent.inbox.pop_back();
|
|
||||||
|
let msg = agent.inbox.pop_front();
|
||||||
match msg {
|
match msg {
|
||||||
Some(AgentEvent::Talk(by, subject)) => {
|
Some(AgentEvent::Talk(by, subject)) => {
|
||||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
@ -1304,9 +1311,9 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
_ => {
|
||||||
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
if agent.behavior.can(BehaviorCapability::SPEAK) {
|
||||||
// no new events, continue looking towards the last interacting player for some
|
// No new events, continue looking towards the last interacting player for some
|
||||||
// time
|
// time
|
||||||
if let Some(Target { target, .. }) = &agent.target {
|
if let Some(Target { target, .. }) = &agent.target {
|
||||||
self.look_toward(controller, read_data, target);
|
self.look_toward(controller, read_data, target);
|
||||||
@ -1348,7 +1355,6 @@ impl<'a> AgentData<'a> {
|
|||||||
controller: &mut Controller,
|
controller: &mut Controller,
|
||||||
terrain: &TerrainGrid,
|
terrain: &TerrainGrid,
|
||||||
tgt_pos: &Pos,
|
tgt_pos: &Pos,
|
||||||
dt: &DeltaTime,
|
|
||||||
) {
|
) {
|
||||||
if let Some(body) = self.body {
|
if let Some(body) = self.body {
|
||||||
if body.can_strafe() && !self.is_gliding {
|
if body.can_strafe() && !self.is_gliding {
|
||||||
@ -1375,7 +1381,6 @@ impl<'a> AgentData<'a> {
|
|||||||
self.jump_if(controller, bearing.z > 1.5);
|
self.jump_if(controller, bearing.z > 1.5);
|
||||||
controller.inputs.move_z = bearing.z;
|
controller.inputs.move_z = bearing.z;
|
||||||
}
|
}
|
||||||
agent.action_state.timer += dt.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to consume a healing item, and return whether any healing items
|
/// Attempt to consume a healing item, and return whether any healing items
|
||||||
@ -1457,7 +1462,7 @@ impl<'a> AgentData<'a> {
|
|||||||
})
|
})
|
||||||
.filter(|(e, e_pos, e_health, e_stats, e_inventory, e_alignment, char_state)| {
|
.filter(|(e, e_pos, e_health, e_stats, e_inventory, e_alignment, char_state)| {
|
||||||
let mut search_dist = SEARCH_DIST;
|
let mut search_dist = SEARCH_DIST;
|
||||||
let mut listen_dist = LISTEN_DIST;
|
let mut listen_dist = MAX_LISTEN_DIST;
|
||||||
if char_state.map_or(false, |c_s| c_s.is_stealthy()) {
|
if char_state.map_or(false, |c_s| c_s.is_stealthy()) {
|
||||||
// TODO: make sneak more effective based on a stat like e_stats.fitness
|
// TODO: make sneak more effective based on a stat like e_stats.fitness
|
||||||
search_dist *= SNEAK_COEFFICIENT;
|
search_dist *= SNEAK_COEFFICIENT;
|
||||||
@ -3597,6 +3602,49 @@ impl<'a> AgentData<'a> {
|
|||||||
controller.inputs.move_z = bearing.z;
|
controller.inputs.move_z = bearing.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_elevated_awareness(
|
||||||
|
&self,
|
||||||
|
agent: &mut Agent,
|
||||||
|
controller: &mut Controller,
|
||||||
|
read_data: &ReadData,
|
||||||
|
) {
|
||||||
|
// Currently this means that we are in a safezone
|
||||||
|
if invulnerability_is_in_buffs(read_data.buffs.get(*self.entity)) {
|
||||||
|
self.idle(agent, controller, &read_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_enemy = matches!(self.alignment, Some(Alignment::Enemy));
|
||||||
|
|
||||||
|
if let Some(sound) = agent.sounds_heard.last() {
|
||||||
|
let sound_pos = Pos(sound.pos);
|
||||||
|
let dist_sqrd = self.pos.0.distance_squared(sound_pos.0);
|
||||||
|
|
||||||
|
if is_enemy {
|
||||||
|
let far_enough = dist_sqrd > 10.0_f32.powi(2);
|
||||||
|
|
||||||
|
if far_enough {
|
||||||
|
self.follow(agent, controller, &read_data.terrain, &sound_pos);
|
||||||
|
} else {
|
||||||
|
// TODO: Change this to a search action instead of idle
|
||||||
|
self.idle(agent, controller, &read_data);
|
||||||
|
}
|
||||||
|
} else if self.flees {
|
||||||
|
let aggro = agent.psyche.aggro;
|
||||||
|
let close_enough = dist_sqrd < 35.0_f32.powi(2);
|
||||||
|
let loud_sound = sound.vol >= 10.0;
|
||||||
|
|
||||||
|
if close_enough && (aggro <= 0.5 || (aggro <= 0.7 && loud_sound)) {
|
||||||
|
self.flee(agent, controller, &read_data.terrain, &sound_pos);
|
||||||
|
} else {
|
||||||
|
self.idle(agent, controller, &read_data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.idle(agent, controller, &read_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32) -> bool {
|
fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32) -> bool {
|
||||||
@ -3614,6 +3662,8 @@ fn should_stop_attacking(health: Option<&Health>, buffs: Option<&Buffs>) -> bool
|
|||||||
health.map_or(true, |a| a.is_dead) || invulnerability_is_in_buffs(buffs)
|
health.map_or(true, |a| a.is_dead) || invulnerability_is_in_buffs(buffs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: The logic that is used in this function and throughout the code
|
||||||
|
// shouldn't be used to mean that a character is in a safezone.
|
||||||
fn invulnerability_is_in_buffs(buffs: Option<&Buffs>) -> bool {
|
fn invulnerability_is_in_buffs(buffs: Option<&Buffs>) -> bool {
|
||||||
buffs.map_or(false, |b| b.kinds.contains_key(&BuffKind::Invulnerability))
|
buffs.map_or(false, |b| b.kinds.contains_key(&BuffKind::Invulnerability))
|
||||||
}
|
}
|
||||||
@ -3648,3 +3698,40 @@ fn aim_projectile(speed: f32, pos: Vec3<f32>, tgt: Vec3<f32>) -> Option<Dir> {
|
|||||||
|
|
||||||
Dir::from_unnormalized(to_tgt)
|
Dir::from_unnormalized(to_tgt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn forget_old_sounds(agent: &mut Agent, read_data: &ReadData) {
|
||||||
|
if !agent.sounds_heard.is_empty() {
|
||||||
|
// Keep (retain) only newer sounds
|
||||||
|
agent
|
||||||
|
.sounds_heard
|
||||||
|
.retain(|&sound| read_data.time.0 - sound.time <= SECONDS_BEFORE_FORGET_SOUNDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrement_awareness(agent: &mut Agent) {
|
||||||
|
let mut decrement = AWARENESS_DECREMENT_CONSTANT;
|
||||||
|
let awareness = agent.awareness;
|
||||||
|
|
||||||
|
let too_high = awareness >= 100.0;
|
||||||
|
let high = awareness >= 50.0;
|
||||||
|
let medium = awareness >= 30.0;
|
||||||
|
let low = awareness > 15.0;
|
||||||
|
let positive = awareness >= 0.0;
|
||||||
|
let negative = awareness < 0.0;
|
||||||
|
|
||||||
|
if too_high {
|
||||||
|
decrement *= 3.0;
|
||||||
|
} else if high {
|
||||||
|
decrement *= 1.0;
|
||||||
|
} else if medium {
|
||||||
|
decrement *= 2.5;
|
||||||
|
} else if low {
|
||||||
|
decrement *= 0.70;
|
||||||
|
} else if positive {
|
||||||
|
decrement *= 0.5;
|
||||||
|
} else if negative {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.awareness -= decrement;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user