veloren_common_systems/
mount.rs

1use common::{
2    combat::RiderEffects,
3    comp::{
4        Body, Buff, BuffCategory, BuffChange, Buffs, CharacterActivity, Collider, ControlAction,
5        Controller, InputKind, Mass, Ori, PhysicsState, Pos, Scale, Stats, Vel, buff::DestInfo,
6    },
7    event::{BuffEvent, EmitExt},
8    event_emitters,
9    link::Is,
10    mounting::{Mount, Rider, VolumeRider},
11    resources::Time,
12    terrain::TerrainGrid,
13    uid::IdMaps,
14};
15use common_ecs::{Job, Origin, Phase, System};
16use specs::{Entities, Join, LendJoin, Read, ReadExpect, ReadStorage, WriteStorage};
17use vek::*;
18
19event_emitters! {
20    struct Events[EventEmitters] {
21        buff: BuffEvent,
22    }
23}
24
25/// This system is responsible for controlling mounts
26#[derive(Default)]
27pub struct Sys;
28impl<'a> System<'a> for Sys {
29    type SystemData = (
30        Read<'a, IdMaps>,
31        Read<'a, Time>,
32        ReadExpect<'a, TerrainGrid>,
33        Events<'a>,
34        Entities<'a>,
35        WriteStorage<'a, Controller>,
36        ReadStorage<'a, Is<Rider>>,
37        ReadStorage<'a, Is<Mount>>,
38        ReadStorage<'a, Is<VolumeRider>>,
39        WriteStorage<'a, Pos>,
40        WriteStorage<'a, Vel>,
41        WriteStorage<'a, Ori>,
42        WriteStorage<'a, CharacterActivity>,
43        WriteStorage<'a, PhysicsState>,
44        ReadStorage<'a, Body>,
45        ReadStorage<'a, Scale>,
46        ReadStorage<'a, Collider>,
47        ReadStorage<'a, Buffs>,
48        ReadStorage<'a, Stats>,
49        ReadStorage<'a, Mass>,
50        ReadStorage<'a, RiderEffects>,
51    );
52
53    const NAME: &'static str = "mount";
54    const ORIGIN: Origin = Origin::Common;
55    const PHASE: Phase = Phase::Create;
56
57    fn run(
58        _job: &mut Job<Self>,
59        (
60            id_maps,
61            time,
62            terrain,
63            events,
64            entities,
65            mut controllers,
66            is_riders,
67            is_mounts,
68            is_volume_riders,
69            mut positions,
70            mut velocities,
71            mut orientations,
72            mut character_activities,
73            mut physics_states,
74            bodies,
75            scales,
76            colliders,
77            buffs,
78            stats,
79            masses,
80            rider_effects,
81        ): Self::SystemData,
82    ) {
83        let mut emitters = events.get_emitters();
84        // For each mount...
85        for (entity, is_mount, body, rider_effects) in
86            (&entities, &is_mounts, bodies.maybe(), rider_effects.maybe()).join()
87        {
88            let Some(rider_entity) = id_maps.uid_entity(is_mount.rider) else {
89                continue;
90            };
91
92            // Rider effects from mount.
93            if let Some(rider_effects) = rider_effects
94                && let Some(target_buffs) = buffs.get(rider_entity)
95            {
96                for effect in rider_effects.0.iter() {
97                    let emit_buff = !target_buffs.buffs.iter().any(|(_, buff)| {
98                        buff.cat_ids.iter()
99                            .any(|cat_id| matches!(cat_id, BuffCategory::FromLink(link) if link.is_link(is_mount.get_link())))
100                            && buff.kind == effect.kind && buff.data.strength >= effect.data.strength
101                    });
102
103                    if emit_buff {
104                        let dest_info = DestInfo {
105                            stats: stats.get(rider_entity),
106                            mass: masses.get(rider_entity),
107                        };
108                        let mut cat_ids = effect.cat_ids.clone();
109                        cat_ids.push(BuffCategory::FromLink(
110                            is_mount.get_link().downgrade().into_dyn(),
111                        ));
112
113                        emitters.emit(BuffEvent {
114                            entity: rider_entity,
115                            buff_change: BuffChange::Add(Buff::new(
116                                effect.kind,
117                                effect.data,
118                                cat_ids,
119                                common::comp::BuffSource::Character { by: is_mount.mount },
120                                *time,
121                                dest_info,
122                                masses.get(entity),
123                            )),
124                        });
125                    }
126                }
127            }
128            // ...find the rider...
129            let Some(inputs_and_actions) = controllers.get_mut(rider_entity).map(|c| {
130                // Only take inputs and actions from the rider if the mount is not
131                // intelligent (TODO: expand the definition of 'intelligent').
132                if body.is_some_and(|b| !b.has_free_will()) {
133                    let actions = c
134                        .actions
135                        .extract_if(.., |action| match action {
136                            ControlAction::StartInput { input: i, .. }
137                            | ControlAction::CancelInput { input: i } => {
138                                matches!(
139                                    i,
140                                    InputKind::Jump
141                                        | InputKind::WallJump
142                                        | InputKind::Fly
143                                        | InputKind::Roll
144                                )
145                            },
146                            _ => false,
147                        })
148                        .collect();
149                    Some((c.inputs.clone(), actions))
150                } else {
151                    None
152                }
153            }) else {
154                continue;
155            };
156
157            // ...apply the mount's position/ori/velocity to the rider...
158            let pos = positions.get(entity).copied();
159            let ori = orientations.get(entity).copied();
160            let vel = velocities.get(entity).copied();
161            if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
162                let mounter_body = bodies.get(rider_entity);
163                let mounting_offset = body.map_or(Vec3::unit_z(), Body::mount_offset)
164                    * scales.get(entity).map_or(1.0, |s| s.0)
165                    + mounter_body.map_or(Vec3::zero(), Body::rider_offset)
166                        * scales.get(rider_entity).map_or(1.0, |s| s.0);
167                let _ =
168                    positions.insert(rider_entity, Pos(pos.0 + ori.to_quat() * mounting_offset));
169                let _ = orientations.insert(rider_entity, ori);
170                let _ = velocities.insert(rider_entity, vel);
171            }
172            // ...and apply the rider's inputs to the mount's controller
173            if let Some((inputs, actions)) = inputs_and_actions
174                && let Some(controller) = controllers.get_mut(entity)
175            {
176                controller.inputs = inputs;
177                controller.actions = actions;
178            }
179        }
180
181        // Since physics state isn't updated while riding we set it to default.
182        // TODO: Could this be done only once when the link is first created? Has to
183        // happen on both server and client.
184        for (physics_state, _) in (
185            &mut physics_states,
186            is_riders.mask() | is_volume_riders.mask(),
187        )
188            .join()
189        {
190            *physics_state = PhysicsState::default();
191        }
192
193        // For each volume rider.
194        for (entity, is_volume_rider) in (&entities, &is_volume_riders).join() {
195            if let Some((mat, _)) = is_volume_rider.pos.get_mount_mat(
196                &terrain,
197                &id_maps,
198                |e| positions.get(e).copied().zip(orientations.get(e).copied()),
199                &colliders,
200            ) {
201                if let Some(pos) = positions.get_mut(entity) {
202                    pos.0 = mat.mul_point(Vec3::zero());
203                }
204                if let Some(ori) = orientations.get_mut(entity) {
205                    *ori = Ori::from_unnormalized_vec(mat.mul_direction(Vec3::unit_y()))
206                        .unwrap_or_default();
207                }
208            }
209            let v = match is_volume_rider.pos.kind {
210                common::mounting::Volume::Terrain => Vec3::zero(),
211                common::mounting::Volume::Entity(uid) => {
212                    if let Some(v) = id_maps.uid_entity(uid).and_then(|e| velocities.get(e)) {
213                        v.0
214                    } else {
215                        Vec3::zero()
216                    }
217                },
218            };
219            if let Some(vel) = velocities.get_mut(entity) {
220                vel.0 = v;
221            }
222
223            // Check if the volume has buffs if they do apply them to the rider via a
224            // BuffEvent
225
226            // TODO: This is code copy of the mounting effects. We can probably consolidate
227            // at some point.
228            if let Some(target_buffs) = buffs.get(entity)
229                && let Some(block_buffs) = is_volume_rider.block.mount_buffs()
230            {
231                for effect in block_buffs.iter() {
232                    let emit_buff = !target_buffs.buffs.iter().any(|(_, buff)| {
233                        buff.cat_ids.iter()
234                            .any(|cat_id| matches!(cat_id, BuffCategory::FromLink(link) if link.is_link(is_volume_rider.get_link())))
235                            && buff.kind == effect.kind && buff.data.strength >= effect.data.strength
236                    });
237
238                    if emit_buff {
239                        let dest_info = DestInfo {
240                            stats: stats.get(entity),
241                            mass: masses.get(entity),
242                        };
243                        let mut cat_ids = effect.cat_ids.clone();
244                        cat_ids.push(BuffCategory::FromLink(
245                            is_volume_rider.get_link().downgrade().into_dyn(),
246                        ));
247
248                        emitters.emit(BuffEvent {
249                            entity,
250                            buff_change: BuffChange::Add(Buff::new(
251                                effect.kind,
252                                effect.data,
253                                cat_ids,
254                                common::comp::BuffSource::Block,
255                                *time,
256                                dest_info,
257                                masses.get(entity),
258                            )),
259                        });
260                    }
261                }
262            }
263
264            let inputs = controllers.get_mut(entity).map(|c| {
265                let actions: Vec<_> = c
266                    .actions
267                    .extract_if(.., |action| match action {
268                        ControlAction::StartInput { input: i, .. }
269                        | ControlAction::CancelInput { input: i } => {
270                            matches!(
271                                i,
272                                InputKind::Jump
273                                    | InputKind::WallJump
274                                    | InputKind::Fly
275                                    | InputKind::Roll
276                            )
277                        },
278                        _ => false,
279                    })
280                    .collect();
281                let inputs = c.inputs.clone();
282
283                (actions, inputs)
284            });
285
286            if is_volume_rider.block.is_controller()
287                && let Some((actions, inputs)) = inputs
288            {
289                if let Some(mut character_activity) = character_activities
290                    .get_mut(entity)
291                    .filter(|c| c.steer_dir != inputs.move_dir.y)
292                {
293                    character_activity.steer_dir = inputs.move_dir.y;
294                }
295                match is_volume_rider.pos.kind {
296                    common::mounting::Volume::Entity(uid) => {
297                        if let Some(controller) =
298                            id_maps.uid_entity(uid).and_then(|e| controllers.get_mut(e))
299                        {
300                            controller.inputs = inputs;
301                            controller.actions = actions;
302                        }
303                    },
304                    common::mounting::Volume::Terrain => {},
305                }
306            }
307        }
308    }
309}