Added CharacterActivity, made NPCs look at the player when speaking to them

This commit is contained in:
Joshua Barretto 2023-04-04 15:55:57 +01:00
parent 85c572f6e2
commit 3e0f5295c0
27 changed files with 491 additions and 257 deletions

View File

@ -40,6 +40,7 @@ macro_rules! synced_components {
sticky: Sticky, sticky: Sticky,
immovable: Immovable, immovable: Immovable,
character_state: CharacterState, character_state: CharacterState,
character_activity: CharacterActivity,
shockwave: Shockwave, shockwave: Shockwave,
beam_segment: BeamSegment, beam_segment: BeamSegment,
alignment: Alignment, alignment: Alignment,
@ -201,6 +202,10 @@ impl NetSync for CharacterState {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for CharacterActivity {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
impl NetSync for Shockwave { impl NetSync for Shockwave {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }

View File

@ -625,6 +625,11 @@ impl Awareness {
self.reached = false; self.reached = false;
} }
} }
pub fn set_maximally_aware(&mut self) {
self.reached = true;
self.level = Self::ALERT;
}
} }
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)] #[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]

View File

@ -983,7 +983,7 @@ impl Body {
} }
/// Returns the eye height for this creature. /// Returns the eye height for this creature.
pub fn eye_height(&self) -> f32 { self.height() * 0.9 } pub fn eye_height(&self, scale: f32) -> f32 { self.height() * 0.9 * scale }
pub fn default_light_offset(&self) -> Vec3<f32> { pub fn default_light_offset(&self) -> Vec3<f32> {
// TODO: Make this a manifest // TODO: Make this a manifest

View File

@ -12,6 +12,7 @@ use crate::{
utils::{AbilityInfo, StageSection}, utils::{AbilityInfo, StageSection},
*, *,
}, },
util::Dir,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage}; use specs::{Component, DerefFlaggedStorage};
@ -30,6 +31,7 @@ pub struct StateUpdate {
pub should_strafe: bool, pub should_strafe: bool,
pub queued_inputs: BTreeMap<InputKind, InputAttr>, pub queued_inputs: BTreeMap<InputKind, InputAttr>,
pub removed_inputs: Vec<InputKind>, pub removed_inputs: Vec<InputKind>,
pub character_activity: CharacterActivity,
} }
pub struct OutputEvents<'a> { pub struct OutputEvents<'a> {
@ -60,6 +62,7 @@ impl From<&JoinData<'_>> for StateUpdate {
character: data.character.clone(), character: data.character.clone(),
queued_inputs: BTreeMap::new(), queued_inputs: BTreeMap::new(),
removed_inputs: Vec::new(), removed_inputs: Vec::new(),
character_activity: *data.character_activity,
} }
} }
} }
@ -979,3 +982,20 @@ impl Default for CharacterState {
impl Component for CharacterState { impl Component for CharacterState {
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>; type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
} }
/// Contains information about the visual activity of a character.
///
/// For now this only includes the direction they're looking in, but later it
/// might include markers indicating that they're available for
/// trade/interaction, more details about their stance or appearance, facial
/// expression, etc.
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct CharacterActivity {
/// `None` means that the look direction should be derived from the
/// orientation
pub look_dir: Option<Dir>,
}
impl Component for CharacterActivity {
type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
}

View File

@ -1,4 +1,5 @@
use vek::Vec2; use vek::Vec2;
// TODO: Move this to common/src/, it's not a component
/// Cardinal directions /// Cardinal directions
pub enum Direction { pub enum Direction {

View File

@ -73,7 +73,7 @@ pub use self::{
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs, Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
ModifierKind, ModifierKind,
}, },
character_state::{CharacterState, StateUpdate}, character_state::{CharacterActivity, CharacterState, StateUpdate},
chat::{ chat::{
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
}, },

View File

@ -91,7 +91,10 @@ impl CharacterBehavior for Data {
// Shoots all projectiles simultaneously // Shoots all projectiles simultaneously
for i in 0..self.static_data.num_projectiles { for i in 0..self.static_data.num_projectiles {
// Gets offsets // Gets offsets
let body_offsets = data.body.projectile_offsets(update.ori.look_vec()); let body_offsets = data.body.projectile_offsets(
update.ori.look_vec(),
data.scale.map_or(1.0, |s| s.0),
);
let pos = Pos(data.pos.0 + body_offsets); let pos = Pos(data.pos.0 + body_offsets);
// Adds a slight spread to the projectiles. First projectile has no spread, // Adds a slight spread to the projectiles. First projectile has no spread,
// and spread increases linearly with number of projectiles created. // and spread increases linearly with number of projectiles created.

View File

@ -149,7 +149,7 @@ impl CharacterBehavior for Data {
let collision_vector = Vec3::new( let collision_vector = Vec3::new(
data.pos.0.x + (summon_frac * 2.0 * PI).sin() * obstacle_xy, data.pos.0.x + (summon_frac * 2.0 * PI).sin() * obstacle_xy,
data.pos.0.y + (summon_frac * 2.0 * PI).cos() * obstacle_xy, data.pos.0.y + (summon_frac * 2.0 * PI).cos() * obstacle_xy,
data.pos.0.z + data.body.eye_height(), data.pos.0.z + data.body.eye_height(data.scale.map_or(1.0, |s| s.0)),
); );
// Check for collision in z up to 50 blocks // Check for collision in z up to 50 blocks

View File

@ -3,8 +3,8 @@ use crate::{
self, self,
character_state::OutputEvents, character_state::OutputEvents,
item::{tool::AbilityMap, MaterialStatManifest}, item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller, ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, ControlAction,
ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory, Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate, InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate,
Stats, Vel, Stats, Vel,
}, },
@ -120,6 +120,7 @@ pub struct JoinData<'a> {
pub entity: Entity, pub entity: Entity,
pub uid: &'a Uid, pub uid: &'a Uid,
pub character: &'a CharacterState, pub character: &'a CharacterState,
pub character_activity: &'a CharacterActivity,
pub pos: &'a Pos, pub pos: &'a Pos,
pub vel: &'a Vel, pub vel: &'a Vel,
pub ori: &'a Ori, pub ori: &'a Ori,
@ -153,6 +154,7 @@ pub struct JoinStruct<'a> {
pub entity: Entity, pub entity: Entity,
pub uid: &'a Uid, pub uid: &'a Uid,
pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>, pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>,
pub character_activity: FlaggedAccessMut<'a, &'a mut CharacterActivity, CharacterActivity>,
pub pos: &'a mut Pos, pub pos: &'a mut Pos,
pub vel: &'a mut Vel, pub vel: &'a mut Vel,
pub ori: &'a mut Ori, pub ori: &'a mut Ori,
@ -190,6 +192,7 @@ impl<'a> JoinData<'a> {
entity: j.entity, entity: j.entity,
uid: j.uid, uid: j.uid,
character: &j.char_state, character: &j.char_state,
character_activity: &j.character_activity,
pos: j.pos, pos: j.pos,
vel: j.vel, vel: j.vel,
ori: j.ori, ori: j.ori,

View File

@ -114,7 +114,9 @@ impl CharacterBehavior for Data {
get_crit_data(data, self.static_data.ability_info); get_crit_data(data, self.static_data.ability_info);
let tool_stats = get_tool_stats(data, self.static_data.ability_info); let tool_stats = get_tool_stats(data, self.static_data.ability_info);
// Gets offsets // Gets offsets
let body_offsets = data.body.projectile_offsets(update.ori.look_vec()); let body_offsets = data
.body
.projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
let pos = Pos(data.pos.0 + body_offsets); let pos = Pos(data.pos.0 + body_offsets);
let projectile = arrow.create_projectile( let projectile = arrow.create_projectile(
Some(*data.uid), Some(*data.uid),

View File

@ -95,7 +95,9 @@ impl CharacterBehavior for Data {
get_crit_data(data, self.static_data.ability_info); get_crit_data(data, self.static_data.ability_info);
let tool_stats = get_tool_stats(data, self.static_data.ability_info); let tool_stats = get_tool_stats(data, self.static_data.ability_info);
// Gets offsets // Gets offsets
let body_offsets = data.body.projectile_offsets(update.ori.look_vec()); let body_offsets = data
.body
.projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
let pos = Pos(data.pos.0 + body_offsets); let pos = Pos(data.pos.0 + body_offsets);
let projectile = self.static_data.projectile.create_projectile( let projectile = self.static_data.projectile.create_projectile(
Some(*data.uid), Some(*data.uid),

View File

@ -298,10 +298,10 @@ impl Body {
/// Returns the position where a projectile should be fired relative to this /// Returns the position where a projectile should be fired relative to this
/// body /// body
pub fn projectile_offsets(&self, ori: Vec3<f32>) -> Vec3<f32> { pub fn projectile_offsets(&self, ori: Vec3<f32>, scale: f32) -> Vec3<f32> {
let body_offsets_z = match self { let body_offsets_z = match self {
Body::Golem(_) => self.height() * 0.4, Body::Golem(_) => self.height() * 0.4,
_ => self.eye_height(), _ => self.eye_height(scale),
}; };
let dim = self.dimensions(); let dim = self.dimensions();
@ -611,6 +611,9 @@ pub fn handle_orientation(
.ori .ori
.slerped_towards(target_ori, target_fraction.min(1.0)) .slerped_towards(target_ori, target_fraction.min(1.0))
}; };
// Look at things
update.character_activity.look_dir = Some(data.controller.inputs.look_dir);
} }
/// Updates components to move player as if theyre swimming /// Updates components to move player as if theyre swimming

View File

@ -199,6 +199,7 @@ impl State {
ecs.register::<comp::Sticky>(); ecs.register::<comp::Sticky>();
ecs.register::<comp::Immovable>(); ecs.register::<comp::Immovable>();
ecs.register::<comp::CharacterState>(); ecs.register::<comp::CharacterState>();
ecs.register::<comp::CharacterActivity>();
ecs.register::<comp::Object>(); ecs.register::<comp::Object>();
ecs.register::<comp::Group>(); ecs.register::<comp::Group>();
ecs.register::<comp::Shockwave>(); ecs.register::<comp::Shockwave>();

View File

@ -8,9 +8,9 @@ use common::{
self, self,
character_state::OutputEvents, character_state::OutputEvents,
inventory::item::{tool::AbilityMap, MaterialStatManifest}, inventory::item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos,
Stance, StateUpdate, Stats, Vel, Scale, SkillSet, Stance, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
link::Is, link::Is,
@ -65,6 +65,7 @@ impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
ReadData<'a>, ReadData<'a>,
WriteStorage<'a, CharacterState>, WriteStorage<'a, CharacterState>,
WriteStorage<'a, CharacterActivity>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>, WriteStorage<'a, Ori>,
@ -84,6 +85,7 @@ impl<'a> System<'a> for Sys {
( (
read_data, read_data,
mut character_states, mut character_states,
mut character_activities,
mut positions, mut positions,
mut velocities, mut velocities,
mut orientations, mut orientations,
@ -106,6 +108,7 @@ impl<'a> System<'a> for Sys {
entity, entity,
uid, uid,
mut char_state, mut char_state,
character_activity,
pos, pos,
vel, vel,
ori, ori,
@ -116,13 +119,13 @@ impl<'a> System<'a> for Sys {
controller, controller,
health, health,
body, body,
physics, (physics, scale, stat, skill_set, active_abilities, is_rider),
(scale, stat, skill_set, active_abilities, is_rider),
combo, combo,
) in ( ) in (
&read_data.entities, &read_data.entities,
&read_data.uids, &read_data.uids,
&mut character_states, &mut character_states,
&mut character_activities,
&mut positions, &mut positions,
&mut velocities, &mut velocities,
&mut orientations, &mut orientations,
@ -133,8 +136,8 @@ impl<'a> System<'a> for Sys {
&mut controllers, &mut controllers,
read_data.healths.maybe(), read_data.healths.maybe(),
&read_data.bodies, &read_data.bodies,
&read_data.physics_states,
( (
&read_data.physics_states,
read_data.scales.maybe(), read_data.scales.maybe(),
&read_data.stats, &read_data.stats,
&read_data.skill_sets, &read_data.skill_sets,
@ -182,6 +185,7 @@ impl<'a> System<'a> for Sys {
entity, entity,
uid, uid,
char_state, char_state,
character_activity,
pos, pos,
vel, vel,
ori, ori,
@ -261,6 +265,9 @@ impl Sys {
if *join.char_state != state_update.character { if *join.char_state != state_update.character {
*join.char_state = state_update.character *join.char_state = state_update.character
} }
if *join.character_activity != state_update.character_activity {
*join.character_activity = state_update.character_activity
}
if *join.density != state_update.density { if *join.density != state_update.density {
*join.density = state_update.density *join.density = state_update.density
} }

View File

@ -2,7 +2,7 @@ use common::{
comp::{ comp::{
ability::Stance, ability::Stance,
agent::{Sound, SoundKind}, agent::{Sound, SoundKind},
Body, BuffChange, ControlEvent, Controller, Pos, Body, BuffChange, ControlEvent, Controller, Pos, Scale,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
uid::UidAllocator, uid::UidAllocator,
@ -22,6 +22,7 @@ pub struct ReadData<'a> {
server_bus: Read<'a, EventBus<ServerEvent>>, server_bus: Read<'a, EventBus<ServerEvent>>,
positions: ReadStorage<'a, Pos>, positions: ReadStorage<'a, Pos>,
bodies: ReadStorage<'a, Body>, bodies: ReadStorage<'a, Body>,
scales: ReadStorage<'a, Scale>,
} }
#[derive(Default)] #[derive(Default)]
@ -91,13 +92,15 @@ impl<'a> System<'a> for Sys {
}, },
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)), ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
ControlEvent::Utterance(kind) => { ControlEvent::Utterance(kind) => {
if let (Some(pos), Some(body)) = ( if let (Some(pos), Some(body), scale) = (
read_data.positions.get(entity), read_data.positions.get(entity),
read_data.bodies.get(entity), read_data.bodies.get(entity),
read_data.scales.get(entity),
) { ) {
let sound = Sound::new( let sound = Sound::new(
SoundKind::Utterance(kind, *body), SoundKind::Utterance(kind, *body),
pos.0 + Vec3::unit_z() * body.eye_height(), pos.0
+ Vec3::unit_z() * body.eye_height(scale.map_or(1.0, |s| s.0)),
8.0, // TODO: Come up with a better way of determining this 8.0, // TODO: Come up with a better way of determining this
1.0, 1.0,
); );

View File

@ -68,13 +68,14 @@ impl<'a> System<'a> for Sys {
let mut outcomes_emitter = outcomes.emitter(); let mut outcomes_emitter = outcomes.emitter();
// Attacks // Attacks
for (attacker, uid, pos, ori, melee_attack, body) in ( for (attacker, uid, pos, ori, melee_attack, body, scale) in (
&read_data.entities, &read_data.entities,
&read_data.uids, &read_data.uids,
&read_data.positions, &read_data.positions,
&read_data.orientations, &read_data.orientations,
&mut melee_attacks, &mut melee_attacks,
&read_data.bodies, &read_data.bodies,
read_data.scales.maybe(),
) )
.join() .join()
{ {
@ -87,7 +88,7 @@ impl<'a> System<'a> for Sys {
melee_attack.applied = true; melee_attack.applied = true;
// Scales // Scales
let eye_pos = pos.0 + Vec3::unit_z() * body.eye_height(); let eye_pos = pos.0 + Vec3::unit_z() * body.eye_height(scale.map_or(1.0, |s| s.0));
let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0); let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0);
let height = body.height() * scale; let height = body.height() * scale;
// TODO: use Capsule Prisms instead of Cylinders // TODO: use Capsule Prisms instead of Cylinders

View File

@ -23,7 +23,7 @@ use common::{
item_drop, item_drop,
projectile::ProjectileConstructor, projectile::ProjectileConstructor,
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller,
HealthChange, InputKind, InventoryAction, Pos, UnresolvedChatMsg, UtteranceKind, HealthChange, InputKind, InventoryAction, Pos, Scale, UnresolvedChatMsg, UtteranceKind,
}, },
effect::{BuffEffect, Effect}, effect::{BuffEffect, Effect},
event::{Emitter, ServerEvent}, event::{Emitter, ServerEvent},
@ -514,8 +514,10 @@ impl<'a> AgentData<'a> {
target: EcsEntity, target: EcsEntity,
) -> bool { ) -> bool {
if let Some(tgt_pos) = read_data.positions.get(target) { if let Some(tgt_pos) = read_data.positions.get(target) {
let eye_offset = self.body.map_or(0.0, |b| b.eye_height()); let eye_offset = self.body.map_or(0.0, |b| b.eye_height(self.scale));
let tgt_eye_offset = read_data.bodies.get(target).map_or(0.0, |b| b.eye_height()); let tgt_eye_offset = read_data.bodies.get(target).map_or(0.0, |b| {
b.eye_height(read_data.scales.get(target).map_or(1.0, |s| s.0))
});
if let Some(dir) = Dir::from_unnormalized( if let Some(dir) = Dir::from_unnormalized(
Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset) Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset)
- Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), - Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
@ -794,8 +796,8 @@ impl<'a> AgentData<'a> {
}, },
}; };
let is_detected = |entity: &EcsEntity, e_pos: &Pos| { let is_detected = |entity: &EcsEntity, e_pos: &Pos, e_scale: Option<&Scale>| {
self.detects_other(agent, controller, entity, e_pos, read_data) self.detects_other(agent, controller, entity, e_pos, e_scale, read_data)
}; };
let target = entities_nearby let target = entities_nearby
@ -805,7 +807,7 @@ impl<'a> AgentData<'a> {
.filter_map(|(entity, attack_target)| { .filter_map(|(entity, attack_target)| {
get_pos(entity).map(|pos| (entity, pos, attack_target)) get_pos(entity).map(|pos| (entity, pos, attack_target))
}) })
.filter(|(entity, e_pos, _)| is_detected(entity, e_pos)) .filter(|(entity, e_pos, _)| is_detected(entity, e_pos, read_data.scales.get(*entity)))
.min_by_key(|(_, e_pos, attack_target)| { .min_by_key(|(_, e_pos, attack_target)| {
( (
*attack_target, *attack_target,
@ -997,9 +999,11 @@ impl<'a> AgentData<'a> {
.angle_between((tgt_data.pos.0 - self.pos.0).xy()) .angle_between((tgt_data.pos.0 - self.pos.0).xy())
.to_degrees(); .to_degrees();
let eye_offset = self.body.map_or(0.0, |b| b.eye_height()); let eye_offset = self.body.map_or(0.0, |b| b.eye_height(self.scale));
let tgt_eye_height = tgt_data.body.map_or(0.0, |b| b.eye_height()); let tgt_eye_height = tgt_data
.body
.map_or(0.0, |b| b.eye_height(tgt_data.scale.map_or(1.0, |s| s.0)));
let tgt_eye_offset = tgt_eye_height + let tgt_eye_offset = tgt_eye_height +
// Special case for jumping attacks to jump at the body // Special case for jumping attacks to jump at the body
// of the target and not the ground around the target // of the target and not the ground around the target
@ -1037,7 +1041,7 @@ impl<'a> AgentData<'a> {
projectile_speed, projectile_speed,
self.pos.0 self.pos.0
+ self.body.map_or(Vec3::zero(), |body| { + self.body.map_or(Vec3::zero(), |body| {
body.projectile_offsets(self.ori.look_vec()) body.projectile_offsets(self.ori.look_vec(), self.scale)
}), }),
Vec3::new( Vec3::new(
tgt_data.pos.0.x, tgt_data.pos.0.x,
@ -1062,7 +1066,7 @@ impl<'a> AgentData<'a> {
projectile_speed, projectile_speed,
self.pos.0 self.pos.0
+ self.body.map_or(Vec3::zero(), |body| { + self.body.map_or(Vec3::zero(), |body| {
body.projectile_offsets(self.ori.look_vec()) body.projectile_offsets(self.ori.look_vec(), self.scale)
}), }),
Vec3::new( Vec3::new(
tgt_data.pos.0.x, tgt_data.pos.0.x,
@ -1077,7 +1081,7 @@ impl<'a> AgentData<'a> {
projectile_speed, projectile_speed,
self.pos.0 self.pos.0
+ self.body.map_or(Vec3::zero(), |body| { + self.body.map_or(Vec3::zero(), |body| {
body.projectile_offsets(self.ori.look_vec()) body.projectile_offsets(self.ori.look_vec(), self.scale)
}), }),
Vec3::new( Vec3::new(
tgt_data.pos.0.x, tgt_data.pos.0.x,
@ -1684,6 +1688,7 @@ impl<'a> AgentData<'a> {
controller: &Controller, controller: &Controller,
other: EcsEntity, other: EcsEntity,
other_pos: &Pos, other_pos: &Pos,
other_scale: Option<&Scale>,
read_data: &ReadData, read_data: &ReadData,
) -> bool { ) -> bool {
let other_stealth_multiplier = { let other_stealth_multiplier = {
@ -1708,7 +1713,15 @@ impl<'a> AgentData<'a> {
(within_sight_dist) (within_sight_dist)
&& within_fov && within_fov
&& entities_have_line_of_sight(self.pos, self.body, other_pos, other_body, read_data) && entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
other_pos,
other_body,
other_scale,
read_data,
)
} }
pub fn detects_other( pub fn detects_other(
@ -1717,10 +1730,11 @@ impl<'a> AgentData<'a> {
controller: &Controller, controller: &Controller,
other: &EcsEntity, other: &EcsEntity,
other_pos: &Pos, other_pos: &Pos,
other_scale: Option<&Scale>,
read_data: &ReadData, read_data: &ReadData,
) -> bool { ) -> bool {
self.can_sense_directly_near(other_pos) self.can_sense_directly_near(other_pos)
|| self.can_see_entity(agent, controller, *other, other_pos, read_data) || self.can_see_entity(agent, controller, *other, other_pos, other_scale, read_data)
} }
pub fn can_sense_directly_near(&self, e_pos: &Pos) -> bool { pub fn can_sense_directly_near(&self, e_pos: &Pos) -> bool {

View File

@ -247,7 +247,15 @@ impl<'a> AgentData<'a> {
read_data: &ReadData, read_data: &ReadData,
) { ) {
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
)
}; };
let elevation = self.pos.0.z - tgt_data.pos.0.z; let elevation = self.pos.0.z - tgt_data.pos.0.z;
@ -374,8 +382,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -451,8 +461,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -1269,7 +1281,15 @@ impl<'a> AgentData<'a> {
const DESIRED_ENERGY_LEVEL: f32 = 50.0; const DESIRED_ENERGY_LEVEL: f32 = 50.0;
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
)
}; };
// Logic to use abilities // Logic to use abilities
@ -1521,8 +1541,10 @@ impl<'a> AgentData<'a> {
if entities_have_line_of_sight( if entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) && attack_data.angle < 45.0 ) && attack_data.angle < 45.0
{ {
@ -1574,7 +1596,15 @@ impl<'a> AgentData<'a> {
const DESIRED_COMBO_LEVEL: u32 = 8; const DESIRED_COMBO_LEVEL: u32 = 8;
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
)
}; };
// Logic to use abilities // Logic to use abilities
@ -1724,8 +1754,10 @@ impl<'a> AgentData<'a> {
) && entities_have_line_of_sight( ) && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) && attack_data.angle < 90.0 ) && attack_data.angle < 90.0
{ {
@ -1907,8 +1939,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -2130,8 +2164,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -2365,8 +2401,15 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) if entities_have_line_of_sight(
&& attack_data.angle < 15.0 self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
) && attack_data.angle < 15.0
{ {
controller.push_basic_input(InputKind::Primary); controller.push_basic_input(InputKind::Primary);
} else { } else {
@ -2383,8 +2426,15 @@ impl<'a> AgentData<'a> {
read_data: &ReadData, read_data: &ReadData,
) { ) {
controller.inputs.look_dir = self.ori.look_dir(); controller.inputs.look_dir = self.ori.look_dir();
if entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) if entities_have_line_of_sight(
&& attack_data.angle < 15.0 self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
) && attack_data.angle < 15.0
{ {
controller.push_basic_input(InputKind::Primary); controller.push_basic_input(InputKind::Primary);
} else { } else {
@ -2406,8 +2456,15 @@ impl<'a> AgentData<'a> {
.try_normalized() .try_normalized()
.unwrap_or_default(), .unwrap_or_default(),
); );
if entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) if entities_have_line_of_sight(
{ self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
) {
controller.push_basic_input(InputKind::Primary); controller.push_basic_input(InputKind::Primary);
} else { } else {
agent.target = None; agent.target = None;
@ -2467,8 +2524,10 @@ impl<'a> AgentData<'a> {
if entities_have_line_of_sight( if entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) { ) {
// If close to target, use either primary or secondary ability // If close to target, use either primary or secondary ability
@ -2564,8 +2623,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
&& attack_data.angle < 15.0 && attack_data.angle < 15.0
@ -2695,8 +2756,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
&& attack_data.angle < 15.0 && attack_data.angle < 15.0
@ -2931,8 +2994,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -3186,7 +3251,15 @@ impl<'a> AgentData<'a> {
.and_then(|e| read_data.velocities.get(e)) .and_then(|e| read_data.velocities.get(e))
.map_or(0.0, |v| v.0.cross(self.ori.look_vec()).magnitude_squared()); .map_or(0.0, |v| v.0.cross(self.ori.look_vec()).magnitude_squared());
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
)
}; };
if attack_data.dist_sqrd < golem_melee_range.powi(2) { if attack_data.dist_sqrd < golem_melee_range.powi(2) {
@ -3263,7 +3336,15 @@ impl<'a> AgentData<'a> {
let health_fraction = self.health.map_or(0.5, |h| h.fraction()); let health_fraction = self.health.map_or(0.5, |h| h.fraction());
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
)
}; };
// Sets counter at start of combat, using `condition` to keep track of whether // Sets counter at start of combat, using `condition` to keep track of whether
@ -3461,7 +3542,15 @@ impl<'a> AgentData<'a> {
let health_fraction = self.health.map_or(0.5, |h| h.fraction()); let health_fraction = self.health.map_or(0.5, |h| h.fraction());
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
)
}; };
if health_fraction < VINE_CREATION_THRESHOLD if health_fraction < VINE_CREATION_THRESHOLD
@ -3530,7 +3619,15 @@ impl<'a> AgentData<'a> {
} }
let line_of_sight_with_target = || { let line_of_sight_with_target = || {
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data) entities_have_line_of_sight(
self.pos,
self.body,
self.scale,
tgt_data.pos,
tgt_data.body,
tgt_data.scale,
read_data,
)
}; };
let health_fraction = self.health.map_or(0.5, |h| h.fraction()); let health_fraction = self.health.map_or(0.5, |h| h.fraction());
// Sets counter at start of combat, using `condition` to keep track of whether // Sets counter at start of combat, using `condition` to keep track of whether
@ -3671,8 +3768,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -3738,8 +3837,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -3822,8 +3923,10 @@ impl<'a> AgentData<'a> {
if entities_have_line_of_sight( if entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) && attack_data.angle < 45.0 ) && attack_data.angle < 45.0
{ {
@ -3901,8 +4004,10 @@ impl<'a> AgentData<'a> {
} else if entities_have_line_of_sight( } else if entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) { ) {
// if enemy in mid range shoot dagon bombs and steamwave // if enemy in mid range shoot dagon bombs and steamwave
@ -4016,8 +4121,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
{ {
@ -4177,8 +4284,10 @@ impl<'a> AgentData<'a> {
} else if entities_have_line_of_sight( } else if entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) { ) {
// Else if in sight, barrage // Else if in sight, barrage
@ -4241,8 +4350,10 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight( && entities_have_line_of_sight(
self.pos, self.pos,
self.body, self.body,
self.scale,
tgt_data.pos, tgt_data.pos,
tgt_data.body, tgt_data.body,
tgt_data.scale,
read_data, read_data,
) )
&& agent.action_state.timers[DASH_TIMER] > 4.0 && agent.action_state.timers[DASH_TIMER] > 4.0

View File

@ -2,7 +2,7 @@ use crate::data::{ActionMode, AgentData, AttackData, Path, ReadData, TargetData}
use common::{ use common::{
comp::{ comp::{
agent::Psyche, buff::BuffKind, inventory::item::ItemTag, item::ItemDesc, Agent, Alignment, agent::Psyche, buff::BuffKind, inventory::item::ItemTag, item::ItemDesc, Agent, Alignment,
Body, Controller, InputKind, Pos, Body, Controller, InputKind, Pos, Scale,
}, },
consts::GRAVITY, consts::GRAVITY,
terrain::Block, terrain::Block,
@ -146,17 +146,19 @@ pub fn are_our_owners_hostile(
pub fn entities_have_line_of_sight( pub fn entities_have_line_of_sight(
pos: &Pos, pos: &Pos,
body: Option<&Body>, body: Option<&Body>,
scale: f32,
other_pos: &Pos, other_pos: &Pos,
other_body: Option<&Body>, other_body: Option<&Body>,
other_scale: Option<&Scale>,
read_data: &ReadData, read_data: &ReadData,
) -> bool { ) -> bool {
let get_eye_pos = |pos: &Pos, body: Option<&Body>| { let get_eye_pos = |pos: &Pos, body: Option<&Body>, scale: f32| {
let eye_offset = body.map_or(0.0, |b| b.eye_height()); let eye_offset = body.map_or(0.0, |b| b.eye_height(scale));
Pos(pos.0.with_z(pos.0.z + eye_offset)) Pos(pos.0.with_z(pos.0.z + eye_offset))
}; };
let eye_pos = get_eye_pos(pos, body); let eye_pos = get_eye_pos(pos, body, scale);
let other_eye_pos = get_eye_pos(other_pos, other_body); let other_eye_pos = get_eye_pos(other_pos, other_body, other_scale.map_or(1.0, |s| s.0));
positions_have_line_of_sight(&eye_pos, &other_eye_pos, read_data) positions_have_line_of_sight(&eye_pos, &other_eye_pos, read_data)
} }

View File

@ -286,6 +286,7 @@ impl StateExt for State {
.with(poise) .with(poise)
.with(comp::Alignment::Npc) .with(comp::Alignment::Npc)
.with(comp::CharacterState::default()) .with(comp::CharacterState::default())
.with(comp::CharacterActivity::default())
.with(inventory) .with(inventory)
.with(comp::Buffs::default()) .with(comp::Buffs::default())
.with(comp::Combo::default()) .with(comp::Combo::default())
@ -352,6 +353,7 @@ impl StateExt for State {
.with(comp::Controller::default()) .with(comp::Controller::default())
.with(Inventory::with_empty()) .with(Inventory::with_empty())
.with(comp::CharacterState::default()) .with(comp::CharacterState::default())
.with(comp::CharacterActivity::default())
// TODO: some of these are required in order for the character_behavior system to // TODO: some of these are required in order for the character_behavior system to
// recognize a possesed airship; that system should be refactored to use `.maybe()` // recognize a possesed airship; that system should be refactored to use `.maybe()`
.with(comp::Energy::new(ship.into(), 0)) .with(comp::Energy::new(ship.into(), 0))
@ -559,6 +561,7 @@ impl StateExt for State {
z_max: 1.75, z_max: 1.75,
}); });
self.write_component_ignore_entity_dead(entity, comp::CharacterState::default()); self.write_component_ignore_entity_dead(entity, comp::CharacterState::default());
self.write_component_ignore_entity_dead(entity, comp::CharacterActivity::default());
self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid)); self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid));
self.write_component_ignore_entity_dead(entity, comp::Buffs::default()); self.write_component_ignore_entity_dead(entity, comp::Buffs::default());
self.write_component_ignore_entity_dead(entity, comp::Auras::default()); self.write_component_ignore_entity_dead(entity, comp::Auras::default());

View File

@ -139,11 +139,12 @@ impl BehaviorTree {
handle_inbox_trade_accepted, handle_inbox_trade_accepted,
handle_inbox_finished_trade, handle_inbox_finished_trade,
handle_inbox_update_pending_trade, handle_inbox_update_pending_trade,
handle_timed_events,
]); ]);
Self { tree } Self { tree }
} else { } else {
Self { Self {
tree: vec![handle_inbox_cancel_interactions], tree: vec![handle_inbox_cancel_interactions, handle_timed_events],
} }
} }
} }
@ -477,7 +478,6 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
if bdata.agent.allowed_to_speak() if bdata.agent.allowed_to_speak()
&& let Some(target) = bdata.read_data.lookup_actor(actor) && let Some(target) = bdata.read_data.lookup_actor(actor)
&& let Some(target_pos) = bdata.read_data.positions.get(target) && let Some(target_pos) = bdata.read_data.positions.get(target)
&& bdata.agent_data.look_toward(bdata.controller, bdata.read_data, target)
{ {
bdata.agent.target = Some(Target::new( bdata.agent.target = Some(Target::new(
target, target,
@ -486,6 +486,8 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
false, false,
Some(target_pos.0), Some(target_pos.0),
)); ));
// We're always aware of someone we're talking to
bdata.agent.awareness.set_maximally_aware();
bdata.controller.push_action(ControlAction::Talk); bdata.controller.push_action(ControlAction::Talk);
bdata.controller.push_utterance(UtteranceKind::Greeting); bdata.controller.push_utterance(UtteranceKind::Greeting);
@ -497,15 +499,20 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
.agent .agent
.timer .timer
.start(bdata.read_data.time.0, TimerAction::Interact); .start(bdata.read_data.time.0, TimerAction::Interact);
true
} else {
false
} }
}, },
NpcAction::Say(msg) => { NpcAction::Say(msg) => {
bdata.controller.push_utterance(UtteranceKind::Greeting); bdata.controller.push_utterance(UtteranceKind::Greeting);
bdata.agent_data.chat_npc(msg, bdata.event_emitter); bdata.agent_data.chat_npc(msg, bdata.event_emitter);
false
}, },
} }
} } else {
false false
}
} }
/// Handle timed events, like looking at the player we are talking to /// Handle timed events, like looking at the player we are talking to
@ -571,7 +578,14 @@ fn update_last_known_pos(bdata: &mut BehaviorData) -> bool {
let target = target_info.target; let target = target_info.target;
if let Some(target_pos) = read_data.positions.get(target) { if let Some(target_pos) = read_data.positions.get(target) {
if agent_data.detects_other(agent, controller, &target, target_pos, read_data) { if agent_data.detects_other(
agent,
controller,
&target,
target_pos,
read_data.scales.get(target),
read_data,
) {
let updated_pos = Some(target_pos.0); let updated_pos = Some(target_pos.0);
let Target { let Target {
@ -631,9 +645,10 @@ fn update_target_awareness(bdata: &mut BehaviorData) -> bool {
let target = agent.target.map(|t| t.target); let target = agent.target.map(|t| t.target);
let tgt_pos = target.and_then(|t| read_data.positions.get(t)); let tgt_pos = target.and_then(|t| read_data.positions.get(t));
let tgt_scale = target.and_then(|t| read_data.scales.get(t));
if let (Some(target), Some(tgt_pos)) = (target, tgt_pos) { if let (Some(target), Some(tgt_pos)) = (target, tgt_pos) {
if agent_data.can_see_entity(agent, controller, target, tgt_pos, read_data) { if agent_data.can_see_entity(agent, controller, target, tgt_pos, tgt_scale, read_data) {
agent.awareness.change_by(1.75 * read_data.dt.0); agent.awareness.change_by(1.75 * read_data.dt.0);
} else if agent_data.can_sense_directly_near(tgt_pos) { } else if agent_data.can_sense_directly_near(tgt_pos) {
agent.awareness.change_by(0.25); agent.awareness.change_by(0.25);

View File

@ -97,8 +97,9 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
false, false,
target_pos, target_pos,
)); ));
// We're always aware of someone we're talking to
agent.awareness.set_maximally_aware();
if agent_data.look_toward(controller, read_data, target) {
controller.push_action(ControlAction::Stand); controller.push_action(ControlAction::Stand);
controller.push_action(ControlAction::Talk); controller.push_action(ControlAction::Talk);
controller.push_utterance(UtteranceKind::Greeting); controller.push_utterance(UtteranceKind::Greeting);
@ -111,8 +112,8 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
let standard_response_msg = || -> String { let standard_response_msg = || -> String {
if personality.will_ambush() { if personality.will_ambush() {
format!( format!(
"I'm heading to {}! Want to come along? We'll \ "I'm heading to {}! Want to come along? We'll make \
make great travel buddies, hehe.", great travel buddies, hehe.",
destination_name destination_name
) )
} else if personality.is(PersonalityTrait::Extroverted) { } else if personality.is(PersonalityTrait::Extroverted) {
@ -135,17 +136,15 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
if personality.is(PersonalityTrait::Extroverted) { if personality.is(PersonalityTrait::Extroverted) {
format!( format!(
"Greetings fair {}! It has been far too long \ "Greetings fair {}! It has been far too long \
since last I saw you. I'm going to {} right \ since last I saw you. I'm going to {} right now.",
now.",
&tgt_stats.name, destination_name &tgt_stats.name, destination_name
) )
} else if personality.is(PersonalityTrait::Disagreeable) } else if personality.is(PersonalityTrait::Disagreeable) {
{
"Oh. It's you again.".to_string() "Oh. It's you again.".to_string()
} else { } else {
format!( format!(
"Hi again {}! Unfortunately I'm in a hurry \ "Hi again {}! Unfortunately I'm in a hurry right \
right now. See you!", now. See you!",
&tgt_stats.name &tgt_stats.name
) )
} }
@ -166,9 +165,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
PersonalityTrait::Adventurous => { PersonalityTrait::Adventurous => {
"npc-speech-villager_adventurous" "npc-speech-villager_adventurous"
}, },
PersonalityTrait::Closed => { PersonalityTrait::Closed => "npc-speech-villager_closed",
"npc-speech-villager_closed"
},
PersonalityTrait::Conscientious => { PersonalityTrait::Conscientious => {
"npc-speech-villager_conscientious" "npc-speech-villager_conscientious"
}, },
@ -196,18 +193,12 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
PersonalityTrait::Neurotic => { PersonalityTrait::Neurotic => {
"npc-speech-villager_neurotic" "npc-speech-villager_neurotic"
}, },
PersonalityTrait::Seeker => { PersonalityTrait::Seeker => "npc-speech-villager_seeker",
"npc-speech-villager_seeker"
},
PersonalityTrait::SadLoner => { PersonalityTrait::SadLoner => {
"npc-speech-villager_sad_loner" "npc-speech-villager_sad_loner"
}, },
PersonalityTrait::Worried => { PersonalityTrait::Worried => "npc-speech-villager_worried",
"npc-speech-villager_worried" PersonalityTrait::Stable => "npc-speech-villager_stable",
},
PersonalityTrait::Stable => {
"npc-speech-villager_stable"
},
}; };
agent_data.chat_npc(msg, event_emitter); agent_data.chat_npc(msg, event_emitter);
} else { } else {
@ -261,24 +252,21 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
Subject::Person(person) => { Subject::Person(person) => {
if let Some(src_pos) = read_data.positions.get(target) { if let Some(src_pos) = read_data.positions.get(target) {
let msg = if let Some(person_pos) = person.origin { let msg = if let Some(person_pos) = person.origin {
let distance = let distance = Distance::from_dir(person_pos.xy() - src_pos.0.xy());
Distance::from_dir(person_pos.xy() - src_pos.0.xy());
match distance { match distance {
Distance::NextTo | Distance::Near => { Distance::NextTo | Distance::Near => {
format!( format!(
"{} ? I think he's {} {} from here!", "{} ? I think he's {} {} from here!",
person.name(), person.name(),
distance.name(), distance.name(),
Direction::from_dir( Direction::from_dir(person_pos.xy() - src_pos.0.xy(),)
person_pos.xy() - src_pos.0.xy(),
)
.name() .name()
) )
}, },
_ => { _ => {
format!( format!(
"{} ? I think he's gone visiting another town. \ "{} ? I think he's gone visiting another town. Come \
Come back later!", back later!",
person.name() person.name()
) )
}, },
@ -297,7 +285,6 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
} }
} }
} }
}
true true
} }

View File

@ -31,7 +31,8 @@ impl Animation for TalkAnimation {
let slowb = (anim_time * 4.0 + PI / 2.0).sin(); let slowb = (anim_time * 4.0 + PI / 2.0).sin();
let slowc = (anim_time * 12.0 + PI / 2.0).sin(); let slowc = (anim_time * 12.0 + PI / 2.0).sin();
next.head.orientation = Quaternion::rotation_x(slowc * 0.035 + look_dir.z * 0.7); next.head.orientation =
Quaternion::rotation_x(slowc * 0.035 + look_dir.z.atan2(look_dir.xy().magnitude()));
next.hand_l.position = Vec3::new( next.hand_l.position = Vec3::new(
-s_a.hand.0 + 0.5 + slowb * 0.5, -s_a.hand.0 + 0.5 + slowb * 0.5,
s_a.hand.1 + 5.0 + slowc * 1.0, s_a.hand.1 + 5.0 + slowc * 1.0,

View File

@ -346,8 +346,19 @@ pub fn maintain_egui_inner(
ui.label("Body"); ui.label("Body");
ui.label("Poise"); ui.label("Poise");
ui.label("Character State"); ui.label("Character State");
ui.label("Character Activity");
ui.end_row(); ui.end_row();
for (entity, body, stats, pos, _ori, vel, poise, character_state) in ( for (
entity,
body,
stats,
pos,
_ori,
vel,
poise,
character_state,
character_activity,
) in (
&ecs.entities(), &ecs.entities(),
ecs.read_storage::<Body>().maybe(), ecs.read_storage::<Body>().maybe(),
ecs.read_storage::<comp::Stats>().maybe(), ecs.read_storage::<comp::Stats>().maybe(),
@ -356,14 +367,18 @@ pub fn maintain_egui_inner(
ecs.read_storage::<comp::Vel>().maybe(), ecs.read_storage::<comp::Vel>().maybe(),
ecs.read_storage::<Poise>().maybe(), ecs.read_storage::<Poise>().maybe(),
ecs.read_storage::<comp::CharacterState>().maybe(), ecs.read_storage::<comp::CharacterState>().maybe(),
ecs.read_storage::<comp::CharacterActivity>().maybe(),
) )
.join() .join()
.filter(|(_, _, _, pos, _, _, _, _)| { .filter(
|(_, _, _, pos, _, _, _, _, _)| {
client_pos.map_or(true, |client_pos| { client_pos.map_or(true, |client_pos| {
pos.map_or(0.0, |pos| pos.0.distance_squared(client_pos.0)) pos.map_or(0.0, |pos| {
< max_entity_distance pos.0.distance_squared(client_pos.0)
}) }) < max_entity_distance
}) })
},
)
{ {
if ui.button("View").clicked() { if ui.button("View").clicked() {
previous_selected_entity = previous_selected_entity =
@ -420,6 +435,12 @@ pub fn maintain_egui_inner(
ui.label("-"); ui.label("-");
} }
if let Some(character_activity) = character_activity {
ui.label(format!("{:?}", character_activity));
} else {
ui.label("-");
}
ui.end_row(); ui.end_row();
} }
}); });
@ -507,11 +528,11 @@ fn selected_entity_window(
buffs, buffs,
auras, auras,
character_state, character_state,
character_activity,
physics_state, physics_state,
alignment, alignment,
scale, scale,
mass, (mass, density, health, energy),
(density, health, energy),
) in ( ) in (
&ecs.entities(), &ecs.entities(),
ecs.read_storage::<Body>().maybe(), ecs.read_storage::<Body>().maybe(),
@ -523,18 +544,19 @@ fn selected_entity_window(
ecs.read_storage::<comp::Buffs>().maybe(), ecs.read_storage::<comp::Buffs>().maybe(),
ecs.read_storage::<comp::Auras>().maybe(), ecs.read_storage::<comp::Auras>().maybe(),
ecs.read_storage::<comp::CharacterState>().maybe(), ecs.read_storage::<comp::CharacterState>().maybe(),
ecs.read_storage::<comp::CharacterActivity>().maybe(),
ecs.read_storage::<comp::PhysicsState>().maybe(), ecs.read_storage::<comp::PhysicsState>().maybe(),
ecs.read_storage::<comp::Alignment>().maybe(), ecs.read_storage::<comp::Alignment>().maybe(),
ecs.read_storage::<comp::Scale>().maybe(), ecs.read_storage::<comp::Scale>().maybe(),
ecs.read_storage::<comp::Mass>().maybe(),
( (
ecs.read_storage::<comp::Mass>().maybe(),
ecs.read_storage::<comp::Density>().maybe(), ecs.read_storage::<comp::Density>().maybe(),
ecs.read_storage::<comp::Health>().maybe(), ecs.read_storage::<comp::Health>().maybe(),
ecs.read_storage::<comp::Energy>().maybe(), ecs.read_storage::<comp::Energy>().maybe(),
), ),
) )
.join() .join()
.filter(|(e, _, _, _, _, _, _, _, _, _, _, _, _, _, (_, _, _))| e.id() == entity_id) .filter(|(e, _, _, _, _, _, _, _, _, _, _, _, _, _, (_, _, _, _))| e.id() == entity_id)
{ {
let time = ecs.read_resource::<Time>(); let time = ecs.read_resource::<Time>();
if let Some(pos) = pos { if let Some(pos) = pos {
@ -702,6 +724,18 @@ fn selected_entity_window(
}); });
} }
if let Some(character_activity) = character_activity {
CollapsingHeader::new("CharacterActivity").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_character_activity_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| {
two_col_row(ui, "look_dir", format!("{:.3?}", character_activity.look_dir));
});
});
}
if let Some(physics_state) = physics_state { if let Some(physics_state) = physics_state {
CollapsingHeader::new("Physics State").default_open(false).show(ui, |ui| { CollapsingHeader::new("Physics State").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_physics_state_grid") Grid::new("selected_entity_physics_state_grid")

View File

@ -33,9 +33,9 @@ use common::{
comp::{ comp::{
inventory::slot::EquipSlot, inventory::slot::EquipSlot,
item::{tool::AbilityContext, Hands, ItemKind, ToolKind}, item::{tool::AbilityContext, Hands, ItemKind, ToolKind},
ship, Body, CharacterState, Collider, Controller, Health, Inventory, Item, ItemKey, Last, ship, Body, CharacterActivity, CharacterState, Collider, Controller, Health, Inventory,
LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, SkillSet, Stance, Item, ItemKey, Last, LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos,
Vel, Scale, SkillSet, Stance, Vel,
}, },
link::Is, link::Is,
mounting::Rider, mounting::Rider,
@ -741,14 +741,14 @@ impl FigureMgr {
scale, scale,
body, body,
character, character,
character_activity,
last_character, last_character,
physics, physics,
health, health,
inventory, inventory,
item, item,
light_emitter, light_emitter,
is_rider, (is_rider, collider, stance, skillset),
(collider, stance, skillset),
), ),
) in ( ) in (
&ecs.entities(), &ecs.entities(),
@ -759,14 +759,15 @@ impl FigureMgr {
ecs.read_storage::<Scale>().maybe(), ecs.read_storage::<Scale>().maybe(),
&ecs.read_storage::<Body>(), &ecs.read_storage::<Body>(),
ecs.read_storage::<CharacterState>().maybe(), ecs.read_storage::<CharacterState>().maybe(),
ecs.read_storage::<CharacterActivity>().maybe(),
ecs.read_storage::<Last<CharacterState>>().maybe(), ecs.read_storage::<Last<CharacterState>>().maybe(),
&ecs.read_storage::<PhysicsState>(), &ecs.read_storage::<PhysicsState>(),
ecs.read_storage::<Health>().maybe(), ecs.read_storage::<Health>().maybe(),
ecs.read_storage::<Inventory>().maybe(), ecs.read_storage::<Inventory>().maybe(),
ecs.read_storage::<Item>().maybe(), ecs.read_storage::<Item>().maybe(),
ecs.read_storage::<LightEmitter>().maybe(), ecs.read_storage::<LightEmitter>().maybe(),
ecs.read_storage::<Is<Rider>>().maybe(),
( (
ecs.read_storage::<Is<Rider>>().maybe(),
ecs.read_storage::<Collider>().maybe(), ecs.read_storage::<Collider>().maybe(),
ecs.read_storage::<Stance>().maybe(), ecs.read_storage::<Stance>().maybe(),
ecs.read_storage::<SkillSet>().maybe(), ecs.read_storage::<SkillSet>().maybe(),
@ -779,7 +780,13 @@ impl FigureMgr {
let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel) let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel)
/ scale.map_or(1.0, |s| s.0); / scale.map_or(1.0, |s| s.0);
let look_dir = controller.map(|c| c.inputs.look_dir).unwrap_or_default(); // Priortise CharacterActivity as the source of the look direction
let look_dir = character_activity.and_then(|ca| ca.look_dir)
// Failing that, take the controller as the source of truth
.or_else(|| controller.map(|c| c.inputs.look_dir))
// If that still didn't work, fall back to the interpolation orientation
.or_else(|| interpolated.map(|i| i.ori.look_dir()))
.unwrap_or_default();
let is_viewpoint = scene_data.viewpoint_entity == entity; let is_viewpoint = scene_data.viewpoint_entity == entity;
let viewpoint_camera_mode = if is_viewpoint { let viewpoint_camera_mode = if is_viewpoint {
camera_mode camera_mode

View File

@ -547,7 +547,7 @@ impl Scene {
( (
matches!(b, comp::Body::Humanoid(_)), matches!(b, comp::Body::Humanoid(_)),
b.height(), b.height(),
b.eye_height(), b.eye_height(1.0), // Scale is applied later
) )
}); });

View File

@ -12,7 +12,7 @@ use common::{
assets::{AssetExt, DotVoxAsset}, assets::{AssetExt, DotVoxAsset},
comp::{ comp::{
self, aura, beam, body, buff, item::Reagent, object, shockwave, BeamSegment, Body, self, aura, beam, body, buff, item::Reagent, object, shockwave, BeamSegment, Body,
CharacterState, Ori, Pos, Shockwave, Vel, CharacterState, Ori, Pos, Scale, Shockwave, Vel,
}, },
figure::Segment, figure::Segment,
outcome::Outcome, outcome::Outcome,
@ -1264,12 +1264,13 @@ impl ParticleMgr {
let time = state.get_time(); let time = state.get_time();
let mut rng = thread_rng(); let mut rng = thread_rng();
for (interp, pos, buffs, body, ori) in ( for (interp, pos, buffs, body, ori, scale) in (
ecs.read_storage::<Interpolated>().maybe(), ecs.read_storage::<Interpolated>().maybe(),
&ecs.read_storage::<Pos>(), &ecs.read_storage::<Pos>(),
&ecs.read_storage::<comp::Buffs>(), &ecs.read_storage::<comp::Buffs>(),
&ecs.read_storage::<Body>(), &ecs.read_storage::<Body>(),
&ecs.read_storage::<Ori>(), &ecs.read_storage::<Ori>(),
ecs.read_storage::<Scale>().maybe(),
) )
.join() .join()
{ {
@ -1328,7 +1329,8 @@ impl ParticleMgr {
self.scheduler.heartbeats(Duration::from_millis(25)), self.scheduler.heartbeats(Duration::from_millis(25)),
), ),
|| { || {
let start_pos = pos + Vec3::unit_z() * body.eye_height(); let start_pos = pos
+ Vec3::unit_z() * body.eye_height(scale.map_or(1.0, |s| s.0));
let (radius, theta) = let (radius, theta) =
(rng.gen_range(0.0f32..1.0).sqrt(), rng.gen_range(0.0..TAU)); (rng.gen_range(0.0f32..1.0).sqrt(), rng.gen_range(0.0..TAU));
let end_pos = pos let end_pos = pos
@ -1393,7 +1395,9 @@ impl ParticleMgr {
+ multiplicity + multiplicity
* self.scheduler.heartbeats(Duration::from_millis(3)) as usize, * self.scheduler.heartbeats(Duration::from_millis(3)) as usize,
|| { || {
let start_pos = pos + Vec3::unit_z() * body.eye_height() / 2.0; let start_pos = pos
+ Vec3::unit_z() * body.eye_height(scale.map_or(1.0, |s| s.0))
/ 2.0;
let end_pos = start_pos let end_pos = start_pos
+ Vec3::<f32>::zero() + Vec3::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0)) .map(|_| rng.gen_range(-1.0..1.0))