veloren/server/src/sys/agent.rs

261 lines
11 KiB
Rust
Raw Normal View History

pub mod behavior_tree;
pub use server_agent::{action_nodes, attack, consts, data, util};
2022-01-18 03:02:43 +00:00
use crate::{
rtsim::RtSim,
2022-01-18 03:02:43 +00:00
sys::agent::{
2022-08-11 19:15:46 +00:00
behavior_tree::{BehaviorData, BehaviorTree},
data::{AgentData, ReadData},
2022-01-18 03:02:43 +00:00
},
};
use common::{
2020-04-18 18:28:19 +00:00
comp::{
self, inventory::slot::EquipSlot, item::ItemDesc, Agent, Alignment, Body, CharacterState,
Controller, Health, InputKind, Scale,
2020-04-18 18:28:19 +00:00
},
event::{EventBus, ServerEvent},
2021-02-07 07:22:06 +00:00
path::TraversalConfig,
rtsim::RtSimEvent,
};
use common_base::prof_span;
use common_ecs::{Job, Origin, ParMode, Phase, System};
use rand::thread_rng;
use rayon::iter::ParallelIterator;
use specs::{Join, ParJoin, Read, WriteExpect, WriteStorage};
2019-06-09 19:33:20 +00:00
/// This system will allow NPCs to modify their controller
#[derive(Default)]
pub struct Sys;
2021-03-08 11:13:59 +00:00
impl<'a> System<'a> for Sys {
type SystemData = (
2021-02-07 07:22:06 +00:00
ReadData<'a>,
2022-05-09 19:58:13 +00:00
Read<'a, EventBus<ServerEvent>>,
WriteStorage<'a, Agent>,
2019-06-09 14:20:20 +00:00
WriteStorage<'a, Controller>,
WriteExpect<'a, RtSim>,
);
const NAME: &'static str = "agent";
const ORIGIN: Origin = Origin::Server;
const PHASE: Phase = Phase::Create;
2019-08-04 08:21:29 +00:00
fn run(
job: &mut Job<Self>,
(read_data, event_bus, mut agents, mut controllers, mut rtsim): Self::SystemData,
2019-08-04 08:21:29 +00:00
) {
let rtsim = &mut *rtsim;
job.cpu_stats.measure(ParMode::Rayon);
(
2021-02-07 07:22:06 +00:00
&read_data.entities,
2022-09-18 02:11:41 +00:00
(
&read_data.energies,
read_data.healths.maybe(),
read_data.combos.maybe(),
),
2021-03-29 20:30:09 +00:00
(
&read_data.positions,
&read_data.velocities,
&read_data.orientations,
),
2021-02-07 07:22:06 +00:00
read_data.bodies.maybe(),
&read_data.inventories,
2021-11-12 03:37:37 +00:00
(
&read_data.char_states,
&read_data.skill_set,
&read_data.active_abilities,
),
2021-02-07 07:22:06 +00:00
&read_data.physics_states,
&read_data.uids,
&mut agents,
&mut controllers,
2021-02-07 07:22:06 +00:00
read_data.light_emitter.maybe(),
read_data.groups.maybe(),
!&read_data.is_mounts,
)
.par_join()
.for_each_init(
|| {
prof_span!(guard, "agent rayon job");
guard
},
|_guard,
(
2021-02-07 07:22:06 +00:00
entity,
2022-09-18 02:11:41 +00:00
(energy, health, combo),
2021-03-29 20:30:09 +00:00
(pos, vel, ori),
2021-02-07 07:22:06 +00:00
body,
inventory,
2021-11-12 03:37:37 +00:00
(char_state, skill_set, active_abilities),
2021-02-07 07:22:06 +00:00
physics_state,
uid,
agent,
controller,
light_emitter,
group,
2021-02-07 07:22:06 +00:00
_,
)| {
2022-01-18 03:02:43 +00:00
let mut event_emitter = event_bus.emitter();
2022-08-11 19:15:46 +00:00
let mut rng = thread_rng();
2022-01-18 03:02:43 +00:00
// Hack, replace with better system when groups are more sophisticated
// Override alignment if in a group unless entity is owned already
let alignment = if matches!(
2021-02-07 07:22:06 +00:00
&read_data.alignments.get(entity),
&Some(Alignment::Owned(_))
) {
read_data.alignments.get(entity).copied()
} else {
group
2021-02-07 07:22:06 +00:00
.and_then(|g| read_data.group_manager.group_info(*g))
.and_then(|info| read_data.uids.get(info.leader))
.copied()
.map_or_else(
|| read_data.alignments.get(entity).copied(),
|uid| Some(Alignment::Owned(uid)),
)
2021-02-07 07:22:06 +00:00
};
if !matches!(char_state, CharacterState::LeapMelee(_)) {
2021-07-14 10:22:47 +00:00
// Default to looking in orientation direction
// (can be overridden below)
//
2022-07-15 16:59:37 +00:00
// This definitely breaks LeapMelee and
// probably not only that, do we really need this at all?
controller.reset();
controller.inputs.look_dir = ori.look_dir();
}
let scale = read_data.scales.get(entity).map_or(1.0, |Scale(s)| *s);
2020-01-25 18:49:47 +00:00
2021-02-07 07:22:06 +00:00
let glider_equipped = inventory
.equipped(EquipSlot::Glider)
.as_ref()
.map_or(false, |item| {
2022-05-18 20:28:06 +00:00
matches!(&*item.kind(), comp::item::ItemKind::Glider)
2021-02-07 07:22:06 +00:00
});
2021-02-07 07:22:06 +00:00
let is_gliding = matches!(
read_data.char_states.get(entity),
2021-08-01 11:20:46 +00:00
Some(CharacterState::GlideWield(_) | CharacterState::Glide(_))
2021-06-20 03:51:04 +00:00
) && physics_state.on_ground.is_none();
2021-05-30 15:38:47 +00:00
if let Some(pid) = agent.position_pid_controller.as_mut() {
pid.add_measurement(read_data.time.0, pos.0);
}
// This controls how picky NPCs are about their pathfinding.
// Giants are larger and so can afford to be less precise
// when trying to move around the world
// (especially since they would otherwise get stuck on
2021-02-07 07:22:06 +00:00
// obstacles that smaller entities would not).
let node_tolerance = scale * 1.5;
let slow_factor = body.map_or(0.0, |b| b.base_accel() / 250.0).min(1.0);
2021-02-07 07:22:06 +00:00
let traversal_config = TraversalConfig {
node_tolerance,
slow_factor,
2021-06-20 03:51:04 +00:00
on_ground: physics_state.on_ground.is_some(),
in_liquid: physics_state.in_liquid().is_some(),
2021-02-07 07:22:06 +00:00
min_tgt_dist: 1.0,
can_climb: body.map_or(false, Body::can_climb),
can_fly: body.map_or(false, |b| b.fly_thrust().is_some()),
2021-02-07 07:22:06 +00:00
};
let health_fraction = health.map_or(1.0, Health::fraction);
let rtsim_entity = read_data
.rtsim_entities
.get(entity)
.and_then(|rtsim_ent| rtsim.get_entity(rtsim_ent.0));
if traversal_config.can_fly && matches!(body, Some(Body::Ship(_))) {
// hack (kinda): Never turn off flight airships
// since it results in stuttering and falling back to the ground.
//
// TODO: look into `controller.reset()` line above
// and see if it fixes it
2022-01-26 19:09:59 +00:00
controller.push_basic_input(InputKind::Fly);
}
2021-02-07 07:22:06 +00:00
// Package all this agent's data into a convenient struct
let data = AgentData {
entity: &entity,
uid,
pos,
vel,
ori,
energy,
body,
inventory,
skill_set,
2021-02-07 07:22:06 +00:00
physics_state,
alignment: alignment.as_ref(),
traversal_config,
scale,
damage: health_fraction,
2021-02-07 07:22:06 +00:00
light_emitter,
glider_equipped,
is_gliding,
health: read_data.healths.get(entity),
char_state,
2021-11-12 03:37:37 +00:00
active_abilities,
2022-09-18 02:11:41 +00:00
combo,
2022-10-08 18:46:32 +00:00
buffs: read_data.buffs.get(entity),
cached_spatial_grid: &read_data.cached_spatial_grid,
2022-05-28 23:41:31 +00:00
msm: &read_data.msm,
2022-10-12 19:53:26 +00:00
poise: read_data.poises.get(entity),
2021-02-07 07:22:06 +00:00
};
2021-02-07 07:22:06 +00:00
///////////////////////////////////////////////////////////
// Behavior tree
///////////////////////////////////////////////////////////
2021-03-30 00:25:59 +00:00
// The behavior tree is meant to make decisions for agents
// *but should not* mutate any data (only action nodes
// should do that). Each path should lead to one (and only
// one) action node. This makes bugfinding much easier and
// debugging way easier. If you don't think so, try
// debugging the agent code before this MR
// (https://gitlab.com/veloren/veloren/-/merge_requests/1801).
// Each tick should arrive at one (1) action node which
// then determines what the agent does. If this makes you
// uncomfortable, consider dt the response time of the
// NPC. To make the tree easier to read, subtrees can be
// created as methods on `AgentData`. Action nodes are
// also methods on the `AgentData` struct. Action nodes
// are the only parts of this tree that should provide
// inputs.
2022-08-11 19:15:46 +00:00
let mut behavior_data = BehaviorData {
2022-07-28 21:31:44 +00:00
agent,
rtsim_entity,
2022-08-11 19:15:46 +00:00
agent_data: data,
read_data: &read_data,
event_emitter: &mut event_emitter,
2022-07-28 21:31:44 +00:00
controller,
2022-08-11 19:15:46 +00:00
rng: &mut rng,
};
BehaviorTree::root().run(&mut behavior_data);
2022-07-28 21:31:44 +00:00
2021-02-07 07:22:06 +00:00
debug_assert!(controller.inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
debug_assert!(controller.inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
},
);
for (agent, rtsim_entity) in (&mut agents, &read_data.rtsim_entities).join() {
// Entity must be loaded in as it has an agent component :)
// React to all events in the controller
for event in core::mem::take(&mut agent.rtsim_controller.events) {
2021-03-29 14:47:42 +00:00
match event {
RtSimEvent::AddMemory(memory) => {
rtsim.insert_entity_memory(rtsim_entity.0, memory.clone());
2021-03-29 14:47:42 +00:00
},
2021-07-14 15:26:29 +00:00
RtSimEvent::ForgetEnemy(name) => {
rtsim.forget_entity_enemy(rtsim_entity.0, &name);
},
2021-03-29 14:47:42 +00:00
RtSimEvent::SetMood(memory) => {
rtsim.set_entity_mood(rtsim_entity.0, memory.clone());
2021-03-29 14:47:42 +00:00
},
RtSimEvent::PrintMemories => {},
}
}
}
2021-02-07 07:22:06 +00:00
}
}