mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added CharacterActivity, made NPCs look at the player when speaking to them
This commit is contained in:
parent
85c572f6e2
commit
3e0f5295c0
@ -40,6 +40,7 @@ macro_rules! synced_components {
|
||||
sticky: Sticky,
|
||||
immovable: Immovable,
|
||||
character_state: CharacterState,
|
||||
character_activity: CharacterActivity,
|
||||
shockwave: Shockwave,
|
||||
beam_segment: BeamSegment,
|
||||
alignment: Alignment,
|
||||
@ -201,6 +202,10 @@ impl NetSync for CharacterState {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for CharacterActivity {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Shockwave {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
@ -625,6 +625,11 @@ impl Awareness {
|
||||
self.reached = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_maximally_aware(&mut self) {
|
||||
self.reached = true;
|
||||
self.level = Self::ALERT;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]
|
||||
|
@ -983,7 +983,7 @@ impl Body {
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
// TODO: Make this a manifest
|
||||
|
@ -12,6 +12,7 @@ use crate::{
|
||||
utils::{AbilityInfo, StageSection},
|
||||
*,
|
||||
},
|
||||
util::Dir,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
@ -30,6 +31,7 @@ pub struct StateUpdate {
|
||||
pub should_strafe: bool,
|
||||
pub queued_inputs: BTreeMap<InputKind, InputAttr>,
|
||||
pub removed_inputs: Vec<InputKind>,
|
||||
pub character_activity: CharacterActivity,
|
||||
}
|
||||
|
||||
pub struct OutputEvents<'a> {
|
||||
@ -60,6 +62,7 @@ impl From<&JoinData<'_>> for StateUpdate {
|
||||
character: data.character.clone(),
|
||||
queued_inputs: BTreeMap::new(),
|
||||
removed_inputs: Vec::new(),
|
||||
character_activity: *data.character_activity,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -979,3 +982,20 @@ impl Default for CharacterState {
|
||||
impl Component for CharacterState {
|
||||
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>>;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use vek::Vec2;
|
||||
// TODO: Move this to common/src/, it's not a component
|
||||
|
||||
/// Cardinal directions
|
||||
pub enum Direction {
|
||||
|
@ -73,7 +73,7 @@ pub use self::{
|
||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
||||
ModifierKind,
|
||||
},
|
||||
character_state::{CharacterState, StateUpdate},
|
||||
character_state::{CharacterActivity, CharacterState, StateUpdate},
|
||||
chat::{
|
||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
||||
},
|
||||
|
@ -91,7 +91,10 @@ impl CharacterBehavior for Data {
|
||||
// Shoots all projectiles simultaneously
|
||||
for i in 0..self.static_data.num_projectiles {
|
||||
// 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);
|
||||
// Adds a slight spread to the projectiles. First projectile has no spread,
|
||||
// and spread increases linearly with number of projectiles created.
|
||||
|
@ -149,7 +149,7 @@ impl CharacterBehavior for Data {
|
||||
let collision_vector = Vec3::new(
|
||||
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.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
|
||||
|
@ -3,8 +3,8 @@ use crate::{
|
||||
self,
|
||||
character_state::OutputEvents,
|
||||
item::{tool::AbilityMap, MaterialStatManifest},
|
||||
ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller,
|
||||
ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
|
||||
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, ControlAction,
|
||||
Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
|
||||
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate,
|
||||
Stats, Vel,
|
||||
},
|
||||
@ -120,6 +120,7 @@ pub struct JoinData<'a> {
|
||||
pub entity: Entity,
|
||||
pub uid: &'a Uid,
|
||||
pub character: &'a CharacterState,
|
||||
pub character_activity: &'a CharacterActivity,
|
||||
pub pos: &'a Pos,
|
||||
pub vel: &'a Vel,
|
||||
pub ori: &'a Ori,
|
||||
@ -153,6 +154,7 @@ pub struct JoinStruct<'a> {
|
||||
pub entity: Entity,
|
||||
pub uid: &'a Uid,
|
||||
pub char_state: FlaggedAccessMut<'a, &'a mut CharacterState, CharacterState>,
|
||||
pub character_activity: FlaggedAccessMut<'a, &'a mut CharacterActivity, CharacterActivity>,
|
||||
pub pos: &'a mut Pos,
|
||||
pub vel: &'a mut Vel,
|
||||
pub ori: &'a mut Ori,
|
||||
@ -190,6 +192,7 @@ impl<'a> JoinData<'a> {
|
||||
entity: j.entity,
|
||||
uid: j.uid,
|
||||
character: &j.char_state,
|
||||
character_activity: &j.character_activity,
|
||||
pos: j.pos,
|
||||
vel: j.vel,
|
||||
ori: j.ori,
|
||||
|
@ -114,7 +114,9 @@ impl CharacterBehavior for Data {
|
||||
get_crit_data(data, self.static_data.ability_info);
|
||||
let tool_stats = get_tool_stats(data, self.static_data.ability_info);
|
||||
// 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 projectile = arrow.create_projectile(
|
||||
Some(*data.uid),
|
||||
|
@ -95,7 +95,9 @@ impl CharacterBehavior for Data {
|
||||
get_crit_data(data, self.static_data.ability_info);
|
||||
let tool_stats = get_tool_stats(data, self.static_data.ability_info);
|
||||
// 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 projectile = self.static_data.projectile.create_projectile(
|
||||
Some(*data.uid),
|
||||
|
@ -298,10 +298,10 @@ impl Body {
|
||||
|
||||
/// Returns the position where a projectile should be fired relative to this
|
||||
/// 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 {
|
||||
Body::Golem(_) => self.height() * 0.4,
|
||||
_ => self.eye_height(),
|
||||
_ => self.eye_height(scale),
|
||||
};
|
||||
|
||||
let dim = self.dimensions();
|
||||
@ -611,6 +611,9 @@ pub fn handle_orientation(
|
||||
.ori
|
||||
.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
|
||||
|
@ -199,6 +199,7 @@ impl State {
|
||||
ecs.register::<comp::Sticky>();
|
||||
ecs.register::<comp::Immovable>();
|
||||
ecs.register::<comp::CharacterState>();
|
||||
ecs.register::<comp::CharacterActivity>();
|
||||
ecs.register::<comp::Object>();
|
||||
ecs.register::<comp::Group>();
|
||||
ecs.register::<comp::Shockwave>();
|
||||
|
@ -8,9 +8,9 @@ use common::{
|
||||
self,
|
||||
character_state::OutputEvents,
|
||||
inventory::item::{tool::AbilityMap, MaterialStatManifest},
|
||||
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
|
||||
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Scale, SkillSet,
|
||||
Stance, StateUpdate, Stats, Vel,
|
||||
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density,
|
||||
Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos,
|
||||
Scale, SkillSet, Stance, StateUpdate, Stats, Vel,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
link::Is,
|
||||
@ -65,6 +65,7 @@ impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
ReadData<'a>,
|
||||
WriteStorage<'a, CharacterState>,
|
||||
WriteStorage<'a, CharacterActivity>,
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
@ -84,6 +85,7 @@ impl<'a> System<'a> for Sys {
|
||||
(
|
||||
read_data,
|
||||
mut character_states,
|
||||
mut character_activities,
|
||||
mut positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
@ -106,6 +108,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
uid,
|
||||
mut char_state,
|
||||
character_activity,
|
||||
pos,
|
||||
vel,
|
||||
ori,
|
||||
@ -116,13 +119,13 @@ impl<'a> System<'a> for Sys {
|
||||
controller,
|
||||
health,
|
||||
body,
|
||||
physics,
|
||||
(scale, stat, skill_set, active_abilities, is_rider),
|
||||
(physics, scale, stat, skill_set, active_abilities, is_rider),
|
||||
combo,
|
||||
) in (
|
||||
&read_data.entities,
|
||||
&read_data.uids,
|
||||
&mut character_states,
|
||||
&mut character_activities,
|
||||
&mut positions,
|
||||
&mut velocities,
|
||||
&mut orientations,
|
||||
@ -133,8 +136,8 @@ impl<'a> System<'a> for Sys {
|
||||
&mut controllers,
|
||||
read_data.healths.maybe(),
|
||||
&read_data.bodies,
|
||||
&read_data.physics_states,
|
||||
(
|
||||
&read_data.physics_states,
|
||||
read_data.scales.maybe(),
|
||||
&read_data.stats,
|
||||
&read_data.skill_sets,
|
||||
@ -182,6 +185,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
uid,
|
||||
char_state,
|
||||
character_activity,
|
||||
pos,
|
||||
vel,
|
||||
ori,
|
||||
@ -261,6 +265,9 @@ impl Sys {
|
||||
if *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 {
|
||||
*join.density = state_update.density
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use common::{
|
||||
comp::{
|
||||
ability::Stance,
|
||||
agent::{Sound, SoundKind},
|
||||
Body, BuffChange, ControlEvent, Controller, Pos,
|
||||
Body, BuffChange, ControlEvent, Controller, Pos, Scale,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
uid::UidAllocator,
|
||||
@ -22,6 +22,7 @@ pub struct ReadData<'a> {
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
scales: ReadStorage<'a, Scale>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -91,13 +92,15 @@ impl<'a> System<'a> for Sys {
|
||||
},
|
||||
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
|
||||
ControlEvent::Utterance(kind) => {
|
||||
if let (Some(pos), Some(body)) = (
|
||||
if let (Some(pos), Some(body), scale) = (
|
||||
read_data.positions.get(entity),
|
||||
read_data.bodies.get(entity),
|
||||
read_data.scales.get(entity),
|
||||
) {
|
||||
let sound = Sound::new(
|
||||
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
|
||||
1.0,
|
||||
);
|
||||
|
@ -68,13 +68,14 @@ impl<'a> System<'a> for Sys {
|
||||
let mut outcomes_emitter = outcomes.emitter();
|
||||
|
||||
// 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.uids,
|
||||
&read_data.positions,
|
||||
&read_data.orientations,
|
||||
&mut melee_attacks,
|
||||
&read_data.bodies,
|
||||
read_data.scales.maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -87,7 +88,7 @@ impl<'a> System<'a> for Sys {
|
||||
melee_attack.applied = true;
|
||||
|
||||
// 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 height = body.height() * scale;
|
||||
// TODO: use Capsule Prisms instead of Cylinders
|
||||
|
@ -23,7 +23,7 @@ use common::{
|
||||
item_drop,
|
||||
projectile::ProjectileConstructor,
|
||||
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller,
|
||||
HealthChange, InputKind, InventoryAction, Pos, UnresolvedChatMsg, UtteranceKind,
|
||||
HealthChange, InputKind, InventoryAction, Pos, Scale, UnresolvedChatMsg, UtteranceKind,
|
||||
},
|
||||
effect::{BuffEffect, Effect},
|
||||
event::{Emitter, ServerEvent},
|
||||
@ -514,8 +514,10 @@ impl<'a> AgentData<'a> {
|
||||
target: EcsEntity,
|
||||
) -> bool {
|
||||
if let Some(tgt_pos) = read_data.positions.get(target) {
|
||||
let eye_offset = self.body.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());
|
||||
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(read_data.scales.get(target).map_or(1.0, |s| s.0))
|
||||
});
|
||||
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(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| {
|
||||
self.detects_other(agent, controller, entity, e_pos, read_data)
|
||||
let is_detected = |entity: &EcsEntity, e_pos: &Pos, e_scale: Option<&Scale>| {
|
||||
self.detects_other(agent, controller, entity, e_pos, e_scale, read_data)
|
||||
};
|
||||
|
||||
let target = entities_nearby
|
||||
@ -805,7 +807,7 @@ impl<'a> AgentData<'a> {
|
||||
.filter_map(|(entity, 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)| {
|
||||
(
|
||||
*attack_target,
|
||||
@ -997,9 +999,11 @@ impl<'a> AgentData<'a> {
|
||||
.angle_between((tgt_data.pos.0 - self.pos.0).xy())
|
||||
.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 +
|
||||
// Special case for jumping attacks to jump at the body
|
||||
// of the target and not the ground around the target
|
||||
@ -1037,7 +1041,7 @@ impl<'a> AgentData<'a> {
|
||||
projectile_speed,
|
||||
self.pos.0
|
||||
+ 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(
|
||||
tgt_data.pos.0.x,
|
||||
@ -1062,7 +1066,7 @@ impl<'a> AgentData<'a> {
|
||||
projectile_speed,
|
||||
self.pos.0
|
||||
+ 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(
|
||||
tgt_data.pos.0.x,
|
||||
@ -1077,7 +1081,7 @@ impl<'a> AgentData<'a> {
|
||||
projectile_speed,
|
||||
self.pos.0
|
||||
+ 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(
|
||||
tgt_data.pos.0.x,
|
||||
@ -1684,6 +1688,7 @@ impl<'a> AgentData<'a> {
|
||||
controller: &Controller,
|
||||
other: EcsEntity,
|
||||
other_pos: &Pos,
|
||||
other_scale: Option<&Scale>,
|
||||
read_data: &ReadData,
|
||||
) -> bool {
|
||||
let other_stealth_multiplier = {
|
||||
@ -1708,7 +1713,15 @@ impl<'a> AgentData<'a> {
|
||||
|
||||
(within_sight_dist)
|
||||
&& 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(
|
||||
@ -1717,10 +1730,11 @@ impl<'a> AgentData<'a> {
|
||||
controller: &Controller,
|
||||
other: &EcsEntity,
|
||||
other_pos: &Pos,
|
||||
other_scale: Option<&Scale>,
|
||||
read_data: &ReadData,
|
||||
) -> bool {
|
||||
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 {
|
||||
|
@ -247,7 +247,15 @@ impl<'a> AgentData<'a> {
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
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;
|
||||
@ -374,8 +382,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -451,8 +461,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -1269,7 +1281,15 @@ impl<'a> AgentData<'a> {
|
||||
const DESIRED_ENERGY_LEVEL: f32 = 50.0;
|
||||
|
||||
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
|
||||
@ -1521,8 +1541,10 @@ impl<'a> AgentData<'a> {
|
||||
if entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
) && attack_data.angle < 45.0
|
||||
{
|
||||
@ -1574,7 +1596,15 @@ impl<'a> AgentData<'a> {
|
||||
const DESIRED_COMBO_LEVEL: u32 = 8;
|
||||
|
||||
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
|
||||
@ -1724,8 +1754,10 @@ impl<'a> AgentData<'a> {
|
||||
) && entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
) && attack_data.angle < 90.0
|
||||
{
|
||||
@ -1907,8 +1939,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -2130,8 +2164,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -2365,8 +2401,15 @@ impl<'a> AgentData<'a> {
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
||||
&& attack_data.angle < 15.0
|
||||
if entities_have_line_of_sight(
|
||||
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);
|
||||
} else {
|
||||
@ -2383,8 +2426,15 @@ impl<'a> AgentData<'a> {
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
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)
|
||||
&& attack_data.angle < 15.0
|
||||
if entities_have_line_of_sight(
|
||||
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);
|
||||
} else {
|
||||
@ -2406,8 +2456,15 @@ impl<'a> AgentData<'a> {
|
||||
.try_normalized()
|
||||
.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);
|
||||
} else {
|
||||
agent.target = None;
|
||||
@ -2467,8 +2524,10 @@ impl<'a> AgentData<'a> {
|
||||
if entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
) {
|
||||
// If close to target, use either primary or secondary ability
|
||||
@ -2564,8 +2623,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
&& attack_data.angle < 15.0
|
||||
@ -2695,8 +2756,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
&& attack_data.angle < 15.0
|
||||
@ -2931,8 +2994,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -3186,7 +3251,15 @@ impl<'a> AgentData<'a> {
|
||||
.and_then(|e| read_data.velocities.get(e))
|
||||
.map_or(0.0, |v| v.0.cross(self.ori.look_vec()).magnitude_squared());
|
||||
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) {
|
||||
@ -3263,7 +3336,15 @@ impl<'a> AgentData<'a> {
|
||||
|
||||
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
|
||||
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
|
||||
@ -3461,7 +3542,15 @@ impl<'a> AgentData<'a> {
|
||||
|
||||
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
|
||||
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
|
||||
@ -3530,7 +3619,15 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
|
||||
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());
|
||||
// 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(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -3738,8 +3837,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -3822,8 +3923,10 @@ impl<'a> AgentData<'a> {
|
||||
if entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
) && attack_data.angle < 45.0
|
||||
{
|
||||
@ -3901,8 +4004,10 @@ impl<'a> AgentData<'a> {
|
||||
} else if entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
) {
|
||||
// if enemy in mid range shoot dagon bombs and steamwave
|
||||
@ -4016,8 +4121,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
{
|
||||
@ -4177,8 +4284,10 @@ impl<'a> AgentData<'a> {
|
||||
} else if entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
) {
|
||||
// Else if in sight, barrage
|
||||
@ -4241,8 +4350,10 @@ impl<'a> AgentData<'a> {
|
||||
&& entities_have_line_of_sight(
|
||||
self.pos,
|
||||
self.body,
|
||||
self.scale,
|
||||
tgt_data.pos,
|
||||
tgt_data.body,
|
||||
tgt_data.scale,
|
||||
read_data,
|
||||
)
|
||||
&& agent.action_state.timers[DASH_TIMER] > 4.0
|
||||
|
@ -2,7 +2,7 @@ use crate::data::{ActionMode, AgentData, AttackData, Path, ReadData, TargetData}
|
||||
use common::{
|
||||
comp::{
|
||||
agent::Psyche, buff::BuffKind, inventory::item::ItemTag, item::ItemDesc, Agent, Alignment,
|
||||
Body, Controller, InputKind, Pos,
|
||||
Body, Controller, InputKind, Pos, Scale,
|
||||
},
|
||||
consts::GRAVITY,
|
||||
terrain::Block,
|
||||
@ -146,17 +146,19 @@ pub fn are_our_owners_hostile(
|
||||
pub fn entities_have_line_of_sight(
|
||||
pos: &Pos,
|
||||
body: Option<&Body>,
|
||||
scale: f32,
|
||||
other_pos: &Pos,
|
||||
other_body: Option<&Body>,
|
||||
other_scale: Option<&Scale>,
|
||||
read_data: &ReadData,
|
||||
) -> bool {
|
||||
let get_eye_pos = |pos: &Pos, body: Option<&Body>| {
|
||||
let eye_offset = body.map_or(0.0, |b| b.eye_height());
|
||||
let get_eye_pos = |pos: &Pos, body: Option<&Body>, scale: f32| {
|
||||
let eye_offset = body.map_or(0.0, |b| b.eye_height(scale));
|
||||
|
||||
Pos(pos.0.with_z(pos.0.z + eye_offset))
|
||||
};
|
||||
let eye_pos = get_eye_pos(pos, body);
|
||||
let other_eye_pos = get_eye_pos(other_pos, other_body);
|
||||
let eye_pos = get_eye_pos(pos, body, scale);
|
||||
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)
|
||||
}
|
||||
|
@ -286,6 +286,7 @@ impl StateExt for State {
|
||||
.with(poise)
|
||||
.with(comp::Alignment::Npc)
|
||||
.with(comp::CharacterState::default())
|
||||
.with(comp::CharacterActivity::default())
|
||||
.with(inventory)
|
||||
.with(comp::Buffs::default())
|
||||
.with(comp::Combo::default())
|
||||
@ -352,6 +353,7 @@ impl StateExt for State {
|
||||
.with(comp::Controller::default())
|
||||
.with(Inventory::with_empty())
|
||||
.with(comp::CharacterState::default())
|
||||
.with(comp::CharacterActivity::default())
|
||||
// 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()`
|
||||
.with(comp::Energy::new(ship.into(), 0))
|
||||
@ -559,6 +561,7 @@ impl StateExt for State {
|
||||
z_max: 1.75,
|
||||
});
|
||||
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::Buffs::default());
|
||||
self.write_component_ignore_entity_dead(entity, comp::Auras::default());
|
||||
|
@ -139,11 +139,12 @@ impl BehaviorTree {
|
||||
handle_inbox_trade_accepted,
|
||||
handle_inbox_finished_trade,
|
||||
handle_inbox_update_pending_trade,
|
||||
handle_timed_events,
|
||||
]);
|
||||
Self { tree }
|
||||
} else {
|
||||
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()
|
||||
&& let Some(target) = bdata.read_data.lookup_actor(actor)
|
||||
&& 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(
|
||||
target,
|
||||
@ -486,6 +486,8 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
|
||||
false,
|
||||
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_utterance(UtteranceKind::Greeting);
|
||||
@ -497,15 +499,20 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
|
||||
.agent
|
||||
.timer
|
||||
.start(bdata.read_data.time.0, TimerAction::Interact);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
NpcAction::Say(msg) => {
|
||||
bdata.controller.push_utterance(UtteranceKind::Greeting);
|
||||
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
|
||||
@ -571,7 +578,14 @@ fn update_last_known_pos(bdata: &mut BehaviorData) -> bool {
|
||||
let target = target_info.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 Target {
|
||||
@ -631,9 +645,10 @@ fn update_target_awareness(bdata: &mut BehaviorData) -> bool {
|
||||
|
||||
let target = agent.target.map(|t| t.target);
|
||||
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 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);
|
||||
} else if agent_data.can_sense_directly_near(tgt_pos) {
|
||||
agent.awareness.change_by(0.25);
|
||||
|
@ -97,203 +97,190 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
false,
|
||||
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::Talk);
|
||||
controller.push_utterance(UtteranceKind::Greeting);
|
||||
controller.push_action(ControlAction::Stand);
|
||||
controller.push_action(ControlAction::Talk);
|
||||
controller.push_utterance(UtteranceKind::Greeting);
|
||||
|
||||
match subject {
|
||||
Subject::Regular => {
|
||||
if let Some(tgt_stats) = read_data.stats.get(target) {
|
||||
if let Some(destination_name) = &agent.rtsim_controller.heading_to {
|
||||
let personality = &agent.rtsim_controller.personality;
|
||||
let standard_response_msg = || -> String {
|
||||
if personality.will_ambush() {
|
||||
match subject {
|
||||
Subject::Regular => {
|
||||
if let Some(tgt_stats) = read_data.stats.get(target) {
|
||||
if let Some(destination_name) = &agent.rtsim_controller.heading_to {
|
||||
let personality = &agent.rtsim_controller.personality;
|
||||
let standard_response_msg = || -> String {
|
||||
if personality.will_ambush() {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along? We'll make \
|
||||
great travel buddies, hehe.",
|
||||
destination_name
|
||||
)
|
||||
} else if personality.is(PersonalityTrait::Extroverted) {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along?",
|
||||
destination_name
|
||||
)
|
||||
} else if personality.is(PersonalityTrait::Disagreeable) {
|
||||
"Hrm.".to_string()
|
||||
} else {
|
||||
"Hello!".to_string()
|
||||
}
|
||||
};
|
||||
let msg = if false
|
||||
/* TODO: Remembers character */
|
||||
{
|
||||
if personality.will_ambush() {
|
||||
"Just follow me a bit more, hehe.".to_string()
|
||||
} else if personality.is(PersonalityTrait::Extroverted) {
|
||||
if personality.is(PersonalityTrait::Extroverted) {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along? We'll \
|
||||
make great travel buddies, hehe.",
|
||||
destination_name
|
||||
)
|
||||
} else if personality.is(PersonalityTrait::Extroverted) {
|
||||
format!(
|
||||
"I'm heading to {}! Want to come along?",
|
||||
destination_name
|
||||
"Greetings fair {}! It has been far too long \
|
||||
since last I saw you. I'm going to {} right now.",
|
||||
&tgt_stats.name, destination_name
|
||||
)
|
||||
} else if personality.is(PersonalityTrait::Disagreeable) {
|
||||
"Hrm.".to_string()
|
||||
"Oh. It's you again.".to_string()
|
||||
} else {
|
||||
"Hello!".to_string()
|
||||
}
|
||||
};
|
||||
let msg = if false
|
||||
/* TODO: Remembers character */
|
||||
{
|
||||
if personality.will_ambush() {
|
||||
"Just follow me a bit more, hehe.".to_string()
|
||||
} else if personality.is(PersonalityTrait::Extroverted) {
|
||||
if personality.is(PersonalityTrait::Extroverted) {
|
||||
format!(
|
||||
"Greetings fair {}! It has been far too long \
|
||||
since last I saw you. I'm going to {} right \
|
||||
now.",
|
||||
&tgt_stats.name, destination_name
|
||||
)
|
||||
} else if personality.is(PersonalityTrait::Disagreeable)
|
||||
{
|
||||
"Oh. It's you again.".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"Hi again {}! Unfortunately I'm in a hurry \
|
||||
right now. See you!",
|
||||
&tgt_stats.name
|
||||
)
|
||||
}
|
||||
} else {
|
||||
standard_response_msg()
|
||||
format!(
|
||||
"Hi again {}! Unfortunately I'm in a hurry right \
|
||||
now. See you!",
|
||||
&tgt_stats.name
|
||||
)
|
||||
}
|
||||
} else {
|
||||
standard_response_msg()
|
||||
}
|
||||
} else {
|
||||
standard_response_msg()
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
if let Some(extreme_trait) =
|
||||
agent.rtsim_controller.personality.chat_trait(&mut rng)
|
||||
{
|
||||
let msg = match extreme_trait {
|
||||
PersonalityTrait::Open => "npc-speech-villager_open",
|
||||
PersonalityTrait::Adventurous => {
|
||||
"npc-speech-villager_adventurous"
|
||||
},
|
||||
PersonalityTrait::Closed => "npc-speech-villager_closed",
|
||||
PersonalityTrait::Conscientious => {
|
||||
"npc-speech-villager_conscientious"
|
||||
},
|
||||
PersonalityTrait::Busybody => {
|
||||
"npc-speech-villager_busybody"
|
||||
},
|
||||
PersonalityTrait::Unconscientious => {
|
||||
"npc-speech-villager_unconscientious"
|
||||
},
|
||||
PersonalityTrait::Extroverted => {
|
||||
"npc-speech-villager_extroverted"
|
||||
},
|
||||
PersonalityTrait::Introverted => {
|
||||
"npc-speech-villager_introverted"
|
||||
},
|
||||
PersonalityTrait::Agreeable => {
|
||||
"npc-speech-villager_agreeable"
|
||||
},
|
||||
PersonalityTrait::Sociable => {
|
||||
"npc-speech-villager_sociable"
|
||||
},
|
||||
PersonalityTrait::Disagreeable => {
|
||||
"npc-speech-villager_disagreeable"
|
||||
},
|
||||
PersonalityTrait::Neurotic => {
|
||||
"npc-speech-villager_neurotic"
|
||||
},
|
||||
PersonalityTrait::Seeker => "npc-speech-villager_seeker",
|
||||
PersonalityTrait::SadLoner => {
|
||||
"npc-speech-villager_sad_loner"
|
||||
},
|
||||
PersonalityTrait::Worried => "npc-speech-villager_worried",
|
||||
PersonalityTrait::Stable => "npc-speech-villager_stable",
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
if let Some(extreme_trait) =
|
||||
agent.rtsim_controller.personality.chat_trait(&mut rng)
|
||||
{
|
||||
let msg = match extreme_trait {
|
||||
PersonalityTrait::Open => "npc-speech-villager_open",
|
||||
PersonalityTrait::Adventurous => {
|
||||
"npc-speech-villager_adventurous"
|
||||
},
|
||||
PersonalityTrait::Closed => {
|
||||
"npc-speech-villager_closed"
|
||||
},
|
||||
PersonalityTrait::Conscientious => {
|
||||
"npc-speech-villager_conscientious"
|
||||
},
|
||||
PersonalityTrait::Busybody => {
|
||||
"npc-speech-villager_busybody"
|
||||
},
|
||||
PersonalityTrait::Unconscientious => {
|
||||
"npc-speech-villager_unconscientious"
|
||||
},
|
||||
PersonalityTrait::Extroverted => {
|
||||
"npc-speech-villager_extroverted"
|
||||
},
|
||||
PersonalityTrait::Introverted => {
|
||||
"npc-speech-villager_introverted"
|
||||
},
|
||||
PersonalityTrait::Agreeable => {
|
||||
"npc-speech-villager_agreeable"
|
||||
},
|
||||
PersonalityTrait::Sociable => {
|
||||
"npc-speech-villager_sociable"
|
||||
},
|
||||
PersonalityTrait::Disagreeable => {
|
||||
"npc-speech-villager_disagreeable"
|
||||
},
|
||||
PersonalityTrait::Neurotic => {
|
||||
"npc-speech-villager_neurotic"
|
||||
},
|
||||
PersonalityTrait::Seeker => {
|
||||
"npc-speech-villager_seeker"
|
||||
},
|
||||
PersonalityTrait::SadLoner => {
|
||||
"npc-speech-villager_sad_loner"
|
||||
},
|
||||
PersonalityTrait::Worried => {
|
||||
"npc-speech-villager_worried"
|
||||
},
|
||||
PersonalityTrait::Stable => {
|
||||
"npc-speech-villager_stable"
|
||||
},
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
} else {
|
||||
agent_data.chat_npc("npc-speech-villager", event_emitter);
|
||||
}
|
||||
agent_data.chat_npc("npc-speech-villager", event_emitter);
|
||||
}
|
||||
}
|
||||
},
|
||||
Subject::Trade => {
|
||||
if agent.behavior.can_trade(agent_data.alignment.copied(), by) {
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
controller.push_initiate_invite(by, InviteKind::Trade);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_advertisement",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
} else {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_busy",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// TODO: maybe make some travellers willing to trade with
|
||||
// simpler goods like potions
|
||||
}
|
||||
},
|
||||
Subject::Trade => {
|
||||
if agent.behavior.can_trade(agent_data.alignment.copied(), by) {
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
controller.push_initiate_invite(by, InviteKind::Trade);
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_decline_trade",
|
||||
"npc-speech-merchant_advertisement",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
} else {
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-merchant_busy",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
}
|
||||
},
|
||||
Subject::Mood => {
|
||||
// TODO: Reimplement in rtsim2
|
||||
},
|
||||
Subject::Location(location) => {
|
||||
if let Some(tgt_pos) = read_data.positions.get(target) {
|
||||
let raw_dir = location.origin.as_::<f32>() - tgt_pos.0.xy();
|
||||
let dist = Distance::from_dir(raw_dir).name();
|
||||
let dir = Direction::from_dir(raw_dir).name();
|
||||
} else {
|
||||
// TODO: maybe make some travellers willing to trade with
|
||||
// simpler goods like potions
|
||||
agent_data.chat_npc_if_allowed_to_speak(
|
||||
"npc-speech-villager_decline_trade",
|
||||
agent,
|
||||
event_emitter,
|
||||
);
|
||||
}
|
||||
},
|
||||
Subject::Mood => {
|
||||
// TODO: Reimplement in rtsim2
|
||||
},
|
||||
Subject::Location(location) => {
|
||||
if let Some(tgt_pos) = read_data.positions.get(target) {
|
||||
let raw_dir = location.origin.as_::<f32>() - tgt_pos.0.xy();
|
||||
let dist = Distance::from_dir(raw_dir).name();
|
||||
let dir = Direction::from_dir(raw_dir).name();
|
||||
|
||||
let msg = format!(
|
||||
"{} ? I think it's {} {} from here!",
|
||||
location.name, dist, dir
|
||||
);
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
}
|
||||
},
|
||||
Subject::Person(person) => {
|
||||
if let Some(src_pos) = read_data.positions.get(target) {
|
||||
let msg = if let Some(person_pos) = person.origin {
|
||||
let distance =
|
||||
Distance::from_dir(person_pos.xy() - src_pos.0.xy());
|
||||
match distance {
|
||||
Distance::NextTo | Distance::Near => {
|
||||
format!(
|
||||
"{} ? I think he's {} {} from here!",
|
||||
person.name(),
|
||||
distance.name(),
|
||||
Direction::from_dir(
|
||||
person_pos.xy() - src_pos.0.xy(),
|
||||
)
|
||||
let msg = format!(
|
||||
"{} ? I think it's {} {} from here!",
|
||||
location.name, dist, dir
|
||||
);
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
}
|
||||
},
|
||||
Subject::Person(person) => {
|
||||
if let Some(src_pos) = read_data.positions.get(target) {
|
||||
let msg = if let Some(person_pos) = person.origin {
|
||||
let distance = Distance::from_dir(person_pos.xy() - src_pos.0.xy());
|
||||
match distance {
|
||||
Distance::NextTo | Distance::Near => {
|
||||
format!(
|
||||
"{} ? I think he's {} {} from here!",
|
||||
person.name(),
|
||||
distance.name(),
|
||||
Direction::from_dir(person_pos.xy() - src_pos.0.xy(),)
|
||||
.name()
|
||||
)
|
||||
},
|
||||
_ => {
|
||||
format!(
|
||||
"{} ? I think he's gone visiting another town. \
|
||||
Come back later!",
|
||||
person.name()
|
||||
)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"{} ? Sorry, I don't know where you can find him.",
|
||||
person.name()
|
||||
)
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
}
|
||||
},
|
||||
Subject::Work => {},
|
||||
}
|
||||
)
|
||||
},
|
||||
_ => {
|
||||
format!(
|
||||
"{} ? I think he's gone visiting another town. Come \
|
||||
back later!",
|
||||
person.name()
|
||||
)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"{} ? Sorry, I don't know where you can find him.",
|
||||
person.name()
|
||||
)
|
||||
};
|
||||
agent_data.chat_npc(msg, event_emitter);
|
||||
}
|
||||
},
|
||||
Subject::Work => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ impl Animation for TalkAnimation {
|
||||
let slowb = (anim_time * 4.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(
|
||||
-s_a.hand.0 + 0.5 + slowb * 0.5,
|
||||
s_a.hand.1 + 5.0 + slowc * 1.0,
|
||||
|
@ -346,8 +346,19 @@ pub fn maintain_egui_inner(
|
||||
ui.label("Body");
|
||||
ui.label("Poise");
|
||||
ui.label("Character State");
|
||||
ui.label("Character Activity");
|
||||
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.read_storage::<Body>().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::<Poise>().maybe(),
|
||||
ecs.read_storage::<comp::CharacterState>().maybe(),
|
||||
ecs.read_storage::<comp::CharacterActivity>().maybe(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, _, _, pos, _, _, _, _)| {
|
||||
client_pos.map_or(true, |client_pos| {
|
||||
pos.map_or(0.0, |pos| pos.0.distance_squared(client_pos.0))
|
||||
< max_entity_distance
|
||||
})
|
||||
})
|
||||
.filter(
|
||||
|(_, _, _, pos, _, _, _, _, _)| {
|
||||
client_pos.map_or(true, |client_pos| {
|
||||
pos.map_or(0.0, |pos| {
|
||||
pos.0.distance_squared(client_pos.0)
|
||||
}) < max_entity_distance
|
||||
})
|
||||
},
|
||||
)
|
||||
{
|
||||
if ui.button("View").clicked() {
|
||||
previous_selected_entity =
|
||||
@ -420,6 +435,12 @@ pub fn maintain_egui_inner(
|
||||
ui.label("-");
|
||||
}
|
||||
|
||||
if let Some(character_activity) = character_activity {
|
||||
ui.label(format!("{:?}", character_activity));
|
||||
} else {
|
||||
ui.label("-");
|
||||
}
|
||||
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
@ -507,11 +528,11 @@ fn selected_entity_window(
|
||||
buffs,
|
||||
auras,
|
||||
character_state,
|
||||
character_activity,
|
||||
physics_state,
|
||||
alignment,
|
||||
scale,
|
||||
mass,
|
||||
(density, health, energy),
|
||||
(mass, density, health, energy),
|
||||
) in (
|
||||
&ecs.entities(),
|
||||
ecs.read_storage::<Body>().maybe(),
|
||||
@ -523,18 +544,19 @@ fn selected_entity_window(
|
||||
ecs.read_storage::<comp::Buffs>().maybe(),
|
||||
ecs.read_storage::<comp::Auras>().maybe(),
|
||||
ecs.read_storage::<comp::CharacterState>().maybe(),
|
||||
ecs.read_storage::<comp::CharacterActivity>().maybe(),
|
||||
ecs.read_storage::<comp::PhysicsState>().maybe(),
|
||||
ecs.read_storage::<comp::Alignment>().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::Health>().maybe(),
|
||||
ecs.read_storage::<comp::Energy>().maybe(),
|
||||
),
|
||||
)
|
||||
.join()
|
||||
.filter(|(e, _, _, _, _, _, _, _, _, _, _, _, _, _, (_, _, _))| e.id() == entity_id)
|
||||
.filter(|(e, _, _, _, _, _, _, _, _, _, _, _, _, _, (_, _, _, _))| e.id() == entity_id)
|
||||
{
|
||||
let time = ecs.read_resource::<Time>();
|
||||
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 {
|
||||
CollapsingHeader::new("Physics State").default_open(false).show(ui, |ui| {
|
||||
Grid::new("selected_entity_physics_state_grid")
|
||||
|
@ -33,9 +33,9 @@ use common::{
|
||||
comp::{
|
||||
inventory::slot::EquipSlot,
|
||||
item::{tool::AbilityContext, Hands, ItemKind, ToolKind},
|
||||
ship, Body, CharacterState, Collider, Controller, Health, Inventory, Item, ItemKey, Last,
|
||||
LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, SkillSet, Stance,
|
||||
Vel,
|
||||
ship, Body, CharacterActivity, CharacterState, Collider, Controller, Health, Inventory,
|
||||
Item, ItemKey, Last, LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos,
|
||||
Scale, SkillSet, Stance, Vel,
|
||||
},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
@ -741,14 +741,14 @@ impl FigureMgr {
|
||||
scale,
|
||||
body,
|
||||
character,
|
||||
character_activity,
|
||||
last_character,
|
||||
physics,
|
||||
health,
|
||||
inventory,
|
||||
item,
|
||||
light_emitter,
|
||||
is_rider,
|
||||
(collider, stance, skillset),
|
||||
(is_rider, collider, stance, skillset),
|
||||
),
|
||||
) in (
|
||||
&ecs.entities(),
|
||||
@ -759,14 +759,15 @@ impl FigureMgr {
|
||||
ecs.read_storage::<Scale>().maybe(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
ecs.read_storage::<CharacterState>().maybe(),
|
||||
ecs.read_storage::<CharacterActivity>().maybe(),
|
||||
ecs.read_storage::<Last<CharacterState>>().maybe(),
|
||||
&ecs.read_storage::<PhysicsState>(),
|
||||
ecs.read_storage::<Health>().maybe(),
|
||||
ecs.read_storage::<Inventory>().maybe(),
|
||||
ecs.read_storage::<Item>().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::<Stance>().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)
|
||||
/ 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 viewpoint_camera_mode = if is_viewpoint {
|
||||
camera_mode
|
||||
|
@ -547,7 +547,7 @@ impl Scene {
|
||||
(
|
||||
matches!(b, comp::Body::Humanoid(_)),
|
||||
b.height(),
|
||||
b.eye_height(),
|
||||
b.eye_height(1.0), // Scale is applied later
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -12,7 +12,7 @@ use common::{
|
||||
assets::{AssetExt, DotVoxAsset},
|
||||
comp::{
|
||||
self, aura, beam, body, buff, item::Reagent, object, shockwave, BeamSegment, Body,
|
||||
CharacterState, Ori, Pos, Shockwave, Vel,
|
||||
CharacterState, Ori, Pos, Scale, Shockwave, Vel,
|
||||
},
|
||||
figure::Segment,
|
||||
outcome::Outcome,
|
||||
@ -1264,12 +1264,13 @@ impl ParticleMgr {
|
||||
let time = state.get_time();
|
||||
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::<Pos>(),
|
||||
&ecs.read_storage::<comp::Buffs>(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
&ecs.read_storage::<Ori>(),
|
||||
ecs.read_storage::<Scale>().maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -1328,7 +1329,8 @@ impl ParticleMgr {
|
||||
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) =
|
||||
(rng.gen_range(0.0f32..1.0).sqrt(), rng.gen_range(0.0..TAU));
|
||||
let end_pos = pos
|
||||
@ -1393,7 +1395,9 @@ impl ParticleMgr {
|
||||
+ multiplicity
|
||||
* 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
|
||||
+ Vec3::<f32>::zero()
|
||||
.map(|_| rng.gen_range(-1.0..1.0))
|
||||
|
Loading…
Reference in New Issue
Block a user