@basmilius/sparkle 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/index.d.mts +1192 -14
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +4552 -370
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +2 -1
  6. package/src/aurora/consts.ts +3 -0
  7. package/src/aurora/index.ts +4 -0
  8. package/src/aurora/layer.ts +152 -0
  9. package/src/aurora/simulation.ts +19 -0
  10. package/src/aurora/types.ts +13 -0
  11. package/src/balloons/consts.ts +3 -0
  12. package/src/balloons/index.ts +6 -0
  13. package/src/balloons/layer.ts +138 -0
  14. package/src/balloons/particle.ts +110 -0
  15. package/src/balloons/simulation.ts +19 -0
  16. package/src/balloons/types.ts +14 -0
  17. package/src/bubbles/consts.ts +3 -0
  18. package/src/bubbles/index.ts +4 -0
  19. package/src/bubbles/layer.ts +233 -0
  20. package/src/bubbles/simulation.ts +20 -0
  21. package/src/bubbles/types.ts +21 -0
  22. package/src/canvas.ts +20 -1
  23. package/src/color.ts +10 -0
  24. package/src/confetti/consts.ts +13 -13
  25. package/src/confetti/index.ts +6 -0
  26. package/src/confetti/layer.ts +152 -0
  27. package/src/confetti/particle.ts +105 -0
  28. package/src/confetti/shapes.ts +104 -0
  29. package/src/confetti/simulation.ts +9 -203
  30. package/src/confetti/types.ts +4 -1
  31. package/src/distance.ts +1 -1
  32. package/src/donuts/consts.ts +19 -0
  33. package/src/donuts/donut.ts +12 -0
  34. package/src/donuts/index.ts +3 -0
  35. package/src/donuts/layer.ts +270 -0
  36. package/src/donuts/simulation.ts +25 -0
  37. package/src/fireflies/consts.ts +3 -0
  38. package/src/fireflies/index.ts +6 -0
  39. package/src/fireflies/layer.ts +152 -0
  40. package/src/fireflies/particle.ts +124 -0
  41. package/src/fireflies/simulation.ts +18 -0
  42. package/src/fireflies/types.ts +17 -0
  43. package/src/firepit/consts.ts +3 -0
  44. package/src/firepit/index.ts +4 -0
  45. package/src/firepit/layer.ts +174 -0
  46. package/src/firepit/simulation.ts +17 -0
  47. package/src/firepit/types.ts +20 -0
  48. package/src/fireworks/explosion.ts +8 -8
  49. package/src/fireworks/firework.ts +9 -8
  50. package/src/fireworks/index.ts +6 -2
  51. package/src/fireworks/layer.ts +452 -0
  52. package/src/fireworks/simulation.ts +9 -484
  53. package/src/fireworks/spark.ts +7 -7
  54. package/src/glitter/consts.ts +13 -0
  55. package/src/glitter/index.ts +4 -0
  56. package/src/glitter/layer.ts +173 -0
  57. package/src/glitter/simulation.ts +19 -0
  58. package/src/glitter/types.ts +23 -0
  59. package/src/index.ts +28 -0
  60. package/src/lanterns/consts.ts +13 -0
  61. package/src/lanterns/index.ts +4 -0
  62. package/src/lanterns/layer.ts +166 -0
  63. package/src/lanterns/simulation.ts +17 -0
  64. package/src/lanterns/types.ts +14 -0
  65. package/src/layer.ts +24 -0
  66. package/src/layered.ts +185 -0
  67. package/src/leaves/consts.ts +16 -0
  68. package/src/leaves/index.ts +4 -0
  69. package/src/leaves/layer.ts +251 -0
  70. package/src/leaves/simulation.ts +18 -0
  71. package/src/leaves/types.ts +16 -0
  72. package/src/lightning/consts.ts +3 -0
  73. package/src/lightning/index.ts +6 -0
  74. package/src/lightning/layer.ts +41 -0
  75. package/src/lightning/simulation.ts +17 -0
  76. package/src/lightning/system.ts +196 -0
  77. package/src/lightning/types.ts +12 -0
  78. package/src/matrix/consts.ts +5 -0
  79. package/src/matrix/index.ts +4 -0
  80. package/src/matrix/layer.ts +146 -0
  81. package/src/matrix/simulation.ts +18 -0
  82. package/src/matrix/types.ts +8 -0
  83. package/src/orbits/consts.ts +13 -0
  84. package/src/orbits/index.ts +4 -0
  85. package/src/orbits/layer.ts +183 -0
  86. package/src/orbits/simulation.ts +19 -0
  87. package/src/orbits/types.ts +16 -0
  88. package/src/particles/consts.ts +3 -0
  89. package/src/particles/index.ts +4 -0
  90. package/src/particles/layer.ts +317 -0
  91. package/src/particles/simulation.ts +26 -0
  92. package/src/particles/types.ts +10 -0
  93. package/src/petals/consts.ts +13 -0
  94. package/src/petals/index.ts +4 -0
  95. package/src/petals/layer.ts +158 -0
  96. package/src/petals/simulation.ts +18 -0
  97. package/src/petals/types.ts +15 -0
  98. package/src/plasma/consts.ts +3 -0
  99. package/src/plasma/index.ts +4 -0
  100. package/src/plasma/layer.ts +92 -0
  101. package/src/plasma/simulation.ts +17 -0
  102. package/src/plasma/types.ts +5 -0
  103. package/src/rain/consts.ts +3 -0
  104. package/src/rain/index.ts +6 -0
  105. package/src/rain/layer.ts +172 -0
  106. package/src/rain/particle.ts +132 -0
  107. package/src/rain/simulation.ts +21 -0
  108. package/src/rain/types.ts +22 -0
  109. package/src/sandstorm/consts.ts +3 -0
  110. package/src/sandstorm/index.ts +4 -0
  111. package/src/sandstorm/layer.ts +135 -0
  112. package/src/sandstorm/simulation.ts +18 -0
  113. package/src/sandstorm/types.ts +10 -0
  114. package/src/shooting-stars/index.ts +3 -0
  115. package/src/shooting-stars/system.ts +149 -0
  116. package/src/shooting-stars/types.ts +10 -0
  117. package/src/simulation-canvas.ts +47 -0
  118. package/src/snow/consts.ts +2 -2
  119. package/src/snow/index.ts +1 -0
  120. package/src/snow/layer.ts +263 -0
  121. package/src/snow/simulation.ts +4 -288
  122. package/src/sparklers/consts.ts +3 -0
  123. package/src/sparklers/index.ts +6 -0
  124. package/src/sparklers/layer.ts +174 -0
  125. package/src/sparklers/particle.ts +89 -0
  126. package/src/sparklers/simulation.ts +30 -0
  127. package/src/sparklers/types.ts +13 -0
  128. package/src/stars/consts.ts +3 -0
  129. package/src/stars/index.ts +4 -0
  130. package/src/stars/layer.ts +133 -0
  131. package/src/stars/simulation.ts +22 -0
  132. package/src/stars/types.ts +12 -0
  133. package/src/streamers/consts.ts +14 -0
  134. package/src/streamers/index.ts +4 -0
  135. package/src/streamers/layer.ts +211 -0
  136. package/src/streamers/simulation.ts +16 -0
  137. package/src/streamers/types.ts +14 -0
  138. package/src/trail.ts +140 -0
  139. package/src/waves/consts.ts +3 -0
  140. package/src/waves/index.ts +4 -0
  141. package/src/waves/layer.ts +167 -0
  142. package/src/waves/simulation.ts +18 -0
  143. package/src/waves/types.ts +9 -0
  144. package/src/wormhole/consts.ts +3 -0
  145. package/src/wormhole/index.ts +4 -0
  146. package/src/wormhole/layer.ts +181 -0
  147. package/src/wormhole/simulation.ts +17 -0
  148. package/src/wormhole/types.ts +10 -0
@@ -0,0 +1,452 @@
1
+ import { SimulationLayer } from '../layer';
2
+ import type { Point } from '../point';
3
+ import { MULBERRY } from './consts';
4
+ import { Explosion } from './explosion';
5
+ import { Firework } from './firework';
6
+ import { Spark } from './spark';
7
+ import { EXPLOSION_CONFIGS, type ExplosionType, type FireworkSimulationConfig, type FireworkVariant } from './types';
8
+
9
+ export class FireworkLayer extends SimulationLayer {
10
+ #explosions: Explosion[] = [];
11
+ #fireworks: Firework[] = [];
12
+ #sparks: Spark[] = [];
13
+ #hue: number = 120;
14
+ #spawnTimer: number = 0;
15
+ #positionRandom = MULBERRY.fork();
16
+ readonly #autoSpawn: boolean;
17
+ readonly #baseSize: number;
18
+ readonly #scale: number;
19
+ readonly #tailWidth: number;
20
+ #width: number = 960;
21
+ #height: number = 540;
22
+
23
+ constructor(config: FireworkSimulationConfig = {}) {
24
+ super();
25
+
26
+ const scale = config.scale ?? 1;
27
+ this.#autoSpawn = config.autoSpawn ?? true;
28
+ this.#baseSize = 5 * scale;
29
+ this.#scale = scale;
30
+ this.#tailWidth = 2 * scale;
31
+ }
32
+
33
+ onResize(width: number, height: number): void {
34
+ this.#width = width;
35
+ this.#height = height;
36
+ }
37
+
38
+ fireExplosion(variant: FireworkVariant, position?: Point): void {
39
+ const pos = position ?? {x: this.#width / 2, y: this.#height * 0.4};
40
+ this.#hue = MULBERRY.nextBetween(0, 360);
41
+ this.#createExplosion(pos, this.#hue, variant);
42
+ }
43
+
44
+ tick(dt: number, width: number, height: number): void {
45
+ this.#width = width;
46
+ this.#height = height;
47
+ this.#spawnTimer += dt;
48
+
49
+ const isSmall = innerWidth < 991;
50
+ const spawnInterval = isSmall ? 60 : 30;
51
+
52
+ if (this.#autoSpawn && this.#fireworks.length < 6 && this.#spawnTimer >= spawnInterval) {
53
+ this.#spawnTimer -= spawnInterval;
54
+ let count = MULBERRY.nextBetween(1, 100) < 10 ? 2 : 1;
55
+
56
+ while (count--) {
57
+ this.#hue = MULBERRY.nextBetween(0, 360);
58
+ this.#createFirework();
59
+ }
60
+ }
61
+
62
+ for (const firework of this.#fireworks) {
63
+ firework.tick(dt);
64
+ this.#sparks.push(...firework.collectSparks());
65
+ }
66
+
67
+ for (const explosion of this.#explosions) {
68
+ explosion.tick(dt);
69
+ }
70
+
71
+ for (const spark of this.#sparks) {
72
+ spark.tick(dt);
73
+ }
74
+
75
+ const newExplosions: Explosion[] = [];
76
+ const newSparks: Spark[] = [];
77
+
78
+ for (const explosion of this.#explosions) {
79
+ if (explosion.checkSplit()) {
80
+ for (let i = 0; i < 4; i++) {
81
+ const angle = explosion.angle + (Math.PI / 2) * i + Math.PI / 4;
82
+
83
+ newExplosions.push(new Explosion(
84
+ explosion.position,
85
+ explosion.hue,
86
+ this.#baseSize * 0.6,
87
+ 'peony',
88
+ this.#scale,
89
+ angle,
90
+ MULBERRY.nextBetween(3, 6)
91
+ ));
92
+ }
93
+ }
94
+
95
+ if (explosion.checkCrackle()) {
96
+ for (let j = 0; j < 8; j++) {
97
+ newSparks.push(new Spark(
98
+ explosion.position,
99
+ explosion.hue + MULBERRY.nextBetween(-30, 30)
100
+ ));
101
+ }
102
+ }
103
+ }
104
+
105
+ this.#explosions.push(...newExplosions);
106
+ this.#sparks.push(...newSparks);
107
+
108
+ this.#explosions = this.#explosions.filter(e => !e.isDead);
109
+ this.#sparks = this.#sparks.filter(s => !s.isDead);
110
+ }
111
+
112
+ draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
113
+ if (ctx.canvas.width !== width || ctx.canvas.height !== height) {
114
+ ctx.canvas.width = width;
115
+ ctx.canvas.height = height;
116
+ }
117
+
118
+ ctx.globalCompositeOperation = 'lighter';
119
+
120
+ for (const spark of this.#sparks) {
121
+ spark.draw(ctx);
122
+ }
123
+
124
+ for (const explosion of this.#explosions) {
125
+ explosion.draw(ctx);
126
+ }
127
+
128
+ for (const firework of this.#fireworks) {
129
+ firework.draw(ctx);
130
+ }
131
+
132
+ ctx.globalCompositeOperation = 'source-over';
133
+ }
134
+
135
+ #createExplosion(position: Point, hue: number, variant?: FireworkVariant): void {
136
+ const selected = variant ?? this.#pickVariant();
137
+
138
+ if (selected === 'saturn') {
139
+ this.#createSaturnExplosion(position, hue);
140
+ return;
141
+ }
142
+
143
+ if (selected === 'dahlia') {
144
+ this.#createDahliaExplosion(position, hue);
145
+ return;
146
+ }
147
+
148
+ if (selected === 'heart') {
149
+ this.#createHeartExplosion(position, hue);
150
+ return;
151
+ }
152
+
153
+ if (selected === 'spiral') {
154
+ this.#createSpiralExplosion(position, hue);
155
+ return;
156
+ }
157
+
158
+ if (selected === 'flower') {
159
+ this.#createFlowerExplosion(position, hue);
160
+ return;
161
+ }
162
+
163
+ if (selected === 'concentric') {
164
+ this.#createConcentricExplosion(position, hue);
165
+ return;
166
+ }
167
+
168
+ const type: ExplosionType = selected;
169
+ const config = EXPLOSION_CONFIGS[type];
170
+ const particleCount = Math.floor(MULBERRY.nextBetween(config.particleCount[0], config.particleCount[1]));
171
+
172
+ const effectiveHue = type === 'brocade'
173
+ ? MULBERRY.nextBetween(35, 50)
174
+ : hue;
175
+
176
+ for (let i = 0; i < particleCount; i++) {
177
+ let angle: number | undefined;
178
+ let speed: number | undefined;
179
+
180
+ if (type === 'ring') {
181
+ angle = (i / particleCount) * Math.PI * 2;
182
+ speed = MULBERRY.nextBetween(config.speed[0], config.speed[1]) * 0.5 + config.speed[0] * 0.5;
183
+ } else if (type === 'palm' || type === 'horsetail') {
184
+ const spread = type === 'horsetail' ? Math.PI / 8 : Math.PI / 5;
185
+ angle = -Math.PI / 2 + MULBERRY.nextBetween(-spread, spread);
186
+ }
187
+
188
+ this.#explosions.push(new Explosion(position, effectiveHue, this.#baseSize, type, this.#scale, angle, speed));
189
+ }
190
+ }
191
+
192
+ #createSaturnExplosion(position: Point, hue: number): void {
193
+ const velocity = MULBERRY.nextBetween(4, 6);
194
+ const shellCount = Math.floor(MULBERRY.nextBetween(25, 35));
195
+
196
+ for (let i = 0; i < shellCount; i++) {
197
+ const rad = (i / shellCount) * Math.PI * 2;
198
+
199
+ this.#explosions.push(new Explosion(
200
+ position,
201
+ hue,
202
+ this.#baseSize,
203
+ 'peony',
204
+ this.#scale,
205
+ rad + MULBERRY.nextBetween(-0.05, 0.05),
206
+ velocity + MULBERRY.nextBetween(-0.25, 0.25)
207
+ ));
208
+ }
209
+
210
+ const fillCount = Math.floor(MULBERRY.nextBetween(40, 60));
211
+
212
+ for (let i = 0; i < fillCount; i++) {
213
+ const rad = MULBERRY.nextBetween(0, Math.PI * 2);
214
+ const speed = velocity * MULBERRY.nextBetween(0, 1);
215
+
216
+ this.#explosions.push(new Explosion(
217
+ position,
218
+ hue,
219
+ this.#baseSize,
220
+ 'peony',
221
+ this.#scale,
222
+ rad,
223
+ speed
224
+ ));
225
+ }
226
+
227
+ const ringRotation = MULBERRY.nextBetween(0, Math.PI * 2);
228
+ const ringCount = Math.floor(MULBERRY.nextBetween(40, 55));
229
+ const ringVx = velocity * MULBERRY.nextBetween(2, 3);
230
+ const ringVy = velocity * 0.6;
231
+
232
+ for (let i = 0; i < ringCount; i++) {
233
+ const rad = (i / ringCount) * Math.PI * 2;
234
+
235
+ const cx = Math.cos(rad) * ringVx + MULBERRY.nextBetween(-0.25, 0.25);
236
+ const cy = Math.sin(rad) * ringVy + MULBERRY.nextBetween(-0.25, 0.25);
237
+
238
+ const cosR = Math.cos(ringRotation);
239
+ const sinR = Math.sin(ringRotation);
240
+ const vx = cx * cosR - cy * sinR;
241
+ const vy = cx * sinR + cy * cosR;
242
+
243
+ const screenAngle = Math.atan2(vy, vx);
244
+ const screenSpeed = Math.sqrt(vx * vx + vy * vy);
245
+ const vz = Math.sin(rad) * velocity * 0.8;
246
+
247
+ this.#explosions.push(new Explosion(
248
+ position,
249
+ hue + 60,
250
+ this.#baseSize,
251
+ 'ring',
252
+ this.#scale,
253
+ screenAngle,
254
+ screenSpeed,
255
+ vz
256
+ ));
257
+ }
258
+ }
259
+
260
+ #createDahliaExplosion(position: Point, hue: number): void {
261
+ const petalCount = Math.floor(MULBERRY.nextBetween(6, 9));
262
+ const particlesPerPetal = Math.floor(MULBERRY.nextBetween(8, 12));
263
+
264
+ for (let petal = 0; petal < petalCount; petal++) {
265
+ const baseAngle = (petal / petalCount) * Math.PI * 2;
266
+ const petalHue = hue + (petal % 2 === 0 ? 25 : -25);
267
+
268
+ for (let i = 0; i < particlesPerPetal; i++) {
269
+ const angle = baseAngle + MULBERRY.nextBetween(-0.3, 0.3);
270
+
271
+ this.#explosions.push(new Explosion(
272
+ position,
273
+ petalHue,
274
+ this.#baseSize,
275
+ 'dahlia',
276
+ this.#scale,
277
+ angle
278
+ ));
279
+ }
280
+ }
281
+ }
282
+
283
+ #createHeartExplosion(position: Point, hue: number): void {
284
+ const velocity = MULBERRY.nextBetween(3, 5);
285
+ const count = Math.floor(MULBERRY.nextBetween(60, 80));
286
+ const rotation = MULBERRY.nextBetween(-0.3, 0.3);
287
+
288
+ for (let i = 0; i < count; i++) {
289
+ const t = (i / count) * Math.PI * 2;
290
+
291
+ const hx = 16 * Math.pow(Math.sin(t), 3);
292
+ const hy = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
293
+
294
+ const scale = velocity / 16;
295
+ const vx = hx * scale;
296
+ const vy = hy * scale;
297
+
298
+ const cosR = Math.cos(rotation);
299
+ const sinR = Math.sin(rotation);
300
+ const rvx = vx * cosR - vy * sinR;
301
+ const rvy = vx * sinR + vy * cosR;
302
+
303
+ const angle = Math.atan2(rvy, rvx);
304
+ const speed = Math.sqrt(rvx * rvx + rvy * rvy);
305
+
306
+ this.#explosions.push(new Explosion(
307
+ position,
308
+ hue,
309
+ this.#baseSize,
310
+ 'heart',
311
+ this.#scale,
312
+ angle,
313
+ Math.max(0.1, speed + MULBERRY.nextBetween(-0.15, 0.15))
314
+ ));
315
+ }
316
+ }
317
+
318
+ #createSpiralExplosion(position: Point, hue: number): void {
319
+ const arms = Math.floor(MULBERRY.nextBetween(3, 5));
320
+ const particlesPerArm = Math.floor(MULBERRY.nextBetween(15, 20));
321
+ const twist = MULBERRY.nextBetween(2, 3.5);
322
+ const baseRotation = MULBERRY.nextBetween(0, Math.PI * 2);
323
+
324
+ for (let arm = 0; arm < arms; arm++) {
325
+ const baseAngle = baseRotation + (arm / arms) * Math.PI * 2;
326
+ const armHue = hue + arm * (360 / arms / 3);
327
+
328
+ for (let i = 0; i < particlesPerArm; i++) {
329
+ const progress = i / particlesPerArm;
330
+ const angle = baseAngle + progress * twist;
331
+ const speed = 2 + progress * 8;
332
+
333
+ this.#explosions.push(new Explosion(
334
+ position,
335
+ armHue,
336
+ this.#baseSize,
337
+ 'spiral',
338
+ this.#scale,
339
+ angle,
340
+ speed + MULBERRY.nextBetween(-0.3, 0.3)
341
+ ));
342
+ }
343
+ }
344
+ }
345
+
346
+ #createFlowerExplosion(position: Point, hue: number): void {
347
+ const velocity = MULBERRY.nextBetween(4, 7);
348
+ const count = Math.floor(MULBERRY.nextBetween(70, 90));
349
+ const petals = Math.floor(MULBERRY.nextBetween(2, 4));
350
+ const rotation = MULBERRY.nextBetween(0, Math.PI * 2);
351
+
352
+ for (let i = 0; i < count; i++) {
353
+ const t = (i / count) * Math.PI * 2;
354
+ const r = Math.abs(Math.cos(petals * t));
355
+ const speed = velocity * r;
356
+
357
+ if (speed < 0.3) {
358
+ continue;
359
+ }
360
+
361
+ this.#explosions.push(new Explosion(
362
+ position,
363
+ hue + MULBERRY.nextBetween(-15, 15),
364
+ this.#baseSize,
365
+ 'flower',
366
+ this.#scale,
367
+ t + rotation,
368
+ speed + MULBERRY.nextBetween(-0.2, 0.2)
369
+ ));
370
+ }
371
+ }
372
+
373
+ #createConcentricExplosion(position: Point, hue: number): void {
374
+ const outerCount = Math.floor(MULBERRY.nextBetween(35, 50));
375
+ const outerSpeed = MULBERRY.nextBetween(7, 10);
376
+
377
+ for (let i = 0; i < outerCount; i++) {
378
+ const angle = (i / outerCount) * Math.PI * 2;
379
+
380
+ this.#explosions.push(new Explosion(
381
+ position,
382
+ hue,
383
+ this.#baseSize,
384
+ 'ring',
385
+ this.#scale,
386
+ angle + MULBERRY.nextBetween(-0.05, 0.05),
387
+ outerSpeed + MULBERRY.nextBetween(-0.25, 0.25)
388
+ ));
389
+ }
390
+
391
+ const innerCount = Math.floor(MULBERRY.nextBetween(25, 35));
392
+ const innerSpeed = MULBERRY.nextBetween(3, 5);
393
+
394
+ for (let i = 0; i < innerCount; i++) {
395
+ const angle = (i / innerCount) * Math.PI * 2;
396
+
397
+ this.#explosions.push(new Explosion(
398
+ position,
399
+ hue + 120,
400
+ this.#baseSize,
401
+ 'ring',
402
+ this.#scale,
403
+ angle + MULBERRY.nextBetween(-0.05, 0.05),
404
+ innerSpeed + MULBERRY.nextBetween(-0.25, 0.25)
405
+ ));
406
+ }
407
+ }
408
+
409
+ #createFirework(position?: Point): void {
410
+ const hue = this.#hue;
411
+ const targetX = position?.x || this.#positionRandom.nextBetween(this.#width * .1, this.#width * .9);
412
+ const targetY = position?.y || this.#height * .1 + this.#positionRandom.nextBetween(0, this.#height * .5);
413
+ const startX = this.#width * 0.3 + this.#positionRandom.nextBetween(0, this.#width * 0.4);
414
+
415
+ const firework = new Firework(
416
+ {x: startX, y: this.#height},
417
+ {x: targetX, y: targetY},
418
+ hue,
419
+ this.#tailWidth,
420
+ this.#baseSize
421
+ );
422
+
423
+ firework.addEventListener('remove', () => {
424
+ this.#fireworks.splice(this.#fireworks.indexOf(firework), 1);
425
+ this.#createExplosion(firework.position, hue);
426
+ }, {once: true});
427
+
428
+ this.#fireworks.push(firework);
429
+ }
430
+
431
+ #pickVariant(): FireworkVariant {
432
+ const roll = MULBERRY.nextBetween(0, 100);
433
+
434
+ if (roll < 12) { return 'peony'; }
435
+ if (roll < 22) { return 'chrysanthemum'; }
436
+ if (roll < 29) { return 'willow'; }
437
+ if (roll < 34) { return 'ring'; }
438
+ if (roll < 39) { return 'palm'; }
439
+ if (roll < 44) { return 'crackle'; }
440
+ if (roll < 48) { return 'crossette'; }
441
+ if (roll < 55) { return 'saturn'; }
442
+ if (roll < 62) { return 'dahlia'; }
443
+ if (roll < 67) { return 'brocade'; }
444
+ if (roll < 71) { return 'horsetail'; }
445
+ if (roll < 75) { return 'strobe'; }
446
+ if (roll < 82) { return 'heart'; }
447
+ if (roll < 89) { return 'spiral'; }
448
+ if (roll < 94) { return 'flower'; }
449
+
450
+ return 'concentric';
451
+ }
452
+ }