@adatechnology/game-vfx-three 0.1.3 → 0.2.1

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,157 @@ 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 aliasOf = (base, id, changes) => ({ ...base, id, pageId: `vehicle:${id}`, anchor: CAR_ORIGIN2, ...changes });
3589
+ var VEHICLE_EFFECTS = [
3590
+ /** The engine on fire: `engine_smoke` at its worst condition (black smoke, flames in the hood, a light). */
3591
+ aliasOf(ENGINE_SMOKE, "vehicle_fire", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 3 }, loop: 12 }),
3592
+ /** The exhaust of a car under way: `engine_smoke` in the best condition, for half a second so a moving car asks again. */
3593
+ aliasOf(ENGINE_SMOKE, "vehicle_exhaust", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 0 }, loop: 0.5 }),
3594
+ /** A hole in the tank: the page's own petrol leak (drips, pool, sparks, fire running to the tank). */
3595
+ aliasOf(FUEL_LEAK, "vehicle_leak", {})
3596
+ ];
3597
+ 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
3598
  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])) };
3599
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
3221
3600
  var LEHMER_MULTIPLIER = 16807;
3222
3601
  var LEHMER_MODULUS = 2147483647;
3223
3602
  var HASH_MIX = 2654435761;
@@ -3313,7 +3692,7 @@ var GameVfxThree = (() => {
3313
3692
  }
3314
3693
  var COLUMNS = 6;
3315
3694
  var ROWS = 5;
3316
- var ORIGIN2 = [-2.2, 0, 2.6];
3695
+ var ORIGIN3 = [-2.2, 0, 2.6];
3317
3696
  var BLOCK_SIZE = [0.5, 0.44, 0.25];
3318
3697
  var OBJECT_ID_DRAWS2 = 4;
3319
3698
  var FLOOR2 = 0.13;
@@ -3356,7 +3735,7 @@ var GameVfxThree = (() => {
3356
3735
  const chipScale = random.between(0.8, 1.3) * scale;
3357
3736
  for (let draw = 0; draw < OBJECT_ID_DRAWS2; draw++) random.next();
3358
3737
  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);
3738
+ impact(host.engine, "concrete", [ORIGIN3[0] + x, y, ORIGIN3[2] + 0.13], [0, 0, 1], 0.8);
3360
3739
  } else if (action === "collapse") {
3361
3740
  const { x, y } = args;
3362
3741
  for (const block of blocks) {
@@ -3406,7 +3785,7 @@ var GameVfxThree = (() => {
3406
3785
  }
3407
3786
  };
3408
3787
  }
3409
- var ORIGIN3 = [5.6, 0, 0.8];
3788
+ var ORIGIN4 = [5.6, 0, 0.8];
3410
3789
  var MAX_TILT = 0.25;
3411
3790
  function createCarBehavior() {
3412
3791
  let y = 0;
@@ -3438,8 +3817,13 @@ var GameVfxThree = (() => {
3438
3817
  }
3439
3818
  }
3440
3819
  },
3441
- query(_host, name) {
3442
- if (name === "position") return [ORIGIN3[0], y, ORIGIN3[2]];
3820
+ query(_host, name, args) {
3821
+ if (name === "position") return [ORIGIN4[0], y, ORIGIN4[2]];
3822
+ if (name === "point") {
3823
+ const [x, py, z] = args;
3824
+ const point = carPoint(x, py, z);
3825
+ return [point[0], point[1] + y, point[2]];
3826
+ }
3443
3827
  if (name === "roll") return roll;
3444
3828
  throw new Error(`car: unknown query ${name}`);
3445
3829
  },
@@ -3639,6 +4023,29 @@ var GameVfxThree = (() => {
3639
4023
  }
3640
4024
  };
3641
4025
  }
4026
+ function createRainBehavior() {
4027
+ return {
4028
+ reset() {
4029
+ },
4030
+ act(host, action, args) {
4031
+ const { engine, camera: camera2 } = host;
4032
+ if (action === "init") {
4033
+ const { count, wind } = args;
4034
+ engine.rain.init(count, wind);
4035
+ } else if (action === "update") {
4036
+ const { dt, splash } = args;
4037
+ engine.rain.update(dt, splash, camera2.position);
4038
+ }
4039
+ },
4040
+ query(host, name) {
4041
+ if (name === "count") return host.engine.rain.count;
4042
+ if (name === "on") return host.engine.rain.on;
4043
+ throw new Error(`rain: unknown query ${name}`);
4044
+ },
4045
+ update() {
4046
+ }
4047
+ };
4048
+ }
3642
4049
  var WALKER_COUNT = 10;
3643
4050
  var ARRIVAL_DISTANCE = 0.05;
3644
4051
  var SWAY_AMPLITUDE = 0.04;
@@ -3704,7 +4111,8 @@ var GameVfxThree = (() => {
3704
4111
  blocks: () => createBlocksBehavior(),
3705
4112
  plate: () => createPlateBehavior(),
3706
4113
  cylinder: () => createCylinderBehavior(),
3707
- car: () => createCarBehavior()
4114
+ car: () => createCarBehavior(),
4115
+ rain: () => createRainBehavior()
3708
4116
  };
3709
4117
  var NOOP = () => {
3710
4118
  };
@@ -3775,6 +4183,8 @@ var GameVfxThree = (() => {
3775
4183
  return Math.abs(asNumber(first));
3776
4184
  case "sqrt":
3777
4185
  return Math.sqrt(asNumber(first));
4186
+ case "exp":
4187
+ return Math.exp(asNumber(first));
3778
4188
  case "sin":
3779
4189
  return Math.sin(asNumber(first));
3780
4190
  case "cos":
@@ -3893,7 +4303,7 @@ var GameVfxThree = (() => {
3893
4303
  return evaluateCall(value.$, args.map((entry) => resolve(entry, context)));
3894
4304
  }
3895
4305
  }
3896
- var UP11 = [0, 1, 0];
4306
+ var UP12 = [0, 1, 0];
3897
4307
  var ZERO = [0, 0, 0];
3898
4308
  var FULL_TURN_DRAWN = 6.283;
3899
4309
  function scaledSizes(range, factor) {
@@ -3906,7 +4316,7 @@ var GameVfxThree = (() => {
3906
4316
  p: centre,
3907
4317
  r: radius,
3908
4318
  flat: true,
3909
- dir: options.dir ?? UP11,
4319
+ dir: options.dir ?? UP12,
3910
4320
  cone: 0.35,
3911
4321
  sp: [0.4, 1.4],
3912
4322
  v: wind,
@@ -3923,7 +4333,7 @@ var GameVfxThree = (() => {
3923
4333
  p: centre,
3924
4334
  r: radius * 0.6,
3925
4335
  flat: true,
3926
- dir: UP11,
4336
+ dir: UP12,
3927
4337
  cone: 0.3,
3928
4338
  sp: [0.3, 0.8],
3929
4339
  life: [0.2, 0.4],
@@ -3938,14 +4348,14 @@ var GameVfxThree = (() => {
3938
4348
  if (engine.quality < 2) {
3939
4349
  engine.emit(`${key}e`, "streak", 10 * factor, dt, () => {
3940
4350
  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 };
4351
+ 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
4352
  });
3943
4353
  }
3944
4354
  if (options.noSmoke) return;
3945
4355
  engine.emit(`${key}s`, "normal", (options.smoke || 14) * factor, dt, () => ({
3946
4356
  p: plus(centre, 0, 0.8 * sqrtFactor, 0),
3947
4357
  r: radius * 0.7,
3948
- dir: UP11,
4358
+ dir: UP12,
3949
4359
  cone: 0.35,
3950
4360
  sp: [0.6, 1.4],
3951
4361
  v: wind,
@@ -3985,15 +4395,15 @@ var GameVfxThree = (() => {
3985
4395
  engine.screen.shake += 0.7 * scale;
3986
4396
  engine.screen.white += 0.18 * scale;
3987
4397
  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 });
4398
+ 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
4399
  engine.ring([plus(point, 0, 0.05, 0), 0.3, 7 * scale, 0.55, 16767136]);
3990
4400
  const toCamera = normalized([cameraPosition[0] - point[0], cameraPosition[1] - point[1], cameraPosition[2] - point[2]]);
3991
4401
  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 });
4402
+ 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
4403
  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 });
4404
+ 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
4405
  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 });
4406
+ 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
4407
  }
3998
4408
  engine.decal([point[0], engine.context.groundAt(point[0], point[2]), point[2], 3 * scale, 657930, { grow: 0.15, life: 60 }]);
3999
4409
  }
@@ -4136,6 +4546,7 @@ var GameVfxThree = (() => {
4136
4546
  if (!def) return;
4137
4547
  this.schedule.clear();
4138
4548
  this.variables.clear();
4549
+ for (const [name, value] of Object.entries(this.setup.values ?? {})) this.variables.set(name, value);
4139
4550
  for (const behavior2 of this.deps.behaviors()) behavior2.reset(this.host);
4140
4551
  this.time = 0;
4141
4552
  this.within(() => {
@@ -4194,6 +4605,10 @@ var GameVfxThree = (() => {
4194
4605
  return engine.spawn(step.system, this.value(step.spawn));
4195
4606
  case "emit":
4196
4607
  return engine.emit(step.key, step.system, this.value(step.rate), this.dt, () => this.value(step.spawn));
4608
+ case "mesh": {
4609
+ engine.mesh(this.value(step.spawn));
4610
+ return;
4611
+ }
4197
4612
  case "debris":
4198
4613
  return engine.debrisPiece(this.value(step.spawn));
4199
4614
  case "decal":
@@ -4275,7 +4690,7 @@ var GameVfxThree = (() => {
4275
4690
  this.index = previous;
4276
4691
  }
4277
4692
  };
4278
- var ORIGIN4 = [0, 0, 0];
4693
+ var ORIGIN5 = [0, 0, 0];
4279
4694
  var DEFAULT_HEADING = [0, 0, -1];
4280
4695
  var DEFAULT_MAX_INSTANCES = 48;
4281
4696
  var ENDLESS_SECONDS = 60;
@@ -4320,7 +4735,7 @@ var GameVfxThree = (() => {
4320
4735
  }
4321
4736
  function placementOf(entry) {
4322
4737
  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 };
4738
+ return { origin, anchor: entry.anchor, yaw: entry.yaw, ...entry.orientation ? { orientation: entry.orientation } : {} };
4324
4739
  }
4325
4740
  function drawing(entry, action) {
4326
4741
  const context = engine.context;
@@ -4344,8 +4759,9 @@ var GameVfxThree = (() => {
4344
4759
  random: playOptions.seed !== void 0 ? streamOf(playOptions.seed) : void 0,
4345
4760
  follow: playOptions.follow,
4346
4761
  origin: point,
4347
- anchor: def.anchor ?? ORIGIN4,
4762
+ anchor: def.anchor ?? ORIGIN5,
4348
4763
  yaw: yawOf(heading, playOptions.direction),
4764
+ orientation: playOptions.orientation,
4349
4765
  seconds: playOptions.seconds ?? (def.loop && def.loop > 0 ? def.loop : ENDLESS_SECONDS)
4350
4766
  };
4351
4767
  playing.push(entry);
@@ -4354,7 +4770,8 @@ var GameVfxThree = (() => {
4354
4770
  () => instance.begin(def, paramIndex(def, playOptions.param), {
4355
4771
  placement: placementOf(entry),
4356
4772
  ...playOptions.direction ? { direction: playOptions.direction } : {},
4357
- ...playOptions.normal ? { normal: playOptions.normal } : {}
4773
+ ...playOptions.normal ? { normal: playOptions.normal } : {},
4774
+ ...playOptions.values ? { values: playOptions.values } : {}
4358
4775
  })
4359
4776
  );
4360
4777
  return entry.handle;
@@ -4614,15 +5031,15 @@ var GameVfxThree = (() => {
4614
5031
  return shock;
4615
5032
  }
4616
5033
  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;
5034
+ for (const puff3 of puffs) {
5035
+ if (puff3.ring || puff3.t > PUSH_AGE_LIMIT) continue;
5036
+ const dx = puff3.x - shock.x;
5037
+ const dz = puff3.z - shock.z;
4621
5038
  const distance = Math.hypot(dx, dz) + 1e-3;
4622
- if (Math.abs(distance - reach) < PUSH_BAND && puff2.y < PUSH_CEILING) {
5039
+ if (Math.abs(distance - reach) < PUSH_BAND && puff3.y < PUSH_CEILING) {
4623
5040
  const strength = 4 * dt * (1 - age);
4624
- puff2.vx += dx / distance * strength * 6;
4625
- puff2.vz += dz / distance * strength * 6;
5041
+ puff3.vx += dx / distance * strength * 6;
5042
+ puff3.vz += dz / distance * strength * 6;
4626
5043
  }
4627
5044
  }
4628
5045
  }
@@ -4922,10 +5339,10 @@ var GameVfxThree = (() => {
4922
5339
  const life = random.between(ring2.life * 0.75, ring2.life);
4923
5340
  const h0 = random.between(1, 1.25);
4924
5341
  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;
5342
+ 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 });
5343
+ puff3.ring = ring2;
5344
+ puff3.ph = random.next() * Math.PI * 2;
5345
+ puff3.th = random.next() * Math.PI * 2;
4929
5346
  }
4930
5347
  const stemCount = Math.round((spec.stem || 18) * Math.max(lod, 0.4));
4931
5348
  for (let index = 0; index < stemCount; index++) {
@@ -5726,7 +6143,7 @@ var GameVfxThree = (() => {
5726
6143
  };
5727
6144
  var TWO_PI_ROUNDED = 6.2832;
5728
6145
  var WIDE_CONE = 3.1;
5729
- var UP12 = [0, 1, 0];
6146
+ var UP13 = [0, 1, 0];
5730
6147
  function drawRange(random, range) {
5731
6148
  if (Array.isArray(range)) return random.between(range[0], range[1]);
5732
6149
  return range || 0;
@@ -5754,7 +6171,7 @@ var GameVfxThree = (() => {
5754
6171
  }
5755
6172
  function coneDirection(random, direction, cone, out) {
5756
6173
  if (cone >= WIDE_CONE) return randomUnit(random, out);
5757
- const [dx, dy, dz] = direction ?? UP12;
6174
+ const [dx, dy, dz] = direction ?? UP13;
5758
6175
  const cosTilt = 1 - random.next() * (1 - Math.cos(cone));
5759
6176
  const sinTilt = Math.sqrt(1 - cosTilt * cosTilt);
5760
6177
  const phase = random.next() * TWO_PI_ROUNDED;
@@ -5874,12 +6291,12 @@ var GameVfxThree = (() => {
5874
6291
  }
5875
6292
  };
5876
6293
  var CAPACITY2 = 400;
5877
- var UP13 = [0, 1, 0];
6294
+ var UP14 = [0, 1, 0];
5878
6295
  var DEFAULT_LIFE2 = 60;
5879
6296
  var DEFAULT_GROW = 1.2;
5880
6297
  var EVAPORATION = { fuel: [6, 5], water: [40, 20] };
5881
6298
  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 };
6299
+ 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
6300
  }
5884
6301
  var DecalPool = class {
5885
6302
  constructor(context) {
@@ -5892,7 +6309,7 @@ var GameVfxThree = (() => {
5892
6309
  const random = this.context.random;
5893
6310
  const index = this.next++ % CAPACITY2;
5894
6311
  const decal2 = this.decals[index];
5895
- const normal = options.n ?? UP13;
6312
+ const normal = options.n ?? UP14;
5896
6313
  random.between(0, 6.28);
5897
6314
  const offset = 4e-3 + index % 40 * 4e-4;
5898
6315
  decal2.on = true;
@@ -5933,6 +6350,94 @@ var GameVfxThree = (() => {
5933
6350
  }
5934
6351
  }
5935
6352
  };
6353
+ var CAPACITY3 = 400;
6354
+ var IDENTITY = [0, 0, 0, 1];
6355
+ function multiply(a, b) {
6356
+ const [ax, ay, az, aw] = a;
6357
+ const [bx, by, bz, bw] = b;
6358
+ 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];
6359
+ }
6360
+ var aboutX = (angle) => [Math.sin(angle / 2), 0, 0, Math.cos(angle / 2)];
6361
+ var MeshPool = class {
6362
+ constructor(capacity = CAPACITY3) {
6363
+ __publicField(this, "capacity", capacity);
6364
+ __publicField(this, "particles", []);
6365
+ __publicField(this, "serial", 0);
6366
+ }
6367
+ get count() {
6368
+ return this.particles.length;
6369
+ }
6370
+ spawn(options, frame = IDENTITY) {
6371
+ const velocity = options.v ?? [0, 0, 0];
6372
+ const world = options.wv ?? [0, 0, 0];
6373
+ const particle = {
6374
+ id: ++this.serial,
6375
+ shape: options.shape,
6376
+ radius: options.r,
6377
+ height: options.h ?? 0,
6378
+ segments: options.seg ?? 0,
6379
+ segments2: options.seg2 ?? 0,
6380
+ color: options.c0,
6381
+ additive: options.add === true,
6382
+ lit: options.lit === true,
6383
+ x: options.p[0],
6384
+ y: options.p[1],
6385
+ z: options.p[2],
6386
+ frame: options.roll ? multiply(frame, aboutX(options.roll)) : frame,
6387
+ at: options.at ?? [0, 0, 0],
6388
+ rx: options.rot?.[0] ?? 0,
6389
+ ry: options.rot?.[1] ?? 0,
6390
+ rz: options.rot?.[2] ?? 0,
6391
+ scale: 1,
6392
+ opacity: options.a,
6393
+ age: 0,
6394
+ life: options.life,
6395
+ vx: velocity[0] + world[0],
6396
+ vy: velocity[1] + world[1],
6397
+ vz: velocity[2] + world[2],
6398
+ spin: options.spin ?? [0, 0, 0],
6399
+ gravity: options.g ?? 0,
6400
+ drag: options.drag ?? 0,
6401
+ grow: options.grow ?? 0,
6402
+ ramp: options.ramp,
6403
+ fade: options.fade === true,
6404
+ startOpacity: options.a
6405
+ };
6406
+ this.particles.push(particle);
6407
+ if (this.particles.length > this.capacity) this.particles.shift();
6408
+ return particle;
6409
+ }
6410
+ update(dt) {
6411
+ const live = this.particles;
6412
+ for (let index = 0; index < live.length; index++) {
6413
+ const particle = live[index];
6414
+ particle.age += dt;
6415
+ if (particle.ramp ? particle.age >= particle.life : particle.age > particle.life) {
6416
+ live.splice(index--, 1);
6417
+ continue;
6418
+ }
6419
+ if (particle.fade) particle.opacity = Math.max(0, particle.startOpacity * (1 - particle.age / particle.life));
6420
+ particle.vy -= particle.gravity * dt;
6421
+ if (particle.drag) {
6422
+ const keep = Math.max(0, 1 - particle.drag * dt);
6423
+ particle.vx *= keep;
6424
+ particle.vy *= keep;
6425
+ particle.vz *= keep;
6426
+ }
6427
+ particle.x += particle.vx * dt;
6428
+ particle.y += particle.vy * dt;
6429
+ particle.z += particle.vz * dt;
6430
+ particle.rx += particle.spin[0] * dt;
6431
+ particle.ry += particle.spin[1] * dt;
6432
+ particle.rz += particle.spin[2] * dt;
6433
+ if (particle.grow) particle.scale *= 1 + particle.grow * dt;
6434
+ if (particle.ramp) particle.scale = particle.ramp[0] + Math.min(1, particle.age / particle.life) * particle.ramp[1];
6435
+ }
6436
+ }
6437
+ clear() {
6438
+ this.particles.length = 0;
6439
+ }
6440
+ };
5936
6441
  var DEFAULT_FADE_IN = 0.1;
5937
6442
  var FLOATS_PER_VECTOR = 3;
5938
6443
  var BYTE_MAX2 = 255;
@@ -6222,11 +6727,11 @@ var GameVfxThree = (() => {
6222
6727
  };
6223
6728
  var RING_CAPACITY = 120;
6224
6729
  var LIGHT_CAPACITY = 4;
6225
- var UP14 = [0, 1, 0];
6730
+ var UP15 = [0, 1, 0];
6226
6731
  var DEFAULT_LIGHT_DISTANCE = 14;
6227
6732
  var WaveRingPool = class {
6228
6733
  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 })));
6734
+ __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
6735
  __publicField(this, "count", 0);
6231
6736
  __publicField(this, "next", 0);
6232
6737
  }
@@ -6239,7 +6744,7 @@ var GameVfxThree = (() => {
6239
6744
  ring2.t = 0;
6240
6745
  ring2.duration = duration;
6241
6746
  ring2.color = color;
6242
- ring2.normal = normal ?? UP14;
6747
+ ring2.normal = normal ?? UP15;
6243
6748
  ring2.hold = hold || 0;
6244
6749
  }
6245
6750
  clear() {
@@ -6259,7 +6764,7 @@ var GameVfxThree = (() => {
6259
6764
  var FlashLightPool = class {
6260
6765
  constructor(context) {
6261
6766
  __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 })));
6767
+ __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
6768
  __publicField(this, "fire", [
6264
6769
  { x: 0, y: 0, z: 0, intensity: 0 },
6265
6770
  { x: 0, y: 0, z: 0, intensity: 0 }
@@ -6269,7 +6774,7 @@ var GameVfxThree = (() => {
6269
6774
  get lit() {
6270
6775
  return this.lights.filter((light) => light.intensity > 0).length;
6271
6776
  }
6272
- flash(point, color, intensity, duration, distance, force) {
6777
+ flash(point, color, intensity, duration, distance, force, steady) {
6273
6778
  const { level } = this.context.quality;
6274
6779
  if (level >= 1 && !force) return;
6275
6780
  if (level >= 2 && force !== "boom") return;
@@ -6282,6 +6787,7 @@ var GameVfxThree = (() => {
6282
6787
  light.peak = intensity;
6283
6788
  light.duration = duration;
6284
6789
  light.t = 0;
6790
+ light.steady = steady === true;
6285
6791
  light.intensity = intensity;
6286
6792
  }
6287
6793
  clear() {
@@ -6293,7 +6799,7 @@ var GameVfxThree = (() => {
6293
6799
  if (light.intensity <= 0) continue;
6294
6800
  light.t += dt;
6295
6801
  const age = light.t / light.duration;
6296
- light.intensity = age >= 1 ? 0 : light.peak * (1 - age) * (1 - age);
6802
+ light.intensity = age >= 1 ? 0 : light.steady ? light.peak : light.peak * (1 - age) * (1 - age);
6297
6803
  }
6298
6804
  }
6299
6805
  };
@@ -6301,6 +6807,14 @@ var GameVfxThree = (() => {
6301
6807
  var FIRE_FLICKER_SWAY = 0.25;
6302
6808
  var FIRE_FLICKER_JITTER = 0.08;
6303
6809
  var LENS_FADE = 0.16;
6810
+ function rotate(q, v) {
6811
+ const [qx, qy, qz, qw] = q;
6812
+ const ix = qw * v[0] + qy * v[2] - qz * v[1];
6813
+ const iy = qw * v[1] + qz * v[0] - qx * v[2];
6814
+ const iz = qw * v[2] + qx * v[1] - qy * v[0];
6815
+ const iw = -qx * v[0] - qy * v[1] - qz * v[2];
6816
+ 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];
6817
+ }
6304
6818
  function workshopGround(x, z) {
6305
6819
  return z < -2.9 && z > -4.5 && Math.abs(x) < 13 ? 0.12 : 0;
6306
6820
  }
@@ -6314,6 +6828,8 @@ var GameVfxThree = (() => {
6314
6828
  __publicField(this, "decals");
6315
6829
  __publicField(this, "rings", new WaveRingPool());
6316
6830
  __publicField(this, "lights");
6831
+ __publicField(this, "rain", new RainField(this));
6832
+ __publicField(this, "meshes", new MeshPool());
6317
6833
  __publicField(this, "screen", createScreenState());
6318
6834
  __publicField(this, "lens", []);
6319
6835
  __publicField(this, "post", {});
@@ -6346,7 +6862,11 @@ var GameVfxThree = (() => {
6346
6862
  place(point) {
6347
6863
  const placement = this.placement;
6348
6864
  if (!placement) return point;
6349
- const { anchor, origin, yaw } = placement;
6865
+ const { anchor, origin, yaw, orientation } = placement;
6866
+ if (orientation) {
6867
+ const turned = rotate(orientation, [point[0] - anchor[0], point[1] - anchor[1], point[2] - anchor[2]]);
6868
+ return [origin[0] + turned[0], origin[1] + turned[1], origin[2] + turned[2]];
6869
+ }
6350
6870
  const x = point[0] - anchor[0];
6351
6871
  const z = point[2] - anchor[2];
6352
6872
  const cos2 = Math.cos(yaw);
@@ -6357,6 +6877,7 @@ var GameVfxThree = (() => {
6357
6877
  turn(vector) {
6358
6878
  const placement = this.placement;
6359
6879
  if (!placement) return vector;
6880
+ if (placement.orientation) return rotate(placement.orientation, vector);
6360
6881
  const cos2 = Math.cos(placement.yaw);
6361
6882
  const sin2 = Math.sin(placement.yaw);
6362
6883
  return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
@@ -6385,6 +6906,14 @@ var GameVfxThree = (() => {
6385
6906
  }
6386
6907
  this.accumulators.set(key, carried);
6387
6908
  }
6909
+ /** 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. */
6910
+ mesh(recipe) {
6911
+ const placement = this.placement;
6912
+ const options = placement ? { ...recipe, p: this.place(recipe.p), ...recipe.v ? { v: this.turn(recipe.v) } : {} } : recipe;
6913
+ this.context.trace?.("mesh", [options]);
6914
+ const grouped = recipe.at !== void 0 || recipe.roll !== void 0;
6915
+ return this.meshes.spawn(options, placement && grouped ? placement.orientation ?? [0, Math.sin(placement.yaw / 2), 0, Math.cos(placement.yaw / 2)] : void 0);
6916
+ }
6388
6917
  debrisPiece(recipe) {
6389
6918
  const options = this.placed(recipe);
6390
6919
  this.context.trace?.("debris", [options]);
@@ -6405,10 +6934,11 @@ var GameVfxThree = (() => {
6405
6934
  this.context.trace?.("ring", args);
6406
6935
  this.rings.add(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6407
6936
  }
6937
+ /** `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
6938
  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]);
6939
+ const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5], recipe[6]] : recipe;
6940
+ this.context.trace?.("flash", args.slice(0, 6));
6941
+ this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6412
6942
  }
6413
6943
  /** `fireLight` (W400): sways with the clock and flickers with one draw. */
6414
6944
  fireLight(slot, recipe, intensity, time) {
@@ -6433,6 +6963,7 @@ var GameVfxThree = (() => {
6433
6963
  this.debris.update(dt);
6434
6964
  this.decals.update(dt);
6435
6965
  this.rings.update(dt);
6966
+ this.meshes.update(dt);
6436
6967
  this.lights.update(dt);
6437
6968
  updateScreen(this.screen, dt);
6438
6969
  for (let index = this.lens.length - 1; index >= 0; index--) {
@@ -6449,7 +6980,9 @@ var GameVfxThree = (() => {
6449
6980
  this.debris.clear();
6450
6981
  this.decals.clear();
6451
6982
  this.rings.clear();
6983
+ this.meshes.clear();
6452
6984
  this.lights.clear();
6985
+ this.rain.clear();
6453
6986
  this.accumulators.clear();
6454
6987
  resetScreenState(this.screen);
6455
6988
  this.lens.length = 0;
@@ -6595,16 +7128,16 @@ var GameVfxThree = (() => {
6595
7128
  },
6596
7129
  push(point, radius, speed) {
6597
7130
  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];
7131
+ for (const puff3 of core.puffs.items) {
7132
+ const dx = puff3.x - point[0];
7133
+ const dy = puff3.y - point[1];
7134
+ const dz = puff3.z - point[2];
6602
7135
  const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
6603
7136
  if (distance >= radius) continue;
6604
7137
  const strength = speed * (1 - distance / radius) / (distance + 1e-3);
6605
- puff2.vx += dx * strength;
6606
- puff2.vy += dy * strength;
6607
- puff2.vz += dz * strength;
7138
+ puff3.vx += dx * strength;
7139
+ puff3.vy += dy * strength;
7140
+ puff3.vz += dz * strength;
6608
7141
  }
6609
7142
  },
6610
7143
  stop(handle) {
@@ -6621,6 +7154,12 @@ var GameVfxThree = (() => {
6621
7154
  camera2.y = position[1];
6622
7155
  camera2.z = position[2];
6623
7156
  },
7157
+ setWind(next) {
7158
+ wind.x = next[0];
7159
+ wind.y = next[1];
7160
+ wind.z = next[2];
7161
+ },
7162
+ wind: () => [wind.x, wind.y, wind.z],
6624
7163
  forceLod(scale) {
6625
7164
  core.clock.forcedLod = scale;
6626
7165
  },
@@ -6950,6 +7489,27 @@ var GameVfxThree = (() => {
6950
7489
  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
7490
  network: "A fire entity on the server per building, with a spread state."
6952
7491
  },
7492
+ rain_and_puddles: {
7493
+ name: "Rain, splashes and puddles",
7494
+ short: "Drops with wind, splashes and rings only near the camera, lightning in the storm.",
7495
+ cost: "\u2264 4,000 drops",
7496
+ costNote: "splash up to 12 m",
7497
+ paramLabel: "Intensity",
7498
+ when: "The weather of the world. Rain raises the background noise and masks footsteps (it can be used for stealth).",
7499
+ 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.",
7500
+ 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.",
7501
+ network: "The server sends the weather and the lightning seed, so everyone sees the same flash."
7502
+ },
7503
+ fog_and_ash: {
7504
+ name: "Fog and ash",
7505
+ short: "Low layered mist, ash falling and the lamp post with its cone of light.",
7506
+ cost: "\u2264 120 sprites",
7507
+ costNote: "1 cone of light",
7508
+ when: "Before dawn, near the river and in burnt zones (ash). It cuts the sight of the zombies and of the player.",
7509
+ 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.",
7510
+ budget: "Up to 60 large mist sprites (half resolution) and 60 of ash. The lamp cone is an additive mesh, not real volumetric light.",
7511
+ network: "The weather comes from the server. The density is local, by zone."
7512
+ },
6953
7513
  scraping_sparks: {
6954
7514
  name: "Scraping sparks",
6955
7515
  short: "A bumper dragging on the asphalt: a river of bouncing sparks and a mark on the ground.",
@@ -7001,6 +7561,16 @@ var GameVfxThree = (() => {
7001
7561
  budget: "Texture only: a crack mask with 4 stages and a blood decal on the glass. Glass dust: 6 particles per blow.",
7002
7562
  network: "The server sends the glass health. Blood on the glass is local."
7003
7563
  },
7564
+ headlights_in_rain: {
7565
+ name: "Headlights in the rain",
7566
+ short: "A visible beam, drops lit inside the cone and the horde walking into the light.",
7567
+ cost: "\u2264 600 lit drops",
7568
+ costNote: "2 spots, no shadow",
7569
+ when: "Headlights on at night, with rain or fog.",
7570
+ 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.",
7571
+ 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.",
7572
+ network: "The state of the headlights is already replicated with the car."
7573
+ },
7004
7574
  damage_and_bite: {
7005
7575
  name: "Damage and bite",
7006
7576
  short: "A red edge on the side of the blow, blood on the lens and the pulse of the bite.",
@@ -7318,6 +7888,27 @@ var GameVfxThree = (() => {
7318
7888
  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
7889
  network: "Entidade de fogo no servidor por pr\xE9dio, com estado de espalhar."
7320
7890
  },
7891
+ rain_and_puddles: {
7892
+ name: "Chuva, respingos e po\xE7as",
7893
+ short: "Gotas com vento, respingos e an\xE9is s\xF3 perto da c\xE2mera, rel\xE2mpago no temporal.",
7894
+ cost: "\u2264 4.000 gotas",
7895
+ costNote: "respingo at\xE9 12 m",
7896
+ paramLabel: "Intensidade",
7897
+ when: "Clima do mundo. Chuva aumenta o ru\xEDdo de fundo e mascara passos (d\xE1 para usar na furtividade).",
7898
+ 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.",
7899
+ 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.",
7900
+ network: "O servidor manda o clima e a semente do rel\xE2mpago, para todos verem o mesmo clar\xE3o."
7901
+ },
7902
+ fog_and_ash: {
7903
+ name: "Neblina e cinzas",
7904
+ short: "N\xE9voa rasteira em camadas, cinza caindo e o poste com cone de luz.",
7905
+ cost: "\u2264 120 sprites",
7906
+ costNote: "1 cone de luz",
7907
+ when: "Madrugada, perto de rio e em zonas queimadas (cinza). Reduz a vis\xE3o dos zumbis e do jogador.",
7908
+ 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.",
7909
+ 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.",
7910
+ network: "Clima vem do servidor. Densidade \xE9 local por zona."
7911
+ },
7321
7912
  scraping_sparks: {
7322
7913
  name: "Fa\xEDscas de raspagem",
7323
7914
  short: "Para-choque arrastando no asfalto: rio de fa\xEDscas que quicam e marca no ch\xE3o.",
@@ -7369,6 +7960,16 @@ var GameVfxThree = (() => {
7369
7960
  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
7961
  network: "O servidor manda a vida do vidro. Sangue no vidro \xE9 local."
7371
7962
  },
7963
+ headlights_in_rain: {
7964
+ name: "Far\xF3is na chuva",
7965
+ short: "Feixe vis\xEDvel, gotas acesas dentro do cone e a horda entrando na luz.",
7966
+ cost: "\u2264 600 gotas acesas",
7967
+ costNote: "2 spots sem sombra",
7968
+ when: "Far\xF3is ligados \xE0 noite, com chuva ou neblina.",
7969
+ 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.",
7970
+ 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.",
7971
+ network: "Estado do farol j\xE1 \xE9 replicado com o carro."
7972
+ },
7372
7973
  damage_and_bite: {
7373
7974
  name: "Dano e mordida",
7374
7975
  short: "Borda vermelha do lado do golpe, sangue na lente e o pulso da mordida.",
@@ -7559,11 +8160,11 @@ void main(){
7559
8160
  geometry.setAttribute("aC", attributes[2]);
7560
8161
  geometry.instanceCount = 0;
7561
8162
  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;
8163
+ const mesh2 = new THREE.Mesh(geometry, material);
8164
+ mesh2.name = "vfx-puffs";
8165
+ mesh2.frustumCulled = false;
7565
8166
  return {
7566
- mesh,
8167
+ mesh: mesh2,
7567
8168
  sync() {
7568
8169
  let n = 0;
7569
8170
  for (const p of pool.items) {
@@ -7604,12 +8205,12 @@ void main(){
7604
8205
  geometry.setAttribute("aE", attributes[2]);
7605
8206
  geometry.instanceCount = 0;
7606
8207
  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;
8208
+ const mesh2 = new THREE.Mesh(geometry, material);
8209
+ mesh2.name = "vfx-sparks";
8210
+ mesh2.frustumCulled = false;
8211
+ mesh2.renderOrder = 5;
7611
8212
  return {
7612
- mesh,
8213
+ mesh: mesh2,
7613
8214
  sync() {
7614
8215
  let n = 0;
7615
8216
  for (const s of pool.items) {
@@ -7647,12 +8248,12 @@ void main(){
7647
8248
  geometry.setAttribute("aC", attributes[1]);
7648
8249
  geometry.instanceCount = 0;
7649
8250
  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;
8251
+ const mesh2 = new THREE.Mesh(geometry, material);
8252
+ mesh2.name = "vfx-glows";
8253
+ mesh2.frustumCulled = false;
8254
+ mesh2.renderOrder = 6;
7654
8255
  return {
7655
- mesh,
8256
+ mesh: mesh2,
7656
8257
  sync() {
7657
8258
  let n = 0;
7658
8259
  for (const g of pool.items) {
@@ -7681,14 +8282,14 @@ void main(){
7681
8282
  const rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.85, flatShading: true });
7682
8283
  const plateMaterial = rockMaterial.clone();
7683
8284
  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;
8285
+ const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
8286
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8287
+ mesh2.castShadow = true;
8288
+ mesh2.receiveShadow = true;
8289
+ mesh2.frustumCulled = false;
8290
+ mesh2.count = 0;
8291
+ mesh2.setColorAt(0, new THREE.Color(1, 1, 1));
8292
+ return mesh2;
7692
8293
  };
7693
8294
  const rocks = make(shapes.rock, rockMaterial);
7694
8295
  const plates = make(shapes.plate, plateMaterial);
@@ -7707,20 +8308,20 @@ void main(){
7707
8308
  euler.set(chunk.rx, chunk.ry, chunk.rz);
7708
8309
  quaternion.setFromEuler(euler);
7709
8310
  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;
8311
+ const mesh2 = chunk.plate ? plates : rocks;
7711
8312
  const index = chunk.plate ? plateCount++ : rockCount++;
7712
- mesh.setMatrixAt(index, matrix);
8313
+ mesh2.setMatrixAt(index, matrix);
7713
8314
  const color = colorOf(chunk);
7714
- const colors = mesh.instanceColor.array;
8315
+ const colors = mesh2.instanceColor.array;
7715
8316
  colors[index * 3] = color[0];
7716
8317
  colors[index * 3 + 1] = color[1];
7717
8318
  colors[index * 3 + 2] = color[2];
7718
8319
  }
7719
8320
  rocks.count = rockCount;
7720
8321
  plates.count = plateCount;
7721
- for (const mesh of [rocks, plates]) {
7722
- mesh.instanceMatrix.needsUpdate = true;
7723
- mesh.instanceColor.needsUpdate = true;
8322
+ for (const mesh2 of [rocks, plates]) {
8323
+ mesh2.instanceMatrix.needsUpdate = true;
8324
+ mesh2.instanceColor.needsUpdate = true;
7724
8325
  }
7725
8326
  return rockCount + plateCount;
7726
8327
  },
@@ -7910,28 +8511,28 @@ void main(){
7910
8511
  position.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
7911
8512
  }
7912
8513
  geometry.computeVertexNormals();
7913
- const mesh = new THREE.Mesh(geometry, material);
7914
- mesh.receiveShadow = true;
7915
- return mesh;
8514
+ const mesh2 = new THREE.Mesh(geometry, material);
8515
+ mesh2.receiveShadow = true;
8516
+ return mesh2;
7916
8517
  }
7917
8518
  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;
8519
+ const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
8520
+ mesh2.position.set(mark.x, mark.y, mark.z);
8521
+ mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
8522
+ mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
8523
+ mesh2.castShadow = true;
8524
+ mesh2.receiveShadow = true;
8525
+ return mesh2;
7925
8526
  }
7926
8527
  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;
8528
+ const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
8529
+ mesh2.position.set(mark.x, mark.y, mark.z);
8530
+ mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
8531
+ mesh2.rotation.y = mark.rotation;
8532
+ mesh2.receiveShadow = true;
8533
+ mesh2.scale.multiplyScalar(0.05);
8534
+ blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
8535
+ return mesh2;
7935
8536
  }
7936
8537
  function build(mark) {
7937
8538
  if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
@@ -8153,6 +8754,75 @@ void main() {
8153
8754
  };
8154
8755
  }
8155
8756
 
8757
+ // src/mesh-layer.ts
8758
+ var SPHERE_DEFAULT_WIDTH = 8;
8759
+ var SPHERE_DEFAULT_HEIGHT = 6;
8760
+ function geometryOf(THREE, particle) {
8761
+ switch (particle.shape) {
8762
+ case "cone":
8763
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
8764
+ case "cone_open":
8765
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
8766
+ case "sphere":
8767
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
8768
+ case "icosahedron":
8769
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
8770
+ }
8771
+ }
8772
+ function materialOf(THREE, particle) {
8773
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
8774
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
8775
+ }
8776
+ function createMeshLayer(THREE, pool) {
8777
+ const group = new THREE.Group();
8778
+ group.name = "workshop-meshes";
8779
+ const drawn = /* @__PURE__ */ new Map();
8780
+ const frame = new THREE.Quaternion();
8781
+ const euler = new THREE.Euler();
8782
+ const turn = new THREE.Quaternion();
8783
+ const offset = new THREE.Vector3();
8784
+ function release(mesh2) {
8785
+ mesh2.removeFromParent();
8786
+ mesh2.geometry.dispose();
8787
+ mesh2.material.dispose();
8788
+ }
8789
+ return {
8790
+ group,
8791
+ sync() {
8792
+ const live = /* @__PURE__ */ new Set();
8793
+ for (const particle of pool.particles) {
8794
+ live.add(particle.id);
8795
+ let mesh2 = drawn.get(particle.id);
8796
+ if (!mesh2) {
8797
+ mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
8798
+ mesh2.frustumCulled = false;
8799
+ mesh2.renderOrder = particle.additive ? 5 : 3;
8800
+ drawn.set(particle.id, mesh2);
8801
+ group.add(mesh2);
8802
+ }
8803
+ frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
8804
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
8805
+ mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
8806
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
8807
+ mesh2.quaternion.copy(frame).multiply(turn);
8808
+ mesh2.scale.setScalar(particle.scale);
8809
+ mesh2.material.opacity = particle.opacity;
8810
+ }
8811
+ for (const [id, mesh2] of drawn) {
8812
+ if (live.has(id)) continue;
8813
+ release(mesh2);
8814
+ drawn.delete(id);
8815
+ }
8816
+ return pool.count;
8817
+ },
8818
+ dispose() {
8819
+ for (const mesh2 of drawn.values()) release(mesh2);
8820
+ drawn.clear();
8821
+ group.removeFromParent();
8822
+ }
8823
+ };
8824
+ }
8825
+
8156
8826
  // src/shaders/workshop-sprite.ts
8157
8827
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
8158
8828
  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 +8901,37 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8231
8901
  }
8232
8902
  };
8233
8903
  }
8904
+ function rainLayer(THREE, rain) {
8905
+ const geometry = new THREE.BufferGeometry();
8906
+ const position = new THREE.BufferAttribute(rain.positions, 3).setUsage(THREE.DynamicDrawUsage);
8907
+ geometry.setAttribute("position", position);
8908
+ geometry.setDrawRange(0, 0);
8909
+ const material = new THREE.LineBasicMaterial({ color: 10466255, transparent: true, opacity: 0.35, depthWrite: false });
8910
+ const lines = new THREE.LineSegments(geometry, material);
8911
+ lines.frustumCulled = false;
8912
+ lines.name = "workshop-rain";
8913
+ return {
8914
+ object: lines,
8915
+ sync() {
8916
+ const drawn = rain.on && rain.count > 0;
8917
+ lines.visible = drawn;
8918
+ geometry.setDrawRange(0, drawn ? rain.count * 2 : 0);
8919
+ position.needsUpdate = true;
8920
+ return drawn;
8921
+ },
8922
+ dispose() {
8923
+ geometry.dispose();
8924
+ material.dispose();
8925
+ }
8926
+ };
8927
+ }
8234
8928
  function debrisLayer(THREE, pool) {
8235
8929
  const geometry = new THREE.BoxGeometry(1, 1, 1);
8236
8930
  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";
8931
+ const mesh2 = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
8932
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8933
+ mesh2.frustumCulled = false;
8934
+ mesh2.name = "workshop-debris";
8241
8935
  const euler = new THREE.Euler();
8242
8936
  const quaternion = new THREE.Quaternion();
8243
8937
  const position = new THREE.Vector3();
@@ -8245,16 +8939,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8245
8939
  const matrix = new THREE.Matrix4();
8246
8940
  const colour = new THREE.Color();
8247
8941
  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));
8942
+ for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh2.setMatrixAt(index, hidden);
8943
+ mesh2.setColorAt(0, colour.setHex(16777215));
8250
8944
  return {
8251
- object: mesh,
8945
+ object: mesh2,
8252
8946
  sync() {
8253
8947
  let drawn = false;
8254
8948
  for (let index = 0; index < DEBRIS_CAPACITY; index++) {
8255
8949
  const piece = pool.pieces[index];
8256
8950
  if (!piece.on) {
8257
- mesh.setMatrixAt(index, hidden);
8951
+ mesh2.setMatrixAt(index, hidden);
8258
8952
  continue;
8259
8953
  }
8260
8954
  drawn = true;
@@ -8262,11 +8956,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8262
8956
  euler.set(piece.rx, piece.ry, piece.rz);
8263
8957
  quaternion.setFromEuler(euler);
8264
8958
  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));
8959
+ mesh2.setMatrixAt(index, matrix);
8960
+ mesh2.setColorAt(index, colour.setHex(piece.color));
8267
8961
  }
8268
- mesh.instanceMatrix.needsUpdate = true;
8269
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8962
+ mesh2.instanceMatrix.needsUpdate = true;
8963
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
8270
8964
  return drawn;
8271
8965
  },
8272
8966
  dispose() {
@@ -8323,11 +9017,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8323
9017
  const dryMesh = new THREE.InstancedMesh(geometry, dry, DECAL_CAPACITY);
8324
9018
  const group = new THREE.Group();
8325
9019
  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);
9020
+ for (const mesh2 of [wetMesh, dryMesh]) {
9021
+ mesh2.frustumCulled = false;
9022
+ mesh2.renderOrder = 1;
9023
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
9024
+ group.add(mesh2);
8331
9025
  }
8332
9026
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
8333
9027
  const colour = new THREE.Color();
@@ -8364,13 +9058,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8364
9058
  quaternion.multiply(turn.setFromAxisAngle(forward, index * 2.399963));
8365
9059
  const offset = 4e-3 + index % 40 * 4e-4;
8366
9060
  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);
9061
+ const mesh2 = look.dried ? dryMesh : wetMesh;
9062
+ mesh2.setMatrixAt(index, matrix);
9063
+ mesh2.setColorAt(index, colour);
8370
9064
  }
8371
- for (const mesh of [wetMesh, dryMesh]) {
8372
- mesh.instanceMatrix.needsUpdate = true;
8373
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
9065
+ for (const mesh2 of [wetMesh, dryMesh]) {
9066
+ mesh2.instanceMatrix.needsUpdate = true;
9067
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
8374
9068
  }
8375
9069
  return drawn;
8376
9070
  },
@@ -8384,10 +9078,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8384
9078
  function ringLayer(THREE, pool) {
8385
9079
  const geometry = new THREE.RingGeometry(0.86, 1, 40);
8386
9080
  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";
9081
+ const mesh2 = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
9082
+ mesh2.frustumCulled = false;
9083
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
9084
+ mesh2.name = "workshop-rings";
8391
9085
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
8392
9086
  const colour = new THREE.Color();
8393
9087
  const normal = new THREE.Vector3();
@@ -8396,16 +9090,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8396
9090
  const position = new THREE.Vector3();
8397
9091
  const scale = new THREE.Vector3();
8398
9092
  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);
9093
+ mesh2.setColorAt(0, colour.setHex(0));
9094
+ for (let index = 0; index < RING_CAPACITY3; index++) mesh2.setMatrixAt(index, hidden);
8401
9095
  return {
8402
- object: mesh,
9096
+ object: mesh2,
8403
9097
  sync() {
8404
9098
  let drawn = false;
8405
9099
  for (let index = 0; index < RING_CAPACITY3; index++) {
8406
9100
  const ring2 = pool.rings[index];
8407
9101
  if (!ring2.on) {
8408
- mesh.setMatrixAt(index, hidden);
9102
+ mesh2.setMatrixAt(index, hidden);
8409
9103
  continue;
8410
9104
  }
8411
9105
  drawn = true;
@@ -8413,11 +9107,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8413
9107
  const radius = ring2.from + (ring2.to - ring2.from) * (1 - Math.pow(1 - k, 2));
8414
9108
  quaternion.setFromUnitVectors(forward, normal.set(ring2.normal[0], ring2.normal[1], ring2.normal[2]));
8415
9109
  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));
9110
+ mesh2.setMatrixAt(index, matrix);
9111
+ mesh2.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
8418
9112
  }
8419
- mesh.instanceMatrix.needsUpdate = true;
8420
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
9113
+ mesh2.instanceMatrix.needsUpdate = true;
9114
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
8421
9115
  return drawn;
8422
9116
  },
8423
9117
  dispose() {
@@ -8433,11 +9127,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8433
9127
  spriteLayer(THREE, engine.add, true, 1, pointScale),
8434
9128
  spriteLayer(THREE, engine.normal, false, 0.55, pointScale),
8435
9129
  streakLayer(THREE, engine.streaks),
9130
+ rainLayer(THREE, engine.rain),
8436
9131
  debrisLayer(THREE, engine.debris),
8437
9132
  decalLayer(THREE, engine.decals),
8438
9133
  ringLayer(THREE, engine.rings)
8439
9134
  ];
8440
9135
  for (const layer2 of layers) group.add(layer2.object);
9136
+ const meshes = createMeshLayer(THREE, engine.meshes);
9137
+ group.add(meshes.group);
8441
9138
  const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
8442
9139
  const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
8443
9140
  for (const light of [...flashLights, ...fireLights]) group.add(light);
@@ -8446,6 +9143,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8446
9143
  sync() {
8447
9144
  let drawCalls = 0;
8448
9145
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
9146
+ drawCalls += meshes.sync();
8449
9147
  engine.lights.lights.forEach((source, index) => {
8450
9148
  const light = flashLights[index];
8451
9149
  light.position.set(source.x, source.y, source.z);
@@ -8462,6 +9160,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8462
9160
  },
8463
9161
  dispose() {
8464
9162
  for (const layer2 of layers) layer2.dispose();
9163
+ meshes.dispose();
8465
9164
  group.clear();
8466
9165
  }
8467
9166
  };
@@ -8580,8 +9279,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8580
9279
  return world.play(request.effect, request.point, {
8581
9280
  ...request.direction ? { direction: request.direction } : {},
8582
9281
  ...request.normal ? { normal: request.normal } : {},
9282
+ ...request.orientation ? { orientation: request.orientation } : {},
8583
9283
  ...request.seed !== void 0 ? { seed: request.seed } : {},
8584
- ...request.param !== void 0 ? { param: request.param } : {}
9284
+ ...request.param !== void 0 ? { param: request.param } : {},
9285
+ ...request.values ? { values: request.values } : {}
8585
9286
  });
8586
9287
  },
8587
9288
  stop: (handle) => world.stop(handle),
@@ -9002,8 +9703,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9002
9703
  const uv = geometry.attributes.uv;
9003
9704
  for (let index = 0; index < position.count; index++) uv.setXY(index, position.getX(index) / metres, position.getZ(index) / metres);
9004
9705
  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;
9706
+ const mesh2 = new THREE.Mesh(geometry, material);
9707
+ mesh2.receiveShadow = true;
9007
9708
  let version = -1;
9008
9709
  function sync() {
9009
9710
  if (field.version === version) return false;
@@ -9014,7 +9715,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9014
9715
  return true;
9015
9716
  }
9016
9717
  return {
9017
- mesh,
9718
+ mesh: mesh2,
9018
9719
  sync,
9019
9720
  dispose() {
9020
9721
  geometry.dispose();