@adatechnology/game-fluids-three 0.2.3 → 0.3.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.
@@ -1364,77 +1364,77 @@ var GameFluidsThree = (() => {
1364
1364
  }));
1365
1365
  }
1366
1366
  function spawnPuff(pool, random, init) {
1367
- const puff2 = pool.acquire();
1368
- puff2.x = init.x ?? 0;
1369
- puff2.y = init.y ?? 0;
1370
- puff2.z = init.z ?? 0;
1371
- puff2.vx = init.vx ?? 0;
1372
- puff2.vy = init.vy ?? 0;
1373
- puff2.vz = init.vz ?? 0;
1374
- puff2.s0 = init.s0 ?? 0.2;
1375
- puff2.s1 = init.s1 ?? 1;
1376
- puff2.sg = init.sg ?? 0.1;
1377
- puff2.gk = init.gk ?? 6;
1378
- puff2.life = init.life ?? 3;
1379
- puff2.t = 0;
1380
- puff2.h0 = init.h0 ?? 0;
1381
- puff2.hl = init.hl ?? 0.4;
1382
- puff2.drag = init.drag ?? 3;
1383
- puff2.buoy = init.buoy ?? 0.5;
1384
- puff2.grav = init.grav ?? 0;
1385
- puff2.kind = init.kind ?? 0;
1386
- puff2.seed = random.next();
1387
- puff2.wind = init.wind ?? 1;
1388
- puff2.color = init.color ?? SMOKE_COLORS.grey;
1389
- puff2.ring = init.ring ?? null;
1390
- puff2.ph = init.ph ?? 0;
1391
- puff2.th = init.th ?? 0;
1392
- puff2.rng = random;
1393
- puff2.age = 0;
1394
- puff2.heat = 0;
1395
- puff2.size = 0;
1396
- return puff2;
1397
- }
1398
- function ageOrKeep2(puff2, dt) {
1399
- puff2.t += dt;
1400
- return puff2.t < puff2.life;
1401
- }
1402
- function moveOnRing(puff2, ring2, wind) {
1367
+ const puff3 = pool.acquire();
1368
+ puff3.x = init.x ?? 0;
1369
+ puff3.y = init.y ?? 0;
1370
+ puff3.z = init.z ?? 0;
1371
+ puff3.vx = init.vx ?? 0;
1372
+ puff3.vy = init.vy ?? 0;
1373
+ puff3.vz = init.vz ?? 0;
1374
+ puff3.s0 = init.s0 ?? 0.2;
1375
+ puff3.s1 = init.s1 ?? 1;
1376
+ puff3.sg = init.sg ?? 0.1;
1377
+ puff3.gk = init.gk ?? 6;
1378
+ puff3.life = init.life ?? 3;
1379
+ puff3.t = 0;
1380
+ puff3.h0 = init.h0 ?? 0;
1381
+ puff3.hl = init.hl ?? 0.4;
1382
+ puff3.drag = init.drag ?? 3;
1383
+ puff3.buoy = init.buoy ?? 0.5;
1384
+ puff3.grav = init.grav ?? 0;
1385
+ puff3.kind = init.kind ?? 0;
1386
+ puff3.seed = random.next();
1387
+ puff3.wind = init.wind ?? 1;
1388
+ puff3.color = init.color ?? SMOKE_COLORS.grey;
1389
+ puff3.ring = init.ring ?? null;
1390
+ puff3.ph = init.ph ?? 0;
1391
+ puff3.th = init.th ?? 0;
1392
+ puff3.rng = random;
1393
+ puff3.age = 0;
1394
+ puff3.heat = 0;
1395
+ puff3.size = 0;
1396
+ return puff3;
1397
+ }
1398
+ function ageOrKeep2(puff3, dt) {
1399
+ puff3.t += dt;
1400
+ return puff3.t < puff3.life;
1401
+ }
1402
+ function moveOnRing(puff3, ring2, wind) {
1403
1403
  const ringTime = ring2.t;
1404
1404
  const centerY = ring2.y0 + ring2.rise * (1 - Math.exp(-ringTime * ring2.rk));
1405
1405
  const majorRadius = ring2.R0 + (ring2.R1 - ring2.R0) * (1 - Math.exp(-ringTime * 1.6));
1406
- const theta = puff2.th + ringTime * ring2.roll;
1406
+ const theta = puff3.th + ringTime * ring2.roll;
1407
1407
  const minorRadius = ring2.r * (0.8 + 0.4 * Math.min(1, ringTime));
1408
1408
  const radius = majorRadius + minorRadius * Math.cos(theta);
1409
- puff2.x = ring2.x + Math.cos(puff2.ph) * radius + wind.x * ringTime * 0.6;
1410
- puff2.z = ring2.z + Math.sin(puff2.ph) * radius + wind.z * ringTime * 0.6;
1411
- puff2.y = centerY + minorRadius * Math.sin(theta);
1412
- }
1413
- function moveFree(puff2, dt, age, heat, wind) {
1414
- const damping = Math.exp(-puff2.drag * dt);
1415
- puff2.vx *= damping;
1416
- puff2.vy *= damping;
1417
- puff2.vz *= damping;
1418
- puff2.vy += (puff2.buoy * (heat > 0 ? 1.6 : 1) - puff2.grav) * dt;
1419
- puff2.x += (puff2.vx + wind.x * puff2.wind * age) * dt;
1420
- puff2.y += puff2.vy * dt;
1421
- puff2.z += (puff2.vz + wind.z * puff2.wind * age) * dt;
1422
- if (puff2.y < puff2.s0 * FLOOR_FRACTION) {
1423
- puff2.y = puff2.s0 * FLOOR_FRACTION;
1424
- if (puff2.vy < 0) puff2.vy *= FLOOR_BOUNCE;
1409
+ puff3.x = ring2.x + Math.cos(puff3.ph) * radius + wind.x * ringTime * 0.6;
1410
+ puff3.z = ring2.z + Math.sin(puff3.ph) * radius + wind.z * ringTime * 0.6;
1411
+ puff3.y = centerY + minorRadius * Math.sin(theta);
1412
+ }
1413
+ function moveFree(puff3, dt, age, heat, wind) {
1414
+ const damping = Math.exp(-puff3.drag * dt);
1415
+ puff3.vx *= damping;
1416
+ puff3.vy *= damping;
1417
+ puff3.vz *= damping;
1418
+ puff3.vy += (puff3.buoy * (heat > 0 ? 1.6 : 1) - puff3.grav) * dt;
1419
+ puff3.x += (puff3.vx + wind.x * puff3.wind * age) * dt;
1420
+ puff3.y += puff3.vy * dt;
1421
+ puff3.z += (puff3.vz + wind.z * puff3.wind * age) * dt;
1422
+ if (puff3.y < puff3.s0 * FLOOR_FRACTION) {
1423
+ puff3.y = puff3.s0 * FLOOR_FRACTION;
1424
+ if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
1425
1425
  }
1426
1426
  }
1427
1427
  function updatePuffs(pool, dt, wind) {
1428
1428
  pool.compact(ageOrKeep2, dt);
1429
- for (const puff2 of pool.items) {
1430
- const age = puff2.t / puff2.life;
1431
- const heat = puff2.h0 > 0 ? puff2.h0 * Math.pow(Math.max(0, 1 - puff2.t / puff2.hl), 1.25) : 0;
1432
- if (puff2.ring) moveOnRing(puff2, puff2.ring, wind);
1433
- else moveFree(puff2, dt, age, heat, wind);
1434
- const size = puff2.s0 + (puff2.s1 - puff2.s0) * (1 - Math.exp(-puff2.t * puff2.gk)) + puff2.sg * Math.min(puff2.t, GROWTH_CAP_SECONDS);
1435
- puff2.size = size * Math.min(1, puff2.t * ARRIVAL_FRAME + ARRIVAL_BASE);
1436
- puff2.age = age;
1437
- puff2.heat = heat;
1429
+ for (const puff3 of pool.items) {
1430
+ const age = puff3.t / puff3.life;
1431
+ const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
1432
+ if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
1433
+ else moveFree(puff3, dt, age, heat, wind);
1434
+ const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
1435
+ puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
1436
+ puff3.age = age;
1437
+ puff3.heat = heat;
1438
1438
  }
1439
1439
  }
1440
1440
  var GROUND_LIFT = 0.02;
@@ -1491,9 +1491,9 @@ var GameFluidsThree = (() => {
1491
1491
  var HOT_GLOW_COLOR = [1, 0.45, 0.12];
1492
1492
  function addPuff(core, random, init) {
1493
1493
  core.spawned.puffs++;
1494
- const puff2 = spawnPuff(core.puffs, random, init);
1495
- core.trace?.puff?.(puff2);
1496
- return puff2;
1494
+ const puff3 = spawnPuff(core.puffs, random, init);
1495
+ core.trace?.puff?.(puff3);
1496
+ return puff3;
1497
1497
  }
1498
1498
  function addSpark(core, init) {
1499
1499
  core.spawned.sparks++;
@@ -1675,6 +1675,7 @@ var GameFluidsThree = (() => {
1675
1675
  var neg = call("neg");
1676
1676
  var abs = call("abs");
1677
1677
  var sqrt = call("sqrt");
1678
+ var exp = call("exp");
1678
1679
  var sin = call("sin");
1679
1680
  var cos = call("cos");
1680
1681
  var atan2 = call("atan2");
@@ -1720,10 +1721,12 @@ var GameFluidsThree = (() => {
1720
1721
  var INDEX = { $: "index" };
1721
1722
  var QUALITY = { $: "quality" };
1722
1723
  var FRESH = { $: "fresh" };
1724
+ var DIRECTION = { $: "direction" };
1723
1725
  var CAMERA_POSITION = { $: "cameraPosition" };
1724
1726
  var burst = (system, count, spawn2) => ({ op: "burst", system, count, spawn: spawn2 });
1725
1727
  var spawn = (system, options) => ({ op: "spawn", system, spawn: options });
1726
1728
  var emit = (key, system, rate, options) => ({ op: "emit", key, system, rate, spawn: options });
1729
+ var mesh = (options) => ({ op: "mesh", spawn: options });
1727
1730
  var debris = (options) => ({ op: "debris", spawn: options });
1728
1731
  var decal = (...args) => ({ op: "decal", args });
1729
1732
  var ring = (...args) => ({ op: "ring", args });
@@ -2735,6 +2738,241 @@ var GameFluidsThree = (() => {
2735
2738
  )
2736
2739
  ]
2737
2740
  };
2741
+ var SPOTS_X = [-3, 1, 5];
2742
+ var SPOTS_Z = [-1, -2.4, 0.4];
2743
+ var FOG_AND_ASH = {
2744
+ id: "fog_and_ash",
2745
+ pageId: "neblina",
2746
+ team: "world",
2747
+ budget: { particles: 120, lights: 1 },
2748
+ loop: 0,
2749
+ view: { eye: [3, 1.8, 7.5], target: [-3, 1.2, -1] },
2750
+ setup: [
2751
+ cue("fog", { density: 0.075 }),
2752
+ cue("lamp_cone", { visible: true }),
2753
+ cue("zombie_visible", { index: -1, visible: false }),
2754
+ repeat(
2755
+ SPOTS_X.length,
2756
+ set("turn", rnd(-0.6, 0.6)),
2757
+ behavior("walkers", "place", { index: INDEX, x: pick(SPOTS_X, INDEX), z: pick(SPOTS_Z, INDEX), facing: variable("turn") })
2758
+ )
2759
+ ],
2760
+ timeline: [],
2761
+ tick: [
2762
+ 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 }),
2763
+ 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 })
2764
+ ]
2765
+ };
2766
+ var RAIN_CAPACITY = 4e3;
2767
+ var FALL_SPEED = 15;
2768
+ var WIND_PER_FALL = 0.07;
2769
+ var STREAK_SLANT = 0.04;
2770
+ var STREAK_LENGTH = 0.6;
2771
+ var BOX_X = [-15, 15];
2772
+ var BOX_Z = [-6, 16];
2773
+ var START_HEIGHT = [0, 16];
2774
+ var REENTRY_HEIGHT = [14, 17];
2775
+ var SPLASH_CHANCE = 0.35;
2776
+ var SPLASH_RANGE = 12;
2777
+ var SPRITE_CHANCE = 0.5;
2778
+ var UP9 = [0, 1, 0];
2779
+ var WORKSHOP_PUDDLES = [
2780
+ [-1.5, 2.2, 1.6, 0.9],
2781
+ [3, 3.6, 1.1, 0.7],
2782
+ [-6, 3.2, 1.3, 0.8],
2783
+ [1.4, -2.2, 0.9, 0.5]
2784
+ ];
2785
+ var inPuddle = (puddles, x, z) => puddles.some((p) => ((x - p[0]) / p[2]) ** 2 + ((z - p[1]) / p[3]) ** 2 < 1);
2786
+ var RainField = class {
2787
+ constructor(engine) {
2788
+ __publicField2(this, "engine", engine);
2789
+ __publicField2(this, "positions", new Float32Array(RAIN_CAPACITY * 6));
2790
+ __publicField2(this, "count", 0);
2791
+ __publicField2(this, "wind", 1);
2792
+ __publicField2(this, "on", false);
2793
+ __publicField2(this, "centerX", 0);
2794
+ __publicField2(this, "centerZ", 0);
2795
+ __publicField2(this, "puddles", WORKSHOP_PUDDLES);
2796
+ __publicField2(this, "puddleAt");
2797
+ }
2798
+ /** `initRain` (W321): `count` streaks (thinned by the quality), each drawn at a random place in the box. */
2799
+ init(count, wind) {
2800
+ const { random } = this.engine;
2801
+ this.count = Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult));
2802
+ this.wind = wind;
2803
+ this.on = true;
2804
+ for (let index = 0; index < this.count; index++) {
2805
+ const offset = index * 6;
2806
+ const x = random.between(BOX_X[0], BOX_X[1]);
2807
+ const y = random.between(START_HEIGHT[0], START_HEIGHT[1]);
2808
+ const z = random.between(BOX_Z[0], BOX_Z[1]);
2809
+ this.set(offset, x, y, z);
2810
+ }
2811
+ }
2812
+ /**
2813
+ * 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
2814
+ * starts at a random place of the box, as `init` draws it) and the wind slants them. The golden never calls it.
2815
+ */
2816
+ drive(count, wind, centerX, centerZ) {
2817
+ const { random } = this.engine;
2818
+ const target = Math.max(0, Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult)));
2819
+ this.wind = wind;
2820
+ this.centerX = centerX;
2821
+ this.centerZ = centerZ;
2822
+ this.on = target > 0;
2823
+ for (let index = this.count; index < target; index++) {
2824
+ 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);
2825
+ }
2826
+ this.count = target;
2827
+ }
2828
+ /** `updateRain` (W322-329). `camera` is where the eye is; `splash` lets the drops that land leave a splash. */
2829
+ update(dt, splash, camera2) {
2830
+ if (!this.on) return;
2831
+ const { random, context } = this.engine;
2832
+ const positions = this.positions;
2833
+ const fall = FALL_SPEED * dt;
2834
+ const slant = this.wind * fall * WIND_PER_FALL;
2835
+ const reach = SPLASH_RANGE / (1 + context.quality.level);
2836
+ for (let index = 0; index < this.count; index++) {
2837
+ const o = index * 6;
2838
+ positions[o] += slant;
2839
+ positions[o + 3] += slant;
2840
+ positions[o + 1] -= fall;
2841
+ positions[o + 4] -= fall;
2842
+ const ground = context.groundAt(positions[o], positions[o + 2]);
2843
+ if (positions[o + 4] < ground) {
2844
+ const x = positions[o];
2845
+ const z = positions[o + 2];
2846
+ if (splash && random.next() < SPLASH_CHANCE) {
2847
+ const distance2 = Math.hypot(x - camera2[0], z - camera2[2]);
2848
+ if (distance2 < reach) {
2849
+ if (random.next() < SPRITE_CHANCE) {
2850
+ 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 });
2851
+ }
2852
+ if (this.puddleAt ? this.puddleAt(x, z) : inPuddle(this.puddles, x, z)) {
2853
+ this.engine.ring([[x, ground + 0.012, z], 0.01, random.between(0.12, 0.25), random.between(0.4, 0.6), 6978192]);
2854
+ }
2855
+ }
2856
+ }
2857
+ const nx = random.between(BOX_X[0], BOX_X[1]) + this.centerX;
2858
+ const ny = random.between(REENTRY_HEIGHT[0], REENTRY_HEIGHT[1]);
2859
+ const nz = random.between(BOX_Z[0], BOX_Z[1]) + this.centerZ;
2860
+ this.set(o, nx, ny, nz);
2861
+ }
2862
+ }
2863
+ }
2864
+ /** `rain.visible = false; RN.on = false` (W440): the field stops; the streaks stay where they were. */
2865
+ clear() {
2866
+ this.on = false;
2867
+ }
2868
+ set(offset, x, y, z) {
2869
+ const positions = this.positions;
2870
+ positions[offset] = x;
2871
+ positions[offset + 1] = y;
2872
+ positions[offset + 2] = z;
2873
+ positions[offset + 3] = x + this.wind * STREAK_SLANT;
2874
+ positions[offset + 4] = y - STREAK_LENGTH;
2875
+ positions[offset + 5] = z;
2876
+ }
2877
+ };
2878
+ var DROPS = [900, 2200, 4e3];
2879
+ var SLANT = [0.3, 1.2, 4.5];
2880
+ var STORM = 2;
2881
+ 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)));
2882
+ var RAIN_AND_PUDDLES = {
2883
+ id: "rain_and_puddles",
2884
+ pageId: "chuva",
2885
+ team: "world",
2886
+ params: { id: "intensity", options: ["drizzle", "rain", "storm"], default: 1 },
2887
+ budget: { particles: 4e3, decals: 4 },
2888
+ loop: 0,
2889
+ view: { eye: [2, 2, 7], target: [-1, 0.6, 1] },
2890
+ setup: [
2891
+ cue("lamp_cone", { visible: true }),
2892
+ ...WORKSHOP_PUDDLES.map(([x, z, radiusX, radiusZ]) => decal(x, 0, z, radiusX * 2, 790550, { grow: 0, life: 1e6, ar: radiusZ / radiusX }))
2893
+ ],
2894
+ timeline: [
2895
+ 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])) }),
2896
+ set("nextStrike", rnd(2, 4)),
2897
+ set("flash", -1)
2898
+ ],
2899
+ tick: [
2900
+ behavior("rain", "update", { dt: DELTA, splash: gt(PARAM, 0) }),
2901
+ when(eq(PARAM, STORM), [
2902
+ when(gt(TIME, variable("nextStrike")), [set("nextStrike", add(TIME, rnd(3.5, 6))), set("flash", 0)]),
2903
+ when(ge(variable("flash"), 0), [
2904
+ set("flash", add(variable("flash"), DELTA)),
2905
+ set("light", FLASH_STEPS),
2906
+ cue("lightning", { intensity: variable("light") }),
2907
+ screen({ max: { white: mul(variable("light"), 0.12) } }),
2908
+ when(gt(variable("flash"), 0.4), [set("flash", -1), cue("lightning", { intensity: 0 })])
2909
+ ]),
2910
+ 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 })
2911
+ ])
2912
+ ]
2913
+ };
2914
+ var CAR_ORIGIN2 = [5.6, 0, 0.8];
2915
+ var CAR_ROTATION2 = [0, -0.4, 0];
2916
+ var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
2917
+ var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
2918
+ var BEAM_FROM = carPoint(0, 0.74, 2.15);
2919
+ var BEAM_DIRECTION = carDirection(BEAM_FROM, carPoint(0, 0.74, 3.15));
2920
+ var BEAM_SIDE = [BEAM_DIRECTION[2], 0, -BEAM_DIRECTION[0]];
2921
+ var WALKERS = 6;
2922
+ var WALK_SPEED2 = 0.7;
2923
+ var DROPS2 = 1800;
2924
+ var SLANT2 = 0.8;
2925
+ var startWalking = [
2926
+ set("spot", query("car", "point", rnd(-2, 2), 0, rnd(8, 18))),
2927
+ set("goal", query("car", "point", rnd(-0.6, 0.6), 0, 2.6)),
2928
+ behavior("walkers", "place", { index: INDEX, x: pick(variable("spot"), 0), z: pick(variable("spot"), 2), facing: 0 }),
2929
+ behavior("walkers", "walk", { index: INDEX, to: variable("goal"), speed: WALK_SPEED2 })
2930
+ ];
2931
+ var HEADLIGHTS_IN_RAIN = {
2932
+ id: "headlights_in_rain",
2933
+ pageId: "farol",
2934
+ team: "vehicles",
2935
+ budget: { particles: 600, lights: 2 },
2936
+ loop: 0,
2937
+ view: { eye: [-2.5, 2, 5.5], target: [4, 1, 6] },
2938
+ setup: [
2939
+ cue("fog", { density: 0.05 }),
2940
+ cue("headlights", { on: true, intensity: 2.4, color: 16773584 }),
2941
+ behavior("rain", "init", { count: DROPS2, wind: SLANT2 }),
2942
+ repeat(WALKERS, ...startWalking)
2943
+ ],
2944
+ timeline: [],
2945
+ tick: [
2946
+ behavior("rain", "update", { dt: DELTA, splash: true }),
2947
+ emit(
2948
+ "lit",
2949
+ "streak",
2950
+ 600,
2951
+ local(
2952
+ "depth",
2953
+ rnd(1, 13),
2954
+ local(
2955
+ "spread",
2956
+ mul(variable("depth"), 0.33, sqrt(rand())),
2957
+ local(
2958
+ "turn",
2959
+ rnd(0, 6.283),
2960
+ local("base", vadd(BEAM_FROM, vscale(BEAM_DIRECTION, variable("depth")), vscale(BEAM_SIDE, mul(cos(variable("turn")), variable("spread")))), {
2961
+ p: vwith(variable("base"), 1, add(vy(variable("base")), mul(sin(variable("turn")), variable("spread"), 0.6))),
2962
+ v: [0.8, -14, 0],
2963
+ sp: 0,
2964
+ life: 0.06,
2965
+ len: 0.035,
2966
+ c0: 13622512,
2967
+ a: 0.9
2968
+ })
2969
+ )
2970
+ )
2971
+ )
2972
+ ),
2973
+ repeat(WALKERS, when(not(query("walkers", "is_walking", INDEX)), startWalking))
2974
+ ]
2975
+ };
2738
2976
  var YAW = -Math.PI / 2;
2739
2977
  var COS = Math.cos(YAW);
2740
2978
  var SIN = Math.sin(YAW);
@@ -2762,10 +3000,6 @@ var GameFluidsThree = (() => {
2762
3000
  fireLight(0, [vx(variable("contact")), 0.34, vz(variable("contact"))], 1.6, mul(TIME, 3))
2763
3001
  ]
2764
3002
  };
2765
- var CAR_ORIGIN2 = [5.6, 0, 0.8];
2766
- var CAR_ROTATION2 = [0, -0.4, 0];
2767
- var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
2768
- var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
2769
3003
  var HOOD = carPoint(0, 1.02, 1.4);
2770
3004
  var EXHAUST = carPoint(0.5, 0.3, -2.2);
2771
3005
  var EXHAUST_DIRECTION = carDirection(EXHAUST, carPoint(0.5, 0.3, -3));
@@ -2799,7 +3033,7 @@ var GameFluidsThree = (() => {
2799
3033
  ], [fireLightOff(0)])
2800
3034
  ]
2801
3035
  };
2802
- var UP9 = [0, 1, 0];
3036
+ var UP10 = [0, 1, 0];
2803
3037
  var doorVelocity = (side) => {
2804
3038
  const out = carPoint(side * 4, 3, 0.3);
2805
3039
  const centre = carPoint(0, 0, 0.3);
@@ -2809,7 +3043,7 @@ var GameFluidsThree = (() => {
2809
3043
  call2("explode", vadd(query("car", "position"), [0, 0.6, 0]), 1.8, 2759188),
2810
3044
  cue("car_burnt"),
2811
3045
  behavior("car", "launch", { rise: 4.5, spin: 0.9 }),
2812
- 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 })),
3046
+ 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 })),
2813
3047
  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 }),
2814
3048
  ...[-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 })),
2815
3049
  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 })
@@ -2979,7 +3213,7 @@ var GameFluidsThree = (() => {
2979
3213
  ])
2980
3214
  ]
2981
3215
  };
2982
- var WALKERS = 6;
3216
+ var WALKERS2 = 6;
2983
3217
  var NEAR = 9;
2984
3218
  var RANGE = 7;
2985
3219
  var RESTING_BPM = 70;
@@ -2995,7 +3229,7 @@ var GameFluidsThree = (() => {
2995
3229
  setup: [
2996
3230
  cue("zombie_visible", { index: -1, visible: false }),
2997
3231
  repeat(
2998
- WALKERS,
3232
+ WALKERS2,
2999
3233
  set("startX", rnd(-4, 4)),
3000
3234
  set("startZ", sub(rnd(-4, -2), mul(INDEX, 0.6))),
3001
3235
  behavior("walkers", "place", { index: INDEX, x: variable("startX"), z: variable("startZ"), facing: 0 }),
@@ -3005,7 +3239,7 @@ var GameFluidsThree = (() => {
3005
3239
  timeline: [],
3006
3240
  tick: [
3007
3241
  set("nearest", 99),
3008
- repeat(WALKERS, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
3242
+ repeat(WALKERS2, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
3009
3243
  set("fear", clamp(div(sub(NEAR, variable("nearest")), RANGE), 0, 1)),
3010
3244
  set("bpm", add(RESTING_BPM, mul(BPM_RISE, variable("fear")))),
3011
3245
  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"))) } })]),
@@ -3072,7 +3306,7 @@ var GameFluidsThree = (() => {
3072
3306
  ]
3073
3307
  };
3074
3308
  var FACING4 = [0, 0, 1];
3075
- var UP10 = [0, 1, 0];
3309
+ var UP11 = [0, 1, 0];
3076
3310
  var ORIGIN = [0, 0, 0];
3077
3311
  var FORWARD4 = [0, 0, -1];
3078
3312
  var IMPACT_MATERIALS = ["concrete", "metal", "wood", "glass", "flesh", "water"];
@@ -3202,12 +3436,148 @@ var GameFluidsThree = (() => {
3202
3436
  team: "destruction",
3203
3437
  budget: { particles: 20, debris: 8 },
3204
3438
  loop: 2,
3205
- timeline: [call2("chips", ORIGIN, UP10, 8, 0.9), call2("rust_at", ORIGIN, 6)]
3439
+ timeline: [call2("chips", ORIGIN, UP11, 8, 0.9), call2("rust_at", ORIGIN, 6)]
3206
3440
  }
3207
3441
  ];
3208
- 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];
3442
+ var PI = Math.PI;
3443
+ var SMOKE = { color: 9079430, width: 10, height: 8, gravity: -0.08, drag: 2.2, growth: 2.1 };
3444
+ var FLASH_SECONDS = 0.055;
3445
+ var FLASH_RAMP = [0.6, 0.9];
3446
+ var FLASH_OPACITY = 0.95;
3447
+ var FLAME_COLORS = [16742946, 16760896, 13121050, 16773296];
3448
+ var FLAME_ADDITIVE = 3;
3449
+ var ORIGIN2 = [0, 0, 0];
3450
+ var SIZE = variable("size");
3451
+ var wide = (width) => mul(sub(rand(), 0.5), width);
3452
+ var puff2 = (point, velocity, world, radius, life, opacity) => mesh({
3453
+ shape: "sphere",
3454
+ p: point,
3455
+ v: velocity,
3456
+ wv: world,
3457
+ r: radius,
3458
+ seg: SMOKE.width,
3459
+ seg2: SMOKE.height,
3460
+ life,
3461
+ c0: SMOKE.color,
3462
+ a: opacity,
3463
+ fade: true,
3464
+ lit: true,
3465
+ g: SMOKE.gravity,
3466
+ drag: SMOKE.drag,
3467
+ grow: SMOKE.growth
3468
+ });
3469
+ var flameBit = (point, velocity, world, radius, life) => [
3470
+ set("bitPoint", point),
3471
+ set("bitVelocity", velocity),
3472
+ set("bitWorld", world),
3473
+ set("bitRadius", radius),
3474
+ set("bitLife", life),
3475
+ set("bitColour", floor(mul(rand(), FLAME_COLORS.length))),
3476
+ mesh({
3477
+ shape: "icosahedron",
3478
+ p: variable("bitPoint"),
3479
+ v: variable("bitVelocity"),
3480
+ wv: variable("bitWorld"),
3481
+ r: variable("bitRadius"),
3482
+ seg: 1,
3483
+ rot: [mul(rand(), 3), mul(rand(), 3), 0],
3484
+ spin: [3, 4, 2],
3485
+ life: variable("bitLife"),
3486
+ c0: pick(FLAME_COLORS, variable("bitColour")),
3487
+ add: eq(variable("bitColour"), FLAME_ADDITIVE),
3488
+ a: 0.9,
3489
+ fade: true,
3490
+ g: -1.4,
3491
+ drag: 1.4,
3492
+ grow: 2.2
3493
+ })
3494
+ ];
3495
+ 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 });
3496
+ var MUZZLE_FLASH2 = {
3497
+ id: "viewmodel_muzzle_flash",
3498
+ pageId: "event:viewmodel_muzzle_flash",
3499
+ team: "weapons",
3500
+ budget: { particles: 12, lights: 1 },
3501
+ loop: 0.1,
3502
+ timeline: [
3503
+ set("angles", []),
3504
+ repeat(4, append("angles", add(div(mul(INDEX, PI), 2), mul(rand(), 0.6)))),
3505
+ set("roll", mul(rand(), PI)),
3506
+ flash(ORIGIN2, 16756832, mul(4, SIZE), FLASH_SECONDS, mul(2, SIZE), true, true),
3507
+ flashPiece("cone_open", mul(0.013, SIZE), mul(0.085, SIZE), 10, [mul(0.042, SIZE), 0, 0], [0, 0, -PI / 2], 16757575),
3508
+ repeat(
3509
+ 4,
3510
+ set("angle", pick(variable("angles"), INDEX)),
3511
+ flashPiece(
3512
+ "cone",
3513
+ mul(5e-3, SIZE),
3514
+ mul(0.04, SIZE),
3515
+ 6,
3516
+ [mul(0.012, SIZE), mul(cos(variable("angle")), 0.016, SIZE), mul(sin(variable("angle")), 0.016, SIZE)],
3517
+ [variable("angle"), 0, -PI / 2],
3518
+ 16757575
3519
+ )
3520
+ ),
3521
+ flashPiece("sphere", mul(0.016, SIZE), void 0, 12, [mul(8e-3, SIZE), 0, 0], [0, 0, 0], 16773568),
3522
+ repeat(
3523
+ 6,
3524
+ puff2(
3525
+ ORIGIN2,
3526
+ [add(0.15, mul(rand(), 0.35)), add(0.03, mul(rand(), 0.12)), mul(sub(rand(), 0.5), 0.14)],
3527
+ [0, 0, 0],
3528
+ mul(0.013, SIZE),
3529
+ add(1.3, mul(rand(), 0.6)),
3530
+ 0.45
3531
+ )
3532
+ )
3533
+ ]
3534
+ };
3535
+ var SMOKE_PUFF = {
3536
+ id: "weapon_smoke_puff",
3537
+ pageId: "event:weapon_smoke_puff",
3538
+ team: "weapons",
3539
+ budget: { particles: 1 },
3540
+ loop: 0.1,
3541
+ timeline: [puff2(ORIGIN2, void 0, [variable("vx"), variable("vy"), variable("vz")], variable("radius"), variable("life"), variable("opacity"))]
3542
+ };
3543
+ var BACKBLAST = {
3544
+ id: "launcher_backblast",
3545
+ pageId: "event:launcher_backblast",
3546
+ team: "weapons",
3547
+ budget: { particles: 40 },
3548
+ loop: 0.1,
3549
+ timeline: [
3550
+ 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)),
3551
+ repeat(14, ...flameBit(ORIGIN2, [neg(add(2, mul(rand(), 2))), 0, 0], [wide(1), wide(0.6), wide(1)], 0.02, 0.25))
3552
+ ]
3553
+ };
3554
+ var FLAME_JET = {
3555
+ id: "flame_jet",
3556
+ pageId: "event:flame_jet",
3557
+ team: "weapons",
3558
+ budget: { particles: 30 },
3559
+ loop: 0.1,
3560
+ timeline: [
3561
+ repeat(
3562
+ floor(mul(420, variable("dt"), variable("scale"))),
3563
+ set("speed", mul(add(2.2, mul(rand(), 1.1)), variable("scale"))),
3564
+ ...flameBit(
3565
+ [mul(rand(), variable("speed"), min(variable("dt"), 0.3)), 0, 0],
3566
+ [variable("speed"), 0, 0],
3567
+ [wide(0.35), mul(sub(rand(), 0.3), 0.3), wide(0.35)],
3568
+ add(0.02, mul(rand(), 0.022)),
3569
+ add(0.4, mul(rand(), 0.35))
3570
+ )
3571
+ ),
3572
+ when(lt(rand(), 0.5), [
3573
+ 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)
3574
+ ])
3575
+ ]
3576
+ };
3577
+ var VIEWMODEL_EFFECTS = [MUZZLE_FLASH2, SMOKE_PUFF, BACKBLAST, FLAME_JET];
3578
+ 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];
3209
3579
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
3210
- var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries(EVENT_EFFECTS.map((effect) => [effect.id, effect])) };
3580
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS].map((effect) => [effect.id, effect])) };
3211
3581
  var LEHMER_MULTIPLIER = 16807;
3212
3582
  var LEHMER_MODULUS = 2147483647;
3213
3583
  var HASH_MIX = 2654435761;
@@ -3303,7 +3673,7 @@ var GameFluidsThree = (() => {
3303
3673
  }
3304
3674
  var COLUMNS = 6;
3305
3675
  var ROWS = 5;
3306
- var ORIGIN2 = [-2.2, 0, 2.6];
3676
+ var ORIGIN3 = [-2.2, 0, 2.6];
3307
3677
  var BLOCK_SIZE = [0.5, 0.44, 0.25];
3308
3678
  var OBJECT_ID_DRAWS2 = 4;
3309
3679
  var FLOOR2 = 0.13;
@@ -3346,7 +3716,7 @@ var GameFluidsThree = (() => {
3346
3716
  const chipScale = random.between(0.8, 1.3) * scale2;
3347
3717
  for (let draw = 0; draw < OBJECT_ID_DRAWS2; draw++) random.next();
3348
3718
  best.holes.push({ x: offsetX, y: offsetY, scale: scale2, chipTurn, chipScale });
3349
- impact(host.engine, "concrete", [ORIGIN2[0] + x, y, ORIGIN2[2] + 0.13], [0, 0, 1], 0.8);
3719
+ impact(host.engine, "concrete", [ORIGIN3[0] + x, y, ORIGIN3[2] + 0.13], [0, 0, 1], 0.8);
3350
3720
  } else if (action === "collapse") {
3351
3721
  const { x, y } = args;
3352
3722
  for (const block of blocks) {
@@ -3396,7 +3766,7 @@ var GameFluidsThree = (() => {
3396
3766
  }
3397
3767
  };
3398
3768
  }
3399
- var ORIGIN3 = [5.6, 0, 0.8];
3769
+ var ORIGIN4 = [5.6, 0, 0.8];
3400
3770
  var MAX_TILT = 0.25;
3401
3771
  function createCarBehavior() {
3402
3772
  let y = 0;
@@ -3428,8 +3798,13 @@ var GameFluidsThree = (() => {
3428
3798
  }
3429
3799
  }
3430
3800
  },
3431
- query(_host, name) {
3432
- if (name === "position") return [ORIGIN3[0], y, ORIGIN3[2]];
3801
+ query(_host, name, args) {
3802
+ if (name === "position") return [ORIGIN4[0], y, ORIGIN4[2]];
3803
+ if (name === "point") {
3804
+ const [x, py, z] = args;
3805
+ const point = carPoint(x, py, z);
3806
+ return [point[0], point[1] + y, point[2]];
3807
+ }
3433
3808
  if (name === "roll") return roll;
3434
3809
  throw new Error(`car: unknown query ${name}`);
3435
3810
  },
@@ -3629,6 +4004,29 @@ var GameFluidsThree = (() => {
3629
4004
  }
3630
4005
  };
3631
4006
  }
4007
+ function createRainBehavior() {
4008
+ return {
4009
+ reset() {
4010
+ },
4011
+ act(host, action, args) {
4012
+ const { engine, camera: camera2 } = host;
4013
+ if (action === "init") {
4014
+ const { count, wind } = args;
4015
+ engine.rain.init(count, wind);
4016
+ } else if (action === "update") {
4017
+ const { dt, splash } = args;
4018
+ engine.rain.update(dt, splash, camera2.position);
4019
+ }
4020
+ },
4021
+ query(host, name) {
4022
+ if (name === "count") return host.engine.rain.count;
4023
+ if (name === "on") return host.engine.rain.on;
4024
+ throw new Error(`rain: unknown query ${name}`);
4025
+ },
4026
+ update() {
4027
+ }
4028
+ };
4029
+ }
3632
4030
  var WALKER_COUNT = 10;
3633
4031
  var ARRIVAL_DISTANCE = 0.05;
3634
4032
  var SWAY_AMPLITUDE = 0.04;
@@ -3694,7 +4092,8 @@ var GameFluidsThree = (() => {
3694
4092
  blocks: () => createBlocksBehavior(),
3695
4093
  plate: () => createPlateBehavior(),
3696
4094
  cylinder: () => createCylinderBehavior(),
3697
- car: () => createCarBehavior()
4095
+ car: () => createCarBehavior(),
4096
+ rain: () => createRainBehavior()
3698
4097
  };
3699
4098
  var NOOP = () => {
3700
4099
  };
@@ -3765,6 +4164,8 @@ var GameFluidsThree = (() => {
3765
4164
  return Math.abs(asNumber(first));
3766
4165
  case "sqrt":
3767
4166
  return Math.sqrt(asNumber(first));
4167
+ case "exp":
4168
+ return Math.exp(asNumber(first));
3768
4169
  case "sin":
3769
4170
  return Math.sin(asNumber(first));
3770
4171
  case "cos":
@@ -3883,7 +4284,7 @@ var GameFluidsThree = (() => {
3883
4284
  return evaluateCall(value.$, args.map((entry) => resolve(entry, context)));
3884
4285
  }
3885
4286
  }
3886
- var UP11 = [0, 1, 0];
4287
+ var UP12 = [0, 1, 0];
3887
4288
  var ZERO = [0, 0, 0];
3888
4289
  var FULL_TURN_DRAWN = 6.283;
3889
4290
  function scaledSizes(range2, factor) {
@@ -3896,7 +4297,7 @@ var GameFluidsThree = (() => {
3896
4297
  p: centre,
3897
4298
  r: radius,
3898
4299
  flat: true,
3899
- dir: options.dir ?? UP11,
4300
+ dir: options.dir ?? UP12,
3900
4301
  cone: 0.35,
3901
4302
  sp: [0.4, 1.4],
3902
4303
  v: wind,
@@ -3913,7 +4314,7 @@ var GameFluidsThree = (() => {
3913
4314
  p: centre,
3914
4315
  r: radius * 0.6,
3915
4316
  flat: true,
3916
- dir: UP11,
4317
+ dir: UP12,
3917
4318
  cone: 0.3,
3918
4319
  sp: [0.3, 0.8],
3919
4320
  life: [0.2, 0.4],
@@ -3928,14 +4329,14 @@ var GameFluidsThree = (() => {
3928
4329
  if (engine.quality < 2) {
3929
4330
  engine.emit(`${key}e`, "streak", 10 * factor, dt, () => {
3930
4331
  const jitter = plus(wind, engine.random.between(-0.4, 0.4), 0, engine.random.between(-0.4, 0.4));
3931
- 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 };
4332
+ 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 };
3932
4333
  });
3933
4334
  }
3934
4335
  if (options.noSmoke) return;
3935
4336
  engine.emit(`${key}s`, "normal", (options.smoke || 14) * factor, dt, () => ({
3936
4337
  p: plus(centre, 0, 0.8 * sqrtFactor, 0),
3937
4338
  r: radius * 0.7,
3938
- dir: UP11,
4339
+ dir: UP12,
3939
4340
  cone: 0.35,
3940
4341
  sp: [0.6, 1.4],
3941
4342
  v: wind,
@@ -3975,15 +4376,15 @@ var GameFluidsThree = (() => {
3975
4376
  engine.screen.shake += 0.7 * scale2;
3976
4377
  engine.screen.white += 0.18 * scale2;
3977
4378
  engine.burst("add", 55 * scale2, { p: point, r: 0.3 * scale2, sp: [2, 7 * scale2], drag: 4, life: [0.25, 0.6], size: [0.7 * scale2, 1.4 * scale2], grow: 1.7, c0: 16773296, c1: 10103304, fi: 0.04 });
3978
- engine.burst("add", 28 * scale2, { p: plus(point, 0, 0.3, 0), r: 0.3, dir: UP11, cone: 0.7, sp: [2, 5 * scale2], drag: 1.5, life: [0.6, 1.1], size: [0.6 * scale2, 1 * scale2], grow: 1.3, c0: 16760928, c1: 5902853, g: -2, fi: 0.05 });
4379
+ engine.burst("add", 28 * scale2, { p: plus(point, 0, 0.3, 0), r: 0.3, dir: UP12, cone: 0.7, sp: [2, 5 * scale2], drag: 1.5, life: [0.6, 1.1], size: [0.6 * scale2, 1 * scale2], grow: 1.3, c0: 16760928, c1: 5902853, g: -2, fi: 0.05 });
3979
4380
  engine.ring([plus(point, 0, 0.05, 0), 0.3, 7 * scale2, 0.55, 16767136]);
3980
4381
  const toCamera = normalized([cameraPosition[0] - point[0], cameraPosition[1] - point[1], cameraPosition[2] - point[2]]);
3981
4382
  engine.ring([plus(point, 0, 1, 0), 0.3, 5 * scale2, 0.35, 16773328, toCamera]);
3982
- engine.burst("normal", 32 * scale2, { p: plus(point, 0, 0.6, 0), r: 0.8 * scale2, dir: UP11, cone: 1.1, sp: [1, 3], drag: 1.2, life: [3, 6], size: [1 * scale2, 1.8 * scale2], grow: 2.6, c0: 2828068, c1: 987155, a: 0.6, fi: 0.18, g: -0.35 });
4383
+ engine.burst("normal", 32 * scale2, { p: plus(point, 0, 0.6, 0), r: 0.8 * scale2, dir: UP12, cone: 1.1, sp: [1, 3], drag: 1.2, life: [3, 6], size: [1 * scale2, 1.8 * scale2], grow: 2.6, c0: 2828068, c1: 987155, a: 0.6, fi: 0.18, g: -0.35 });
3983
4384
  ringOfDust(engine, point, scale2);
3984
- if (engine.quality < 2) engine.burst("streak", 44 * scale2, { 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 });
4385
+ if (engine.quality < 2) engine.burst("streak", 44 * scale2, { 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 });
3985
4386
  for (let index = 0; index < engine.scaled(16 * scale2); index++) {
3986
- 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 });
4387
+ 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 });
3987
4388
  }
3988
4389
  engine.decal([point[0], engine.context.groundAt(point[0], point[2]), point[2], 3 * scale2, 657930, { grow: 0.15, life: 60 }]);
3989
4390
  }
@@ -4126,6 +4527,7 @@ var GameFluidsThree = (() => {
4126
4527
  if (!def) return;
4127
4528
  this.schedule.clear();
4128
4529
  this.variables.clear();
4530
+ for (const [name, value] of Object.entries(this.setup.values ?? {})) this.variables.set(name, value);
4129
4531
  for (const behavior2 of this.deps.behaviors()) behavior2.reset(this.host);
4130
4532
  this.time = 0;
4131
4533
  this.within(() => {
@@ -4184,6 +4586,10 @@ var GameFluidsThree = (() => {
4184
4586
  return engine.spawn(step.system, this.value(step.spawn));
4185
4587
  case "emit":
4186
4588
  return engine.emit(step.key, step.system, this.value(step.rate), this.dt, () => this.value(step.spawn));
4589
+ case "mesh": {
4590
+ engine.mesh(this.value(step.spawn));
4591
+ return;
4592
+ }
4187
4593
  case "debris":
4188
4594
  return engine.debrisPiece(this.value(step.spawn));
4189
4595
  case "decal":
@@ -4265,7 +4671,7 @@ var GameFluidsThree = (() => {
4265
4671
  this.index = previous;
4266
4672
  }
4267
4673
  };
4268
- var ORIGIN4 = [0, 0, 0];
4674
+ var ORIGIN5 = [0, 0, 0];
4269
4675
  var DEFAULT_HEADING = [0, 0, -1];
4270
4676
  var DEFAULT_MAX_INSTANCES = 48;
4271
4677
  var ENDLESS_SECONDS = 60;
@@ -4310,7 +4716,7 @@ var GameFluidsThree = (() => {
4310
4716
  }
4311
4717
  function placementOf(entry) {
4312
4718
  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;
4313
- return { origin, anchor: entry.anchor, yaw: entry.yaw };
4719
+ return { origin, anchor: entry.anchor, yaw: entry.yaw, ...entry.orientation ? { orientation: entry.orientation } : {} };
4314
4720
  }
4315
4721
  function drawing(entry, action) {
4316
4722
  const context = engine.context;
@@ -4334,8 +4740,9 @@ var GameFluidsThree = (() => {
4334
4740
  random: playOptions.seed !== void 0 ? streamOf(playOptions.seed) : void 0,
4335
4741
  follow: playOptions.follow,
4336
4742
  origin: point,
4337
- anchor: def.anchor ?? ORIGIN4,
4743
+ anchor: def.anchor ?? ORIGIN5,
4338
4744
  yaw: yawOf(heading, playOptions.direction),
4745
+ orientation: playOptions.orientation,
4339
4746
  seconds: playOptions.seconds ?? (def.loop && def.loop > 0 ? def.loop : ENDLESS_SECONDS)
4340
4747
  };
4341
4748
  playing.push(entry);
@@ -4344,7 +4751,8 @@ var GameFluidsThree = (() => {
4344
4751
  () => instance.begin(def, paramIndex(def, playOptions.param), {
4345
4752
  placement: placementOf(entry),
4346
4753
  ...playOptions.direction ? { direction: playOptions.direction } : {},
4347
- ...playOptions.normal ? { normal: playOptions.normal } : {}
4754
+ ...playOptions.normal ? { normal: playOptions.normal } : {},
4755
+ ...playOptions.values ? { values: playOptions.values } : {}
4348
4756
  })
4349
4757
  );
4350
4758
  return entry.handle;
@@ -4604,15 +5012,15 @@ var GameFluidsThree = (() => {
4604
5012
  return shock;
4605
5013
  }
4606
5014
  function pushPuffs(puffs, shock, reach, dt, age) {
4607
- for (const puff2 of puffs) {
4608
- if (puff2.ring || puff2.t > PUSH_AGE_LIMIT) continue;
4609
- const dx = puff2.x - shock.x;
4610
- const dz = puff2.z - shock.z;
5015
+ for (const puff3 of puffs) {
5016
+ if (puff3.ring || puff3.t > PUSH_AGE_LIMIT) continue;
5017
+ const dx = puff3.x - shock.x;
5018
+ const dz = puff3.z - shock.z;
4611
5019
  const distance2 = Math.hypot(dx, dz) + 1e-3;
4612
- if (Math.abs(distance2 - reach) < PUSH_BAND && puff2.y < PUSH_CEILING) {
5020
+ if (Math.abs(distance2 - reach) < PUSH_BAND && puff3.y < PUSH_CEILING) {
4613
5021
  const strength = 4 * dt * (1 - age);
4614
- puff2.vx += dx / distance2 * strength * 6;
4615
- puff2.vz += dz / distance2 * strength * 6;
5022
+ puff3.vx += dx / distance2 * strength * 6;
5023
+ puff3.vz += dz / distance2 * strength * 6;
4616
5024
  }
4617
5025
  }
4618
5026
  }
@@ -4912,10 +5320,10 @@ var GameFluidsThree = (() => {
4912
5320
  const life = random.between(ring2.life * 0.75, ring2.life);
4913
5321
  const h0 = random.between(1, 1.25);
4914
5322
  const hl = random.between(0.9, 1.6);
4915
- 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 });
4916
- puff2.ring = ring2;
4917
- puff2.ph = random.next() * Math.PI * 2;
4918
- puff2.th = random.next() * Math.PI * 2;
5323
+ 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 });
5324
+ puff3.ring = ring2;
5325
+ puff3.ph = random.next() * Math.PI * 2;
5326
+ puff3.th = random.next() * Math.PI * 2;
4919
5327
  }
4920
5328
  const stemCount = Math.round((spec.stem || 18) * Math.max(lod, 0.4));
4921
5329
  for (let index = 0; index < stemCount; index++) {
@@ -5716,7 +6124,7 @@ var GameFluidsThree = (() => {
5716
6124
  };
5717
6125
  var TWO_PI_ROUNDED = 6.2832;
5718
6126
  var WIDE_CONE = 3.1;
5719
- var UP12 = [0, 1, 0];
6127
+ var UP13 = [0, 1, 0];
5720
6128
  function drawRange(random, range2) {
5721
6129
  if (Array.isArray(range2)) return random.between(range2[0], range2[1]);
5722
6130
  return range2 || 0;
@@ -5744,7 +6152,7 @@ var GameFluidsThree = (() => {
5744
6152
  }
5745
6153
  function coneDirection(random, direction, cone, out) {
5746
6154
  if (cone >= WIDE_CONE) return randomUnit(random, out);
5747
- const [dx, dy, dz] = direction ?? UP12;
6155
+ const [dx, dy, dz] = direction ?? UP13;
5748
6156
  const cosTilt = 1 - random.next() * (1 - Math.cos(cone));
5749
6157
  const sinTilt = Math.sqrt(1 - cosTilt * cosTilt);
5750
6158
  const phase = random.next() * TWO_PI_ROUNDED;
@@ -5864,12 +6272,12 @@ var GameFluidsThree = (() => {
5864
6272
  }
5865
6273
  };
5866
6274
  var CAPACITY2 = 400;
5867
- var UP13 = [0, 1, 0];
6275
+ var UP14 = [0, 1, 0];
5868
6276
  var DEFAULT_LIFE2 = 60;
5869
6277
  var DEFAULT_GROW = 1.2;
5870
6278
  var EVAPORATION = { fuel: [6, 5], water: [40, 20] };
5871
6279
  function createDecal() {
5872
- 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 };
6280
+ 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 };
5873
6281
  }
5874
6282
  var DecalPool = class {
5875
6283
  constructor(context) {
@@ -5882,7 +6290,7 @@ var GameFluidsThree = (() => {
5882
6290
  const random = this.context.random;
5883
6291
  const index = this.next++ % CAPACITY2;
5884
6292
  const decal2 = this.decals[index];
5885
- const normal = options.n ?? UP13;
6293
+ const normal = options.n ?? UP14;
5886
6294
  random.between(0, 6.28);
5887
6295
  const offset = 4e-3 + index % 40 * 4e-4;
5888
6296
  decal2.on = true;
@@ -5923,6 +6331,94 @@ var GameFluidsThree = (() => {
5923
6331
  }
5924
6332
  }
5925
6333
  };
6334
+ var CAPACITY3 = 400;
6335
+ var IDENTITY = [0, 0, 0, 1];
6336
+ function multiply(a, b) {
6337
+ const [ax, ay, az, aw] = a;
6338
+ const [bx, by, bz, bw] = b;
6339
+ 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];
6340
+ }
6341
+ var aboutX = (angle) => [Math.sin(angle / 2), 0, 0, Math.cos(angle / 2)];
6342
+ var MeshPool = class {
6343
+ constructor(capacity = CAPACITY3) {
6344
+ __publicField2(this, "capacity", capacity);
6345
+ __publicField2(this, "particles", []);
6346
+ __publicField2(this, "serial", 0);
6347
+ }
6348
+ get count() {
6349
+ return this.particles.length;
6350
+ }
6351
+ spawn(options, frame = IDENTITY) {
6352
+ const velocity = options.v ?? [0, 0, 0];
6353
+ const world = options.wv ?? [0, 0, 0];
6354
+ const particle = {
6355
+ id: ++this.serial,
6356
+ shape: options.shape,
6357
+ radius: options.r,
6358
+ height: options.h ?? 0,
6359
+ segments: options.seg ?? 0,
6360
+ segments2: options.seg2 ?? 0,
6361
+ color: options.c0,
6362
+ additive: options.add === true,
6363
+ lit: options.lit === true,
6364
+ x: options.p[0],
6365
+ y: options.p[1],
6366
+ z: options.p[2],
6367
+ frame: options.roll ? multiply(frame, aboutX(options.roll)) : frame,
6368
+ at: options.at ?? [0, 0, 0],
6369
+ rx: options.rot?.[0] ?? 0,
6370
+ ry: options.rot?.[1] ?? 0,
6371
+ rz: options.rot?.[2] ?? 0,
6372
+ scale: 1,
6373
+ opacity: options.a,
6374
+ age: 0,
6375
+ life: options.life,
6376
+ vx: velocity[0] + world[0],
6377
+ vy: velocity[1] + world[1],
6378
+ vz: velocity[2] + world[2],
6379
+ spin: options.spin ?? [0, 0, 0],
6380
+ gravity: options.g ?? 0,
6381
+ drag: options.drag ?? 0,
6382
+ grow: options.grow ?? 0,
6383
+ ramp: options.ramp,
6384
+ fade: options.fade === true,
6385
+ startOpacity: options.a
6386
+ };
6387
+ this.particles.push(particle);
6388
+ if (this.particles.length > this.capacity) this.particles.shift();
6389
+ return particle;
6390
+ }
6391
+ update(dt) {
6392
+ const live = this.particles;
6393
+ for (let index = 0; index < live.length; index++) {
6394
+ const particle = live[index];
6395
+ particle.age += dt;
6396
+ if (particle.ramp ? particle.age >= particle.life : particle.age > particle.life) {
6397
+ live.splice(index--, 1);
6398
+ continue;
6399
+ }
6400
+ if (particle.fade) particle.opacity = Math.max(0, particle.startOpacity * (1 - particle.age / particle.life));
6401
+ particle.vy -= particle.gravity * dt;
6402
+ if (particle.drag) {
6403
+ const keep = Math.max(0, 1 - particle.drag * dt);
6404
+ particle.vx *= keep;
6405
+ particle.vy *= keep;
6406
+ particle.vz *= keep;
6407
+ }
6408
+ particle.x += particle.vx * dt;
6409
+ particle.y += particle.vy * dt;
6410
+ particle.z += particle.vz * dt;
6411
+ particle.rx += particle.spin[0] * dt;
6412
+ particle.ry += particle.spin[1] * dt;
6413
+ particle.rz += particle.spin[2] * dt;
6414
+ if (particle.grow) particle.scale *= 1 + particle.grow * dt;
6415
+ if (particle.ramp) particle.scale = particle.ramp[0] + Math.min(1, particle.age / particle.life) * particle.ramp[1];
6416
+ }
6417
+ }
6418
+ clear() {
6419
+ this.particles.length = 0;
6420
+ }
6421
+ };
5926
6422
  var DEFAULT_FADE_IN = 0.1;
5927
6423
  var FLOATS_PER_VECTOR = 3;
5928
6424
  var BYTE_MAX2 = 255;
@@ -6212,11 +6708,11 @@ var GameFluidsThree = (() => {
6212
6708
  };
6213
6709
  var RING_CAPACITY = 120;
6214
6710
  var LIGHT_CAPACITY = 4;
6215
- var UP14 = [0, 1, 0];
6711
+ var UP15 = [0, 1, 0];
6216
6712
  var DEFAULT_LIGHT_DISTANCE = 14;
6217
6713
  var WaveRingPool = class {
6218
6714
  constructor() {
6219
- __publicField2(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 })));
6715
+ __publicField2(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 })));
6220
6716
  __publicField2(this, "count", 0);
6221
6717
  __publicField2(this, "next", 0);
6222
6718
  }
@@ -6229,7 +6725,7 @@ var GameFluidsThree = (() => {
6229
6725
  ring2.t = 0;
6230
6726
  ring2.duration = duration;
6231
6727
  ring2.color = color;
6232
- ring2.normal = normal ?? UP14;
6728
+ ring2.normal = normal ?? UP15;
6233
6729
  ring2.hold = hold || 0;
6234
6730
  }
6235
6731
  clear() {
@@ -6249,7 +6745,7 @@ var GameFluidsThree = (() => {
6249
6745
  var FlashLightPool = class {
6250
6746
  constructor(context) {
6251
6747
  __publicField2(this, "context", context);
6252
- __publicField2(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 })));
6748
+ __publicField2(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 })));
6253
6749
  __publicField2(this, "fire", [
6254
6750
  { x: 0, y: 0, z: 0, intensity: 0 },
6255
6751
  { x: 0, y: 0, z: 0, intensity: 0 }
@@ -6259,7 +6755,7 @@ var GameFluidsThree = (() => {
6259
6755
  get lit() {
6260
6756
  return this.lights.filter((light) => light.intensity > 0).length;
6261
6757
  }
6262
- flash(point, color, intensity, duration, distance2, force) {
6758
+ flash(point, color, intensity, duration, distance2, force, steady) {
6263
6759
  const { level } = this.context.quality;
6264
6760
  if (level >= 1 && !force) return;
6265
6761
  if (level >= 2 && force !== "boom") return;
@@ -6272,6 +6768,7 @@ var GameFluidsThree = (() => {
6272
6768
  light.peak = intensity;
6273
6769
  light.duration = duration;
6274
6770
  light.t = 0;
6771
+ light.steady = steady === true;
6275
6772
  light.intensity = intensity;
6276
6773
  }
6277
6774
  clear() {
@@ -6283,7 +6780,7 @@ var GameFluidsThree = (() => {
6283
6780
  if (light.intensity <= 0) continue;
6284
6781
  light.t += dt;
6285
6782
  const age = light.t / light.duration;
6286
- light.intensity = age >= 1 ? 0 : light.peak * (1 - age) * (1 - age);
6783
+ light.intensity = age >= 1 ? 0 : light.steady ? light.peak : light.peak * (1 - age) * (1 - age);
6287
6784
  }
6288
6785
  }
6289
6786
  };
@@ -6291,6 +6788,14 @@ var GameFluidsThree = (() => {
6291
6788
  var FIRE_FLICKER_SWAY = 0.25;
6292
6789
  var FIRE_FLICKER_JITTER = 0.08;
6293
6790
  var LENS_FADE = 0.16;
6791
+ function rotate(q, v) {
6792
+ const [qx, qy, qz, qw] = q;
6793
+ const ix = qw * v[0] + qy * v[2] - qz * v[1];
6794
+ const iy = qw * v[1] + qz * v[0] - qx * v[2];
6795
+ const iz = qw * v[2] + qx * v[1] - qy * v[0];
6796
+ const iw = -qx * v[0] - qy * v[1] - qz * v[2];
6797
+ 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];
6798
+ }
6294
6799
  function workshopGround(x, z) {
6295
6800
  return z < -2.9 && z > -4.5 && Math.abs(x) < 13 ? 0.12 : 0;
6296
6801
  }
@@ -6304,6 +6809,8 @@ var GameFluidsThree = (() => {
6304
6809
  __publicField2(this, "decals");
6305
6810
  __publicField2(this, "rings", new WaveRingPool());
6306
6811
  __publicField2(this, "lights");
6812
+ __publicField2(this, "rain", new RainField(this));
6813
+ __publicField2(this, "meshes", new MeshPool());
6307
6814
  __publicField2(this, "screen", createScreenState());
6308
6815
  __publicField2(this, "lens", []);
6309
6816
  __publicField2(this, "post", {});
@@ -6336,7 +6843,11 @@ var GameFluidsThree = (() => {
6336
6843
  place(point) {
6337
6844
  const placement = this.placement;
6338
6845
  if (!placement) return point;
6339
- const { anchor, origin, yaw } = placement;
6846
+ const { anchor, origin, yaw, orientation } = placement;
6847
+ if (orientation) {
6848
+ const turned = rotate(orientation, [point[0] - anchor[0], point[1] - anchor[1], point[2] - anchor[2]]);
6849
+ return [origin[0] + turned[0], origin[1] + turned[1], origin[2] + turned[2]];
6850
+ }
6340
6851
  const x = point[0] - anchor[0];
6341
6852
  const z = point[2] - anchor[2];
6342
6853
  const cos2 = Math.cos(yaw);
@@ -6347,6 +6858,7 @@ var GameFluidsThree = (() => {
6347
6858
  turn(vector) {
6348
6859
  const placement = this.placement;
6349
6860
  if (!placement) return vector;
6861
+ if (placement.orientation) return rotate(placement.orientation, vector);
6350
6862
  const cos2 = Math.cos(placement.yaw);
6351
6863
  const sin2 = Math.sin(placement.yaw);
6352
6864
  return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
@@ -6375,6 +6887,14 @@ var GameFluidsThree = (() => {
6375
6887
  }
6376
6888
  this.accumulators.set(key, carried);
6377
6889
  }
6890
+ /** 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. */
6891
+ mesh(recipe) {
6892
+ const placement = this.placement;
6893
+ const options = placement ? { ...recipe, p: this.place(recipe.p), ...recipe.v ? { v: this.turn(recipe.v) } : {} } : recipe;
6894
+ this.context.trace?.("mesh", [options]);
6895
+ const grouped = recipe.at !== void 0 || recipe.roll !== void 0;
6896
+ return this.meshes.spawn(options, placement && grouped ? placement.orientation ?? [0, Math.sin(placement.yaw / 2), 0, Math.cos(placement.yaw / 2)] : void 0);
6897
+ }
6378
6898
  debrisPiece(recipe) {
6379
6899
  const options = this.placed(recipe);
6380
6900
  this.context.trace?.("debris", [options]);
@@ -6395,10 +6915,11 @@ var GameFluidsThree = (() => {
6395
6915
  this.context.trace?.("ring", args);
6396
6916
  this.rings.add(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6397
6917
  }
6918
+ /** `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. */
6398
6919
  flash(recipe) {
6399
- const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5]] : recipe;
6400
- this.context.trace?.("flash", args);
6401
- this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5]);
6920
+ const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5], recipe[6]] : recipe;
6921
+ this.context.trace?.("flash", args.slice(0, 6));
6922
+ this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6402
6923
  }
6403
6924
  /** `fireLight` (W400): sways with the clock and flickers with one draw. */
6404
6925
  fireLight(slot, recipe, intensity, time) {
@@ -6423,6 +6944,7 @@ var GameFluidsThree = (() => {
6423
6944
  this.debris.update(dt);
6424
6945
  this.decals.update(dt);
6425
6946
  this.rings.update(dt);
6947
+ this.meshes.update(dt);
6426
6948
  this.lights.update(dt);
6427
6949
  updateScreen(this.screen, dt);
6428
6950
  for (let index = this.lens.length - 1; index >= 0; index--) {
@@ -6439,7 +6961,9 @@ var GameFluidsThree = (() => {
6439
6961
  this.debris.clear();
6440
6962
  this.decals.clear();
6441
6963
  this.rings.clear();
6964
+ this.meshes.clear();
6442
6965
  this.lights.clear();
6966
+ this.rain.clear();
6443
6967
  this.accumulators.clear();
6444
6968
  resetScreenState(this.screen);
6445
6969
  this.lens.length = 0;
@@ -6585,16 +7109,16 @@ var GameFluidsThree = (() => {
6585
7109
  },
6586
7110
  push(point, radius, speed) {
6587
7111
  spriteSystem.push(point, radius, speed);
6588
- for (const puff2 of core.puffs.items) {
6589
- const dx = puff2.x - point[0];
6590
- const dy = puff2.y - point[1];
6591
- const dz = puff2.z - point[2];
7112
+ for (const puff3 of core.puffs.items) {
7113
+ const dx = puff3.x - point[0];
7114
+ const dy = puff3.y - point[1];
7115
+ const dz = puff3.z - point[2];
6592
7116
  const distance2 = Math.sqrt(dx * dx + dy * dy + dz * dz);
6593
7117
  if (distance2 >= radius) continue;
6594
7118
  const strength = speed * (1 - distance2 / radius) / (distance2 + 1e-3);
6595
- puff2.vx += dx * strength;
6596
- puff2.vy += dy * strength;
6597
- puff2.vz += dz * strength;
7119
+ puff3.vx += dx * strength;
7120
+ puff3.vy += dy * strength;
7121
+ puff3.vz += dz * strength;
6598
7122
  }
6599
7123
  },
6600
7124
  stop(handle) {
@@ -6611,6 +7135,12 @@ var GameFluidsThree = (() => {
6611
7135
  camera2.y = position[1];
6612
7136
  camera2.z = position[2];
6613
7137
  },
7138
+ setWind(next) {
7139
+ wind.x = next[0];
7140
+ wind.y = next[1];
7141
+ wind.z = next[2];
7142
+ },
7143
+ wind: () => [wind.x, wind.y, wind.z],
6614
7144
  forceLod(scale2) {
6615
7145
  core.clock.forcedLod = scale2;
6616
7146
  },
@@ -6792,11 +7322,11 @@ void main(){
6792
7322
  geometry.setAttribute("aC", attributes[2]);
6793
7323
  geometry.instanceCount = 0;
6794
7324
  const material = new THREE.ShaderMaterial({ uniforms, vertexShader: PUFF_VERTEX, fragmentShader: PUFF_FRAGMENT });
6795
- const mesh = new THREE.Mesh(geometry, material);
6796
- mesh.name = "vfx-puffs";
6797
- mesh.frustumCulled = false;
7325
+ const mesh2 = new THREE.Mesh(geometry, material);
7326
+ mesh2.name = "vfx-puffs";
7327
+ mesh2.frustumCulled = false;
6798
7328
  return {
6799
- mesh,
7329
+ mesh: mesh2,
6800
7330
  sync() {
6801
7331
  let n = 0;
6802
7332
  for (const p of pool.items) {
@@ -6837,12 +7367,12 @@ void main(){
6837
7367
  geometry.setAttribute("aE", attributes[2]);
6838
7368
  geometry.instanceCount = 0;
6839
7369
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: SPARK_VERTEX, fragmentShader: SPARK_FRAGMENT });
6840
- const mesh = new THREE.Mesh(geometry, material);
6841
- mesh.name = "vfx-sparks";
6842
- mesh.frustumCulled = false;
6843
- mesh.renderOrder = 5;
7370
+ const mesh2 = new THREE.Mesh(geometry, material);
7371
+ mesh2.name = "vfx-sparks";
7372
+ mesh2.frustumCulled = false;
7373
+ mesh2.renderOrder = 5;
6844
7374
  return {
6845
- mesh,
7375
+ mesh: mesh2,
6846
7376
  sync() {
6847
7377
  let n = 0;
6848
7378
  for (const s of pool.items) {
@@ -6880,12 +7410,12 @@ void main(){
6880
7410
  geometry.setAttribute("aC", attributes[1]);
6881
7411
  geometry.instanceCount = 0;
6882
7412
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: GLOW_VERTEX, fragmentShader: GLOW_FRAGMENT });
6883
- const mesh = new THREE.Mesh(geometry, material);
6884
- mesh.name = "vfx-glows";
6885
- mesh.frustumCulled = false;
6886
- mesh.renderOrder = 6;
7413
+ const mesh2 = new THREE.Mesh(geometry, material);
7414
+ mesh2.name = "vfx-glows";
7415
+ mesh2.frustumCulled = false;
7416
+ mesh2.renderOrder = 6;
6887
7417
  return {
6888
- mesh,
7418
+ mesh: mesh2,
6889
7419
  sync() {
6890
7420
  let n = 0;
6891
7421
  for (const g of pool.items) {
@@ -6914,14 +7444,14 @@ void main(){
6914
7444
  const rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.85, flatShading: true });
6915
7445
  const plateMaterial = rockMaterial.clone();
6916
7446
  const make = (geometry, material) => {
6917
- const mesh = new THREE.InstancedMesh(geometry, material, capacity);
6918
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
6919
- mesh.castShadow = true;
6920
- mesh.receiveShadow = true;
6921
- mesh.frustumCulled = false;
6922
- mesh.count = 0;
6923
- mesh.setColorAt(0, new THREE.Color(1, 1, 1));
6924
- return mesh;
7447
+ const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
7448
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
7449
+ mesh2.castShadow = true;
7450
+ mesh2.receiveShadow = true;
7451
+ mesh2.frustumCulled = false;
7452
+ mesh2.count = 0;
7453
+ mesh2.setColorAt(0, new THREE.Color(1, 1, 1));
7454
+ return mesh2;
6925
7455
  };
6926
7456
  const rocks = make(shapes.rock, rockMaterial);
6927
7457
  const plates = make(shapes.plate, plateMaterial);
@@ -6940,20 +7470,20 @@ void main(){
6940
7470
  euler.set(chunk.rx, chunk.ry, chunk.rz);
6941
7471
  quaternion.setFromEuler(euler);
6942
7472
  matrix.compose(position.set(chunk.x, chunk.y, chunk.z), quaternion, scale2.set(chunk.sx, chunk.sy, chunk.sz));
6943
- const mesh = chunk.plate ? plates : rocks;
7473
+ const mesh2 = chunk.plate ? plates : rocks;
6944
7474
  const index = chunk.plate ? plateCount++ : rockCount++;
6945
- mesh.setMatrixAt(index, matrix);
7475
+ mesh2.setMatrixAt(index, matrix);
6946
7476
  const color = colorOf(chunk);
6947
- const colors = mesh.instanceColor.array;
7477
+ const colors = mesh2.instanceColor.array;
6948
7478
  colors[index * 3] = color[0];
6949
7479
  colors[index * 3 + 1] = color[1];
6950
7480
  colors[index * 3 + 2] = color[2];
6951
7481
  }
6952
7482
  rocks.count = rockCount;
6953
7483
  plates.count = plateCount;
6954
- for (const mesh of [rocks, plates]) {
6955
- mesh.instanceMatrix.needsUpdate = true;
6956
- mesh.instanceColor.needsUpdate = true;
7484
+ for (const mesh2 of [rocks, plates]) {
7485
+ mesh2.instanceMatrix.needsUpdate = true;
7486
+ mesh2.instanceColor.needsUpdate = true;
6957
7487
  }
6958
7488
  return rockCount + plateCount;
6959
7489
  },
@@ -7137,28 +7667,28 @@ void main(){
7137
7667
  position.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
7138
7668
  }
7139
7669
  geometry.computeVertexNormals();
7140
- const mesh = new THREE.Mesh(geometry, material);
7141
- mesh.receiveShadow = true;
7142
- return mesh;
7670
+ const mesh2 = new THREE.Mesh(geometry, material);
7671
+ mesh2.receiveShadow = true;
7672
+ return mesh2;
7143
7673
  }
7144
7674
  function addRock(mark) {
7145
- const mesh = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
7146
- mesh.position.set(mark.x, mark.y, mark.z);
7147
- mesh.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
7148
- mesh.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
7149
- mesh.castShadow = true;
7150
- mesh.receiveShadow = true;
7151
- return mesh;
7675
+ const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
7676
+ mesh2.position.set(mark.x, mark.y, mark.z);
7677
+ mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
7678
+ mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
7679
+ mesh2.castShadow = true;
7680
+ mesh2.receiveShadow = true;
7681
+ return mesh2;
7152
7682
  }
7153
7683
  function addBlood(mark) {
7154
- const mesh = new THREE.Mesh(bloodGeometry, bloodMaterial);
7155
- mesh.position.set(mark.x, mark.y, mark.z);
7156
- mesh.scale.set(mark.scaleX, 1, mark.scaleZ);
7157
- mesh.rotation.y = mark.rotation;
7158
- mesh.receiveShadow = true;
7159
- mesh.scale.multiplyScalar(0.05);
7160
- blood.push({ mesh, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
7161
- return mesh;
7684
+ const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
7685
+ mesh2.position.set(mark.x, mark.y, mark.z);
7686
+ mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
7687
+ mesh2.rotation.y = mark.rotation;
7688
+ mesh2.receiveShadow = true;
7689
+ mesh2.scale.multiplyScalar(0.05);
7690
+ blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
7691
+ return mesh2;
7162
7692
  }
7163
7693
  function build(mark) {
7164
7694
  if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
@@ -7369,6 +7899,73 @@ void main() {
7369
7899
  }
7370
7900
  };
7371
7901
  }
7902
+ var SPHERE_DEFAULT_WIDTH = 8;
7903
+ var SPHERE_DEFAULT_HEIGHT = 6;
7904
+ function geometryOf(THREE, particle) {
7905
+ switch (particle.shape) {
7906
+ case "cone":
7907
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
7908
+ case "cone_open":
7909
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
7910
+ case "sphere":
7911
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
7912
+ case "icosahedron":
7913
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
7914
+ }
7915
+ }
7916
+ function materialOf(THREE, particle) {
7917
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
7918
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
7919
+ }
7920
+ function createMeshLayer(THREE, pool) {
7921
+ const group = new THREE.Group();
7922
+ group.name = "workshop-meshes";
7923
+ const drawn = /* @__PURE__ */ new Map();
7924
+ const frame = new THREE.Quaternion();
7925
+ const euler = new THREE.Euler();
7926
+ const turn = new THREE.Quaternion();
7927
+ const offset = new THREE.Vector3();
7928
+ function release(mesh2) {
7929
+ mesh2.removeFromParent();
7930
+ mesh2.geometry.dispose();
7931
+ mesh2.material.dispose();
7932
+ }
7933
+ return {
7934
+ group,
7935
+ sync() {
7936
+ const live = /* @__PURE__ */ new Set();
7937
+ for (const particle of pool.particles) {
7938
+ live.add(particle.id);
7939
+ let mesh2 = drawn.get(particle.id);
7940
+ if (!mesh2) {
7941
+ mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
7942
+ mesh2.frustumCulled = false;
7943
+ mesh2.renderOrder = particle.additive ? 5 : 3;
7944
+ drawn.set(particle.id, mesh2);
7945
+ group.add(mesh2);
7946
+ }
7947
+ frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
7948
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
7949
+ mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
7950
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
7951
+ mesh2.quaternion.copy(frame).multiply(turn);
7952
+ mesh2.scale.setScalar(particle.scale);
7953
+ mesh2.material.opacity = particle.opacity;
7954
+ }
7955
+ for (const [id, mesh2] of drawn) {
7956
+ if (live.has(id)) continue;
7957
+ release(mesh2);
7958
+ drawn.delete(id);
7959
+ }
7960
+ return pool.count;
7961
+ },
7962
+ dispose() {
7963
+ for (const mesh2 of drawn.values()) release(mesh2);
7964
+ drawn.clear();
7965
+ group.removeFromParent();
7966
+ }
7967
+ };
7968
+ }
7372
7969
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
7373
7970
  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;}`;
7374
7971
  var WORKSHOP_SPRITE_FRAGMENT = `uniform float uSoft;varying vec3 vC;varying float vA;
@@ -7444,13 +8041,37 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7444
8041
  }
7445
8042
  };
7446
8043
  }
8044
+ function rainLayer(THREE, rain) {
8045
+ const geometry = new THREE.BufferGeometry();
8046
+ const position = new THREE.BufferAttribute(rain.positions, 3).setUsage(THREE.DynamicDrawUsage);
8047
+ geometry.setAttribute("position", position);
8048
+ geometry.setDrawRange(0, 0);
8049
+ const material = new THREE.LineBasicMaterial({ color: 10466255, transparent: true, opacity: 0.35, depthWrite: false });
8050
+ const lines = new THREE.LineSegments(geometry, material);
8051
+ lines.frustumCulled = false;
8052
+ lines.name = "workshop-rain";
8053
+ return {
8054
+ object: lines,
8055
+ sync() {
8056
+ const drawn = rain.on && rain.count > 0;
8057
+ lines.visible = drawn;
8058
+ geometry.setDrawRange(0, drawn ? rain.count * 2 : 0);
8059
+ position.needsUpdate = true;
8060
+ return drawn;
8061
+ },
8062
+ dispose() {
8063
+ geometry.dispose();
8064
+ material.dispose();
8065
+ }
8066
+ };
8067
+ }
7447
8068
  function debrisLayer(THREE, pool) {
7448
8069
  const geometry = new THREE.BoxGeometry(1, 1, 1);
7449
8070
  const material = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.45, metalness: 0.25, flatShading: true });
7450
- const mesh = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
7451
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
7452
- mesh.frustumCulled = false;
7453
- mesh.name = "workshop-debris";
8071
+ const mesh2 = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
8072
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8073
+ mesh2.frustumCulled = false;
8074
+ mesh2.name = "workshop-debris";
7454
8075
  const euler = new THREE.Euler();
7455
8076
  const quaternion = new THREE.Quaternion();
7456
8077
  const position = new THREE.Vector3();
@@ -7458,16 +8079,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7458
8079
  const matrix = new THREE.Matrix4();
7459
8080
  const colour = new THREE.Color();
7460
8081
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
7461
- for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh.setMatrixAt(index, hidden);
7462
- mesh.setColorAt(0, colour.setHex(16777215));
8082
+ for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh2.setMatrixAt(index, hidden);
8083
+ mesh2.setColorAt(0, colour.setHex(16777215));
7463
8084
  return {
7464
- object: mesh,
8085
+ object: mesh2,
7465
8086
  sync() {
7466
8087
  let drawn = false;
7467
8088
  for (let index = 0; index < DEBRIS_CAPACITY; index++) {
7468
8089
  const piece = pool.pieces[index];
7469
8090
  if (!piece.on) {
7470
- mesh.setMatrixAt(index, hidden);
8091
+ mesh2.setMatrixAt(index, hidden);
7471
8092
  continue;
7472
8093
  }
7473
8094
  drawn = true;
@@ -7475,11 +8096,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7475
8096
  euler.set(piece.rx, piece.ry, piece.rz);
7476
8097
  quaternion.setFromEuler(euler);
7477
8098
  matrix.compose(position.set(piece.x, piece.y, piece.z), quaternion, scale2.set(piece.sx * shrink, piece.sy * shrink, piece.sz * shrink));
7478
- mesh.setMatrixAt(index, matrix);
7479
- mesh.setColorAt(index, colour.setHex(piece.color));
8099
+ mesh2.setMatrixAt(index, matrix);
8100
+ mesh2.setColorAt(index, colour.setHex(piece.color));
7480
8101
  }
7481
- mesh.instanceMatrix.needsUpdate = true;
7482
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8102
+ mesh2.instanceMatrix.needsUpdate = true;
8103
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
7483
8104
  return drawn;
7484
8105
  },
7485
8106
  dispose() {
@@ -7536,11 +8157,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7536
8157
  const dryMesh = new THREE.InstancedMesh(geometry, dry, DECAL_CAPACITY);
7537
8158
  const group = new THREE.Group();
7538
8159
  group.name = "workshop-decals";
7539
- for (const mesh of [wetMesh, dryMesh]) {
7540
- mesh.frustumCulled = false;
7541
- mesh.renderOrder = 1;
7542
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
7543
- group.add(mesh);
8160
+ for (const mesh2 of [wetMesh, dryMesh]) {
8161
+ mesh2.frustumCulled = false;
8162
+ mesh2.renderOrder = 1;
8163
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8164
+ group.add(mesh2);
7544
8165
  }
7545
8166
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
7546
8167
  const colour = new THREE.Color();
@@ -7577,13 +8198,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7577
8198
  quaternion.multiply(turn.setFromAxisAngle(forward, index * 2.399963));
7578
8199
  const offset = 4e-3 + index % 40 * 4e-4;
7579
8200
  matrix.compose(position.set(decal2.x + normal.x * offset, decal2.y + normal.y * offset, decal2.z + normal.z * offset), quaternion, scale2.set(look.sx, look.sy, 1));
7580
- const mesh = look.dried ? dryMesh : wetMesh;
7581
- mesh.setMatrixAt(index, matrix);
7582
- mesh.setColorAt(index, colour);
8201
+ const mesh2 = look.dried ? dryMesh : wetMesh;
8202
+ mesh2.setMatrixAt(index, matrix);
8203
+ mesh2.setColorAt(index, colour);
7583
8204
  }
7584
- for (const mesh of [wetMesh, dryMesh]) {
7585
- mesh.instanceMatrix.needsUpdate = true;
7586
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8205
+ for (const mesh2 of [wetMesh, dryMesh]) {
8206
+ mesh2.instanceMatrix.needsUpdate = true;
8207
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
7587
8208
  }
7588
8209
  return drawn;
7589
8210
  },
@@ -7597,10 +8218,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7597
8218
  function ringLayer(THREE, pool) {
7598
8219
  const geometry = new THREE.RingGeometry(0.86, 1, 40);
7599
8220
  const material = new THREE.MeshBasicMaterial({ color: 16777215, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide });
7600
- const mesh = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
7601
- mesh.frustumCulled = false;
7602
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
7603
- mesh.name = "workshop-rings";
8221
+ const mesh2 = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
8222
+ mesh2.frustumCulled = false;
8223
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8224
+ mesh2.name = "workshop-rings";
7604
8225
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
7605
8226
  const colour = new THREE.Color();
7606
8227
  const normal = new THREE.Vector3();
@@ -7609,16 +8230,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7609
8230
  const position = new THREE.Vector3();
7610
8231
  const scale2 = new THREE.Vector3();
7611
8232
  const matrix = new THREE.Matrix4();
7612
- mesh.setColorAt(0, colour.setHex(0));
7613
- for (let index = 0; index < RING_CAPACITY3; index++) mesh.setMatrixAt(index, hidden);
8233
+ mesh2.setColorAt(0, colour.setHex(0));
8234
+ for (let index = 0; index < RING_CAPACITY3; index++) mesh2.setMatrixAt(index, hidden);
7614
8235
  return {
7615
- object: mesh,
8236
+ object: mesh2,
7616
8237
  sync() {
7617
8238
  let drawn = false;
7618
8239
  for (let index = 0; index < RING_CAPACITY3; index++) {
7619
8240
  const ring2 = pool.rings[index];
7620
8241
  if (!ring2.on) {
7621
- mesh.setMatrixAt(index, hidden);
8242
+ mesh2.setMatrixAt(index, hidden);
7622
8243
  continue;
7623
8244
  }
7624
8245
  drawn = true;
@@ -7626,11 +8247,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7626
8247
  const radius = ring2.from + (ring2.to - ring2.from) * (1 - Math.pow(1 - k, 2));
7627
8248
  quaternion.setFromUnitVectors(forward, normal.set(ring2.normal[0], ring2.normal[1], ring2.normal[2]));
7628
8249
  matrix.compose(position.set(ring2.point[0], ring2.point[1], ring2.point[2]), quaternion, scale2.set(radius, radius, 1));
7629
- mesh.setMatrixAt(index, matrix);
7630
- mesh.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
8250
+ mesh2.setMatrixAt(index, matrix);
8251
+ mesh2.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
7631
8252
  }
7632
- mesh.instanceMatrix.needsUpdate = true;
7633
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8253
+ mesh2.instanceMatrix.needsUpdate = true;
8254
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
7634
8255
  return drawn;
7635
8256
  },
7636
8257
  dispose() {
@@ -7646,11 +8267,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7646
8267
  spriteLayer(THREE, engine.add, true, 1, pointScale),
7647
8268
  spriteLayer(THREE, engine.normal, false, 0.55, pointScale),
7648
8269
  streakLayer(THREE, engine.streaks),
8270
+ rainLayer(THREE, engine.rain),
7649
8271
  debrisLayer(THREE, engine.debris),
7650
8272
  decalLayer(THREE, engine.decals),
7651
8273
  ringLayer(THREE, engine.rings)
7652
8274
  ];
7653
8275
  for (const layer2 of layers) group.add(layer2.object);
8276
+ const meshes = createMeshLayer(THREE, engine.meshes);
8277
+ group.add(meshes.group);
7654
8278
  const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
7655
8279
  const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
7656
8280
  for (const light of [...flashLights, ...fireLights]) group.add(light);
@@ -7659,6 +8283,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7659
8283
  sync() {
7660
8284
  let drawCalls = 0;
7661
8285
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
8286
+ drawCalls += meshes.sync();
7662
8287
  engine.lights.lights.forEach((source, index) => {
7663
8288
  const light = flashLights[index];
7664
8289
  light.position.set(source.x, source.y, source.z);
@@ -7675,6 +8300,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7675
8300
  },
7676
8301
  dispose() {
7677
8302
  for (const layer2 of layers) layer2.dispose();
8303
+ meshes.dispose();
7678
8304
  group.clear();
7679
8305
  }
7680
8306
  };
@@ -7790,8 +8416,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7790
8416
  return world.play(request.effect, request.point, {
7791
8417
  ...request.direction ? { direction: request.direction } : {},
7792
8418
  ...request.normal ? { normal: request.normal } : {},
8419
+ ...request.orientation ? { orientation: request.orientation } : {},
7793
8420
  ...request.seed !== void 0 ? { seed: request.seed } : {},
7794
- ...request.param !== void 0 ? { param: request.param } : {}
8421
+ ...request.param !== void 0 ? { param: request.param } : {},
8422
+ ...request.values ? { values: request.values } : {}
7795
8423
  });
7796
8424
  },
7797
8425
  stop: (handle) => world.stop(handle),
@@ -8605,7 +9233,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8605
9233
  if (lastRow < 0) return;
8606
9234
  const profile = profileOf2();
8607
9235
  const rainScale = 1 - options.weather().rain;
8608
- const seconds = dryingSeconds * rainScale;
9236
+ const seconds = dryingSeconds * rainScale * (options.weather().drying ?? 1);
8609
9237
  for (let row = firstRow; row <= lastRow; row++) {
8610
9238
  const offset = row * width;
8611
9239
  for (let column = firstColumn; column <= lastColumn; column++) {
@@ -8729,6 +9357,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8729
9357
  }
8730
9358
  var DEFAULT_STEP_RATE = 180;
8731
9359
  var DEFAULT_MAX_STEPS = 6;
9360
+ var WASH_BELOW_DRYNESS = 0.5;
8732
9361
  function createFilm(options) {
8733
9362
  const { layout, random } = options;
8734
9363
  const cellCount = layout.width * layout.height;
@@ -8739,7 +9368,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8739
9368
  dryness: new Float32Array(cellCount),
8740
9369
  dampness: new Float32Array(cellCount)
8741
9370
  };
8742
- let weatherState = { rain: 0 };
9371
+ let weatherState = { rain: 0, drying: 1 };
8743
9372
  let currentProfile = options.profile;
8744
9373
  const context = { field, random, profile: () => currentProfile };
8745
9374
  const drops = createDropPool(layout, context);
@@ -8777,9 +9406,29 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8777
9406
  simulator.advance({ dt, dryingSeconds });
8778
9407
  },
8779
9408
  setWeather(weather) {
8780
- weatherState = { rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)) };
9409
+ weatherState = {
9410
+ rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)),
9411
+ drying: Math.max(0, weather.drying ?? weatherState.drying ?? 1)
9412
+ };
8781
9413
  },
8782
9414
  weather: () => ({ ...weatherState }),
9415
+ wash(maxDryness = WASH_BELOW_DRYNESS) {
9416
+ simulator.refreshBounds();
9417
+ const { firstRow, lastRow, firstColumn, lastColumn } = simulator.bounds;
9418
+ let cleared = 0;
9419
+ for (let row = firstRow; row <= lastRow; row++) {
9420
+ for (let column = firstColumn; column <= lastColumn; column++) {
9421
+ const index = row * layout.width + column;
9422
+ if (field.thickness[index] > 0 && field.dryness[index] < maxDryness) {
9423
+ field.thickness[index] = 0;
9424
+ field.dryness[index] = 0;
9425
+ field.dampness[index] = 0;
9426
+ cleared++;
9427
+ }
9428
+ }
9429
+ }
9430
+ return cleared;
9431
+ },
8783
9432
  clear() {
8784
9433
  field.thickness.fill(0);
8785
9434
  field.dryness.fill(0);
@@ -9292,11 +9941,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9292
9941
  const index = [];
9293
9942
  for (let i = 0; i < this.ring; i++) index.push(0, 1 + i, 1 + (i + 1) % this.ring);
9294
9943
  geometry.setIndex(index);
9295
- const mesh = new T.Mesh(geometry);
9296
- mesh.name = name;
9297
- mesh.frustumCulled = false;
9298
- mesh.renderOrder = order;
9299
- return { mesh, position, color };
9944
+ const mesh2 = new T.Mesh(geometry);
9945
+ mesh2.name = name;
9946
+ mesh2.frustumCulled = false;
9947
+ mesh2.renderOrder = order;
9948
+ return { mesh: mesh2, position, color };
9300
9949
  }
9301
9950
  assign(def, materials) {
9302
9951
  this.outer.mesh.material = materials.stainFilm(def);
@@ -10036,12 +10685,12 @@ void main(){
10036
10685
  this.group = new THREE.Group();
10037
10686
  this.group.name = "film_grid";
10038
10687
  for (const region of layout.regions) {
10039
- const mesh = new THREE.Mesh(createStripGeometry(THREE, layout, region), this.material.material);
10040
- mesh.frustumCulled = false;
10041
- mesh.renderOrder = 3;
10042
- mesh.name = `film_${region.id}`;
10043
- this.strips.push(mesh);
10044
- this.group.add(mesh);
10688
+ const mesh2 = new THREE.Mesh(createStripGeometry(THREE, layout, region), this.material.material);
10689
+ mesh2.frustumCulled = false;
10690
+ mesh2.renderOrder = 3;
10691
+ mesh2.name = `film_${region.id}`;
10692
+ this.strips.push(mesh2);
10693
+ this.group.add(mesh2);
10045
10694
  }
10046
10695
  this.dropsView = createFilmDrops(THREE, layout.rules.dropCapacity, profile);
10047
10696
  this.group.add(this.dropsView.drops, this.dropsView.bead);
@@ -10086,6 +10735,19 @@ void main(){
10086
10735
  this.pending = this.union(this.pending, rect);
10087
10736
  return this.takePending();
10088
10737
  }
10738
+ /** Clears the cells less than `maxDryness` dry and sends the rectangle that held them up with the next update. Returns how many it cleared. */
10739
+ wash(maxDryness) {
10740
+ const cleared = this.film.wash(maxDryness);
10741
+ if (cleared > 0) {
10742
+ const bounds = this.film.simulator.bounds;
10743
+ if (bounds.lastRow >= 0) {
10744
+ const rect = { firstRow: bounds.firstRow, lastRow: bounds.lastRow, firstColumn: bounds.firstColumn, lastColumn: bounds.lastColumn };
10745
+ this.encode(rect);
10746
+ this.pending = this.union(this.pending, rect);
10747
+ }
10748
+ }
10749
+ return cleared;
10750
+ }
10089
10751
  takePending() {
10090
10752
  const rect = this.pending;
10091
10753
  this.pending = null;
@@ -10141,7 +10803,7 @@ void main(){
10141
10803
  return { firstRow: Math.min(a.firstRow, b.firstRow), lastRow: Math.max(a.lastRow, b.lastRow), firstColumn: Math.min(a.firstColumn, b.firstColumn), lastColumn: Math.max(a.lastColumn, b.lastColumn) };
10142
10804
  }
10143
10805
  dispose() {
10144
- for (const mesh of this.strips) mesh.geometry.dispose();
10806
+ for (const mesh2 of this.strips) mesh2.geometry.dispose();
10145
10807
  this.material.dispose();
10146
10808
  this.dropsView.dispose();
10147
10809
  this.texture.dispose();
@@ -10157,11 +10819,12 @@ void main(){
10157
10819
  var DEFAULT_GRID_METRES = 2;
10158
10820
  var DEFAULT_CELL2 = 0.0125;
10159
10821
  var BAKE_RANGE_FACTOR = 1.25;
10822
+ var DEFAULT_WASHABLE = ["blood", "old_blood"];
10160
10823
  var EDGE_MARGIN = 0.35;
10161
10824
  var FLOOR_REACH = 0.3;
10162
10825
  var DROP_HEIGHT = 2.5;
10163
10826
  var DEFAULT_DROP_RADIUS = 3;
10164
- var FALL_SPEED = 2;
10827
+ var FALL_SPEED2 = 2;
10165
10828
  var VISIBLE_THICKNESS3 = 0.035;
10166
10829
  var DEFAULT_HALF = 50;
10167
10830
  var FREE_AFTER_EMPTY = 2;
@@ -10182,6 +10845,7 @@ void main(){
10182
10845
  __publicField(this, "staging", /* @__PURE__ */ new Map());
10183
10846
  __publicField(this, "now");
10184
10847
  __publicField(this, "rain", 0);
10848
+ __publicField(this, "drying", 1);
10185
10849
  __publicField(this, "time", 0);
10186
10850
  __publicField(this, "bakedCount", 0);
10187
10851
  __publicField(this, "last", { grids: 0, drops: 0, wetCells: 0, simMs: 0, starved: 0, baked: 0, uploadedTexels: 0 });
@@ -10201,13 +10865,24 @@ void main(){
10201
10865
  }
10202
10866
  setWeather(weather) {
10203
10867
  this.rain = Math.min(1, Math.max(0, weather.rain));
10204
- for (const grid of this.grids) grid.film.setWeather({ rain: this.rain });
10868
+ if (weather.drying !== void 0) this.drying = Math.max(0, weather.drying);
10869
+ for (const grid of this.grids) grid.film.setWeather({ rain: this.rain, drying: this.drying });
10870
+ }
10871
+ /**
10872
+ * Rain washes the fresh stain away (the visor's `ZG.wipe` with mode "chuva"): in the grids of the washable liquids (default blood and old blood), the
10873
+ * cells that are less than `maxDryness` dry (default 0.5) are cleared. Returns how many cells it cleared.
10874
+ */
10875
+ wash(maxDryness) {
10876
+ const washable = this.options.washable ?? DEFAULT_WASHABLE;
10877
+ let cleared = 0;
10878
+ for (const grid of this.grids) if (washable.includes(grid.profile.id)) cleared += grid.wash(maxDryness);
10879
+ return cleared;
10205
10880
  }
10206
10881
  /** Throws a drop of a liquid: it lands in the grid under it (made on the spot) and leaves the impact splash. Null when it is out of range or has no surface. */
10207
10882
  launch(request) {
10208
10883
  const grid = this.gridFor(request.fluid, request.point, request.surfaces, request.tint);
10209
10884
  if (!grid) return null;
10210
- const velocity = request.velocity ?? [0, -FALL_SPEED, 0];
10885
+ const velocity = request.velocity ?? [0, -FALL_SPEED2, 0];
10211
10886
  grid.film.launch(request.point, velocity, request.radius ?? DEFAULT_DROP_RADIUS);
10212
10887
  return grid;
10213
10888
  }
@@ -10232,7 +10907,7 @@ void main(){
10232
10907
  const layout = layoutFromSurfaces({ ...found, center: point, columns: this.columns(), rows: this.columns(), cellSize: this.options.cellSize ?? DEFAULT_CELL2 });
10233
10908
  if (this.grids.length >= (this.options.maxGrids ?? DEFAULT_MAX_GRIDS)) this.evict(point);
10234
10909
  const grid = new FilmGrid(this.THREE, { layout, profile, random: this.options.random, center: point, ...tint ? { tint } : {}, ...this.options.base === void 0 ? {} : { base: this.options.base } });
10235
- grid.film.setWeather({ rain: this.rain });
10910
+ grid.film.setWeather({ rain: this.rain, drying: this.drying });
10236
10911
  this.grids.push(grid);
10237
10912
  this.group.add(grid.group);
10238
10913
  return grid;
@@ -10309,7 +10984,7 @@ void main(){
10309
10984
  /** Whether `point` is over the grid, away from its edge, so a splash there does not fall off the strip. */
10310
10985
  covers(grid, point) {
10311
10986
  const layout = grid.film.layout;
10312
- const wide = layout.width * layout.cellSize;
10987
+ const wide2 = layout.width * layout.cellSize;
10313
10988
  for (const region of layout.regions) {
10314
10989
  const local2 = [point[0] - region.origin[0], point[1] - region.origin[1], point[2] - region.origin[2]];
10315
10990
  const above2 = local2[0] * region.normal[0] + local2[1] * region.normal[1] + local2[2] * region.normal[2];
@@ -10317,7 +10992,7 @@ void main(){
10317
10992
  const sideways = local2[0] * region.columnAxis[0] + local2[1] * region.columnAxis[1] + local2[2] * region.columnAxis[2];
10318
10993
  const down = local2[0] * region.rowAxis[0] + local2[1] * region.rowAxis[1] + local2[2] * region.rowAxis[2];
10319
10994
  const length2 = region.rowCount * layout.cellSize;
10320
- if (sideways > EDGE_MARGIN && sideways < wide - EDGE_MARGIN && down > EDGE_MARGIN && down < length2 - EDGE_MARGIN) return true;
10995
+ if (sideways > EDGE_MARGIN && sideways < wide2 - EDGE_MARGIN && down > EDGE_MARGIN && down < length2 - EDGE_MARGIN) return true;
10321
10996
  }
10322
10997
  return false;
10323
10998
  }
@@ -10470,6 +11145,7 @@ void main(){
10470
11145
  return landed;
10471
11146
  },
10472
11147
  setWeather: (weather) => manager.setWeather(weather),
11148
+ wash: (maxDryness) => manager.wash(maxDryness),
10473
11149
  update(dt) {
10474
11150
  if (leaks.size === 0) return;
10475
11151
  const streams = new Map(fluids.streams().map((stream) => [stream.id, stream]));