veloren_server/sys/
object.rs

1use common::{
2    CachedSpatialGrid, Damage, DamageKind, DamageSource, Explosion, RadiusEffect,
3    comp::{Body, Object, PhysicsState, Pos, Teleporting, Vel, object},
4    consts::TELEPORTER_RADIUS,
5    effect::Effect,
6    event::{ChangeBodyEvent, DeleteEvent, EmitExt, EventBus, ExplosionEvent, ShootEvent},
7    event_emitters,
8    outcome::Outcome,
9    resources::{DeltaTime, Time},
10};
11use common_ecs::{Job, Origin, Phase, System};
12use specs::{Entities, Join, LendJoin, Read, ReadStorage};
13use vek::Rgb;
14
15event_emitters! {
16    struct Events[Emitters] {
17        delete: DeleteEvent,
18        explosion: ExplosionEvent,
19        shoot: ShootEvent,
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        ReadStorage<'a, PhysicsState>,
38        ReadStorage<'a, Object>,
39        ReadStorage<'a, Body>,
40        ReadStorage<'a, Teleporting>,
41    );
42
43    const NAME: &'static str = "object";
44    const ORIGIN: Origin = Origin::Server;
45    const PHASE: Phase = Phase::Create;
46
47    fn run(
48        _job: &mut Job<Self>,
49        (
50            entities,
51            events,
52            _dt,
53            time,
54            outcome_bus,
55            spatial_grid,
56            positions,
57            velocities,
58            physics_states,
59            objects,
60            bodies,
61            teleporting,
62        ): Self::SystemData,
63    ) {
64        let mut emitters = events.get_emitters();
65
66        // Objects
67        for (entity, pos, vel, physics, object, body) in (
68            &entities,
69            &positions,
70            &velocities,
71            physics_states.maybe(),
72            &objects,
73            bodies.maybe(),
74        )
75            .join()
76        {
77            match object {
78                Object::Bomb { owner } => {
79                    if physics.is_some_and(|physics| physics.on_surface().is_some()) {
80                        emitters.emit(DeleteEvent(entity));
81                        emitters.emit(ExplosionEvent {
82                            pos: pos.0,
83                            explosion: Explosion {
84                                effects: vec![
85                                    RadiusEffect::Entity(Effect::Damage(Damage {
86                                        source: DamageSource::Explosion,
87                                        kind: DamageKind::Energy,
88                                        value: 40.0,
89                                    })),
90                                    RadiusEffect::Entity(Effect::Poise(-100.0)),
91                                    RadiusEffect::TerrainDestruction(4.0, Rgb::black()),
92                                ],
93                                radius: 12.0,
94                                reagent: None,
95                                min_falloff: 0.75,
96                            },
97                            owner: *owner,
98                        });
99                    }
100                },
101                Object::SurpriseEgg { .. } => {
102                    if physics.is_some_and(|physics| physics.on_surface().is_some()) {
103                        emitters.emit(DeleteEvent(entity));
104                        outcome_bus.emit_now(Outcome::SurpriseEgg { pos: pos.0 });
105                    }
106                },
107                Object::Firework { owner, reagent } => {
108                    if vel.0.z < 0.0 {
109                        const ENABLE_RECURSIVE_FIREWORKS: bool = true;
110                        if ENABLE_RECURSIVE_FIREWORKS {
111                            use common::{
112                                comp::{LightEmitter, Projectile},
113                                util::Dir,
114                            };
115                            use rand::Rng;
116                            use std::{f32::consts::PI, time::Duration};
117                            use vek::Vec3;
118                            let mut rng = rand::thread_rng();
119                            // Note that if the expected fireworks per firework is > 1, this will
120                            // eventually cause enough server lag that more players can't log in.
121                            let thresholds: &[(f32, usize)] = &[(0.25, 2), (0.7, 1)];
122                            let expected = {
123                                let mut total = 0.0;
124                                let mut cumulative_probability = 0.0;
125                                for (p, n) in thresholds {
126                                    total += (p - cumulative_probability) * *n as f32;
127                                    cumulative_probability += p;
128                                }
129                                total
130                            };
131                            assert!(expected < 1.0);
132                            let num_fireworks = (|| {
133                                let x = rng.gen_range(0.0..1.0);
134                                for (p, n) in thresholds {
135                                    if x < *p {
136                                        return *n;
137                                    }
138                                }
139                                0
140                            })();
141                            for _ in 0..num_fireworks {
142                                let speed: f32 = rng.gen_range(40.0..80.0);
143                                let theta: f32 = rng.gen_range(0.0..2.0 * PI);
144                                let phi: f32 = rng.gen_range(0.25 * PI..0.5 * PI);
145                                let dir = Dir::from_unnormalized(Vec3::new(
146                                    theta.cos(),
147                                    theta.sin(),
148                                    phi.sin(),
149                                ))
150                                .expect("nonzero vector should normalize");
151                                emitters.emit(ShootEvent {
152                                    entity,
153                                    pos: *pos,
154                                    dir,
155                                    body: Body::Object(object::Body::for_firework(*reagent)),
156                                    light: Some(LightEmitter {
157                                        animated: true,
158                                        flicker: 2.0,
159                                        strength: 2.0,
160                                        col: Rgb::new(1.0, 1.0, 0.0),
161                                    }),
162                                    projectile: Projectile {
163                                        hit_solid: Vec::new(),
164                                        hit_entity: Vec::new(),
165                                        time_left: Duration::from_secs(60),
166                                        owner: *owner,
167                                        ignore_group: true,
168                                        is_sticky: true,
169                                        is_point: true,
170                                    },
171                                    speed,
172                                    object: Some(Object::Firework {
173                                        owner: *owner,
174                                        reagent: *reagent,
175                                    }),
176                                });
177                            }
178                        }
179                        emitters.emit(DeleteEvent(entity));
180                        emitters.emit(ExplosionEvent {
181                            pos: pos.0,
182                            explosion: Explosion {
183                                effects: vec![
184                                    RadiusEffect::Entity(Effect::Damage(Damage {
185                                        source: DamageSource::Explosion,
186                                        kind: DamageKind::Energy,
187                                        value: 5.0,
188                                    })),
189                                    RadiusEffect::Entity(Effect::Poise(-40.0)),
190                                    RadiusEffect::TerrainDestruction(4.0, Rgb::black()),
191                                ],
192                                radius: 12.0,
193                                reagent: Some(*reagent),
194                                min_falloff: 0.0,
195                            },
196                            owner: *owner,
197                        });
198                    }
199                },
200                Object::DeleteAfter {
201                    spawned_at,
202                    timeout,
203                } => {
204                    if (time.0 - spawned_at.0).max(0.0) > timeout.as_secs_f64() {
205                        emitters.emit(DeleteEvent(entity));
206                    }
207                },
208                Object::Portal { .. } => {
209                    let is_active = spatial_grid
210                        .0
211                        .in_circle_aabr(pos.0.xy(), TELEPORTER_RADIUS)
212                        .any(|entity| {
213                            (&positions, &teleporting)
214                                .lend_join()
215                                .get(entity, &entities)
216                                .is_some_and(|(teleporter_pos, _)| {
217                                    pos.0.distance_squared(teleporter_pos.0)
218                                        <= TELEPORTER_RADIUS.powi(2)
219                                })
220                        });
221
222                    if body.is_some_and(|body| {
223                        (*body == Body::Object(object::Body::PortalActive)) != is_active
224                    }) {
225                        emitters.emit(ChangeBodyEvent {
226                            entity,
227                            new_body: Body::Object(if is_active {
228                                outcome_bus.emit_now(Outcome::PortalActivated { pos: pos.0 });
229                                object::Body::PortalActive
230                            } else {
231                                object::Body::Portal
232                            }),
233                        });
234                    }
235                },
236            }
237        }
238    }
239}