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#[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 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}