mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Fix pet idle movement, add a way to configure agent behaviour through manifests
This commit is contained in:
parent
3afeca67c5
commit
52b5967914
@ -7,5 +7,8 @@
|
|||||||
inventory: (
|
inventory: (
|
||||||
loadout: FromBody,
|
loadout: FromBody,
|
||||||
),
|
),
|
||||||
|
agent: (
|
||||||
|
idle_wander_factor: 0.0,
|
||||||
|
),
|
||||||
meta: [],
|
meta: [],
|
||||||
)
|
)
|
||||||
|
@ -252,6 +252,9 @@ pub struct Psyche {
|
|||||||
/// than `sight_dist`. `None` implied that the agent is always aggro
|
/// than `sight_dist`. `None` implied that the agent is always aggro
|
||||||
/// towards enemies that it is aware of.
|
/// towards enemies that it is aware of.
|
||||||
pub aggro_dist: Option<f32>,
|
pub aggro_dist: Option<f32>,
|
||||||
|
/// A factor that controls how much further an agent will wander when in the
|
||||||
|
/// idle state. `1.0` is normal.
|
||||||
|
pub idle_wander_factor: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a Body> for Psyche {
|
impl<'a> From<&'a Body> for Psyche {
|
||||||
@ -369,6 +372,7 @@ impl<'a> From<&'a Body> for Psyche {
|
|||||||
Body::Humanoid(_) => Some(20.0),
|
Body::Humanoid(_) => Some(20.0),
|
||||||
_ => None, // Always aggressive if detected
|
_ => None, // Always aggressive if detected
|
||||||
},
|
},
|
||||||
|
idle_wander_factor: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -710,6 +714,12 @@ impl Agent {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_idle_wander_factor(mut self, idle_wander_factor: f32) -> Self {
|
||||||
|
self.psyche.idle_wander_factor = idle_wander_factor;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_position_pid_controller(
|
pub fn with_position_pid_controller(
|
||||||
mut self,
|
mut self,
|
||||||
|
@ -38,6 +38,14 @@ impl Default for AlignmentMark {
|
|||||||
fn default() -> Self { Self::Alignment(Alignment::Wild) }
|
fn default() -> Self { Self::Alignment(Alignment::Wild) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct AgentConfig {
|
||||||
|
pub has_agency: Option<bool>,
|
||||||
|
pub no_flee: Option<bool>,
|
||||||
|
pub idle_wander_factor: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub enum LoadoutKind {
|
pub enum LoadoutKind {
|
||||||
FromBody,
|
FromBody,
|
||||||
@ -109,6 +117,10 @@ pub struct EntityConfig {
|
|||||||
/// Alignment, can be Uninit
|
/// Alignment, can be Uninit
|
||||||
pub alignment: AlignmentMark,
|
pub alignment: AlignmentMark,
|
||||||
|
|
||||||
|
/// Parameterises agent behaviour
|
||||||
|
#[serde(default)]
|
||||||
|
pub agent: AgentConfig,
|
||||||
|
|
||||||
/// Loot
|
/// Loot
|
||||||
/// See LootSpec in lottery
|
/// See LootSpec in lottery
|
||||||
pub loot: LootSpec<String>,
|
pub loot: LootSpec<String>,
|
||||||
@ -154,11 +166,12 @@ pub fn try_all_entity_configs() -> Result<Vec<String>, Error> {
|
|||||||
pub struct EntityInfo {
|
pub struct EntityInfo {
|
||||||
pub pos: Vec3<f32>,
|
pub pos: Vec3<f32>,
|
||||||
pub is_waypoint: bool, // Edge case, overrides everything else
|
pub is_waypoint: bool, // Edge case, overrides everything else
|
||||||
// Agent
|
|
||||||
pub has_agency: bool,
|
|
||||||
pub alignment: Alignment,
|
pub alignment: Alignment,
|
||||||
|
/// Parameterises agent behaviour
|
||||||
|
pub has_agency: bool,
|
||||||
pub agent_mark: Option<agent::Mark>,
|
pub agent_mark: Option<agent::Mark>,
|
||||||
pub no_flee: bool,
|
pub no_flee: bool,
|
||||||
|
pub idle_wander_factor: f32,
|
||||||
// Stats
|
// Stats
|
||||||
pub body: Body,
|
pub body: Body,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
@ -186,9 +199,13 @@ impl EntityInfo {
|
|||||||
Self {
|
Self {
|
||||||
pos,
|
pos,
|
||||||
is_waypoint: false,
|
is_waypoint: false,
|
||||||
has_agency: true,
|
|
||||||
alignment: Alignment::Wild,
|
alignment: Alignment::Wild,
|
||||||
|
|
||||||
|
has_agency: true,
|
||||||
agent_mark: None,
|
agent_mark: None,
|
||||||
|
no_flee: false,
|
||||||
|
idle_wander_factor: 1.0,
|
||||||
|
|
||||||
body: Body::Humanoid(humanoid::Body::random()),
|
body: Body::Humanoid(humanoid::Body::random()),
|
||||||
name: None,
|
name: None,
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
@ -199,7 +216,6 @@ impl EntityInfo {
|
|||||||
skillset_asset: None,
|
skillset_asset: None,
|
||||||
pet: None,
|
pet: None,
|
||||||
trading_information: None,
|
trading_information: None,
|
||||||
no_flee: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +246,7 @@ impl EntityInfo {
|
|||||||
name,
|
name,
|
||||||
body,
|
body,
|
||||||
alignment,
|
alignment,
|
||||||
|
agent,
|
||||||
inventory,
|
inventory,
|
||||||
loot,
|
loot,
|
||||||
meta,
|
meta,
|
||||||
@ -270,6 +287,16 @@ impl EntityInfo {
|
|||||||
// NOTE: set loadout after body, as it's used with default equipement
|
// NOTE: set loadout after body, as it's used with default equipement
|
||||||
self = self.with_inventory(inventory, config_asset, loadout_rng);
|
self = self.with_inventory(inventory, config_asset, loadout_rng);
|
||||||
|
|
||||||
|
// Prefer the new configuration, if possible
|
||||||
|
let AgentConfig {
|
||||||
|
has_agency,
|
||||||
|
no_flee,
|
||||||
|
idle_wander_factor,
|
||||||
|
} = agent;
|
||||||
|
self.has_agency = has_agency.unwrap_or(self.has_agency);
|
||||||
|
self.no_flee = no_flee.unwrap_or(self.no_flee);
|
||||||
|
self.idle_wander_factor = idle_wander_factor.unwrap_or(self.idle_wander_factor);
|
||||||
|
|
||||||
for field in meta {
|
for field in meta {
|
||||||
match field {
|
match field {
|
||||||
Meta::SkillSetAsset(asset) => {
|
Meta::SkillSetAsset(asset) => {
|
||||||
@ -619,11 +646,12 @@ mod tests {
|
|||||||
for config_asset in entity_configs {
|
for config_asset in entity_configs {
|
||||||
let EntityConfig {
|
let EntityConfig {
|
||||||
body,
|
body,
|
||||||
|
agent: _,
|
||||||
inventory,
|
inventory,
|
||||||
name,
|
name,
|
||||||
loot,
|
loot,
|
||||||
meta,
|
meta,
|
||||||
alignment: _alignment, // can't fail if serialized, it's a boring enum
|
alignment: _, // can't fail if serialized, it's a boring enum
|
||||||
} = EntityConfig::from_asset_expect_owned(&config_asset);
|
} = EntityConfig::from_asset_expect_owned(&config_asset);
|
||||||
|
|
||||||
validate_body(&body, &config_asset);
|
validate_body(&body, &config_asset);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
consts::{
|
consts::{
|
||||||
AVG_FOLLOW_DIST, DEFAULT_ATTACK_RANGE, IDLE_HEALING_ITEM_THRESHOLD, PARTIAL_PATH_DIST,
|
AVG_FOLLOW_DIST, DEFAULT_ATTACK_RANGE, IDLE_HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST,
|
||||||
SEPARATION_BIAS, SEPARATION_DIST, STD_AWARENESS_DECAY_RATE,
|
PARTIAL_PATH_DIST, SEPARATION_BIAS, SEPARATION_DIST, STD_AWARENESS_DECAY_RATE,
|
||||||
},
|
},
|
||||||
data::{AgentData, AttackData, Path, ReadData, Tactic, TargetData},
|
data::{AgentData, AttackData, Path, ReadData, Tactic, TargetData},
|
||||||
util::{
|
util::{
|
||||||
@ -442,11 +442,26 @@ impl<'a> AgentData<'a> {
|
|||||||
controller.push_basic_input(InputKind::Jump);
|
controller.push_basic_input(InputKind::Jump);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
agent.bearing += Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 0.1
|
|
||||||
- agent.bearing * 0.003
|
let diff = Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5);
|
||||||
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
|
agent.bearing += (diff * 0.1 - agent.bearing * 0.01)
|
||||||
(self.pos.0 - patrol_origin).xy() * 0.0002
|
* agent.psyche.idle_wander_factor.max(0.0).sqrt();
|
||||||
});
|
if let Some(patrol_origin) = agent.patrol_origin
|
||||||
|
// Use owner as patrol origin otherwise
|
||||||
|
.or_else(|| if let Some(Alignment::Owned(owner_uid)) = self.alignment
|
||||||
|
&& let Some(owner) = get_entity_by_id(owner_uid.id(), read_data)
|
||||||
|
&& let Some(pos) = read_data.positions.get(owner)
|
||||||
|
{
|
||||||
|
Some(pos.0)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
{
|
||||||
|
agent.bearing += ((patrol_origin.xy() - self.pos.0.xy())
|
||||||
|
/ (0.01 + MAX_PATROL_DIST * agent.psyche.idle_wander_factor))
|
||||||
|
* 0.015
|
||||||
|
* agent.psyche.idle_wander_factor;
|
||||||
|
}
|
||||||
|
|
||||||
// Stop if we're too close to a wall
|
// Stop if we're too close to a wall
|
||||||
// or about to walk off a cliff
|
// or about to walk off a cliff
|
||||||
@ -491,7 +506,7 @@ impl<'a> AgentData<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if agent.bearing.magnitude_squared() > 0.5f32.powi(2) {
|
if agent.bearing.magnitude_squared() > 0.5f32.powi(2) {
|
||||||
controller.inputs.move_dir = agent.bearing * 0.65;
|
controller.inputs.move_dir = agent.bearing;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put away weapon
|
// Put away weapon
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
pub const DAMAGE_MEMORY_DURATION: f64 = 0.25;
|
pub const DAMAGE_MEMORY_DURATION: f64 = 0.25;
|
||||||
pub const FLEE_DURATION: f32 = 3.0;
|
pub const FLEE_DURATION: f32 = 3.0;
|
||||||
pub const NPC_PICKUP_RANGE: f32 = 2.5;
|
pub const NPC_PICKUP_RANGE: f32 = 2.5;
|
||||||
pub const MAX_FOLLOW_DIST: f32 = 12.0;
|
pub const MAX_PATROL_DIST: f32 = 50.0;
|
||||||
pub const MAX_PATH_DIST: f32 = 170.0;
|
pub const MAX_PATH_DIST: f32 = 170.0;
|
||||||
pub const PARTIAL_PATH_DIST: f32 = 50.0;
|
pub const PARTIAL_PATH_DIST: f32 = 50.0;
|
||||||
pub const SEPARATION_DIST: f32 = 10.0;
|
pub const SEPARATION_DIST: f32 = 10.0;
|
||||||
|
@ -1506,8 +1506,10 @@ fn handle_spawn(
|
|||||||
let pos = position(server, target, "target")?;
|
let pos = position(server, target, "target")?;
|
||||||
let mut agent = comp::Agent::from_body(&body());
|
let mut agent = comp::Agent::from_body(&body());
|
||||||
|
|
||||||
// If unowned, the agent should stay in a particular place
|
if matches!(alignment, comp::Alignment::Owned(_)) {
|
||||||
if !matches!(alignment, comp::Alignment::Owned(_)) {
|
agent.psyche.idle_wander_factor = 0.25;
|
||||||
|
} else {
|
||||||
|
// If unowned, the agent should stay in a particular place
|
||||||
agent = agent.with_patrol_origin(pos.0);
|
agent = agent.with_patrol_origin(pos.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +54,14 @@ fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet:
|
|||||||
|
|
||||||
// Create an agent for this entity using its body
|
// Create an agent for this entity using its body
|
||||||
if let Some(body) = ecs.read_storage().get(pet_entity) {
|
if let Some(body) = ecs.read_storage().get(pet_entity) {
|
||||||
|
// Pets can trade with their owner
|
||||||
let mut agent = Agent::from_body(body).with_behavior(
|
let mut agent = Agent::from_body(body).with_behavior(
|
||||||
Behavior::default().maybe_with_capabilities(Some(BehaviorCapability::TRADE)),
|
Behavior::default().maybe_with_capabilities(Some(BehaviorCapability::TRADE)),
|
||||||
);
|
);
|
||||||
agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
|
agent.behavior.trading_behavior = TradingBehavior::AcceptFood;
|
||||||
|
// Pets shouldn't wander too far from their owner
|
||||||
|
agent.psyche.idle_wander_factor = 0.25;
|
||||||
|
agent.patrol_origin = None;
|
||||||
let _ = ecs.write_storage().insert(pet_entity, agent);
|
let _ = ecs.write_storage().insert(pet_entity, agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ use self::interaction::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
consts::{
|
consts::{
|
||||||
DAMAGE_MEMORY_DURATION, FLEE_DURATION, HEALING_ITEM_THRESHOLD, MAX_FOLLOW_DIST,
|
DAMAGE_MEMORY_DURATION, FLEE_DURATION, HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST,
|
||||||
NORMAL_FLEE_DIR_DIST, NPC_PICKUP_RANGE, RETARGETING_THRESHOLD_SECONDS,
|
NORMAL_FLEE_DIR_DIST, NPC_PICKUP_RANGE, RETARGETING_THRESHOLD_SECONDS,
|
||||||
STD_AWARENESS_DECAY_RATE,
|
STD_AWARENESS_DECAY_RATE,
|
||||||
},
|
},
|
||||||
@ -415,7 +415,7 @@ fn follow_if_far_away(bdata: &mut BehaviorData) -> bool {
|
|||||||
if let Some(tgt_pos) = bdata.read_data.positions.get(target) {
|
if let Some(tgt_pos) = bdata.read_data.positions.get(target) {
|
||||||
let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0);
|
let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0);
|
||||||
|
|
||||||
if dist_sqrd > (MAX_FOLLOW_DIST).powi(2) {
|
if dist_sqrd > (MAX_PATROL_DIST * bdata.agent.psyche.idle_wander_factor).powi(2) {
|
||||||
bdata
|
bdata
|
||||||
.agent_data
|
.agent_data
|
||||||
.follow(bdata.agent, bdata.controller, bdata.read_data, tgt_pos);
|
.follow(bdata.agent, bdata.controller, bdata.read_data, tgt_pos);
|
||||||
|
@ -426,6 +426,7 @@ impl NpcData {
|
|||||||
agent_mark,
|
agent_mark,
|
||||||
alignment,
|
alignment,
|
||||||
no_flee,
|
no_flee,
|
||||||
|
idle_wander_factor,
|
||||||
// stats
|
// stats
|
||||||
body,
|
body,
|
||||||
name,
|
name,
|
||||||
@ -518,7 +519,9 @@ impl NpcData {
|
|||||||
agent = agent.with_patrol_origin(pos);
|
agent = agent.with_patrol_origin(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
agent.with_no_flee_if(matches!(agent_mark, Some(agent::Mark::Guard)) || no_flee)
|
agent
|
||||||
|
.with_no_flee_if(matches!(agent_mark, Some(agent::Mark::Guard)) || no_flee)
|
||||||
|
.with_idle_wander_factor(idle_wander_factor)
|
||||||
});
|
});
|
||||||
|
|
||||||
let agent = if matches!(alignment, comp::Alignment::Enemy)
|
let agent = if matches!(alignment, comp::Alignment::Enemy)
|
||||||
|
Loading…
Reference in New Issue
Block a user