Skip to main content

veloren_common_systems/
character_behavior.rs

1use common_net::synced_components::Heads;
2use specs::{
3    Entities, LazyUpdate, LendJoin, Read, ReadExpect, ReadStorage, SystemData, WriteStorage, shred,
4};
5
6use common::{
7    comp::{
8        self, ActiveAbilities, Beam, Body, Buffs, CharacterActivity, CharacterState, Combo,
9        Controller, Density, Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori,
10        PhysicsState, Poise, Pos, PreviousPhysCache, Scale, SkillSet, Stance, StateUpdate, Stats,
11        Vel,
12        character_state::{CharacterStateEvents, OutputEvents},
13        inventory::item::{MaterialStatManifest, tool::AbilityMap},
14    },
15    event::{self, EventBus, KnockbackEvent, LocalEvent},
16    link::Is,
17    mounting::{Rider, VolumeRider},
18    outcome::Outcome,
19    resources::{DeltaTime, Time},
20    states::{
21        behavior::{JoinData, JoinStruct},
22        idle,
23    },
24    terrain::TerrainGrid,
25    uid::{IdMaps, Uid},
26};
27use common_ecs::{Job, Origin, Phase, System};
28
29#[derive(SystemData)]
30pub struct ReadData<'a> {
31    entities: Entities<'a>,
32    events: CharacterStateEvents<'a>,
33    local_bus: Read<'a, EventBus<LocalEvent>>,
34    dt: Read<'a, DeltaTime>,
35    time: Read<'a, Time>,
36    lazy_update: Read<'a, LazyUpdate>,
37    healths: ReadStorage<'a, Health>,
38    heads: ReadStorage<'a, Heads>,
39    bodies: ReadStorage<'a, Body>,
40    masses: ReadStorage<'a, Mass>,
41    scales: ReadStorage<'a, Scale>,
42    physics_states: ReadStorage<'a, PhysicsState>,
43    melee_attacks: ReadStorage<'a, Melee>,
44    beams: ReadStorage<'a, Beam>,
45    uids: ReadStorage<'a, Uid>,
46    is_riders: ReadStorage<'a, Is<Rider>>,
47    is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
48    stats: ReadStorage<'a, Stats>,
49    skill_sets: ReadStorage<'a, SkillSet>,
50    active_abilities: ReadStorage<'a, ActiveAbilities>,
51    msm: ReadExpect<'a, MaterialStatManifest>,
52    ability_map: ReadExpect<'a, AbilityMap>,
53    combos: ReadStorage<'a, Combo>,
54    alignments: ReadStorage<'a, comp::Alignment>,
55    terrain: ReadExpect<'a, TerrainGrid>,
56    inventories: ReadStorage<'a, Inventory>,
57    stances: ReadStorage<'a, Stance>,
58    prev_phys_caches: ReadStorage<'a, PreviousPhysCache>,
59    buffs: ReadStorage<'a, Buffs>,
60}
61
62/// ## Character Behavior System
63/// Passes `JoinData` to `CharacterState`'s `behavior` handler fn's. Receives a
64/// `StateUpdate` in return and performs updates to ECS Components from that.
65#[derive(Default)]
66pub struct Sys;
67
68impl<'a> System<'a> for Sys {
69    type SystemData = (
70        ReadData<'a>,
71        WriteStorage<'a, CharacterState>,
72        WriteStorage<'a, CharacterActivity>,
73        WriteStorage<'a, Pos>,
74        WriteStorage<'a, Vel>,
75        WriteStorage<'a, Ori>,
76        WriteStorage<'a, Density>,
77        WriteStorage<'a, Energy>,
78        WriteStorage<'a, Controller>,
79        WriteStorage<'a, Poise>,
80        Read<'a, EventBus<Outcome>>,
81        Read<'a, IdMaps>,
82    );
83
84    const NAME: &'static str = "character_behavior";
85    const ORIGIN: Origin = Origin::Common;
86    const PHASE: Phase = Phase::Create;
87
88    fn run(
89        _job: &mut Job<Self>,
90        (
91            read_data,
92            mut character_states,
93            mut character_activities,
94            mut positions,
95            mut velocities,
96            mut orientations,
97            mut densities,
98            mut energies,
99            mut controllers,
100            mut poises,
101            outcomes,
102            id_maps,
103        ): Self::SystemData,
104    ) {
105        let mut local_emitter = read_data.local_bus.emitter();
106        let mut outcomes_emitter = outcomes.emitter();
107        let mut emitters = read_data.events.get_emitters();
108
109        let mut local_events = Vec::new();
110        let mut output_events = OutputEvents::new(&mut local_events, &mut emitters);
111
112        let join = (
113            &read_data.entities,
114            &read_data.uids,
115            &mut character_states,
116            &mut character_activities,
117            &mut positions,
118            &mut velocities,
119            &mut orientations,
120            &read_data.masses,
121            &mut densities,
122            &mut energies,
123            read_data.inventories.maybe(),
124            &mut controllers,
125            read_data.healths.maybe(),
126            read_data.heads.maybe(),
127            (
128                &read_data.bodies,
129                &read_data.physics_states,
130                read_data.scales.maybe(),
131                &read_data.stats,
132                &read_data.skill_sets,
133                read_data.active_abilities.maybe(),
134                read_data.is_riders.maybe(),
135            ),
136            read_data.combos.maybe(),
137        )
138            .lend_join();
139        join.for_each(|comps| {
140            let (
141                entity,
142                uid,
143                mut char_state,
144                character_activity,
145                pos,
146                vel,
147                ori,
148                mass,
149                density,
150                energy,
151                inventory,
152                controller,
153                health,
154                heads,
155                (body, physics, scale, stat, skill_set, active_abilities, is_rider),
156                combo,
157            ) = comps;
158            // Being dead overrides all other states
159            if health.is_some_and(|h| h.is_dead) {
160                // Do nothing
161                return;
162            }
163
164            // Remove components that entity should not have if not in relevant char state
165            if !char_state.is_melee_attack() {
166                read_data.lazy_update.remove::<Melee>(entity);
167            }
168            if !char_state.is_beam_attack() {
169                read_data.lazy_update.remove::<Beam>(entity);
170            }
171
172            // Enter stunned state if poise damage is enough
173            if let Some(mut poise) = poises.get_mut(entity) {
174                let was_wielded = char_state.is_wield();
175                let poise_state = poise.poise_state();
176                let pos = pos.0;
177                if let (Some((stunned_state, stunned_duration)), impulse_strength) =
178                    poise_state.poise_effect(was_wielded)
179                {
180                    // Reset poise if there is some stunned state to apply
181                    poise.reset(*read_data.time, stunned_duration);
182                    if !comp::is_downed(health, Some(&char_state)) {
183                        *char_state = stunned_state;
184                    }
185                    outcomes_emitter.emit(Outcome::PoiseChange {
186                        pos,
187                        state: poise_state,
188                    });
189                    if let Some(impulse_strength) = impulse_strength {
190                        output_events.emit_server(KnockbackEvent {
191                            entity,
192                            impulse: impulse_strength * *poise.knockback(),
193                        });
194                    }
195                }
196            }
197
198            // Controller actions
199            let actions = std::mem::take(&mut controller.actions);
200
201            let mut join_struct = JoinStruct {
202                entity,
203                uid,
204                char_state,
205                character_activity,
206                pos,
207                vel,
208                ori,
209                scale,
210                mass,
211                density,
212                energy,
213                inventory,
214                controller,
215                health,
216                heads,
217                body,
218                physics,
219                melee_attack: read_data.melee_attacks.get(entity),
220                beam: read_data.beams.get(entity),
221                stat,
222                skill_set,
223                active_abilities,
224                combo,
225                alignment: read_data.alignments.get(entity),
226                terrain: &read_data.terrain,
227                mount_data: read_data.is_riders.get(entity),
228                volume_mount_data: read_data.is_volume_riders.get(entity),
229                stance: read_data.stances.get(entity),
230                id_maps: &id_maps,
231                alignments: &read_data.alignments,
232                prev_phys_caches: &read_data.prev_phys_caches,
233                bodies: &read_data.bodies,
234                buffs: read_data.buffs.get(entity),
235            };
236
237            for action in actions {
238                let j = JoinData::new(
239                    &join_struct,
240                    &read_data.lazy_update,
241                    &read_data.dt,
242                    &read_data.time,
243                    &read_data.msm,
244                    &read_data.ability_map,
245                );
246                let state_update = j.character.handle_event(&j, &mut output_events, action);
247                Self::publish_state_update(&mut join_struct, state_update, &mut output_events);
248            }
249
250            // Mounted occurs after control actions have been handled
251            // If mounted, character state is controlled by mount
252            if is_rider.is_some() && !join_struct.char_state.can_perform_mounted() {
253                // TODO: A better way to swap between mount inputs and rider inputs
254                *join_struct.char_state = CharacterState::Idle(idle::Data::default());
255                return;
256            }
257
258            let j = JoinData::new(
259                &join_struct,
260                &read_data.lazy_update,
261                &read_data.dt,
262                &read_data.time,
263                &read_data.msm,
264                &read_data.ability_map,
265            );
266
267            let state_update = j.character.behavior(&j, &mut output_events);
268            Self::publish_state_update(&mut join_struct, state_update, &mut output_events);
269        });
270
271        local_emitter.append_vec(local_events);
272    }
273}
274
275impl Sys {
276    fn publish_state_update(
277        join: &mut JoinStruct,
278        state_update: StateUpdate,
279        output_events: &mut OutputEvents,
280    ) {
281        // Here we check for equality with the previous value of these components before
282        // updating them so that the modification detection will not be
283        // triggered unnecessarily. This is important for minimizing updates
284        // sent to the clients (and thus keeping bandwidth usage down).
285        //
286        // TODO: if checking equality is expensive for char_state use optional field in
287        // StateUpdate
288        if *join.char_state != state_update.character {
289            *join.char_state = state_update.character
290        }
291        if *join.character_activity != state_update.character_activity {
292            *join.character_activity = state_update.character_activity
293        }
294        if *join.density != state_update.density {
295            *join.density = state_update.density
296        }
297        if *join.energy != state_update.energy {
298            *join.energy = state_update.energy;
299        };
300
301        // These components use a different type of change detection.
302        *join.pos = state_update.pos;
303        *join.vel = state_update.vel;
304        *join.ori = state_update.ori;
305
306        for (input, attr) in state_update.queued_inputs {
307            join.controller.queued_inputs.insert(input, attr);
308        }
309        for input in state_update.removed_inputs {
310            join.controller.queued_inputs.remove(&input);
311        }
312        if state_update.swap_equipped_weapons {
313            output_events.emit_server(event::InventoryManipEvent(
314                join.entity,
315                InventoryManip::SwapEquippedWeapons,
316            ));
317        }
318    }
319}