1use super::super::{Rng, atan2, cos, sin, sqrt};
2
3pub const FULL_TURN: i32 = 360_000;
4
5const PI: f64 = f64::from_bits(0x4009_21FB_5444_2D18);
6const RADIANS: f64 = PI / 180_000.0;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum Pattern {
11 Single,
12 Fan { count: u32, arc: i32 },
13 Ring { count: u32 },
14 Spiral { arms: u32, step: i32 },
15 Crown { count: u32, tilt: i32 },
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Rate {
21 pub charge: u32,
22 pub cost: u32,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct Burst {
28 pub count: u32,
29 pub gap: u32,
30 pub cooldown: u32,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35pub struct Fire {
36 pub pattern: Pattern,
37 pub rate: Rate,
38 pub burst: Option<Burst>,
39 pub windup: u32,
40 pub speed: i32,
41 pub spread: i32,
42}
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct Telegraph {
47 left: u32,
48 total: u32,
49}
50
51impl Telegraph {
52 pub const fn new() -> Self {
53 Self { left: 0, total: 0 }
54 }
55
56 pub fn start(&mut self, ticks: u32) {
57 self.total = ticks.max(1);
58 self.left = self.total;
59 }
60
61 pub fn cancel(&mut self) -> bool {
62 let was = self.is_winding();
63 self.left = 0;
64 self.total = 0;
65 was
66 }
67
68 #[must_use]
69 pub fn tick(&mut self) -> bool {
70 if self.left == 0 {
71 return false;
72 }
73 self.left -= 1;
74 if self.left == 0 {
75 self.total = 0;
76 return true;
77 }
78 false
79 }
80
81 pub const fn is_winding(&self) -> bool {
82 self.left > 0
83 }
84
85 pub const fn left(&self) -> u32 {
86 self.left
87 }
88
89 pub const fn total(&self) -> u32 {
90 self.total
91 }
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub enum Phase {
97 Ready,
98 Winding { left: u32, total: u32 },
99 Burst { left: u32, wait: u32 },
100 Cooling { left: u32 },
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105pub enum Aim2 {
106 Angle(i32),
107 At { target: [i32; 2], vel: [i32; 2] },
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
112pub enum Aim3 {
113 Basis {
114 forward: [i32; 3],
115 up: [i32; 3],
116 },
117 At {
118 target: [i32; 3],
119 vel: [i32; 3],
120 up: [i32; 3],
121 },
122}
123
124#[derive(Clone, Debug)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126pub struct Emitter {
127 pub fire: Fire,
128 charge: u32,
129 phase: Phase,
130 telegraph: Telegraph,
131 spin: i32,
132 #[cfg_attr(feature = "serde", serde(skip))]
133 locals: Vec<(f64, f64)>,
134}
135
136impl Emitter {
137 pub fn new(fire: Fire) -> Self {
138 Self::with_charge(fire, 0)
139 }
140
141 pub fn with_charge(fire: Fire, charge: u32) -> Self {
142 Self {
143 fire,
144 charge,
145 phase: Phase::Ready,
146 telegraph: Telegraph::new(),
147 spin: 0,
148 locals: Vec::new(),
149 }
150 }
151
152 pub fn phase(&self) -> Phase {
153 match self.phase {
154 Phase::Winding { .. } => Phase::Winding {
155 left: self.telegraph.left(),
156 total: self.telegraph.total(),
157 },
158 phase => phase,
159 }
160 }
161
162 pub const fn charge(&self) -> u32 {
163 self.charge
164 }
165
166 pub const fn spin(&self) -> i32 {
167 self.spin
168 }
169
170 pub fn cancel(&mut self) -> bool {
171 if self.telegraph.cancel() {
172 self.phase = Phase::Ready;
173 return true;
174 }
175 false
176 }
177
178 pub fn reset(&mut self) {
179 self.charge = 0;
180 self.phase = Phase::Ready;
181 self.telegraph.cancel();
182 self.spin = 0;
183 }
184
185 #[must_use]
186 pub fn tick(&mut self, trigger: bool) -> u32 {
187 let cost = self.fire.rate.cost.max(1);
188 match self.phase {
189 Phase::Cooling { left } => {
190 self.phase = if left <= 1 {
191 Phase::Ready
192 } else {
193 Phase::Cooling { left: left - 1 }
194 };
195 0
196 }
197 Phase::Winding { .. } => {
198 if self.telegraph.tick() {
199 self.release()
200 } else {
201 0
202 }
203 }
204 Phase::Burst { left, wait } => {
205 if wait > 1 {
206 self.phase = Phase::Burst {
207 left,
208 wait: wait - 1,
209 };
210 return 0;
211 }
212 self.after_volley(left - 1);
213 1
214 }
215 Phase::Ready => {
216 if !trigger {
217 if self.charge < cost {
218 self.charge = self.charge.saturating_add(self.fire.rate.charge).min(cost);
219 }
220 return 0;
221 }
222 self.charge = self.charge.saturating_add(self.fire.rate.charge);
223 if self.charge < cost {
224 return 0;
225 }
226 if self.fire.burst.is_none() && self.fire.windup == 0 {
227 let volleys = self.charge / cost;
228 self.charge %= cost;
229 return volleys;
230 }
231 self.charge -= cost;
232 if self.fire.windup > 0 {
233 self.telegraph.start(self.fire.windup);
234 self.phase = Phase::Winding {
235 left: self.fire.windup,
236 total: self.fire.windup,
237 };
238 return 0;
239 }
240 self.release()
241 }
242 }
243 }
244
245 pub fn tick2(
246 &mut self,
247 origin: [i32; 2],
248 aim: Aim2,
249 trigger: bool,
250 rng: &mut Rng,
251 out: &mut Vec<[i32; 2]>,
252 ) -> u32 {
253 let volleys = self.tick(trigger);
254 for _ in 0..volleys {
255 self.volley2(origin, aim, rng, out);
256 }
257 volleys
258 }
259
260 pub fn tick3(
261 &mut self,
262 origin: [i32; 3],
263 aim: Aim3,
264 trigger: bool,
265 rng: &mut Rng,
266 out: &mut Vec<[i32; 3]>,
267 ) -> u32 {
268 let volleys = self.tick(trigger);
269 for _ in 0..volleys {
270 self.volley3(origin, aim, rng, out);
271 }
272 volleys
273 }
274
275 pub fn volley2(&mut self, origin: [i32; 2], aim: Aim2, rng: &mut Rng, out: &mut Vec<[i32; 2]>) {
276 let base = match aim {
277 Aim2::Angle(angle) => f64::from(angle) * RADIANS,
278 Aim2::At { target, vel } => {
279 let dir = aim_point(origin, target, vel, self.fire.speed);
280 if dir == [0.0; 2] {
281 0.0
282 } else {
283 atan2(dir[1], dir[0])
284 }
285 }
286 };
287 self.fill_locals(true);
288 let speed = f64::from(self.fire.speed);
289 for &(tilt, _) in &self.locals {
290 let mut angle = base + tilt * RADIANS;
291 if self.fire.spread > 0 {
292 angle +=
293 f64::from(rng.range_i32(-self.fire.spread, self.fire.spread + 1)) * RADIANS;
294 }
295 out.push([round(cos(angle) * speed), round(sin(angle) * speed)]);
296 }
297 }
298
299 pub fn volley3(&mut self, origin: [i32; 3], aim: Aim3, rng: &mut Rng, out: &mut Vec<[i32; 3]>) {
300 let (forward, up) = match aim {
301 Aim3::Basis { forward, up } => (forward.map(f64::from), up),
302 Aim3::At { target, vel, up } => (aim_point(origin, target, vel, self.fire.speed), up),
303 };
304 let (f, s, u) = basis(forward, up.map(f64::from));
305 self.fill_locals(false);
306 let speed = f64::from(self.fire.speed);
307 let cone = f64::from(self.fire.spread) * RADIANS;
308 for &(tilt, roll) in &self.locals {
309 let (t, r) = (tilt * RADIANS, roll * RADIANS);
310 let (ct, st, cr, sr) = (cos(t), sin(t), cos(r), sin(r));
311 let mut d: [f64; 3] = std::array::from_fn(|i| ct * f[i] + st * (cr * s[i] + sr * u[i]));
312 if self.fire.spread > 0 {
313 let ca = 1.0 - rng.next_f64() * (1.0 - cos(cone));
314 let sa = sqrt((1.0 - ca * ca).max(0.0));
315 let b = rng.next_f64() * (2.0 * PI);
316 let (_, p, q) = basis(d, u);
317 let (cb, sb) = (cos(b), sin(b));
318 d = std::array::from_fn(|i| ca * d[i] + sa * (cb * p[i] + sb * q[i]));
319 }
320 out.push(d.map(|c| round(c * speed)));
321 }
322 }
323
324 fn release(&mut self) -> u32 {
325 match self.fire.burst {
326 Some(burst) if burst.count > 1 && burst.gap == 0 => {
327 self.after_volley(0);
328 burst.count
329 }
330 Some(burst) if burst.count > 1 => {
331 self.after_volley(burst.count - 1);
332 1
333 }
334 _ => {
335 self.after_volley(0);
336 1
337 }
338 }
339 }
340
341 fn after_volley(&mut self, left: u32) {
342 let burst = self.fire.burst.unwrap_or(Burst {
343 count: 1,
344 gap: 0,
345 cooldown: 0,
346 });
347 self.phase = if left > 0 {
348 Phase::Burst {
349 left,
350 wait: burst.gap,
351 }
352 } else if burst.cooldown > 0 {
353 Phase::Cooling {
354 left: burst.cooldown,
355 }
356 } else {
357 Phase::Ready
358 };
359 }
360
361 fn fill_locals(&mut self, flat: bool) {
362 self.locals.clear();
363 let even = |i: u32, count: u32| f64::from(i) * f64::from(FULL_TURN) / f64::from(count);
364 match self.fire.pattern {
365 Pattern::Single => self.locals.push((0.0, 0.0)),
366 Pattern::Fan { count, arc } => {
367 if count == 1 {
368 self.locals.push((0.0, 0.0));
369 }
370 if count > 1 {
371 let arc = f64::from(arc);
372 let gaps = f64::from(count - 1);
373 self.locals
374 .extend((0..count).map(|i| (f64::from(i) * arc / gaps - arc / 2.0, 0.0)));
375 }
376 }
377 Pattern::Ring { count } => self
378 .locals
379 .extend((0..count).map(|i| (even(i, count), 0.0))),
380 Pattern::Spiral { arms, step } => {
381 let spin = f64::from(self.spin);
382 self.locals
383 .extend((0..arms).map(|i| (spin + even(i, arms), 0.0)));
384 self.spin = wrap(i64::from(self.spin) + i64::from(step));
385 }
386 Pattern::Crown { count, tilt } => {
387 let tilt = f64::from(tilt);
388 if flat {
389 self.locals.extend(
390 [(tilt, 0.0), (-tilt, 0.0)]
391 .into_iter()
392 .take(count.min(2) as usize),
393 );
394 } else {
395 self.locals
396 .extend((0..count).map(|i| (tilt, even(i, count))));
397 }
398 }
399 }
400 }
401}
402
403pub fn intercept<const N: usize>(
404 from: [i32; N],
405 target: [i32; N],
406 vel: [i32; N],
407 speed: i32,
408) -> Option<f64> {
409 let d: [f64; N] = std::array::from_fn(|i| f64::from(target[i]) - f64::from(from[i]));
410 let v = vel.map(f64::from);
411 let dot = |x: &[f64; N], y: &[f64; N]| (0..N).fold(0.0, |sum, i| sum + x[i] * y[i]);
412 let s = f64::from(speed);
413 let a = dot(&v, &v) - s * s;
414 let b = 2.0 * dot(&d, &v);
415 let c = dot(&d, &d);
416 if c == 0.0 || speed <= 0 {
417 return None;
418 }
419 if a == 0.0 {
420 return (b < 0.0).then(|| -c / b);
421 }
422 let disc = b * b - 4.0 * a * c;
423 if disc < 0.0 {
424 return None;
425 }
426 let root = sqrt(disc);
427 let t1 = (-b - root) / (2.0 * a);
428 let t2 = (-b + root) / (2.0 * a);
429 let (low, high) = if t1 < t2 { (t1, t2) } else { (t2, t1) };
430 if low > 0.0 {
431 Some(low)
432 } else if high > 0.0 {
433 Some(high)
434 } else {
435 None
436 }
437}
438
439fn aim_point<const N: usize>(
440 from: [i32; N],
441 target: [i32; N],
442 vel: [i32; N],
443 speed: i32,
444) -> [f64; N] {
445 let t = intercept(from, target, vel, speed).unwrap_or(0.0);
446 std::array::from_fn(|i| f64::from(target[i]) - f64::from(from[i]) + f64::from(vel[i]) * t)
447}
448
449fn basis(forward: [f64; 3], up: [f64; 3]) -> ([f64; 3], [f64; 3], [f64; 3]) {
450 let f = normalize(forward).unwrap_or([1.0, 0.0, 0.0]);
451 let side = [up, [0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]
452 .into_iter()
453 .find_map(|hint| normalize(cross(f, hint)))
454 .unwrap_or([0.0, 1.0, 0.0]);
455 (f, side, cross(side, f))
456}
457
458fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
459 [
460 a[1] * b[2] - a[2] * b[1],
461 a[2] * b[0] - a[0] * b[2],
462 a[0] * b[1] - a[1] * b[0],
463 ]
464}
465
466fn normalize(v: [f64; 3]) -> Option<[f64; 3]> {
467 let len = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
468 (len > 0.0 && len.is_finite()).then(|| v.map(|c| c / len))
469}
470
471fn wrap(angle: i64) -> i32 {
472 let turn = i64::from(FULL_TURN);
473 let mut angle = angle % turn;
474 if angle < 0 {
475 angle += turn;
476 }
477 angle as i32
478}
479
480pub(super) fn round(x: f64) -> i32 {
481 x.round() as i32
482}