@adatechnology/game-vfx-three 0.1.3 → 0.2.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.
@@ -1374,77 +1374,77 @@ var GameVfxThree = (() => {
1374
1374
  }));
1375
1375
  }
1376
1376
  function spawnPuff(pool, random, init) {
1377
- const puff2 = pool.acquire();
1378
- puff2.x = init.x ?? 0;
1379
- puff2.y = init.y ?? 0;
1380
- puff2.z = init.z ?? 0;
1381
- puff2.vx = init.vx ?? 0;
1382
- puff2.vy = init.vy ?? 0;
1383
- puff2.vz = init.vz ?? 0;
1384
- puff2.s0 = init.s0 ?? 0.2;
1385
- puff2.s1 = init.s1 ?? 1;
1386
- puff2.sg = init.sg ?? 0.1;
1387
- puff2.gk = init.gk ?? 6;
1388
- puff2.life = init.life ?? 3;
1389
- puff2.t = 0;
1390
- puff2.h0 = init.h0 ?? 0;
1391
- puff2.hl = init.hl ?? 0.4;
1392
- puff2.drag = init.drag ?? 3;
1393
- puff2.buoy = init.buoy ?? 0.5;
1394
- puff2.grav = init.grav ?? 0;
1395
- puff2.kind = init.kind ?? 0;
1396
- puff2.seed = random.next();
1397
- puff2.wind = init.wind ?? 1;
1398
- puff2.color = init.color ?? SMOKE_COLORS.grey;
1399
- puff2.ring = init.ring ?? null;
1400
- puff2.ph = init.ph ?? 0;
1401
- puff2.th = init.th ?? 0;
1402
- puff2.rng = random;
1403
- puff2.age = 0;
1404
- puff2.heat = 0;
1405
- puff2.size = 0;
1406
- return puff2;
1407
- }
1408
- function ageOrKeep2(puff2, dt) {
1409
- puff2.t += dt;
1410
- return puff2.t < puff2.life;
1411
- }
1412
- function moveOnRing(puff2, ring2, wind) {
1377
+ const puff3 = pool.acquire();
1378
+ puff3.x = init.x ?? 0;
1379
+ puff3.y = init.y ?? 0;
1380
+ puff3.z = init.z ?? 0;
1381
+ puff3.vx = init.vx ?? 0;
1382
+ puff3.vy = init.vy ?? 0;
1383
+ puff3.vz = init.vz ?? 0;
1384
+ puff3.s0 = init.s0 ?? 0.2;
1385
+ puff3.s1 = init.s1 ?? 1;
1386
+ puff3.sg = init.sg ?? 0.1;
1387
+ puff3.gk = init.gk ?? 6;
1388
+ puff3.life = init.life ?? 3;
1389
+ puff3.t = 0;
1390
+ puff3.h0 = init.h0 ?? 0;
1391
+ puff3.hl = init.hl ?? 0.4;
1392
+ puff3.drag = init.drag ?? 3;
1393
+ puff3.buoy = init.buoy ?? 0.5;
1394
+ puff3.grav = init.grav ?? 0;
1395
+ puff3.kind = init.kind ?? 0;
1396
+ puff3.seed = random.next();
1397
+ puff3.wind = init.wind ?? 1;
1398
+ puff3.color = init.color ?? SMOKE_COLORS.grey;
1399
+ puff3.ring = init.ring ?? null;
1400
+ puff3.ph = init.ph ?? 0;
1401
+ puff3.th = init.th ?? 0;
1402
+ puff3.rng = random;
1403
+ puff3.age = 0;
1404
+ puff3.heat = 0;
1405
+ puff3.size = 0;
1406
+ return puff3;
1407
+ }
1408
+ function ageOrKeep2(puff3, dt) {
1409
+ puff3.t += dt;
1410
+ return puff3.t < puff3.life;
1411
+ }
1412
+ function moveOnRing(puff3, ring2, wind) {
1413
1413
  const ringTime = ring2.t;
1414
1414
  const centerY = ring2.y0 + ring2.rise * (1 - Math.exp(-ringTime * ring2.rk));
1415
1415
  const majorRadius = ring2.R0 + (ring2.R1 - ring2.R0) * (1 - Math.exp(-ringTime * 1.6));
1416
- const theta = puff2.th + ringTime * ring2.roll;
1416
+ const theta = puff3.th + ringTime * ring2.roll;
1417
1417
  const minorRadius = ring2.r * (0.8 + 0.4 * Math.min(1, ringTime));
1418
1418
  const radius = majorRadius + minorRadius * Math.cos(theta);
1419
- puff2.x = ring2.x + Math.cos(puff2.ph) * radius + wind.x * ringTime * 0.6;
1420
- puff2.z = ring2.z + Math.sin(puff2.ph) * radius + wind.z * ringTime * 0.6;
1421
- puff2.y = centerY + minorRadius * Math.sin(theta);
1422
- }
1423
- function moveFree(puff2, dt, age, heat, wind) {
1424
- const damping = Math.exp(-puff2.drag * dt);
1425
- puff2.vx *= damping;
1426
- puff2.vy *= damping;
1427
- puff2.vz *= damping;
1428
- puff2.vy += (puff2.buoy * (heat > 0 ? 1.6 : 1) - puff2.grav) * dt;
1429
- puff2.x += (puff2.vx + wind.x * puff2.wind * age) * dt;
1430
- puff2.y += puff2.vy * dt;
1431
- puff2.z += (puff2.vz + wind.z * puff2.wind * age) * dt;
1432
- if (puff2.y < puff2.s0 * FLOOR_FRACTION) {
1433
- puff2.y = puff2.s0 * FLOOR_FRACTION;
1434
- if (puff2.vy < 0) puff2.vy *= FLOOR_BOUNCE;
1419
+ puff3.x = ring2.x + Math.cos(puff3.ph) * radius + wind.x * ringTime * 0.6;
1420
+ puff3.z = ring2.z + Math.sin(puff3.ph) * radius + wind.z * ringTime * 0.6;
1421
+ puff3.y = centerY + minorRadius * Math.sin(theta);
1422
+ }
1423
+ function moveFree(puff3, dt, age, heat, wind) {
1424
+ const damping = Math.exp(-puff3.drag * dt);
1425
+ puff3.vx *= damping;
1426
+ puff3.vy *= damping;
1427
+ puff3.vz *= damping;
1428
+ puff3.vy += (puff3.buoy * (heat > 0 ? 1.6 : 1) - puff3.grav) * dt;
1429
+ puff3.x += (puff3.vx + wind.x * puff3.wind * age) * dt;
1430
+ puff3.y += puff3.vy * dt;
1431
+ puff3.z += (puff3.vz + wind.z * puff3.wind * age) * dt;
1432
+ if (puff3.y < puff3.s0 * FLOOR_FRACTION) {
1433
+ puff3.y = puff3.s0 * FLOOR_FRACTION;
1434
+ if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
1435
1435
  }
1436
1436
  }
1437
1437
  function updatePuffs(pool, dt, wind) {
1438
1438
  pool.compact(ageOrKeep2, dt);
1439
- for (const puff2 of pool.items) {
1440
- const age = puff2.t / puff2.life;
1441
- const heat = puff2.h0 > 0 ? puff2.h0 * Math.pow(Math.max(0, 1 - puff2.t / puff2.hl), 1.25) : 0;
1442
- if (puff2.ring) moveOnRing(puff2, puff2.ring, wind);
1443
- else moveFree(puff2, dt, age, heat, wind);
1444
- const size = puff2.s0 + (puff2.s1 - puff2.s0) * (1 - Math.exp(-puff2.t * puff2.gk)) + puff2.sg * Math.min(puff2.t, GROWTH_CAP_SECONDS);
1445
- puff2.size = size * Math.min(1, puff2.t * ARRIVAL_FRAME + ARRIVAL_BASE);
1446
- puff2.age = age;
1447
- puff2.heat = heat;
1439
+ for (const puff3 of pool.items) {
1440
+ const age = puff3.t / puff3.life;
1441
+ const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
1442
+ if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
1443
+ else moveFree(puff3, dt, age, heat, wind);
1444
+ const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
1445
+ puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
1446
+ puff3.age = age;
1447
+ puff3.heat = heat;
1448
1448
  }
1449
1449
  }
1450
1450
  var GROUND_LIFT = 0.02;
@@ -1501,9 +1501,9 @@ var GameVfxThree = (() => {
1501
1501
  var HOT_GLOW_COLOR = [1, 0.45, 0.12];
1502
1502
  function addPuff(core, random, init) {
1503
1503
  core.spawned.puffs++;
1504
- const puff2 = spawnPuff(core.puffs, random, init);
1505
- core.trace?.puff?.(puff2);
1506
- return puff2;
1504
+ const puff3 = spawnPuff(core.puffs, random, init);
1505
+ core.trace?.puff?.(puff3);
1506
+ return puff3;
1507
1507
  }
1508
1508
  function addSpark(core, init) {
1509
1509
  core.spawned.sparks++;
@@ -1685,6 +1685,7 @@ var GameVfxThree = (() => {
1685
1685
  var neg = call("neg");
1686
1686
  var abs = call("abs");
1687
1687
  var sqrt = call("sqrt");
1688
+ var exp = call("exp");
1688
1689
  var sin = call("sin");
1689
1690
  var cos = call("cos");
1690
1691
  var atan2 = call("atan2");
@@ -1730,10 +1731,12 @@ var GameVfxThree = (() => {
1730
1731
  var INDEX = { $: "index" };
1731
1732
  var QUALITY = { $: "quality" };
1732
1733
  var FRESH = { $: "fresh" };
1734
+ var DIRECTION = { $: "direction" };
1733
1735
  var CAMERA_POSITION = { $: "cameraPosition" };
1734
1736
  var burst = (system, count, spawn2) => ({ op: "burst", system, count, spawn: spawn2 });
1735
1737
  var spawn = (system, options) => ({ op: "spawn", system, spawn: options });
1736
1738
  var emit = (key, system, rate, options) => ({ op: "emit", key, system, rate, spawn: options });
1739
+ var mesh = (options) => ({ op: "mesh", spawn: options });
1737
1740
  var debris = (options) => ({ op: "debris", spawn: options });
1738
1741
  var decal = (...args) => ({ op: "decal", args });
1739
1742
  var ring = (...args) => ({ op: "ring", args });
@@ -2745,6 +2748,241 @@ var GameVfxThree = (() => {
2745
2748
  )
2746
2749
  ]
2747
2750
  };
2751
+ var SPOTS_X = [-3, 1, 5];
2752
+ var SPOTS_Z = [-1, -2.4, 0.4];
2753
+ var FOG_AND_ASH = {
2754
+ id: "fog_and_ash",
2755
+ pageId: "neblina",
2756
+ team: "world",
2757
+ budget: { particles: 120, lights: 1 },
2758
+ loop: 0,
2759
+ view: { eye: [3, 1.8, 7.5], target: [-3, 1.2, -1] },
2760
+ setup: [
2761
+ cue("fog", { density: 0.075 }),
2762
+ cue("lamp_cone", { visible: true }),
2763
+ cue("zombie_visible", { index: -1, visible: false }),
2764
+ repeat(
2765
+ SPOTS_X.length,
2766
+ set("turn", rnd(-0.6, 0.6)),
2767
+ behavior("walkers", "place", { index: INDEX, x: pick(SPOTS_X, INDEX), z: pick(SPOTS_Z, INDEX), facing: variable("turn") })
2768
+ )
2769
+ ],
2770
+ timeline: [],
2771
+ tick: [
2772
+ emit("fog", "normal", 5, { p: [rnd(-14, 10), rnd(0.2, 1.1), rnd(-3, 8)], v: [rnd(0.15, 0.35), 0, rnd(-0.05, 0.05)], sp: 0, life: [9, 13], size: [3, 5], grow: 1.4, c0: 8227488, a: 0.13, fi: 0.35 }),
2773
+ emit("ash", "normal", 14, { p: [rnd(-10, 8), rnd(4, 6), rnd(-3, 7)], v: [rnd(0.1, 0.4), -0.35, rnd(-0.1, 0.1)], sp: [0.05, 0.15], life: [8, 12], size: [0.025, 0.04], c0: 10525328, a: 0.75, fi: 0.1, gnd: 1 })
2774
+ ]
2775
+ };
2776
+ var RAIN_CAPACITY = 4e3;
2777
+ var FALL_SPEED = 15;
2778
+ var WIND_PER_FALL = 0.07;
2779
+ var STREAK_SLANT = 0.04;
2780
+ var STREAK_LENGTH = 0.6;
2781
+ var BOX_X = [-15, 15];
2782
+ var BOX_Z = [-6, 16];
2783
+ var START_HEIGHT = [0, 16];
2784
+ var REENTRY_HEIGHT = [14, 17];
2785
+ var SPLASH_CHANCE = 0.35;
2786
+ var SPLASH_RANGE = 12;
2787
+ var SPRITE_CHANCE = 0.5;
2788
+ var UP9 = [0, 1, 0];
2789
+ var WORKSHOP_PUDDLES = [
2790
+ [-1.5, 2.2, 1.6, 0.9],
2791
+ [3, 3.6, 1.1, 0.7],
2792
+ [-6, 3.2, 1.3, 0.8],
2793
+ [1.4, -2.2, 0.9, 0.5]
2794
+ ];
2795
+ var inPuddle = (puddles, x, z) => puddles.some((p) => ((x - p[0]) / p[2]) ** 2 + ((z - p[1]) / p[3]) ** 2 < 1);
2796
+ var RainField = class {
2797
+ constructor(engine) {
2798
+ __publicField(this, "engine", engine);
2799
+ __publicField(this, "positions", new Float32Array(RAIN_CAPACITY * 6));
2800
+ __publicField(this, "count", 0);
2801
+ __publicField(this, "wind", 1);
2802
+ __publicField(this, "on", false);
2803
+ __publicField(this, "centerX", 0);
2804
+ __publicField(this, "centerZ", 0);
2805
+ __publicField(this, "puddles", WORKSHOP_PUDDLES);
2806
+ __publicField(this, "puddleAt");
2807
+ }
2808
+ /** `initRain` (W321): `count` streaks (thinned by the quality), each drawn at a random place in the box. */
2809
+ init(count, wind) {
2810
+ const { random } = this.engine;
2811
+ this.count = Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult));
2812
+ this.wind = wind;
2813
+ this.on = true;
2814
+ for (let index = 0; index < this.count; index++) {
2815
+ const offset = index * 6;
2816
+ const x = random.between(BOX_X[0], BOX_X[1]);
2817
+ const y = random.between(START_HEIGHT[0], START_HEIGHT[1]);
2818
+ const z = random.between(BOX_Z[0], BOX_Z[1]);
2819
+ this.set(offset, x, y, z);
2820
+ }
2821
+ }
2822
+ /**
2823
+ * What a game does with the field every frame: the box follows the camera (`centerX`, `centerZ`), the streaks thin or thicken to `count` (a new streak
2824
+ * starts at a random place of the box, as `init` draws it) and the wind slants them. The golden never calls it.
2825
+ */
2826
+ drive(count, wind, centerX, centerZ) {
2827
+ const { random } = this.engine;
2828
+ const target = Math.max(0, Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult)));
2829
+ this.wind = wind;
2830
+ this.centerX = centerX;
2831
+ this.centerZ = centerZ;
2832
+ this.on = target > 0;
2833
+ for (let index = this.count; index < target; index++) {
2834
+ this.set(index * 6, random.between(BOX_X[0], BOX_X[1]) + centerX, random.between(START_HEIGHT[0], START_HEIGHT[1]), random.between(BOX_Z[0], BOX_Z[1]) + centerZ);
2835
+ }
2836
+ this.count = target;
2837
+ }
2838
+ /** `updateRain` (W322-329). `camera` is where the eye is; `splash` lets the drops that land leave a splash. */
2839
+ update(dt, splash, camera2) {
2840
+ if (!this.on) return;
2841
+ const { random, context } = this.engine;
2842
+ const positions = this.positions;
2843
+ const fall = FALL_SPEED * dt;
2844
+ const slant = this.wind * fall * WIND_PER_FALL;
2845
+ const reach = SPLASH_RANGE / (1 + context.quality.level);
2846
+ for (let index = 0; index < this.count; index++) {
2847
+ const o = index * 6;
2848
+ positions[o] += slant;
2849
+ positions[o + 3] += slant;
2850
+ positions[o + 1] -= fall;
2851
+ positions[o + 4] -= fall;
2852
+ const ground = context.groundAt(positions[o], positions[o + 2]);
2853
+ if (positions[o + 4] < ground) {
2854
+ const x = positions[o];
2855
+ const z = positions[o + 2];
2856
+ if (splash && random.next() < SPLASH_CHANCE) {
2857
+ const distance = Math.hypot(x - camera2[0], z - camera2[2]);
2858
+ if (distance < reach) {
2859
+ if (random.next() < SPRITE_CHANCE) {
2860
+ this.engine.spawn("normal", { p: [x, ground + 0.01, z], dir: UP9, cone: 0.6, sp: [0.6, 1.3], life: [0.18, 0.3], size: 0.035, c0: 12110044, a: 0.6, g: 9.8, fi: 0 });
2861
+ }
2862
+ if (this.puddleAt ? this.puddleAt(x, z) : inPuddle(this.puddles, x, z)) {
2863
+ this.engine.ring([[x, ground + 0.012, z], 0.01, random.between(0.12, 0.25), random.between(0.4, 0.6), 6978192]);
2864
+ }
2865
+ }
2866
+ }
2867
+ const nx = random.between(BOX_X[0], BOX_X[1]) + this.centerX;
2868
+ const ny = random.between(REENTRY_HEIGHT[0], REENTRY_HEIGHT[1]);
2869
+ const nz = random.between(BOX_Z[0], BOX_Z[1]) + this.centerZ;
2870
+ this.set(o, nx, ny, nz);
2871
+ }
2872
+ }
2873
+ }
2874
+ /** `rain.visible = false; RN.on = false` (W440): the field stops; the streaks stay where they were. */
2875
+ clear() {
2876
+ this.on = false;
2877
+ }
2878
+ set(offset, x, y, z) {
2879
+ const positions = this.positions;
2880
+ positions[offset] = x;
2881
+ positions[offset + 1] = y;
2882
+ positions[offset + 2] = z;
2883
+ positions[offset + 3] = x + this.wind * STREAK_SLANT;
2884
+ positions[offset + 4] = y - STREAK_LENGTH;
2885
+ positions[offset + 5] = z;
2886
+ }
2887
+ };
2888
+ var DROPS = [900, 2200, 4e3];
2889
+ var SLANT = [0.3, 1.2, 4.5];
2890
+ var STORM = 2;
2891
+ var FLASH_STEPS = ite(lt(variable("flash"), 0.08), 1, ite(lt(variable("flash"), 0.16), 0.2, ite(lt(variable("flash"), 0.26), 0.8, 0)));
2892
+ var RAIN_AND_PUDDLES = {
2893
+ id: "rain_and_puddles",
2894
+ pageId: "chuva",
2895
+ team: "world",
2896
+ params: { id: "intensity", options: ["drizzle", "rain", "storm"], default: 1 },
2897
+ budget: { particles: 4e3, decals: 4 },
2898
+ loop: 0,
2899
+ view: { eye: [2, 2, 7], target: [-1, 0.6, 1] },
2900
+ setup: [
2901
+ cue("lamp_cone", { visible: true }),
2902
+ ...WORKSHOP_PUDDLES.map(([x, z, radiusX, radiusZ]) => decal(x, 0, z, radiusX * 2, 790550, { grow: 0, life: 1e6, ar: radiusZ / radiusX }))
2903
+ ],
2904
+ timeline: [
2905
+ behavior("rain", "init", { count: ite(eq(PARAM, 0), DROPS[0], ite(eq(PARAM, 1), DROPS[1], DROPS[2])), wind: ite(eq(PARAM, 0), SLANT[0], ite(eq(PARAM, 1), SLANT[1], SLANT[2])) }),
2906
+ set("nextStrike", rnd(2, 4)),
2907
+ set("flash", -1)
2908
+ ],
2909
+ tick: [
2910
+ behavior("rain", "update", { dt: DELTA, splash: gt(PARAM, 0) }),
2911
+ when(eq(PARAM, STORM), [
2912
+ when(gt(TIME, variable("nextStrike")), [set("nextStrike", add(TIME, rnd(3.5, 6))), set("flash", 0)]),
2913
+ when(ge(variable("flash"), 0), [
2914
+ set("flash", add(variable("flash"), DELTA)),
2915
+ set("light", FLASH_STEPS),
2916
+ cue("lightning", { intensity: variable("light") }),
2917
+ screen({ max: { white: mul(variable("light"), 0.12) } }),
2918
+ when(gt(variable("flash"), 0.4), [set("flash", -1), cue("lightning", { intensity: 0 })])
2919
+ ]),
2920
+ emit("sheet", "normal", 2.5, { p: [-12, 2.5, rnd(-1, 8)], box: [0, 2, 0], v: [7, -0.5, 0], sp: 0, life: [3, 4], size: [3, 5], grow: 1.3, c0: 9083568, a: 0.06, fi: 0.3 })
2921
+ ])
2922
+ ]
2923
+ };
2924
+ var CAR_ORIGIN2 = [5.6, 0, 0.8];
2925
+ var CAR_ROTATION2 = [0, -0.4, 0];
2926
+ var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
2927
+ var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
2928
+ var BEAM_FROM = carPoint(0, 0.74, 2.15);
2929
+ var BEAM_DIRECTION = carDirection(BEAM_FROM, carPoint(0, 0.74, 3.15));
2930
+ var BEAM_SIDE = [BEAM_DIRECTION[2], 0, -BEAM_DIRECTION[0]];
2931
+ var WALKERS = 6;
2932
+ var WALK_SPEED2 = 0.7;
2933
+ var DROPS2 = 1800;
2934
+ var SLANT2 = 0.8;
2935
+ var startWalking = [
2936
+ set("spot", query("car", "point", rnd(-2, 2), 0, rnd(8, 18))),
2937
+ set("goal", query("car", "point", rnd(-0.6, 0.6), 0, 2.6)),
2938
+ behavior("walkers", "place", { index: INDEX, x: pick(variable("spot"), 0), z: pick(variable("spot"), 2), facing: 0 }),
2939
+ behavior("walkers", "walk", { index: INDEX, to: variable("goal"), speed: WALK_SPEED2 })
2940
+ ];
2941
+ var HEADLIGHTS_IN_RAIN = {
2942
+ id: "headlights_in_rain",
2943
+ pageId: "farol",
2944
+ team: "vehicles",
2945
+ budget: { particles: 600, lights: 2 },
2946
+ loop: 0,
2947
+ view: { eye: [-2.5, 2, 5.5], target: [4, 1, 6] },
2948
+ setup: [
2949
+ cue("fog", { density: 0.05 }),
2950
+ cue("headlights", { on: true, intensity: 2.4, color: 16773584 }),
2951
+ behavior("rain", "init", { count: DROPS2, wind: SLANT2 }),
2952
+ repeat(WALKERS, ...startWalking)
2953
+ ],
2954
+ timeline: [],
2955
+ tick: [
2956
+ behavior("rain", "update", { dt: DELTA, splash: true }),
2957
+ emit(
2958
+ "lit",
2959
+ "streak",
2960
+ 600,
2961
+ local(
2962
+ "depth",
2963
+ rnd(1, 13),
2964
+ local(
2965
+ "spread",
2966
+ mul(variable("depth"), 0.33, sqrt(rand())),
2967
+ local(
2968
+ "turn",
2969
+ rnd(0, 6.283),
2970
+ local("base", vadd(BEAM_FROM, vscale(BEAM_DIRECTION, variable("depth")), vscale(BEAM_SIDE, mul(cos(variable("turn")), variable("spread")))), {
2971
+ p: vwith(variable("base"), 1, add(vy(variable("base")), mul(sin(variable("turn")), variable("spread"), 0.6))),
2972
+ v: [0.8, -14, 0],
2973
+ sp: 0,
2974
+ life: 0.06,
2975
+ len: 0.035,
2976
+ c0: 13622512,
2977
+ a: 0.9
2978
+ })
2979
+ )
2980
+ )
2981
+ )
2982
+ ),
2983
+ repeat(WALKERS, when(not(query("walkers", "is_walking", INDEX)), startWalking))
2984
+ ]
2985
+ };
2748
2986
  var YAW = -Math.PI / 2;
2749
2987
  var COS = Math.cos(YAW);
2750
2988
  var SIN = Math.sin(YAW);
@@ -2772,10 +3010,6 @@ var GameVfxThree = (() => {
2772
3010
  fireLight(0, [vx(variable("contact")), 0.34, vz(variable("contact"))], 1.6, mul(TIME, 3))
2773
3011
  ]
2774
3012
  };
2775
- var CAR_ORIGIN2 = [5.6, 0, 0.8];
2776
- var CAR_ROTATION2 = [0, -0.4, 0];
2777
- var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
2778
- var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
2779
3013
  var HOOD = carPoint(0, 1.02, 1.4);
2780
3014
  var EXHAUST = carPoint(0.5, 0.3, -2.2);
2781
3015
  var EXHAUST_DIRECTION = carDirection(EXHAUST, carPoint(0.5, 0.3, -3));
@@ -2809,7 +3043,7 @@ var GameVfxThree = (() => {
2809
3043
  ], [fireLightOff(0)])
2810
3044
  ]
2811
3045
  };
2812
- var UP9 = [0, 1, 0];
3046
+ var UP10 = [0, 1, 0];
2813
3047
  var doorVelocity = (side) => {
2814
3048
  const out = carPoint(side * 4, 3, 0.3);
2815
3049
  const centre = carPoint(0, 0, 0.3);
@@ -2819,7 +3053,7 @@ var GameVfxThree = (() => {
2819
3053
  call2("explode", vadd(query("car", "position"), [0, 0.6, 0]), 1.8, 2759188),
2820
3054
  cue("car_burnt"),
2821
3055
  behavior("car", "launch", { rise: 4.5, spin: 0.9 }),
2822
- repeat(scaled(30), debris({ p: vadd(query("car", "position"), [0, 1.3, 0]), dir: UP9, cone: 1.3, sp: [2, 6], size: [0.06, 6e-3, 0.06], c: 11061464, life: 20 })),
3056
+ repeat(scaled(30), debris({ p: vadd(query("car", "position"), [0, 1.3, 0]), dir: UP10, cone: 1.3, sp: [2, 6], size: [0.06, 6e-3, 0.06], c: 11061464, life: 20 })),
2823
3057
  debris({ p: carPoint(0, 1, 1.4), v: [rnd(-1, 1), 9, rnd(-1, 1)], sp: 0, size: [1.6, 0.06, 1.2], c: 3808536, spin: 6, life: 20, bounce: 0.2 }),
2824
3058
  ...[-1, 1].map((side) => debris({ p: carPoint(side * 0.95, 0.7, 0.3), v: doorVelocity(side), sp: 0, size: [0.06, 0.7, 1.1], c: 3808536, spin: 5, life: 20, bounce: 0.2 })),
2825
3059
  debris({ p: carPoint(0.95, 0.38, 1.35), v: [3, 4, 2], sp: 0, size: [0.3, 0.76, 0.76], c: 1118481, spin: 8, life: 20, bounce: 0.4 })
@@ -2989,7 +3223,7 @@ var GameVfxThree = (() => {
2989
3223
  ])
2990
3224
  ]
2991
3225
  };
2992
- var WALKERS = 6;
3226
+ var WALKERS2 = 6;
2993
3227
  var NEAR = 9;
2994
3228
  var RANGE = 7;
2995
3229
  var RESTING_BPM = 70;
@@ -3005,7 +3239,7 @@ var GameVfxThree = (() => {
3005
3239
  setup: [
3006
3240
  cue("zombie_visible", { index: -1, visible: false }),
3007
3241
  repeat(
3008
- WALKERS,
3242
+ WALKERS2,
3009
3243
  set("startX", rnd(-4, 4)),
3010
3244
  set("startZ", sub(rnd(-4, -2), mul(INDEX, 0.6))),
3011
3245
  behavior("walkers", "place", { index: INDEX, x: variable("startX"), z: variable("startZ"), facing: 0 }),
@@ -3015,7 +3249,7 @@ var GameVfxThree = (() => {
3015
3249
  timeline: [],
3016
3250
  tick: [
3017
3251
  set("nearest", 99),
3018
- repeat(WALKERS, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
3252
+ repeat(WALKERS2, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
3019
3253
  set("fear", clamp(div(sub(NEAR, variable("nearest")), RANGE), 0, 1)),
3020
3254
  set("bpm", add(RESTING_BPM, mul(BPM_RISE, variable("fear")))),
3021
3255
  when(lt(mod(div(mul(TIME, variable("bpm")), 60), 1), div(mul(DELTA, variable("bpm")), 60)), [screen({ set: { beat: add(0.4, mul(0.6, variable("fear"))) } })]),
@@ -3082,7 +3316,7 @@ var GameVfxThree = (() => {
3082
3316
  ]
3083
3317
  };
3084
3318
  var FACING4 = [0, 0, 1];
3085
- var UP10 = [0, 1, 0];
3319
+ var UP11 = [0, 1, 0];
3086
3320
  var ORIGIN = [0, 0, 0];
3087
3321
  var FORWARD4 = [0, 0, -1];
3088
3322
  var IMPACT_MATERIALS = ["concrete", "metal", "wood", "glass", "flesh", "water"];
@@ -3212,12 +3446,148 @@ var GameVfxThree = (() => {
3212
3446
  team: "destruction",
3213
3447
  budget: { particles: 20, debris: 8 },
3214
3448
  loop: 2,
3215
- timeline: [call2("chips", ORIGIN, UP10, 8, 0.9), call2("rust_at", ORIGIN, 6)]
3449
+ timeline: [call2("chips", ORIGIN, UP11, 8, 0.9), call2("rust_at", ORIGIN, 6)]
3216
3450
  }
3217
3451
  ];
3218
- var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
3452
+ var PI = Math.PI;
3453
+ var SMOKE = { color: 9079430, width: 10, height: 8, gravity: -0.08, drag: 2.2, growth: 2.1 };
3454
+ var FLASH_SECONDS = 0.055;
3455
+ var FLASH_RAMP = [0.6, 0.9];
3456
+ var FLASH_OPACITY = 0.95;
3457
+ var FLAME_COLORS = [16742946, 16760896, 13121050, 16773296];
3458
+ var FLAME_ADDITIVE = 3;
3459
+ var ORIGIN2 = [0, 0, 0];
3460
+ var SIZE = variable("size");
3461
+ var wide = (width) => mul(sub(rand(), 0.5), width);
3462
+ var puff2 = (point, velocity, world, radius, life, opacity) => mesh({
3463
+ shape: "sphere",
3464
+ p: point,
3465
+ v: velocity,
3466
+ wv: world,
3467
+ r: radius,
3468
+ seg: SMOKE.width,
3469
+ seg2: SMOKE.height,
3470
+ life,
3471
+ c0: SMOKE.color,
3472
+ a: opacity,
3473
+ fade: true,
3474
+ lit: true,
3475
+ g: SMOKE.gravity,
3476
+ drag: SMOKE.drag,
3477
+ grow: SMOKE.growth
3478
+ });
3479
+ var flameBit = (point, velocity, world, radius, life) => [
3480
+ set("bitPoint", point),
3481
+ set("bitVelocity", velocity),
3482
+ set("bitWorld", world),
3483
+ set("bitRadius", radius),
3484
+ set("bitLife", life),
3485
+ set("bitColour", floor(mul(rand(), FLAME_COLORS.length))),
3486
+ mesh({
3487
+ shape: "icosahedron",
3488
+ p: variable("bitPoint"),
3489
+ v: variable("bitVelocity"),
3490
+ wv: variable("bitWorld"),
3491
+ r: variable("bitRadius"),
3492
+ seg: 1,
3493
+ rot: [mul(rand(), 3), mul(rand(), 3), 0],
3494
+ spin: [3, 4, 2],
3495
+ life: variable("bitLife"),
3496
+ c0: pick(FLAME_COLORS, variable("bitColour")),
3497
+ add: eq(variable("bitColour"), FLAME_ADDITIVE),
3498
+ a: 0.9,
3499
+ fade: true,
3500
+ g: -1.4,
3501
+ drag: 1.4,
3502
+ grow: 2.2
3503
+ })
3504
+ ];
3505
+ var flashPiece = (shape, radius, height, width, at3, rot, color) => mesh({ shape, r: radius, ...height === void 0 ? {} : { h: height }, seg: width, seg2: shape === "sphere" ? 8 : 1, p: ORIGIN2, at: at3, rot, roll: variable("roll"), ramp: FLASH_RAMP, life: FLASH_SECONDS, c0: color, a: FLASH_OPACITY, add: true });
3506
+ var MUZZLE_FLASH2 = {
3507
+ id: "viewmodel_muzzle_flash",
3508
+ pageId: "event:viewmodel_muzzle_flash",
3509
+ team: "weapons",
3510
+ budget: { particles: 12, lights: 1 },
3511
+ loop: 0.1,
3512
+ timeline: [
3513
+ set("angles", []),
3514
+ repeat(4, append("angles", add(div(mul(INDEX, PI), 2), mul(rand(), 0.6)))),
3515
+ set("roll", mul(rand(), PI)),
3516
+ flash(ORIGIN2, 16756832, mul(4, SIZE), FLASH_SECONDS, mul(2, SIZE), true, true),
3517
+ flashPiece("cone_open", mul(0.013, SIZE), mul(0.085, SIZE), 10, [mul(0.042, SIZE), 0, 0], [0, 0, -PI / 2], 16757575),
3518
+ repeat(
3519
+ 4,
3520
+ set("angle", pick(variable("angles"), INDEX)),
3521
+ flashPiece(
3522
+ "cone",
3523
+ mul(5e-3, SIZE),
3524
+ mul(0.04, SIZE),
3525
+ 6,
3526
+ [mul(0.012, SIZE), mul(cos(variable("angle")), 0.016, SIZE), mul(sin(variable("angle")), 0.016, SIZE)],
3527
+ [variable("angle"), 0, -PI / 2],
3528
+ 16757575
3529
+ )
3530
+ ),
3531
+ flashPiece("sphere", mul(0.016, SIZE), void 0, 12, [mul(8e-3, SIZE), 0, 0], [0, 0, 0], 16773568),
3532
+ repeat(
3533
+ 6,
3534
+ puff2(
3535
+ ORIGIN2,
3536
+ [add(0.15, mul(rand(), 0.35)), add(0.03, mul(rand(), 0.12)), mul(sub(rand(), 0.5), 0.14)],
3537
+ [0, 0, 0],
3538
+ mul(0.013, SIZE),
3539
+ add(1.3, mul(rand(), 0.6)),
3540
+ 0.45
3541
+ )
3542
+ )
3543
+ ]
3544
+ };
3545
+ var SMOKE_PUFF = {
3546
+ id: "weapon_smoke_puff",
3547
+ pageId: "event:weapon_smoke_puff",
3548
+ team: "weapons",
3549
+ budget: { particles: 1 },
3550
+ loop: 0.1,
3551
+ timeline: [puff2(ORIGIN2, void 0, [variable("vx"), variable("vy"), variable("vz")], variable("radius"), variable("life"), variable("opacity"))]
3552
+ };
3553
+ var BACKBLAST = {
3554
+ id: "launcher_backblast",
3555
+ pageId: "event:launcher_backblast",
3556
+ team: "weapons",
3557
+ budget: { particles: 40 },
3558
+ loop: 0.1,
3559
+ timeline: [
3560
+ repeat(26, puff2(ORIGIN2, [neg(add(1.5, mul(rand(), 3))), 0, 0], [wide(1.4), wide(0.8), wide(1.4)], add(0.03, mul(rand(), 0.03)), add(1.8, mul(rand(), 1.4)), 0.5)),
3561
+ repeat(14, ...flameBit(ORIGIN2, [neg(add(2, mul(rand(), 2))), 0, 0], [wide(1), wide(0.6), wide(1)], 0.02, 0.25))
3562
+ ]
3563
+ };
3564
+ var FLAME_JET = {
3565
+ id: "flame_jet",
3566
+ pageId: "event:flame_jet",
3567
+ team: "weapons",
3568
+ budget: { particles: 30 },
3569
+ loop: 0.1,
3570
+ timeline: [
3571
+ repeat(
3572
+ floor(mul(420, variable("dt"), variable("scale"))),
3573
+ set("speed", mul(add(2.2, mul(rand(), 1.1)), variable("scale"))),
3574
+ ...flameBit(
3575
+ [mul(rand(), variable("speed"), min(variable("dt"), 0.3)), 0, 0],
3576
+ [variable("speed"), 0, 0],
3577
+ [wide(0.35), mul(sub(rand(), 0.3), 0.3), wide(0.35)],
3578
+ add(0.02, mul(rand(), 0.022)),
3579
+ add(0.4, mul(rand(), 0.35))
3580
+ )
3581
+ ),
3582
+ when(lt(rand(), 0.5), [
3583
+ puff2([add(mul(1.1, variable("scale")), mul(rand(), 0.4)), 0, 0], void 0, [mul(vx(DIRECTION), 0.6), 0.35, mul(vz(DIRECTION), 0.6)], 0.03, 1.6, 0.4)
3584
+ ])
3585
+ ]
3586
+ };
3587
+ var VIEWMODEL_EFFECTS = [MUZZLE_FLASH2, SMOKE_PUFF, BACKBLAST, FLAME_JET];
3588
+ var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, RAIN_AND_PUDDLES, FOG_AND_ASH, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, HEADLIGHTS_IN_RAIN, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
3219
3589
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
3220
- var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries(EVENT_EFFECTS.map((effect) => [effect.id, effect])) };
3590
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS].map((effect) => [effect.id, effect])) };
3221
3591
  var LEHMER_MULTIPLIER = 16807;
3222
3592
  var LEHMER_MODULUS = 2147483647;
3223
3593
  var HASH_MIX = 2654435761;
@@ -3313,7 +3683,7 @@ var GameVfxThree = (() => {
3313
3683
  }
3314
3684
  var COLUMNS = 6;
3315
3685
  var ROWS = 5;
3316
- var ORIGIN2 = [-2.2, 0, 2.6];
3686
+ var ORIGIN3 = [-2.2, 0, 2.6];
3317
3687
  var BLOCK_SIZE = [0.5, 0.44, 0.25];
3318
3688
  var OBJECT_ID_DRAWS2 = 4;
3319
3689
  var FLOOR2 = 0.13;
@@ -3356,7 +3726,7 @@ var GameVfxThree = (() => {
3356
3726
  const chipScale = random.between(0.8, 1.3) * scale;
3357
3727
  for (let draw = 0; draw < OBJECT_ID_DRAWS2; draw++) random.next();
3358
3728
  best.holes.push({ x: offsetX, y: offsetY, scale, chipTurn, chipScale });
3359
- impact(host.engine, "concrete", [ORIGIN2[0] + x, y, ORIGIN2[2] + 0.13], [0, 0, 1], 0.8);
3729
+ impact(host.engine, "concrete", [ORIGIN3[0] + x, y, ORIGIN3[2] + 0.13], [0, 0, 1], 0.8);
3360
3730
  } else if (action === "collapse") {
3361
3731
  const { x, y } = args;
3362
3732
  for (const block of blocks) {
@@ -3406,7 +3776,7 @@ var GameVfxThree = (() => {
3406
3776
  }
3407
3777
  };
3408
3778
  }
3409
- var ORIGIN3 = [5.6, 0, 0.8];
3779
+ var ORIGIN4 = [5.6, 0, 0.8];
3410
3780
  var MAX_TILT = 0.25;
3411
3781
  function createCarBehavior() {
3412
3782
  let y = 0;
@@ -3438,8 +3808,13 @@ var GameVfxThree = (() => {
3438
3808
  }
3439
3809
  }
3440
3810
  },
3441
- query(_host, name) {
3442
- if (name === "position") return [ORIGIN3[0], y, ORIGIN3[2]];
3811
+ query(_host, name, args) {
3812
+ if (name === "position") return [ORIGIN4[0], y, ORIGIN4[2]];
3813
+ if (name === "point") {
3814
+ const [x, py, z] = args;
3815
+ const point = carPoint(x, py, z);
3816
+ return [point[0], point[1] + y, point[2]];
3817
+ }
3443
3818
  if (name === "roll") return roll;
3444
3819
  throw new Error(`car: unknown query ${name}`);
3445
3820
  },
@@ -3639,6 +4014,29 @@ var GameVfxThree = (() => {
3639
4014
  }
3640
4015
  };
3641
4016
  }
4017
+ function createRainBehavior() {
4018
+ return {
4019
+ reset() {
4020
+ },
4021
+ act(host, action, args) {
4022
+ const { engine, camera: camera2 } = host;
4023
+ if (action === "init") {
4024
+ const { count, wind } = args;
4025
+ engine.rain.init(count, wind);
4026
+ } else if (action === "update") {
4027
+ const { dt, splash } = args;
4028
+ engine.rain.update(dt, splash, camera2.position);
4029
+ }
4030
+ },
4031
+ query(host, name) {
4032
+ if (name === "count") return host.engine.rain.count;
4033
+ if (name === "on") return host.engine.rain.on;
4034
+ throw new Error(`rain: unknown query ${name}`);
4035
+ },
4036
+ update() {
4037
+ }
4038
+ };
4039
+ }
3642
4040
  var WALKER_COUNT = 10;
3643
4041
  var ARRIVAL_DISTANCE = 0.05;
3644
4042
  var SWAY_AMPLITUDE = 0.04;
@@ -3704,7 +4102,8 @@ var GameVfxThree = (() => {
3704
4102
  blocks: () => createBlocksBehavior(),
3705
4103
  plate: () => createPlateBehavior(),
3706
4104
  cylinder: () => createCylinderBehavior(),
3707
- car: () => createCarBehavior()
4105
+ car: () => createCarBehavior(),
4106
+ rain: () => createRainBehavior()
3708
4107
  };
3709
4108
  var NOOP = () => {
3710
4109
  };
@@ -3775,6 +4174,8 @@ var GameVfxThree = (() => {
3775
4174
  return Math.abs(asNumber(first));
3776
4175
  case "sqrt":
3777
4176
  return Math.sqrt(asNumber(first));
4177
+ case "exp":
4178
+ return Math.exp(asNumber(first));
3778
4179
  case "sin":
3779
4180
  return Math.sin(asNumber(first));
3780
4181
  case "cos":
@@ -3893,7 +4294,7 @@ var GameVfxThree = (() => {
3893
4294
  return evaluateCall(value.$, args.map((entry) => resolve(entry, context)));
3894
4295
  }
3895
4296
  }
3896
- var UP11 = [0, 1, 0];
4297
+ var UP12 = [0, 1, 0];
3897
4298
  var ZERO = [0, 0, 0];
3898
4299
  var FULL_TURN_DRAWN = 6.283;
3899
4300
  function scaledSizes(range, factor) {
@@ -3906,7 +4307,7 @@ var GameVfxThree = (() => {
3906
4307
  p: centre,
3907
4308
  r: radius,
3908
4309
  flat: true,
3909
- dir: options.dir ?? UP11,
4310
+ dir: options.dir ?? UP12,
3910
4311
  cone: 0.35,
3911
4312
  sp: [0.4, 1.4],
3912
4313
  v: wind,
@@ -3923,7 +4324,7 @@ var GameVfxThree = (() => {
3923
4324
  p: centre,
3924
4325
  r: radius * 0.6,
3925
4326
  flat: true,
3926
- dir: UP11,
4327
+ dir: UP12,
3927
4328
  cone: 0.3,
3928
4329
  sp: [0.3, 0.8],
3929
4330
  life: [0.2, 0.4],
@@ -3938,14 +4339,14 @@ var GameVfxThree = (() => {
3938
4339
  if (engine.quality < 2) {
3939
4340
  engine.emit(`${key}e`, "streak", 10 * factor, dt, () => {
3940
4341
  const jitter = plus(wind, engine.random.between(-0.4, 0.4), 0, engine.random.between(-0.4, 0.4));
3941
- return { p: centre, box: [radius, 0, radius], dir: UP11, cone: 0.5, sp: [1.5, 3.5], v: jitter, life: [0.8, 1.8], len: 0.03, c0: 16756816, c1: 6295552, g: -0.6, drag: 0.4 };
4342
+ return { p: centre, box: [radius, 0, radius], dir: UP12, cone: 0.5, sp: [1.5, 3.5], v: jitter, life: [0.8, 1.8], len: 0.03, c0: 16756816, c1: 6295552, g: -0.6, drag: 0.4 };
3942
4343
  });
3943
4344
  }
3944
4345
  if (options.noSmoke) return;
3945
4346
  engine.emit(`${key}s`, "normal", (options.smoke || 14) * factor, dt, () => ({
3946
4347
  p: plus(centre, 0, 0.8 * sqrtFactor, 0),
3947
4348
  r: radius * 0.7,
3948
- dir: UP11,
4349
+ dir: UP12,
3949
4350
  cone: 0.35,
3950
4351
  sp: [0.6, 1.4],
3951
4352
  v: wind,
@@ -3985,15 +4386,15 @@ var GameVfxThree = (() => {
3985
4386
  engine.screen.shake += 0.7 * scale;
3986
4387
  engine.screen.white += 0.18 * scale;
3987
4388
  engine.burst("add", 55 * scale, { p: point, r: 0.3 * scale, sp: [2, 7 * scale], drag: 4, life: [0.25, 0.6], size: [0.7 * scale, 1.4 * scale], grow: 1.7, c0: 16773296, c1: 10103304, fi: 0.04 });
3988
- engine.burst("add", 28 * scale, { p: plus(point, 0, 0.3, 0), r: 0.3, dir: UP11, cone: 0.7, sp: [2, 5 * scale], drag: 1.5, life: [0.6, 1.1], size: [0.6 * scale, 1 * scale], grow: 1.3, c0: 16760928, c1: 5902853, g: -2, fi: 0.05 });
4389
+ engine.burst("add", 28 * scale, { p: plus(point, 0, 0.3, 0), r: 0.3, dir: UP12, cone: 0.7, sp: [2, 5 * scale], drag: 1.5, life: [0.6, 1.1], size: [0.6 * scale, 1 * scale], grow: 1.3, c0: 16760928, c1: 5902853, g: -2, fi: 0.05 });
3989
4390
  engine.ring([plus(point, 0, 0.05, 0), 0.3, 7 * scale, 0.55, 16767136]);
3990
4391
  const toCamera = normalized([cameraPosition[0] - point[0], cameraPosition[1] - point[1], cameraPosition[2] - point[2]]);
3991
4392
  engine.ring([plus(point, 0, 1, 0), 0.3, 5 * scale, 0.35, 16773328, toCamera]);
3992
- engine.burst("normal", 32 * scale, { p: plus(point, 0, 0.6, 0), r: 0.8 * scale, dir: UP11, cone: 1.1, sp: [1, 3], drag: 1.2, life: [3, 6], size: [1 * scale, 1.8 * scale], grow: 2.6, c0: 2828068, c1: 987155, a: 0.6, fi: 0.18, g: -0.35 });
4393
+ engine.burst("normal", 32 * scale, { p: plus(point, 0, 0.6, 0), r: 0.8 * scale, dir: UP12, cone: 1.1, sp: [1, 3], drag: 1.2, life: [3, 6], size: [1 * scale, 1.8 * scale], grow: 2.6, c0: 2828068, c1: 987155, a: 0.6, fi: 0.18, g: -0.35 });
3993
4394
  ringOfDust(engine, point, scale);
3994
- if (engine.quality < 2) engine.burst("streak", 44 * scale, { p: point, dir: UP11, cone: 1.45, sp: [10, 26], life: [0.3, 0.7], len: 0.03, c0: 16760944, c1: 8396800, g: 9.8, gnd: 2 });
4395
+ if (engine.quality < 2) engine.burst("streak", 44 * scale, { p: point, dir: UP12, cone: 1.45, sp: [10, 26], life: [0.3, 0.7], len: 0.03, c0: 16760944, c1: 8396800, g: 9.8, gnd: 2 });
3995
4396
  for (let index = 0; index < engine.scaled(16 * scale); index++) {
3996
- engine.debrisPiece({ p: point, dir: UP11, cone: 1.2, sp: [3, 9], size: [0.12, 0.06, 0.1], k: [0.6, 1.6], c: chipColor || 2762274, life: 9 });
4397
+ engine.debrisPiece({ p: point, dir: UP12, cone: 1.2, sp: [3, 9], size: [0.12, 0.06, 0.1], k: [0.6, 1.6], c: chipColor || 2762274, life: 9 });
3997
4398
  }
3998
4399
  engine.decal([point[0], engine.context.groundAt(point[0], point[2]), point[2], 3 * scale, 657930, { grow: 0.15, life: 60 }]);
3999
4400
  }
@@ -4136,6 +4537,7 @@ var GameVfxThree = (() => {
4136
4537
  if (!def) return;
4137
4538
  this.schedule.clear();
4138
4539
  this.variables.clear();
4540
+ for (const [name, value] of Object.entries(this.setup.values ?? {})) this.variables.set(name, value);
4139
4541
  for (const behavior2 of this.deps.behaviors()) behavior2.reset(this.host);
4140
4542
  this.time = 0;
4141
4543
  this.within(() => {
@@ -4194,6 +4596,10 @@ var GameVfxThree = (() => {
4194
4596
  return engine.spawn(step.system, this.value(step.spawn));
4195
4597
  case "emit":
4196
4598
  return engine.emit(step.key, step.system, this.value(step.rate), this.dt, () => this.value(step.spawn));
4599
+ case "mesh": {
4600
+ engine.mesh(this.value(step.spawn));
4601
+ return;
4602
+ }
4197
4603
  case "debris":
4198
4604
  return engine.debrisPiece(this.value(step.spawn));
4199
4605
  case "decal":
@@ -4275,7 +4681,7 @@ var GameVfxThree = (() => {
4275
4681
  this.index = previous;
4276
4682
  }
4277
4683
  };
4278
- var ORIGIN4 = [0, 0, 0];
4684
+ var ORIGIN5 = [0, 0, 0];
4279
4685
  var DEFAULT_HEADING = [0, 0, -1];
4280
4686
  var DEFAULT_MAX_INSTANCES = 48;
4281
4687
  var ENDLESS_SECONDS = 60;
@@ -4320,7 +4726,7 @@ var GameVfxThree = (() => {
4320
4726
  }
4321
4727
  function placementOf(entry) {
4322
4728
  const origin = entry.follow ? [entry.origin[0] + positionOf2(entry.follow)[0], entry.origin[1] + positionOf2(entry.follow)[1], entry.origin[2] + positionOf2(entry.follow)[2]] : entry.origin;
4323
- return { origin, anchor: entry.anchor, yaw: entry.yaw };
4729
+ return { origin, anchor: entry.anchor, yaw: entry.yaw, ...entry.orientation ? { orientation: entry.orientation } : {} };
4324
4730
  }
4325
4731
  function drawing(entry, action) {
4326
4732
  const context = engine.context;
@@ -4344,8 +4750,9 @@ var GameVfxThree = (() => {
4344
4750
  random: playOptions.seed !== void 0 ? streamOf(playOptions.seed) : void 0,
4345
4751
  follow: playOptions.follow,
4346
4752
  origin: point,
4347
- anchor: def.anchor ?? ORIGIN4,
4753
+ anchor: def.anchor ?? ORIGIN5,
4348
4754
  yaw: yawOf(heading, playOptions.direction),
4755
+ orientation: playOptions.orientation,
4349
4756
  seconds: playOptions.seconds ?? (def.loop && def.loop > 0 ? def.loop : ENDLESS_SECONDS)
4350
4757
  };
4351
4758
  playing.push(entry);
@@ -4354,7 +4761,8 @@ var GameVfxThree = (() => {
4354
4761
  () => instance.begin(def, paramIndex(def, playOptions.param), {
4355
4762
  placement: placementOf(entry),
4356
4763
  ...playOptions.direction ? { direction: playOptions.direction } : {},
4357
- ...playOptions.normal ? { normal: playOptions.normal } : {}
4764
+ ...playOptions.normal ? { normal: playOptions.normal } : {},
4765
+ ...playOptions.values ? { values: playOptions.values } : {}
4358
4766
  })
4359
4767
  );
4360
4768
  return entry.handle;
@@ -4614,15 +5022,15 @@ var GameVfxThree = (() => {
4614
5022
  return shock;
4615
5023
  }
4616
5024
  function pushPuffs(puffs, shock, reach, dt, age) {
4617
- for (const puff2 of puffs) {
4618
- if (puff2.ring || puff2.t > PUSH_AGE_LIMIT) continue;
4619
- const dx = puff2.x - shock.x;
4620
- const dz = puff2.z - shock.z;
5025
+ for (const puff3 of puffs) {
5026
+ if (puff3.ring || puff3.t > PUSH_AGE_LIMIT) continue;
5027
+ const dx = puff3.x - shock.x;
5028
+ const dz = puff3.z - shock.z;
4621
5029
  const distance = Math.hypot(dx, dz) + 1e-3;
4622
- if (Math.abs(distance - reach) < PUSH_BAND && puff2.y < PUSH_CEILING) {
5030
+ if (Math.abs(distance - reach) < PUSH_BAND && puff3.y < PUSH_CEILING) {
4623
5031
  const strength = 4 * dt * (1 - age);
4624
- puff2.vx += dx / distance * strength * 6;
4625
- puff2.vz += dz / distance * strength * 6;
5032
+ puff3.vx += dx / distance * strength * 6;
5033
+ puff3.vz += dz / distance * strength * 6;
4626
5034
  }
4627
5035
  }
4628
5036
  }
@@ -4922,10 +5330,10 @@ var GameVfxThree = (() => {
4922
5330
  const life = random.between(ring2.life * 0.75, ring2.life);
4923
5331
  const h0 = random.between(1, 1.25);
4924
5332
  const hl = random.between(0.9, 1.6);
4925
- const puff2 = addPuff(core, random, { x, y, z, s0: spec.radius * 0.2, s1, sg: spec.radius * 0.06, gk: 3, life, h0, hl, color, kind: 0 });
4926
- puff2.ring = ring2;
4927
- puff2.ph = random.next() * Math.PI * 2;
4928
- puff2.th = random.next() * Math.PI * 2;
5333
+ const puff3 = addPuff(core, random, { x, y, z, s0: spec.radius * 0.2, s1, sg: spec.radius * 0.06, gk: 3, life, h0, hl, color, kind: 0 });
5334
+ puff3.ring = ring2;
5335
+ puff3.ph = random.next() * Math.PI * 2;
5336
+ puff3.th = random.next() * Math.PI * 2;
4929
5337
  }
4930
5338
  const stemCount = Math.round((spec.stem || 18) * Math.max(lod, 0.4));
4931
5339
  for (let index = 0; index < stemCount; index++) {
@@ -5726,7 +6134,7 @@ var GameVfxThree = (() => {
5726
6134
  };
5727
6135
  var TWO_PI_ROUNDED = 6.2832;
5728
6136
  var WIDE_CONE = 3.1;
5729
- var UP12 = [0, 1, 0];
6137
+ var UP13 = [0, 1, 0];
5730
6138
  function drawRange(random, range) {
5731
6139
  if (Array.isArray(range)) return random.between(range[0], range[1]);
5732
6140
  return range || 0;
@@ -5754,7 +6162,7 @@ var GameVfxThree = (() => {
5754
6162
  }
5755
6163
  function coneDirection(random, direction, cone, out) {
5756
6164
  if (cone >= WIDE_CONE) return randomUnit(random, out);
5757
- const [dx, dy, dz] = direction ?? UP12;
6165
+ const [dx, dy, dz] = direction ?? UP13;
5758
6166
  const cosTilt = 1 - random.next() * (1 - Math.cos(cone));
5759
6167
  const sinTilt = Math.sqrt(1 - cosTilt * cosTilt);
5760
6168
  const phase = random.next() * TWO_PI_ROUNDED;
@@ -5874,12 +6282,12 @@ var GameVfxThree = (() => {
5874
6282
  }
5875
6283
  };
5876
6284
  var CAPACITY2 = 400;
5877
- var UP13 = [0, 1, 0];
6285
+ var UP14 = [0, 1, 0];
5878
6286
  var DEFAULT_LIFE2 = 60;
5879
6287
  var DEFAULT_GROW = 1.2;
5880
6288
  var EVAPORATION = { fuel: [6, 5], water: [40, 20] };
5881
6289
  function createDecal() {
5882
- return { on: false, x: 0, y: 0, z: 0, sx: 0, sy: 0, t: 0, life: 0, grow: 0, color: 0, normal: UP13, fluid: void 0, dryColor: -1, dryAt: 0, dryTime: 0 };
6290
+ return { on: false, x: 0, y: 0, z: 0, sx: 0, sy: 0, t: 0, life: 0, grow: 0, color: 0, normal: UP14, fluid: void 0, dryColor: -1, dryAt: 0, dryTime: 0 };
5883
6291
  }
5884
6292
  var DecalPool = class {
5885
6293
  constructor(context) {
@@ -5892,7 +6300,7 @@ var GameVfxThree = (() => {
5892
6300
  const random = this.context.random;
5893
6301
  const index = this.next++ % CAPACITY2;
5894
6302
  const decal2 = this.decals[index];
5895
- const normal = options.n ?? UP13;
6303
+ const normal = options.n ?? UP14;
5896
6304
  random.between(0, 6.28);
5897
6305
  const offset = 4e-3 + index % 40 * 4e-4;
5898
6306
  decal2.on = true;
@@ -5933,6 +6341,94 @@ var GameVfxThree = (() => {
5933
6341
  }
5934
6342
  }
5935
6343
  };
6344
+ var CAPACITY3 = 400;
6345
+ var IDENTITY = [0, 0, 0, 1];
6346
+ function multiply(a, b) {
6347
+ const [ax, ay, az, aw] = a;
6348
+ const [bx, by, bz, bw] = b;
6349
+ return [aw * bx + ax * bw + ay * bz - az * by, aw * by - ax * bz + ay * bw + az * bx, aw * bz + ax * by - ay * bx + az * bw, aw * bw - ax * bx - ay * by - az * bz];
6350
+ }
6351
+ var aboutX = (angle) => [Math.sin(angle / 2), 0, 0, Math.cos(angle / 2)];
6352
+ var MeshPool = class {
6353
+ constructor(capacity = CAPACITY3) {
6354
+ __publicField(this, "capacity", capacity);
6355
+ __publicField(this, "particles", []);
6356
+ __publicField(this, "serial", 0);
6357
+ }
6358
+ get count() {
6359
+ return this.particles.length;
6360
+ }
6361
+ spawn(options, frame = IDENTITY) {
6362
+ const velocity = options.v ?? [0, 0, 0];
6363
+ const world = options.wv ?? [0, 0, 0];
6364
+ const particle = {
6365
+ id: ++this.serial,
6366
+ shape: options.shape,
6367
+ radius: options.r,
6368
+ height: options.h ?? 0,
6369
+ segments: options.seg ?? 0,
6370
+ segments2: options.seg2 ?? 0,
6371
+ color: options.c0,
6372
+ additive: options.add === true,
6373
+ lit: options.lit === true,
6374
+ x: options.p[0],
6375
+ y: options.p[1],
6376
+ z: options.p[2],
6377
+ frame: options.roll ? multiply(frame, aboutX(options.roll)) : frame,
6378
+ at: options.at ?? [0, 0, 0],
6379
+ rx: options.rot?.[0] ?? 0,
6380
+ ry: options.rot?.[1] ?? 0,
6381
+ rz: options.rot?.[2] ?? 0,
6382
+ scale: 1,
6383
+ opacity: options.a,
6384
+ age: 0,
6385
+ life: options.life,
6386
+ vx: velocity[0] + world[0],
6387
+ vy: velocity[1] + world[1],
6388
+ vz: velocity[2] + world[2],
6389
+ spin: options.spin ?? [0, 0, 0],
6390
+ gravity: options.g ?? 0,
6391
+ drag: options.drag ?? 0,
6392
+ grow: options.grow ?? 0,
6393
+ ramp: options.ramp,
6394
+ fade: options.fade === true,
6395
+ startOpacity: options.a
6396
+ };
6397
+ this.particles.push(particle);
6398
+ if (this.particles.length > this.capacity) this.particles.shift();
6399
+ return particle;
6400
+ }
6401
+ update(dt) {
6402
+ const live = this.particles;
6403
+ for (let index = 0; index < live.length; index++) {
6404
+ const particle = live[index];
6405
+ particle.age += dt;
6406
+ if (particle.ramp ? particle.age >= particle.life : particle.age > particle.life) {
6407
+ live.splice(index--, 1);
6408
+ continue;
6409
+ }
6410
+ if (particle.fade) particle.opacity = Math.max(0, particle.startOpacity * (1 - particle.age / particle.life));
6411
+ particle.vy -= particle.gravity * dt;
6412
+ if (particle.drag) {
6413
+ const keep = Math.max(0, 1 - particle.drag * dt);
6414
+ particle.vx *= keep;
6415
+ particle.vy *= keep;
6416
+ particle.vz *= keep;
6417
+ }
6418
+ particle.x += particle.vx * dt;
6419
+ particle.y += particle.vy * dt;
6420
+ particle.z += particle.vz * dt;
6421
+ particle.rx += particle.spin[0] * dt;
6422
+ particle.ry += particle.spin[1] * dt;
6423
+ particle.rz += particle.spin[2] * dt;
6424
+ if (particle.grow) particle.scale *= 1 + particle.grow * dt;
6425
+ if (particle.ramp) particle.scale = particle.ramp[0] + Math.min(1, particle.age / particle.life) * particle.ramp[1];
6426
+ }
6427
+ }
6428
+ clear() {
6429
+ this.particles.length = 0;
6430
+ }
6431
+ };
5936
6432
  var DEFAULT_FADE_IN = 0.1;
5937
6433
  var FLOATS_PER_VECTOR = 3;
5938
6434
  var BYTE_MAX2 = 255;
@@ -6222,11 +6718,11 @@ var GameVfxThree = (() => {
6222
6718
  };
6223
6719
  var RING_CAPACITY = 120;
6224
6720
  var LIGHT_CAPACITY = 4;
6225
- var UP14 = [0, 1, 0];
6721
+ var UP15 = [0, 1, 0];
6226
6722
  var DEFAULT_LIGHT_DISTANCE = 14;
6227
6723
  var WaveRingPool = class {
6228
6724
  constructor() {
6229
- __publicField(this, "rings", Array.from({ length: RING_CAPACITY }, () => ({ on: false, point: UP14, from: 0, to: 0, t: 0, duration: 1, color: 0, normal: UP14, hold: 0 })));
6725
+ __publicField(this, "rings", Array.from({ length: RING_CAPACITY }, () => ({ on: false, point: UP15, from: 0, to: 0, t: 0, duration: 1, color: 0, normal: UP15, hold: 0 })));
6230
6726
  __publicField(this, "count", 0);
6231
6727
  __publicField(this, "next", 0);
6232
6728
  }
@@ -6239,7 +6735,7 @@ var GameVfxThree = (() => {
6239
6735
  ring2.t = 0;
6240
6736
  ring2.duration = duration;
6241
6737
  ring2.color = color;
6242
- ring2.normal = normal ?? UP14;
6738
+ ring2.normal = normal ?? UP15;
6243
6739
  ring2.hold = hold || 0;
6244
6740
  }
6245
6741
  clear() {
@@ -6259,7 +6755,7 @@ var GameVfxThree = (() => {
6259
6755
  var FlashLightPool = class {
6260
6756
  constructor(context) {
6261
6757
  __publicField(this, "context", context);
6262
- __publicField(this, "lights", Array.from({ length: LIGHT_CAPACITY }, () => ({ x: 0, y: 0, z: 0, color: 16760944, distance: DEFAULT_LIGHT_DISTANCE, peak: 0, duration: 1, t: 0, intensity: 0 })));
6758
+ __publicField(this, "lights", Array.from({ length: LIGHT_CAPACITY }, () => ({ x: 0, y: 0, z: 0, color: 16760944, distance: DEFAULT_LIGHT_DISTANCE, peak: 0, duration: 1, t: 0, intensity: 0, steady: false })));
6263
6759
  __publicField(this, "fire", [
6264
6760
  { x: 0, y: 0, z: 0, intensity: 0 },
6265
6761
  { x: 0, y: 0, z: 0, intensity: 0 }
@@ -6269,7 +6765,7 @@ var GameVfxThree = (() => {
6269
6765
  get lit() {
6270
6766
  return this.lights.filter((light) => light.intensity > 0).length;
6271
6767
  }
6272
- flash(point, color, intensity, duration, distance, force) {
6768
+ flash(point, color, intensity, duration, distance, force, steady) {
6273
6769
  const { level } = this.context.quality;
6274
6770
  if (level >= 1 && !force) return;
6275
6771
  if (level >= 2 && force !== "boom") return;
@@ -6282,6 +6778,7 @@ var GameVfxThree = (() => {
6282
6778
  light.peak = intensity;
6283
6779
  light.duration = duration;
6284
6780
  light.t = 0;
6781
+ light.steady = steady === true;
6285
6782
  light.intensity = intensity;
6286
6783
  }
6287
6784
  clear() {
@@ -6293,7 +6790,7 @@ var GameVfxThree = (() => {
6293
6790
  if (light.intensity <= 0) continue;
6294
6791
  light.t += dt;
6295
6792
  const age = light.t / light.duration;
6296
- light.intensity = age >= 1 ? 0 : light.peak * (1 - age) * (1 - age);
6793
+ light.intensity = age >= 1 ? 0 : light.steady ? light.peak : light.peak * (1 - age) * (1 - age);
6297
6794
  }
6298
6795
  }
6299
6796
  };
@@ -6301,6 +6798,14 @@ var GameVfxThree = (() => {
6301
6798
  var FIRE_FLICKER_SWAY = 0.25;
6302
6799
  var FIRE_FLICKER_JITTER = 0.08;
6303
6800
  var LENS_FADE = 0.16;
6801
+ function rotate(q, v) {
6802
+ const [qx, qy, qz, qw] = q;
6803
+ const ix = qw * v[0] + qy * v[2] - qz * v[1];
6804
+ const iy = qw * v[1] + qz * v[0] - qx * v[2];
6805
+ const iz = qw * v[2] + qx * v[1] - qy * v[0];
6806
+ const iw = -qx * v[0] - qy * v[1] - qz * v[2];
6807
+ return [ix * qw + iw * -qx + iy * -qz - iz * -qy, iy * qw + iw * -qy + iz * -qx - ix * -qz, iz * qw + iw * -qz + ix * -qy - iy * -qx];
6808
+ }
6304
6809
  function workshopGround(x, z) {
6305
6810
  return z < -2.9 && z > -4.5 && Math.abs(x) < 13 ? 0.12 : 0;
6306
6811
  }
@@ -6314,6 +6819,8 @@ var GameVfxThree = (() => {
6314
6819
  __publicField(this, "decals");
6315
6820
  __publicField(this, "rings", new WaveRingPool());
6316
6821
  __publicField(this, "lights");
6822
+ __publicField(this, "rain", new RainField(this));
6823
+ __publicField(this, "meshes", new MeshPool());
6317
6824
  __publicField(this, "screen", createScreenState());
6318
6825
  __publicField(this, "lens", []);
6319
6826
  __publicField(this, "post", {});
@@ -6346,7 +6853,11 @@ var GameVfxThree = (() => {
6346
6853
  place(point) {
6347
6854
  const placement = this.placement;
6348
6855
  if (!placement) return point;
6349
- const { anchor, origin, yaw } = placement;
6856
+ const { anchor, origin, yaw, orientation } = placement;
6857
+ if (orientation) {
6858
+ const turned = rotate(orientation, [point[0] - anchor[0], point[1] - anchor[1], point[2] - anchor[2]]);
6859
+ return [origin[0] + turned[0], origin[1] + turned[1], origin[2] + turned[2]];
6860
+ }
6350
6861
  const x = point[0] - anchor[0];
6351
6862
  const z = point[2] - anchor[2];
6352
6863
  const cos2 = Math.cos(yaw);
@@ -6357,6 +6868,7 @@ var GameVfxThree = (() => {
6357
6868
  turn(vector) {
6358
6869
  const placement = this.placement;
6359
6870
  if (!placement) return vector;
6871
+ if (placement.orientation) return rotate(placement.orientation, vector);
6360
6872
  const cos2 = Math.cos(placement.yaw);
6361
6873
  const sin2 = Math.sin(placement.yaw);
6362
6874
  return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
@@ -6385,6 +6897,14 @@ var GameVfxThree = (() => {
6385
6897
  }
6386
6898
  this.accumulators.set(key, carried);
6387
6899
  }
6900
+ /** A solid particle (a cone, a sphere): the group sits at `p` turned by the placement, `v` is turned with it and `wv` is not. */
6901
+ mesh(recipe) {
6902
+ const placement = this.placement;
6903
+ const options = placement ? { ...recipe, p: this.place(recipe.p), ...recipe.v ? { v: this.turn(recipe.v) } : {} } : recipe;
6904
+ this.context.trace?.("mesh", [options]);
6905
+ const grouped = recipe.at !== void 0 || recipe.roll !== void 0;
6906
+ return this.meshes.spawn(options, placement && grouped ? placement.orientation ?? [0, Math.sin(placement.yaw / 2), 0, Math.cos(placement.yaw / 2)] : void 0);
6907
+ }
6388
6908
  debrisPiece(recipe) {
6389
6909
  const options = this.placed(recipe);
6390
6910
  this.context.trace?.("debris", [options]);
@@ -6405,10 +6925,11 @@ var GameVfxThree = (() => {
6405
6925
  this.context.trace?.("ring", args);
6406
6926
  this.rings.add(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6407
6927
  }
6928
+ /** `steady` (a seventh number): the light holds its intensity for its duration and goes out, where the Workshop's fades with the square of its age. */
6408
6929
  flash(recipe) {
6409
- const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5]] : recipe;
6410
- this.context.trace?.("flash", args);
6411
- this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5]);
6930
+ const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5], recipe[6]] : recipe;
6931
+ this.context.trace?.("flash", args.slice(0, 6));
6932
+ this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6412
6933
  }
6413
6934
  /** `fireLight` (W400): sways with the clock and flickers with one draw. */
6414
6935
  fireLight(slot, recipe, intensity, time) {
@@ -6433,6 +6954,7 @@ var GameVfxThree = (() => {
6433
6954
  this.debris.update(dt);
6434
6955
  this.decals.update(dt);
6435
6956
  this.rings.update(dt);
6957
+ this.meshes.update(dt);
6436
6958
  this.lights.update(dt);
6437
6959
  updateScreen(this.screen, dt);
6438
6960
  for (let index = this.lens.length - 1; index >= 0; index--) {
@@ -6449,7 +6971,9 @@ var GameVfxThree = (() => {
6449
6971
  this.debris.clear();
6450
6972
  this.decals.clear();
6451
6973
  this.rings.clear();
6974
+ this.meshes.clear();
6452
6975
  this.lights.clear();
6976
+ this.rain.clear();
6453
6977
  this.accumulators.clear();
6454
6978
  resetScreenState(this.screen);
6455
6979
  this.lens.length = 0;
@@ -6595,16 +7119,16 @@ var GameVfxThree = (() => {
6595
7119
  },
6596
7120
  push(point, radius, speed) {
6597
7121
  spriteSystem.push(point, radius, speed);
6598
- for (const puff2 of core.puffs.items) {
6599
- const dx = puff2.x - point[0];
6600
- const dy = puff2.y - point[1];
6601
- const dz = puff2.z - point[2];
7122
+ for (const puff3 of core.puffs.items) {
7123
+ const dx = puff3.x - point[0];
7124
+ const dy = puff3.y - point[1];
7125
+ const dz = puff3.z - point[2];
6602
7126
  const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
6603
7127
  if (distance >= radius) continue;
6604
7128
  const strength = speed * (1 - distance / radius) / (distance + 1e-3);
6605
- puff2.vx += dx * strength;
6606
- puff2.vy += dy * strength;
6607
- puff2.vz += dz * strength;
7129
+ puff3.vx += dx * strength;
7130
+ puff3.vy += dy * strength;
7131
+ puff3.vz += dz * strength;
6608
7132
  }
6609
7133
  },
6610
7134
  stop(handle) {
@@ -6621,6 +7145,12 @@ var GameVfxThree = (() => {
6621
7145
  camera2.y = position[1];
6622
7146
  camera2.z = position[2];
6623
7147
  },
7148
+ setWind(next) {
7149
+ wind.x = next[0];
7150
+ wind.y = next[1];
7151
+ wind.z = next[2];
7152
+ },
7153
+ wind: () => [wind.x, wind.y, wind.z],
6624
7154
  forceLod(scale) {
6625
7155
  core.clock.forcedLod = scale;
6626
7156
  },
@@ -6950,6 +7480,27 @@ var GameVfxThree = (() => {
6950
7480
  budget: "Up to 600 particles per building and 2 fire lights. Beyond 80 m only the smoke column and the glow of the windows remain, which become a landmark.",
6951
7481
  network: "A fire entity on the server per building, with a spread state."
6952
7482
  },
7483
+ rain_and_puddles: {
7484
+ name: "Rain, splashes and puddles",
7485
+ short: "Drops with wind, splashes and rings only near the camera, lightning in the storm.",
7486
+ cost: "\u2264 4,000 drops",
7487
+ costNote: "splash up to 12 m",
7488
+ paramLabel: "Intensity",
7489
+ when: "The weather of the world. Rain raises the background noise and masks footsteps (it can be used for stealth).",
7490
+ look: "A fine drizzle, almost vertical. Rain with splashes on the asphalt and rings in the puddles, which are dark and mirror-like. A storm with drops slanted by the wind, sheets of water passing and a double flash of lightning that lights the scene for 0.2 s and reveals the horde.",
7491
+ budget: "Up to 4,000 drops tied to the camera in a 30 m box. Splashes and rings only within 12 m and only on what the camera sees.",
7492
+ network: "The server sends the weather and the lightning seed, so everyone sees the same flash."
7493
+ },
7494
+ fog_and_ash: {
7495
+ name: "Fog and ash",
7496
+ short: "Low layered mist, ash falling and the lamp post with its cone of light.",
7497
+ cost: "\u2264 120 sprites",
7498
+ costNote: "1 cone of light",
7499
+ when: "Before dawn, near the river and in burnt zones (ash). It cuts the sight of the zombies and of the player.",
7500
+ look: "The engine fog plus a layer of low mist that moves slowly, fine ash falling in a spiral and the beam of the lamp posts in view. Zombie silhouettes show before their details.",
7501
+ budget: "Up to 60 large mist sprites (half resolution) and 60 of ash. The lamp cone is an additive mesh, not real volumetric light.",
7502
+ network: "The weather comes from the server. The density is local, by zone."
7503
+ },
6953
7504
  scraping_sparks: {
6954
7505
  name: "Scraping sparks",
6955
7506
  short: "A bumper dragging on the asphalt: a river of bouncing sparks and a mark on the ground.",
@@ -7001,6 +7552,16 @@ var GameVfxThree = (() => {
7001
7552
  budget: "Texture only: a crack mask with 4 stages and a blood decal on the glass. Glass dust: 6 particles per blow.",
7002
7553
  network: "The server sends the glass health. Blood on the glass is local."
7003
7554
  },
7555
+ headlights_in_rain: {
7556
+ name: "Headlights in the rain",
7557
+ short: "A visible beam, drops lit inside the cone and the horde walking into the light.",
7558
+ cost: "\u2264 600 lit drops",
7559
+ costNote: "2 spots, no shadow",
7560
+ when: "Headlights on at night, with rain or fog.",
7561
+ look: "Two cones of light in the air, rain drops that shine only inside the beam and zombie silhouettes that light up when they enter the light. Outside the cone the rain almost disappears.",
7562
+ budget: "2 spotlights without shadow, 2 additive cones and up to 600 extra drops only inside the cone. Headlights of cars beyond 40 m stay as a cone only, with no real light.",
7563
+ network: "The state of the headlights is already replicated with the car."
7564
+ },
7004
7565
  damage_and_bite: {
7005
7566
  name: "Damage and bite",
7006
7567
  short: "A red edge on the side of the blow, blood on the lens and the pulse of the bite.",
@@ -7318,6 +7879,27 @@ var GameVfxThree = (() => {
7318
7879
  budget: "At\xE9 600 part\xEDculas por pr\xE9dio e 2 luzes de fogo. Al\xE9m de 80 m fica s\xF3 a coluna de fuma\xE7a e o brilho das janelas, que viram ponto de refer\xEAncia.",
7319
7880
  network: "Entidade de fogo no servidor por pr\xE9dio, com estado de espalhar."
7320
7881
  },
7882
+ rain_and_puddles: {
7883
+ name: "Chuva, respingos e po\xE7as",
7884
+ short: "Gotas com vento, respingos e an\xE9is s\xF3 perto da c\xE2mera, rel\xE2mpago no temporal.",
7885
+ cost: "\u2264 4.000 gotas",
7886
+ costNote: "respingo at\xE9 12 m",
7887
+ paramLabel: "Intensidade",
7888
+ when: "Clima do mundo. Chuva aumenta o ru\xEDdo de fundo e mascara passos (d\xE1 para usar na furtividade).",
7889
+ look: "Garoa fina quase vertical. Chuva com respingos no asfalto e an\xE9is nas po\xE7as, que ficam escuras e espelhadas. Temporal com gotas inclinadas pelo vento, cortinas de \xE1gua passando e rel\xE2mpago duplo que clareia a cena por 0,2 s e revela a horda.",
7890
+ budget: "At\xE9 4.000 gotas presas \xE0 c\xE2mera numa caixa de 30 m. Respingos e an\xE9is s\xF3 a menos de 12 m e s\xF3 em cima do que a c\xE2mera v\xEA.",
7891
+ network: "O servidor manda o clima e a semente do rel\xE2mpago, para todos verem o mesmo clar\xE3o."
7892
+ },
7893
+ fog_and_ash: {
7894
+ name: "Neblina e cinzas",
7895
+ short: "N\xE9voa rasteira em camadas, cinza caindo e o poste com cone de luz.",
7896
+ cost: "\u2264 120 sprites",
7897
+ costNote: "1 cone de luz",
7898
+ when: "Madrugada, perto de rio e em zonas queimadas (cinza). Reduz a vis\xE3o dos zumbis e do jogador.",
7899
+ look: "Neblina do motor mais uma camada de n\xE9voa baixa que se move devagar, cinza fina caindo em espiral e o feixe dos postes vis\xEDvel. Silhuetas de zumbis aparecem antes dos detalhes.",
7900
+ budget: "At\xE9 60 sprites grandes de n\xE9voa (meia resolu\xE7\xE3o) e 60 de cinza. O cone do poste \xE9 uma malha aditiva, n\xE3o luz volum\xE9trica real.",
7901
+ network: "Clima vem do servidor. Densidade \xE9 local por zona."
7902
+ },
7321
7903
  scraping_sparks: {
7322
7904
  name: "Fa\xEDscas de raspagem",
7323
7905
  short: "Para-choque arrastando no asfalto: rio de fa\xEDscas que quicam e marca no ch\xE3o.",
@@ -7369,6 +7951,16 @@ var GameVfxThree = (() => {
7369
7951
  budget: "S\xF3 textura: uma m\xE1scara de rachadura com 4 est\xE1gios e um decal de sangue no vidro. P\xF3 de vidro: 6 part\xEDculas por golpe.",
7370
7952
  network: "O servidor manda a vida do vidro. Sangue no vidro \xE9 local."
7371
7953
  },
7954
+ headlights_in_rain: {
7955
+ name: "Far\xF3is na chuva",
7956
+ short: "Feixe vis\xEDvel, gotas acesas dentro do cone e a horda entrando na luz.",
7957
+ cost: "\u2264 600 gotas acesas",
7958
+ costNote: "2 spots sem sombra",
7959
+ when: "Far\xF3is ligados \xE0 noite, com chuva ou neblina.",
7960
+ look: "Dois cones de luz vis\xEDveis no ar, gotas de chuva que brilham s\xF3 dentro do feixe e silhuetas de zumbi que acendem quando entram na luz. Fora do cone, a chuva quase some.",
7961
+ budget: "2 spotlights sem sombra, 2 cones aditivos e at\xE9 600 gotas extras s\xF3 dentro do cone. Far\xF3is de carros a mais de 40 m ficam s\xF3 como cone, sem luz real.",
7962
+ network: "Estado do farol j\xE1 \xE9 replicado com o carro."
7963
+ },
7372
7964
  damage_and_bite: {
7373
7965
  name: "Dano e mordida",
7374
7966
  short: "Borda vermelha do lado do golpe, sangue na lente e o pulso da mordida.",
@@ -7559,11 +8151,11 @@ void main(){
7559
8151
  geometry.setAttribute("aC", attributes[2]);
7560
8152
  geometry.instanceCount = 0;
7561
8153
  const material = new THREE.ShaderMaterial({ uniforms, vertexShader: PUFF_VERTEX, fragmentShader: PUFF_FRAGMENT });
7562
- const mesh = new THREE.Mesh(geometry, material);
7563
- mesh.name = "vfx-puffs";
7564
- mesh.frustumCulled = false;
8154
+ const mesh2 = new THREE.Mesh(geometry, material);
8155
+ mesh2.name = "vfx-puffs";
8156
+ mesh2.frustumCulled = false;
7565
8157
  return {
7566
- mesh,
8158
+ mesh: mesh2,
7567
8159
  sync() {
7568
8160
  let n = 0;
7569
8161
  for (const p of pool.items) {
@@ -7604,12 +8196,12 @@ void main(){
7604
8196
  geometry.setAttribute("aE", attributes[2]);
7605
8197
  geometry.instanceCount = 0;
7606
8198
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: SPARK_VERTEX, fragmentShader: SPARK_FRAGMENT });
7607
- const mesh = new THREE.Mesh(geometry, material);
7608
- mesh.name = "vfx-sparks";
7609
- mesh.frustumCulled = false;
7610
- mesh.renderOrder = 5;
8199
+ const mesh2 = new THREE.Mesh(geometry, material);
8200
+ mesh2.name = "vfx-sparks";
8201
+ mesh2.frustumCulled = false;
8202
+ mesh2.renderOrder = 5;
7611
8203
  return {
7612
- mesh,
8204
+ mesh: mesh2,
7613
8205
  sync() {
7614
8206
  let n = 0;
7615
8207
  for (const s of pool.items) {
@@ -7647,12 +8239,12 @@ void main(){
7647
8239
  geometry.setAttribute("aC", attributes[1]);
7648
8240
  geometry.instanceCount = 0;
7649
8241
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: GLOW_VERTEX, fragmentShader: GLOW_FRAGMENT });
7650
- const mesh = new THREE.Mesh(geometry, material);
7651
- mesh.name = "vfx-glows";
7652
- mesh.frustumCulled = false;
7653
- mesh.renderOrder = 6;
8242
+ const mesh2 = new THREE.Mesh(geometry, material);
8243
+ mesh2.name = "vfx-glows";
8244
+ mesh2.frustumCulled = false;
8245
+ mesh2.renderOrder = 6;
7654
8246
  return {
7655
- mesh,
8247
+ mesh: mesh2,
7656
8248
  sync() {
7657
8249
  let n = 0;
7658
8250
  for (const g of pool.items) {
@@ -7681,14 +8273,14 @@ void main(){
7681
8273
  const rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.85, flatShading: true });
7682
8274
  const plateMaterial = rockMaterial.clone();
7683
8275
  const make = (geometry, material) => {
7684
- const mesh = new THREE.InstancedMesh(geometry, material, capacity);
7685
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
7686
- mesh.castShadow = true;
7687
- mesh.receiveShadow = true;
7688
- mesh.frustumCulled = false;
7689
- mesh.count = 0;
7690
- mesh.setColorAt(0, new THREE.Color(1, 1, 1));
7691
- return mesh;
8276
+ const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
8277
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8278
+ mesh2.castShadow = true;
8279
+ mesh2.receiveShadow = true;
8280
+ mesh2.frustumCulled = false;
8281
+ mesh2.count = 0;
8282
+ mesh2.setColorAt(0, new THREE.Color(1, 1, 1));
8283
+ return mesh2;
7692
8284
  };
7693
8285
  const rocks = make(shapes.rock, rockMaterial);
7694
8286
  const plates = make(shapes.plate, plateMaterial);
@@ -7707,20 +8299,20 @@ void main(){
7707
8299
  euler.set(chunk.rx, chunk.ry, chunk.rz);
7708
8300
  quaternion.setFromEuler(euler);
7709
8301
  matrix.compose(position.set(chunk.x, chunk.y, chunk.z), quaternion, scale.set(chunk.sx, chunk.sy, chunk.sz));
7710
- const mesh = chunk.plate ? plates : rocks;
8302
+ const mesh2 = chunk.plate ? plates : rocks;
7711
8303
  const index = chunk.plate ? plateCount++ : rockCount++;
7712
- mesh.setMatrixAt(index, matrix);
8304
+ mesh2.setMatrixAt(index, matrix);
7713
8305
  const color = colorOf(chunk);
7714
- const colors = mesh.instanceColor.array;
8306
+ const colors = mesh2.instanceColor.array;
7715
8307
  colors[index * 3] = color[0];
7716
8308
  colors[index * 3 + 1] = color[1];
7717
8309
  colors[index * 3 + 2] = color[2];
7718
8310
  }
7719
8311
  rocks.count = rockCount;
7720
8312
  plates.count = plateCount;
7721
- for (const mesh of [rocks, plates]) {
7722
- mesh.instanceMatrix.needsUpdate = true;
7723
- mesh.instanceColor.needsUpdate = true;
8313
+ for (const mesh2 of [rocks, plates]) {
8314
+ mesh2.instanceMatrix.needsUpdate = true;
8315
+ mesh2.instanceColor.needsUpdate = true;
7724
8316
  }
7725
8317
  return rockCount + plateCount;
7726
8318
  },
@@ -7910,28 +8502,28 @@ void main(){
7910
8502
  position.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
7911
8503
  }
7912
8504
  geometry.computeVertexNormals();
7913
- const mesh = new THREE.Mesh(geometry, material);
7914
- mesh.receiveShadow = true;
7915
- return mesh;
8505
+ const mesh2 = new THREE.Mesh(geometry, material);
8506
+ mesh2.receiveShadow = true;
8507
+ return mesh2;
7916
8508
  }
7917
8509
  function addRock(mark) {
7918
- const mesh = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
7919
- mesh.position.set(mark.x, mark.y, mark.z);
7920
- mesh.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
7921
- mesh.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
7922
- mesh.castShadow = true;
7923
- mesh.receiveShadow = true;
7924
- return mesh;
8510
+ const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
8511
+ mesh2.position.set(mark.x, mark.y, mark.z);
8512
+ mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
8513
+ mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
8514
+ mesh2.castShadow = true;
8515
+ mesh2.receiveShadow = true;
8516
+ return mesh2;
7925
8517
  }
7926
8518
  function addBlood(mark) {
7927
- const mesh = new THREE.Mesh(bloodGeometry, bloodMaterial);
7928
- mesh.position.set(mark.x, mark.y, mark.z);
7929
- mesh.scale.set(mark.scaleX, 1, mark.scaleZ);
7930
- mesh.rotation.y = mark.rotation;
7931
- mesh.receiveShadow = true;
7932
- mesh.scale.multiplyScalar(0.05);
7933
- blood.push({ mesh, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
7934
- return mesh;
8519
+ const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
8520
+ mesh2.position.set(mark.x, mark.y, mark.z);
8521
+ mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
8522
+ mesh2.rotation.y = mark.rotation;
8523
+ mesh2.receiveShadow = true;
8524
+ mesh2.scale.multiplyScalar(0.05);
8525
+ blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
8526
+ return mesh2;
7935
8527
  }
7936
8528
  function build(mark) {
7937
8529
  if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
@@ -8153,6 +8745,75 @@ void main() {
8153
8745
  };
8154
8746
  }
8155
8747
 
8748
+ // src/mesh-layer.ts
8749
+ var SPHERE_DEFAULT_WIDTH = 8;
8750
+ var SPHERE_DEFAULT_HEIGHT = 6;
8751
+ function geometryOf(THREE, particle) {
8752
+ switch (particle.shape) {
8753
+ case "cone":
8754
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
8755
+ case "cone_open":
8756
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
8757
+ case "sphere":
8758
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
8759
+ case "icosahedron":
8760
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
8761
+ }
8762
+ }
8763
+ function materialOf(THREE, particle) {
8764
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
8765
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
8766
+ }
8767
+ function createMeshLayer(THREE, pool) {
8768
+ const group = new THREE.Group();
8769
+ group.name = "workshop-meshes";
8770
+ const drawn = /* @__PURE__ */ new Map();
8771
+ const frame = new THREE.Quaternion();
8772
+ const euler = new THREE.Euler();
8773
+ const turn = new THREE.Quaternion();
8774
+ const offset = new THREE.Vector3();
8775
+ function release(mesh2) {
8776
+ mesh2.removeFromParent();
8777
+ mesh2.geometry.dispose();
8778
+ mesh2.material.dispose();
8779
+ }
8780
+ return {
8781
+ group,
8782
+ sync() {
8783
+ const live = /* @__PURE__ */ new Set();
8784
+ for (const particle of pool.particles) {
8785
+ live.add(particle.id);
8786
+ let mesh2 = drawn.get(particle.id);
8787
+ if (!mesh2) {
8788
+ mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
8789
+ mesh2.frustumCulled = false;
8790
+ mesh2.renderOrder = particle.additive ? 5 : 3;
8791
+ drawn.set(particle.id, mesh2);
8792
+ group.add(mesh2);
8793
+ }
8794
+ frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
8795
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
8796
+ mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
8797
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
8798
+ mesh2.quaternion.copy(frame).multiply(turn);
8799
+ mesh2.scale.setScalar(particle.scale);
8800
+ mesh2.material.opacity = particle.opacity;
8801
+ }
8802
+ for (const [id, mesh2] of drawn) {
8803
+ if (live.has(id)) continue;
8804
+ release(mesh2);
8805
+ drawn.delete(id);
8806
+ }
8807
+ return pool.count;
8808
+ },
8809
+ dispose() {
8810
+ for (const mesh2 of drawn.values()) release(mesh2);
8811
+ drawn.clear();
8812
+ group.removeFromParent();
8813
+ }
8814
+ };
8815
+ }
8816
+
8156
8817
  // src/shaders/workshop-sprite.ts
8157
8818
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
8158
8819
  void main(){vC=pcol;vA=alpha;vec4 mv=modelViewMatrix*vec4(position,1.);gl_PointSize=clamp(size*uScale/-mv.z,1.,512.);gl_Position=projectionMatrix*mv;}`;
@@ -8231,13 +8892,37 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8231
8892
  }
8232
8893
  };
8233
8894
  }
8895
+ function rainLayer(THREE, rain) {
8896
+ const geometry = new THREE.BufferGeometry();
8897
+ const position = new THREE.BufferAttribute(rain.positions, 3).setUsage(THREE.DynamicDrawUsage);
8898
+ geometry.setAttribute("position", position);
8899
+ geometry.setDrawRange(0, 0);
8900
+ const material = new THREE.LineBasicMaterial({ color: 10466255, transparent: true, opacity: 0.35, depthWrite: false });
8901
+ const lines = new THREE.LineSegments(geometry, material);
8902
+ lines.frustumCulled = false;
8903
+ lines.name = "workshop-rain";
8904
+ return {
8905
+ object: lines,
8906
+ sync() {
8907
+ const drawn = rain.on && rain.count > 0;
8908
+ lines.visible = drawn;
8909
+ geometry.setDrawRange(0, drawn ? rain.count * 2 : 0);
8910
+ position.needsUpdate = true;
8911
+ return drawn;
8912
+ },
8913
+ dispose() {
8914
+ geometry.dispose();
8915
+ material.dispose();
8916
+ }
8917
+ };
8918
+ }
8234
8919
  function debrisLayer(THREE, pool) {
8235
8920
  const geometry = new THREE.BoxGeometry(1, 1, 1);
8236
8921
  const material = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.45, metalness: 0.25, flatShading: true });
8237
- const mesh = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
8238
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8239
- mesh.frustumCulled = false;
8240
- mesh.name = "workshop-debris";
8922
+ const mesh2 = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
8923
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8924
+ mesh2.frustumCulled = false;
8925
+ mesh2.name = "workshop-debris";
8241
8926
  const euler = new THREE.Euler();
8242
8927
  const quaternion = new THREE.Quaternion();
8243
8928
  const position = new THREE.Vector3();
@@ -8245,16 +8930,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8245
8930
  const matrix = new THREE.Matrix4();
8246
8931
  const colour = new THREE.Color();
8247
8932
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
8248
- for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh.setMatrixAt(index, hidden);
8249
- mesh.setColorAt(0, colour.setHex(16777215));
8933
+ for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh2.setMatrixAt(index, hidden);
8934
+ mesh2.setColorAt(0, colour.setHex(16777215));
8250
8935
  return {
8251
- object: mesh,
8936
+ object: mesh2,
8252
8937
  sync() {
8253
8938
  let drawn = false;
8254
8939
  for (let index = 0; index < DEBRIS_CAPACITY; index++) {
8255
8940
  const piece = pool.pieces[index];
8256
8941
  if (!piece.on) {
8257
- mesh.setMatrixAt(index, hidden);
8942
+ mesh2.setMatrixAt(index, hidden);
8258
8943
  continue;
8259
8944
  }
8260
8945
  drawn = true;
@@ -8262,11 +8947,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8262
8947
  euler.set(piece.rx, piece.ry, piece.rz);
8263
8948
  quaternion.setFromEuler(euler);
8264
8949
  matrix.compose(position.set(piece.x, piece.y, piece.z), quaternion, scale.set(piece.sx * shrink, piece.sy * shrink, piece.sz * shrink));
8265
- mesh.setMatrixAt(index, matrix);
8266
- mesh.setColorAt(index, colour.setHex(piece.color));
8950
+ mesh2.setMatrixAt(index, matrix);
8951
+ mesh2.setColorAt(index, colour.setHex(piece.color));
8267
8952
  }
8268
- mesh.instanceMatrix.needsUpdate = true;
8269
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8953
+ mesh2.instanceMatrix.needsUpdate = true;
8954
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
8270
8955
  return drawn;
8271
8956
  },
8272
8957
  dispose() {
@@ -8323,11 +9008,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8323
9008
  const dryMesh = new THREE.InstancedMesh(geometry, dry, DECAL_CAPACITY);
8324
9009
  const group = new THREE.Group();
8325
9010
  group.name = "workshop-decals";
8326
- for (const mesh of [wetMesh, dryMesh]) {
8327
- mesh.frustumCulled = false;
8328
- mesh.renderOrder = 1;
8329
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8330
- group.add(mesh);
9011
+ for (const mesh2 of [wetMesh, dryMesh]) {
9012
+ mesh2.frustumCulled = false;
9013
+ mesh2.renderOrder = 1;
9014
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
9015
+ group.add(mesh2);
8331
9016
  }
8332
9017
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
8333
9018
  const colour = new THREE.Color();
@@ -8364,13 +9049,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8364
9049
  quaternion.multiply(turn.setFromAxisAngle(forward, index * 2.399963));
8365
9050
  const offset = 4e-3 + index % 40 * 4e-4;
8366
9051
  matrix.compose(position.set(decal2.x + normal.x * offset, decal2.y + normal.y * offset, decal2.z + normal.z * offset), quaternion, scale.set(look.sx, look.sy, 1));
8367
- const mesh = look.dried ? dryMesh : wetMesh;
8368
- mesh.setMatrixAt(index, matrix);
8369
- mesh.setColorAt(index, colour);
9052
+ const mesh2 = look.dried ? dryMesh : wetMesh;
9053
+ mesh2.setMatrixAt(index, matrix);
9054
+ mesh2.setColorAt(index, colour);
8370
9055
  }
8371
- for (const mesh of [wetMesh, dryMesh]) {
8372
- mesh.instanceMatrix.needsUpdate = true;
8373
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
9056
+ for (const mesh2 of [wetMesh, dryMesh]) {
9057
+ mesh2.instanceMatrix.needsUpdate = true;
9058
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
8374
9059
  }
8375
9060
  return drawn;
8376
9061
  },
@@ -8384,10 +9069,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8384
9069
  function ringLayer(THREE, pool) {
8385
9070
  const geometry = new THREE.RingGeometry(0.86, 1, 40);
8386
9071
  const material = new THREE.MeshBasicMaterial({ color: 16777215, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide });
8387
- const mesh = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
8388
- mesh.frustumCulled = false;
8389
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8390
- mesh.name = "workshop-rings";
9072
+ const mesh2 = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
9073
+ mesh2.frustumCulled = false;
9074
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
9075
+ mesh2.name = "workshop-rings";
8391
9076
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
8392
9077
  const colour = new THREE.Color();
8393
9078
  const normal = new THREE.Vector3();
@@ -8396,16 +9081,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8396
9081
  const position = new THREE.Vector3();
8397
9082
  const scale = new THREE.Vector3();
8398
9083
  const matrix = new THREE.Matrix4();
8399
- mesh.setColorAt(0, colour.setHex(0));
8400
- for (let index = 0; index < RING_CAPACITY3; index++) mesh.setMatrixAt(index, hidden);
9084
+ mesh2.setColorAt(0, colour.setHex(0));
9085
+ for (let index = 0; index < RING_CAPACITY3; index++) mesh2.setMatrixAt(index, hidden);
8401
9086
  return {
8402
- object: mesh,
9087
+ object: mesh2,
8403
9088
  sync() {
8404
9089
  let drawn = false;
8405
9090
  for (let index = 0; index < RING_CAPACITY3; index++) {
8406
9091
  const ring2 = pool.rings[index];
8407
9092
  if (!ring2.on) {
8408
- mesh.setMatrixAt(index, hidden);
9093
+ mesh2.setMatrixAt(index, hidden);
8409
9094
  continue;
8410
9095
  }
8411
9096
  drawn = true;
@@ -8413,11 +9098,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8413
9098
  const radius = ring2.from + (ring2.to - ring2.from) * (1 - Math.pow(1 - k, 2));
8414
9099
  quaternion.setFromUnitVectors(forward, normal.set(ring2.normal[0], ring2.normal[1], ring2.normal[2]));
8415
9100
  matrix.compose(position.set(ring2.point[0], ring2.point[1], ring2.point[2]), quaternion, scale.set(radius, radius, 1));
8416
- mesh.setMatrixAt(index, matrix);
8417
- mesh.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
9101
+ mesh2.setMatrixAt(index, matrix);
9102
+ mesh2.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
8418
9103
  }
8419
- mesh.instanceMatrix.needsUpdate = true;
8420
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
9104
+ mesh2.instanceMatrix.needsUpdate = true;
9105
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
8421
9106
  return drawn;
8422
9107
  },
8423
9108
  dispose() {
@@ -8433,11 +9118,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8433
9118
  spriteLayer(THREE, engine.add, true, 1, pointScale),
8434
9119
  spriteLayer(THREE, engine.normal, false, 0.55, pointScale),
8435
9120
  streakLayer(THREE, engine.streaks),
9121
+ rainLayer(THREE, engine.rain),
8436
9122
  debrisLayer(THREE, engine.debris),
8437
9123
  decalLayer(THREE, engine.decals),
8438
9124
  ringLayer(THREE, engine.rings)
8439
9125
  ];
8440
9126
  for (const layer2 of layers) group.add(layer2.object);
9127
+ const meshes = createMeshLayer(THREE, engine.meshes);
9128
+ group.add(meshes.group);
8441
9129
  const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
8442
9130
  const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
8443
9131
  for (const light of [...flashLights, ...fireLights]) group.add(light);
@@ -8446,6 +9134,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8446
9134
  sync() {
8447
9135
  let drawCalls = 0;
8448
9136
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
9137
+ drawCalls += meshes.sync();
8449
9138
  engine.lights.lights.forEach((source, index) => {
8450
9139
  const light = flashLights[index];
8451
9140
  light.position.set(source.x, source.y, source.z);
@@ -8462,6 +9151,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8462
9151
  },
8463
9152
  dispose() {
8464
9153
  for (const layer2 of layers) layer2.dispose();
9154
+ meshes.dispose();
8465
9155
  group.clear();
8466
9156
  }
8467
9157
  };
@@ -8580,8 +9270,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8580
9270
  return world.play(request.effect, request.point, {
8581
9271
  ...request.direction ? { direction: request.direction } : {},
8582
9272
  ...request.normal ? { normal: request.normal } : {},
9273
+ ...request.orientation ? { orientation: request.orientation } : {},
8583
9274
  ...request.seed !== void 0 ? { seed: request.seed } : {},
8584
- ...request.param !== void 0 ? { param: request.param } : {}
9275
+ ...request.param !== void 0 ? { param: request.param } : {},
9276
+ ...request.values ? { values: request.values } : {}
8585
9277
  });
8586
9278
  },
8587
9279
  stop: (handle) => world.stop(handle),
@@ -9002,8 +9694,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9002
9694
  const uv = geometry.attributes.uv;
9003
9695
  for (let index = 0; index < position.count; index++) uv.setXY(index, position.getX(index) / metres, position.getZ(index) / metres);
9004
9696
  const material = options.material ?? new THREE.MeshStandardMaterial({ color: 2829359, roughness: 0.93, flatShading: true });
9005
- const mesh = new THREE.Mesh(geometry, material);
9006
- mesh.receiveShadow = true;
9697
+ const mesh2 = new THREE.Mesh(geometry, material);
9698
+ mesh2.receiveShadow = true;
9007
9699
  let version = -1;
9008
9700
  function sync() {
9009
9701
  if (field.version === version) return false;
@@ -9014,7 +9706,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9014
9706
  return true;
9015
9707
  }
9016
9708
  return {
9017
- mesh,
9709
+ mesh: mesh2,
9018
9710
  sync,
9019
9711
  dispose() {
9020
9712
  geometry.dispose();