Move agent code into separate files

This commit is contained in:
James Melkonian 2022-01-18 03:02:43 +00:00 committed by Samuel Keiffer
parent bb21dc2708
commit ac6f53922f
7 changed files with 2582 additions and 2485 deletions

View File

@ -67,6 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed or reduced poise damage from most abilities
- Made the hotbar link to items by item definition id and component composition instead of specific inventory slots.
- Made loot boxes drop items instead of doing nothing in order to loot forcing
- Refactored agent code file structure
### Removed

View File

@ -4,7 +4,7 @@ use crate::{
Body, UtteranceKind,
},
path::Chaser,
rtsim::RtSimController,
rtsim::{Memory, MemoryItem, RtSimController, RtSimEvent},
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
uid::Uid,
};
@ -20,6 +20,8 @@ use super::dialogue::Subject;
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
pub const TRADE_INTERACTION_TIME: f32 = 300.0;
const AWARENESS_DECREMENT_CONSTANT: f32 = 2.1;
const SECONDS_BEFORE_FORGET_SOUNDS: f64 = 180.0;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Alignment {
@ -351,6 +353,17 @@ pub struct Target {
pub aggro_on: bool,
}
impl Target {
pub fn new(target: EcsEntity, hostile: bool, selected_at: f64, aggro_on: bool) -> Self {
Self {
target,
hostile,
selected_at,
aggro_on,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, EnumIter)]
pub enum TimerAction {
Interact,
@ -522,6 +535,61 @@ impl Agent {
self.psyche.aggro_dist = None;
self
}
pub fn decrement_awareness(&mut self, dt: f32) {
let mut decrement = dt * AWARENESS_DECREMENT_CONSTANT;
let awareness = self.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;
}
self.awareness -= decrement;
}
pub fn forget_old_sounds(&mut self, time: f64) {
if !self.sounds_heard.is_empty() {
// Keep (retain) only newer sounds
self.sounds_heard
.retain(|&sound| time - sound.time <= SECONDS_BEFORE_FORGET_SOUNDS);
}
}
pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) }
pub fn forget_enemy(&mut self, target_name: &str) {
self.rtsim_controller
.events
.push(RtSimEvent::ForgetEnemy(target_name.to_owned()));
}
pub fn add_enemy(&mut self, target_name: &str, time: f64) {
self.rtsim_controller
.events
.push(RtSimEvent::AddMemory(Memory {
item: MemoryItem::CharacterFight {
name: target_name.to_owned(),
},
time_to_forget: time + 300.0,
}));
}
}
impl Component for Agent {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
pub const DAMAGE_MEMORY_DURATION: f64 = 0.25;
pub const FLEE_DURATION: f32 = 3.0;
pub const MAX_FOLLOW_DIST: f32 = 12.0;
pub const MAX_PATH_DIST: f32 = 170.0;
pub const PARTIAL_PATH_DIST: f32 = 50.0;
pub const SEPARATION_DIST: f32 = 10.0;
pub const SEPARATION_BIAS: f32 = 0.8;
pub const MAX_FLEE_DIST: f32 = 20.0;
pub const AVG_FOLLOW_DIST: f32 = 6.0;
pub const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
pub const HEALING_ITEM_THRESHOLD: f32 = 0.5;
pub const IDLE_HEALING_ITEM_THRESHOLD: f32 = 0.999;
pub const DEFAULT_ATTACK_RANGE: f32 = 2.0;
pub const AWARENESS_INVESTIGATE_THRESHOLD: f32 = 1.0;

View File

@ -0,0 +1,137 @@
use crate::rtsim::Entity as RtSimData;
use common::{
comp::{
buff::Buffs, group, ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy,
Health, Inventory, LightEmitter, Ori, PhysicsState, Pos, Scale, SkillSet, Stats, Vel,
},
link::Is,
mounting::Mount,
path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity,
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
};
use specs::{
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
World,
};
use std::sync::Arc;
pub struct AgentData<'a> {
pub entity: &'a EcsEntity,
pub rtsim_entity: Option<&'a RtSimData>,
pub uid: &'a Uid,
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub energy: &'a Energy,
pub body: Option<&'a Body>,
pub inventory: &'a Inventory,
pub skill_set: &'a SkillSet,
#[allow(dead_code)] // may be useful for pathing
pub physics_state: &'a PhysicsState,
pub alignment: Option<&'a Alignment>,
pub traversal_config: TraversalConfig,
pub scale: f32,
pub damage: f32,
pub light_emitter: Option<&'a LightEmitter>,
pub glider_equipped: bool,
pub is_gliding: bool,
pub health: Option<&'a Health>,
pub char_state: &'a CharacterState,
pub active_abilities: &'a ActiveAbilities,
pub cached_spatial_grid: &'a common::CachedSpatialGrid,
}
pub struct TargetData<'a> {
pub pos: &'a Pos,
pub body: Option<&'a Body>,
pub scale: Option<&'a Scale>,
}
impl<'a> TargetData<'a> {
pub fn new(pos: &'a Pos, body: Option<&'a Body>, scale: Option<&'a Scale>) -> Self {
Self { pos, body, scale }
}
}
pub struct AttackData {
pub min_attack_dist: f32,
pub dist_sqrd: f32,
pub angle: f32,
}
impl AttackData {
pub fn in_min_range(&self) -> bool { self.dist_sqrd < self.min_attack_dist.powi(2) }
}
#[derive(Eq, PartialEq)]
pub enum Tactic {
Melee,
Axe,
Hammer,
Sword,
Bow,
Staff,
Sceptre,
StoneGolem,
CircleCharge { radius: u32, circle_time: u32 },
QuadLowRanged,
TailSlap,
QuadLowQuick,
QuadLowBasic,
QuadLowBeam,
QuadMedJump,
QuadMedBasic,
Theropod,
Turret,
FixedTurret,
RotatingTurret,
RadialTurret,
Mindflayer,
BirdLargeBreathe,
BirdLargeFire,
BirdLargeBasic,
Minotaur,
ClayGolem,
TidalWarrior,
Yeti,
Tornado,
Harvester,
}
#[derive(SystemData)]
pub struct ReadData<'a> {
pub entities: Entities<'a>,
pub uid_allocator: Read<'a, UidAllocator>,
pub dt: Read<'a, DeltaTime>,
pub time: Read<'a, Time>,
pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
pub group_manager: Read<'a, group::GroupManager>,
pub energies: ReadStorage<'a, Energy>,
pub positions: ReadStorage<'a, Pos>,
pub velocities: ReadStorage<'a, Vel>,
pub orientations: ReadStorage<'a, Ori>,
pub scales: ReadStorage<'a, Scale>,
pub healths: ReadStorage<'a, Health>,
pub inventories: ReadStorage<'a, Inventory>,
pub stats: ReadStorage<'a, Stats>,
pub skill_set: ReadStorage<'a, SkillSet>,
pub physics_states: ReadStorage<'a, PhysicsState>,
pub char_states: ReadStorage<'a, CharacterState>,
pub uids: ReadStorage<'a, Uid>,
pub groups: ReadStorage<'a, group::Group>,
pub terrain: ReadExpect<'a, TerrainGrid>,
pub alignments: ReadStorage<'a, Alignment>,
pub bodies: ReadStorage<'a, Body>,
pub is_mounts: ReadStorage<'a, Is<Mount>>,
pub time_of_day: Read<'a, TimeOfDay>,
pub light_emitter: ReadStorage<'a, LightEmitter>,
#[cfg(feature = "worldgen")]
pub world: ReadExpect<'a, Arc<world::World>>,
pub rtsim_entities: ReadStorage<'a, RtSimEntity>,
pub buffs: ReadStorage<'a, Buffs>,
pub combos: ReadStorage<'a, Combo>,
pub active_abilities: ReadStorage<'a, ActiveAbilities>,
}

View File

@ -0,0 +1,72 @@
use crate::sys::agent::ReadData;
use common::{
comp::{buff::BuffKind, Alignment, Pos},
consts::GRAVITY,
terrain::{Block, TerrainGrid},
util::Dir,
vol::ReadVol,
};
use specs::{
saveload::{Marker, MarkerAllocator},
Entity as EcsEntity,
};
use vek::*;
pub fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32) -> bool {
terrain
.ray(pos.0 + Vec3::unit_z(), tgt_pos.0 + Vec3::unit_z())
.until(Block::is_opaque)
.cast()
.0
.powi(2)
>= dist_sqrd
}
pub fn is_dead_or_invulnerable(entity: EcsEntity, read_data: &ReadData) -> bool {
is_dead(entity, read_data) || is_invulnerable(entity, read_data)
}
pub fn is_dead(entity: EcsEntity, read_data: &ReadData) -> bool {
let health = read_data.healths.get(entity);
health.map_or(false, |a| a.is_dead)
}
// 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.
pub fn is_invulnerable(entity: EcsEntity, read_data: &ReadData) -> bool {
let buffs = read_data.buffs.get(entity);
buffs.map_or(false, |b| b.kinds.contains_key(&BuffKind::Invulnerability))
}
/// Attempts to get alignment of owner if entity has Owned alignment
pub fn try_owner_alignment<'a>(
alignment: Option<&'a Alignment>,
read_data: &'a ReadData,
) -> Option<&'a Alignment> {
if let Some(Alignment::Owned(owner_uid)) = alignment {
if let Some(owner) = get_entity_by_id(owner_uid.id(), read_data) {
return read_data.alignments.get(owner);
}
}
alignment
}
/// Projectile motion: Returns the direction to aim for the projectile to reach
/// target position. Does not take any forces but gravity into account.
pub fn aim_projectile(speed: f32, pos: Vec3<f32>, tgt: Vec3<f32>) -> Option<Dir> {
let mut to_tgt = tgt - pos;
let dist_sqrd = to_tgt.xy().magnitude_squared();
let u_sqrd = speed.powi(2);
to_tgt.z = (u_sqrd
- (u_sqrd.powi(2) - GRAVITY * (GRAVITY * dist_sqrd + 2.0 * to_tgt.z * u_sqrd))
.sqrt()
.max(0.0))
/ GRAVITY;
Dir::from_unnormalized(to_tgt)
}
pub fn get_entity_by_id(id: u64, read_data: &ReadData) -> Option<EcsEntity> {
read_data.uid_allocator.retrieve_entity_internal(id)
}