veloren_server/sys/
object.rs

1use common::{
2    CachedSpatialGrid, Damage, DamageKind, DamageSource, GroupTarget,
3    combat::{Attack, AttackDamage},
4    comp::{Body, Object, Pos, Teleporting, Vel, beam, object},
5    consts::TELEPORTER_RADIUS,
6    event::{ChangeBodyEvent, DeleteEvent, EmitExt, EventBus},
7    event_emitters,
8    outcome::Outcome,
9    resources::{DeltaTime, Secs, Time},
10    states::basic_summon::BeamPillarIndicatorSpecifier,
11};
12use common_ecs::{Job, Origin, Phase, System};
13use hashbrown::HashMap;
14use specs::{Entities, Join, LazyUpdate, LendJoin, Read, ReadStorage, WriteStorage};
15use vek::{QuadraticBezier3, Vec3};
16
17event_emitters! {
18    struct Events[Emitters] {
19        delete: DeleteEvent,
20        change_body: ChangeBodyEvent,
21    }
22}
23
24/// This system is responsible for handling misc object behaviours
25#[derive(Default)]
26pub struct Sys;
27impl<'a> System<'a> for Sys {
28    type SystemData = (
29        Entities<'a>,
30        Events<'a>,
31        Read<'a, DeltaTime>,
32        Read<'a, Time>,
33        Read<'a, EventBus<Outcome>>,
34        Read<'a, CachedSpatialGrid>,
35        ReadStorage<'a, Pos>,
36        ReadStorage<'a, Vel>,
37        WriteStorage<'a, Object>,
38        ReadStorage<'a, Body>,
39        ReadStorage<'a, Teleporting>,
40        ReadStorage<'a, beam::Beam>,
41        Read<'a, LazyUpdate>,
42    );
43
44    const NAME: &'static str = "object";
45    const ORIGIN: Origin = Origin::Server;
46    const PHASE: Phase = Phase::Create;
47
48    fn run(
49        _job: &mut Job<Self>,
50        (
51            entities,
52            events,
53            dt,
54            time,
55            outcome_bus,
56            spatial_grid,
57            positions,
58            velocities,
59            mut objects,
60            bodies,
61            teleporting,
62            beams,
63            updater,
64        ): Self::SystemData,
65    ) {
66        let mut emitters = events.get_emitters();
67
68        // Objects
69        for (entity, pos, vel, object, body) in (
70            &entities,
71            &positions,
72            velocities.maybe(),
73            &mut objects,
74            bodies.maybe(),
75        )
76            .join()
77        {
78            match *object {
79                Object::DeleteAfter {
80                    spawned_at,
81                    timeout,
82                } => {
83                    if (time.0 - spawned_at.0).max(0.0) > timeout.as_secs_f64() {
84                        emitters.emit(DeleteEvent(entity));
85                    }
86                },
87                Object::Portal { .. } => {
88                    let is_active = spatial_grid
89                        .0
90                        .in_circle_aabr(pos.0.xy(), TELEPORTER_RADIUS)
91                        .any(|entity| {
92                            (&positions, &teleporting)
93                                .lend_join()
94                                .get(entity, &entities)
95                                .is_some_and(|(teleporter_pos, _)| {
96                                    pos.0.distance_squared(teleporter_pos.0)
97                                        <= TELEPORTER_RADIUS.powi(2)
98                                })
99                        });
100
101                    if body.is_some_and(|body| {
102                        (*body == Body::Object(object::Body::PortalActive)) != is_active
103                    }) {
104                        emitters.emit(ChangeBodyEvent {
105                            entity,
106                            new_body: Body::Object(if is_active {
107                                outcome_bus.emit_now(Outcome::PortalActivated { pos: pos.0 });
108                                object::Body::PortalActive
109                            } else {
110                                object::Body::Portal
111                            }),
112                            permanent_change: None,
113                        });
114                    }
115                },
116                Object::BeamPillar {
117                    spawned_at,
118                    buildup_duration,
119                    attack_duration,
120                    beam_duration,
121                    radius,
122                    height,
123                    damage,
124                    damage_effect,
125                    dodgeable,
126                    tick_rate,
127                    specifier,
128                    indicator_specifier,
129                } => {
130                    match indicator_specifier {
131                        BeamPillarIndicatorSpecifier::FirePillar => outcome_bus
132                            .emit_now(Outcome::FirePillarIndicator { pos: pos.0, radius }),
133                    }
134
135                    let age = (time.0 - spawned_at.0).max(0.0);
136                    let buildup = buildup_duration.as_secs_f64();
137                    let attack = attack_duration.as_secs_f64();
138
139                    if age > buildup + attack {
140                        emitters.emit(DeleteEvent(entity));
141                    } else if age > buildup && !beams.contains(entity) {
142                        let mut attack_damage = AttackDamage::new(
143                            Damage {
144                                source: DamageSource::Energy,
145                                kind: DamageKind::Energy,
146                                value: damage,
147                            },
148                            Some(GroupTarget::OutOfGroup),
149                            rand::random(),
150                        );
151                        if let Some(combat_effect) = damage_effect {
152                            attack_damage = attack_damage.with_effect(combat_effect);
153                        }
154
155                        updater.insert(entity, beam::Beam {
156                            attack: Attack::default().with_damage(attack_damage),
157                            dodgeable,
158                            start_radius: radius,
159                            end_radius: radius,
160                            range: height,
161                            duration: Secs(beam_duration.as_secs_f64()),
162                            tick_dur: Secs(1.0 / tick_rate as f64),
163                            hit_entities: Vec::new(),
164                            hit_durations: HashMap::new(),
165                            specifier,
166                            bezier: QuadraticBezier3 {
167                                start: pos.0,
168                                ctrl: pos.0,
169                                end: pos.0,
170                            },
171                        });
172                    }
173                },
174                Object::Crux {
175                    ref mut pid_controller,
176                    ..
177                } => {
178                    if let Some(vel) = vel
179                        && let Some(pid_controller) = pid_controller
180                        && let Some(accel) = body.and_then(|body| {
181                            body.fly_thrust()
182                                .map(|fly_thrust| fly_thrust / body.mass().0)
183                        })
184                    {
185                        pid_controller.add_measurement(time.0, pos.0.z);
186                        let dir = pid_controller.calc_err();
187                        pid_controller.limit_integral_windup(|z| *z = z.clamp(-1.0, 1.0));
188
189                        updater
190                            .insert(entity, Vel((vel.0.z + dir * accel * dt.0) * Vec3::unit_z()));
191                    }
192                },
193            }
194        }
195    }
196}