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: (
|
||||
loadout: FromBody,
|
||||
),
|
||||
agent: (
|
||||
idle_wander_factor: 0.0,
|
||||
),
|
||||
meta: [],
|
||||
)
|
@ -252,6 +252,9 @@ pub struct Psyche {
|
||||
/// than `sight_dist`. `None` implied that the agent is always aggro
|
||||
/// towards enemies that it is aware of.
|
||||
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 {
|
||||
@ -369,6 +372,7 @@ impl<'a> From<&'a Body> for Psyche {
|
||||
Body::Humanoid(_) => Some(20.0),
|
||||
_ => None, // Always aggressive if detected
|
||||
},
|
||||
idle_wander_factor: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -710,6 +714,12 @@ impl Agent {
|
||||
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]
|
||||
pub fn with_position_pid_controller(
|
||||
mut self,
|
||||
|
@ -38,6 +38,14 @@ impl Default for AlignmentMark {
|
||||
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)]
|
||||
pub enum LoadoutKind {
|
||||
FromBody,
|
||||
@ -109,6 +117,10 @@ pub struct EntityConfig {
|
||||
/// Alignment, can be Uninit
|
||||
pub alignment: AlignmentMark,
|
||||
|
||||
/// Parameterises agent behaviour
|
||||
#[serde(default)]
|
||||
pub agent: AgentConfig,
|
||||
|
||||
/// Loot
|
||||
/// See LootSpec in lottery
|
||||
pub loot: LootSpec<String>,
|
||||
@ -154,11 +166,12 @@ pub fn try_all_entity_configs() -> Result<Vec<String>, Error> {
|
||||
pub struct EntityInfo {
|
||||
pub pos: Vec3<f32>,
|
||||
pub is_waypoint: bool, // Edge case, overrides everything else
|
||||
// Agent
|
||||
pub has_agency: bool,
|
||||
pub alignment: Alignment,
|
||||
/// Parameterises agent behaviour
|
||||
pub has_agency: bool,
|
||||
pub agent_mark: Option<agent::Mark>,
|
||||
pub no_flee: bool,
|
||||
pub idle_wander_factor: f32,
|
||||
// Stats
|
||||
pub body: Body,
|
||||
pub name: Option<String>,
|
||||
@ -186,9 +199,13 @@ impl EntityInfo {
|
||||
Self {
|
||||
pos,
|
||||
is_waypoint: false,
|
||||
has_agency: true,
|
||||
alignment: Alignment::Wild,
|
||||
|
||||
has_agency: true,
|
||||
agent_mark: None,
|
||||
no_flee: false,
|
||||
idle_wander_factor: 1.0,
|
||||
|
||||
body: Body::Humanoid(humanoid::Body::random()),
|
||||
name: None,
|
||||
scale: 1.0,
|
||||
@ -199,7 +216,6 @@ impl EntityInfo {
|
||||
skillset_asset: None,
|
||||
pet: None,
|
||||
trading_information: None,
|
||||
no_flee: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,6 +246,7 @@ impl EntityInfo {
|
||||
name,
|
||||
body,
|
||||
alignment,
|
||||
agent,
|
||||
inventory,
|
||||
loot,
|
||||
meta,
|
||||
@ -270,6 +287,16 @@ impl EntityInfo {
|
||||
// NOTE: set loadout after body, as it's used with default equipement
|
||||
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 {
|
||||
match field {
|
||||
Meta::SkillSetAsset(asset) => {
|
||||
@ -619,11 +646,12 @@ mod tests {
|
||||
for config_asset in entity_configs {
|
||||
let EntityConfig {
|
||||
body,
|
||||
agent: _,
|
||||
inventory,
|
||||
name,
|
||||
loot,
|
||||
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);
|
||||
|
||||
validate_body(&body, &config_asset);
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
consts::{
|
||||
AVG_FOLLOW_DIST, DEFAULT_ATTACK_RANGE, IDLE_HEALING_ITEM_THRESHOLD, PARTIAL_PATH_DIST,
|
||||
SEPARATION_BIAS, SEPARATION_DIST, STD_AWARENESS_DECAY_RATE,
|
||||
AVG_FOLLOW_DIST, DEFAULT_ATTACK_RANGE, IDLE_HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST,
|
||||
PARTIAL_PATH_DIST, SEPARATION_BIAS, SEPARATION_DIST, STD_AWARENESS_DECAY_RATE,
|
||||
},
|
||||
data::{AgentData, AttackData, Path, ReadData, Tactic, TargetData},
|
||||
util::{
|
||||
@ -442,11 +442,26 @@ impl<'a> AgentData<'a> {
|
||||
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
|
||||
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
|
||||
(self.pos.0 - patrol_origin).xy() * 0.0002
|
||||
});
|
||||
|
||||
let diff = Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5);
|
||||
agent.bearing += (diff * 0.1 - agent.bearing * 0.01)
|
||||
* 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
|
||||
// or about to walk off a cliff
|
||||
@ -491,7 +506,7 @@ impl<'a> AgentData<'a> {
|
||||
};
|
||||
|
||||
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
|
||||
|
@ -1,7 +1,7 @@
|
||||
pub const DAMAGE_MEMORY_DURATION: f64 = 0.25;
|
||||
pub const FLEE_DURATION: f32 = 3.0;
|
||||
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 PARTIAL_PATH_DIST: f32 = 50.0;
|
||||
pub const SEPARATION_DIST: f32 = 10.0;
|
||||
|
@ -1506,8 +1506,10 @@ fn handle_spawn(
|
||||
let pos = position(server, target, "target")?;
|
||||
let mut agent = comp::Agent::from_body(&body());
|
||||
|
||||
if matches!(alignment, comp::Alignment::Owned(_)) {
|
||||
agent.psyche.idle_wander_factor = 0.25;
|
||||
} else {
|
||||
// If unowned, the agent should stay in a particular place
|
||||
if !matches!(alignment, comp::Alignment::Owned(_)) {
|
||||
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
|
||||
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(
|
||||
Behavior::default().maybe_with_capabilities(Some(BehaviorCapability::TRADE)),
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ use self::interaction::{
|
||||
|
||||
use super::{
|
||||
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,
|
||||
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) {
|
||||
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
|
||||
.agent_data
|
||||
.follow(bdata.agent, bdata.controller, bdata.read_data, tgt_pos);
|
||||
|
@ -426,6 +426,7 @@ impl NpcData {
|
||||
agent_mark,
|
||||
alignment,
|
||||
no_flee,
|
||||
idle_wander_factor,
|
||||
// stats
|
||||
body,
|
||||
name,
|
||||
@ -518,7 +519,9 @@ impl NpcData {
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user