veloren_server/sys/agent/
mod.rs

1pub mod behavior_tree;
2use server_agent::data::AgentEvents;
3pub use server_agent::{action_nodes, attack, consts, data, util};
4
5use crate::sys::agent::{
6    behavior_tree::{BehaviorData, BehaviorTree},
7    data::{AgentData, ReadData},
8};
9use common::{
10    comp::{
11        self, Agent, Alignment, Body, CharacterState, Controller, Health, Scale,
12        inventory::slot::EquipSlot, item::ItemDesc,
13    },
14    mounting::Volume,
15    path::TraversalConfig,
16};
17use common_base::prof_span;
18use common_ecs::{Job, Origin, ParMode, Phase, System};
19use rand::thread_rng;
20use rayon::iter::ParallelIterator;
21use specs::{LendJoin, ParJoin, WriteStorage};
22
23/// This system will allow NPCs to modify their controller
24#[derive(Default)]
25pub struct Sys;
26impl<'a> System<'a> for Sys {
27    type SystemData = (
28        ReadData<'a>,
29        AgentEvents<'a>,
30        WriteStorage<'a, Agent>,
31        WriteStorage<'a, Controller>,
32    );
33
34    const NAME: &'static str = "agent";
35    const ORIGIN: Origin = Origin::Server;
36    const PHASE: Phase = Phase::Create;
37
38    fn run(
39        job: &mut Job<Self>,
40        (read_data, events, mut agents, mut controllers): Self::SystemData,
41    ) {
42        job.cpu_stats.measure(ParMode::Rayon);
43
44        (
45            &read_data.entities,
46            (
47                &read_data.energies,
48                read_data.healths.maybe(),
49                read_data.combos.maybe(),
50            ),
51            (
52                &read_data.positions,
53                &read_data.velocities,
54                &read_data.orientations,
55            ),
56            read_data.bodies.maybe(),
57            &read_data.inventories,
58            (
59                &read_data.char_states,
60                &read_data.skill_set,
61                &read_data.active_abilities,
62            ),
63            &read_data.physics_states,
64            &read_data.uids,
65            &mut agents,
66            &mut controllers,
67            read_data.light_emitter.maybe(),
68            read_data.groups.maybe(),
69            read_data.rtsim_entities.maybe(),
70            (
71                !&read_data.is_mounts,
72                read_data.is_riders.maybe(),
73                read_data.is_volume_riders.maybe(),
74            ),
75        )
76            .par_join()
77            .for_each_init(
78                || {
79                    prof_span!(guard, "agent rayon job");
80                    guard
81                },
82                |_guard,
83                 (
84                    entity,
85                    (energy, health, combo),
86                    (pos, vel, ori),
87                    body,
88                    inventory,
89                    (char_state, skill_set, active_abilities),
90                    physics_state,
91                    uid,
92                    agent,
93                    controller,
94                    light_emitter,
95                    group,
96                    rtsim_entity,
97                    (_, is_rider, is_volume_rider),
98                )| {
99                    let mut emitters = events.get_emitters();
100                    let mut rng = thread_rng();
101
102                    // The entity that is moving, if riding it's the mount, otherwise it's itself
103                    let moving_entity = is_rider
104                        .and_then(|is_rider| {
105                            let mut mount = is_rider.mount;
106                            // Find the root mount, i.e the one that's doing the moving.
107                            loop {
108                                let e = read_data.id_maps.uid_entity(mount)?;
109
110                                if let Some(is_rider) = read_data.is_riders.get(e) {
111                                    mount = is_rider.mount;
112                                } else {
113                                    return Some(e);
114                                }
115                            }
116                        })
117                        .or_else(|| {
118                            is_volume_rider.and_then(|is_volume_rider| {
119                                match is_volume_rider.pos.kind {
120                                    Volume::Terrain => None,
121                                    Volume::Entity(uid) => read_data.id_maps.uid_entity(uid),
122                                }
123                            })
124                        })
125                        .unwrap_or(entity);
126
127                    let pos = read_data.positions.get(moving_entity).unwrap_or(pos);
128                    let vel = read_data.velocities.get(moving_entity).unwrap_or(vel);
129                    let moving_body = read_data.bodies.get(moving_entity);
130                    let physics_state = read_data
131                        .physics_states
132                        .get(moving_entity)
133                        .unwrap_or(physics_state);
134
135                    // Hack, replace with better system when groups are more sophisticated
136                    // Override alignment if in a group unless entity is owned already
137                    let alignment = if matches!(
138                        &read_data.alignments.get(entity),
139                        &Some(Alignment::Owned(_))
140                    ) {
141                        read_data.alignments.get(entity).copied()
142                    } else {
143                        group
144                            .and_then(|g| read_data.group_manager.group_info(*g))
145                            .and_then(|info| read_data.uids.get(info.leader))
146                            .copied()
147                            .map_or_else(
148                                || read_data.alignments.get(entity).copied(),
149                                |uid| Some(Alignment::Owned(uid)),
150                            )
151                    };
152
153                    if !matches!(
154                        char_state,
155                        CharacterState::LeapMelee(_) | CharacterState::Glide(_)
156                    ) {
157                        // Default to looking in orientation direction
158                        // (can be overridden below)
159                        //
160                        // This definitely breaks LeapMelee, Glide and
161                        // probably not only that, do we really need this at all?
162                        controller.reset();
163                        controller.inputs.look_dir = ori.look_dir();
164                    }
165
166                    let scale = read_data
167                        .scales
168                        .get(moving_entity)
169                        .map_or(1.0, |Scale(s)| *s);
170
171                    let glider_equipped = inventory
172                        .equipped(EquipSlot::Glider)
173                        .as_ref()
174                        .is_some_and(|item| matches!(&*item.kind(), comp::item::ItemKind::Glider));
175
176                    let is_gliding = matches!(
177                        read_data.char_states.get(entity),
178                        Some(CharacterState::GlideWield(_) | CharacterState::Glide(_))
179                    ) && physics_state.on_ground.is_none();
180
181                    // This controls how picky NPCs are about their pathfinding.
182                    // Giants are larger and so can afford to be less precise
183                    // when trying to move around the world
184                    // (especially since they would otherwise get stuck on
185                    // obstacles that smaller entities would not).
186                    let node_tolerance = scale * 1.5;
187                    let slow_factor =
188                        moving_body.map_or(0.0, |b| 1.0 - 1.0 / (1.0 + b.base_accel() * 0.01));
189                    let traversal_config = TraversalConfig {
190                        node_tolerance,
191                        slow_factor,
192                        on_ground: physics_state.on_ground.is_some(),
193                        in_liquid: physics_state.in_liquid().is_some(),
194                        min_tgt_dist: scale * moving_body.map_or(1.0, |body| body.max_radius()),
195                        can_climb: moving_body.is_some_and(Body::can_climb),
196                        can_fly: moving_body.is_some_and(|b| b.fly_thrust().is_some()),
197                        vectored_propulsion: moving_body.is_some_and(|b| b.vectored_propulsion()),
198                        is_target_loaded: true,
199                    };
200                    let health_fraction = health.map_or(1.0, Health::fraction);
201
202                    // Package all this agent's data into a convenient struct
203                    let data = AgentData {
204                        entity: &entity,
205                        rtsim_entity,
206                        uid,
207                        pos,
208                        vel,
209                        ori,
210                        energy,
211                        body,
212                        inventory,
213                        skill_set,
214                        physics_state,
215                        alignment: alignment.as_ref(),
216                        traversal_config,
217                        scale,
218                        damage: health_fraction,
219                        light_emitter,
220                        glider_equipped,
221                        is_gliding,
222                        health: read_data.healths.get(entity),
223                        heads: read_data.heads.get(entity),
224                        char_state,
225                        active_abilities,
226                        combo,
227                        buffs: read_data.buffs.get(entity),
228                        stats: read_data.stats.get(entity),
229                        cached_spatial_grid: &read_data.cached_spatial_grid,
230                        msm: &read_data.msm,
231                        poise: read_data.poises.get(entity),
232                        stance: read_data.stances.get(entity),
233                    };
234
235                    ///////////////////////////////////////////////////////////
236                    // Behavior tree
237                    ///////////////////////////////////////////////////////////
238                    // The behavior tree is meant to make decisions for agents
239                    // *but should not* mutate any data (only action nodes
240                    // should do that). Each path should lead to one (and only
241                    // one) action node. This makes bugfinding much easier and
242                    // debugging way easier. If you don't think so, try
243                    // debugging the agent code before this MR
244                    // (https://gitlab.com/veloren/veloren/-/merge_requests/1801).
245                    // Each tick should arrive at one (1) action node which
246                    // then determines what the agent does. If this makes you
247                    // uncomfortable, consider dt the response time of the
248                    // NPC. To make the tree easier to read, subtrees can be
249                    // created as methods on `AgentData`. Action nodes are
250                    // also methods on the `AgentData` struct. Action nodes
251                    // are the only parts of this tree that should provide
252                    // inputs.
253                    let mut behavior_data = BehaviorData {
254                        agent,
255                        agent_data: data,
256                        read_data: &read_data,
257                        emitters: &mut emitters,
258                        controller,
259                        rng: &mut rng,
260                    };
261
262                    BehaviorTree::root().run(&mut behavior_data);
263
264                    debug_assert!(controller.inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
265                    debug_assert!(controller.inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
266                },
267            );
268    }
269}