@adatechnology/game-fluids-three 0.2.3 → 0.3.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.
@@ -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,157 @@ 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 aliasOf = (base, id, changes) => ({ ...base, id, pageId: `vehicle:${id}`, anchor: CAR_ORIGIN2, ...changes });
3579
+ var VEHICLE_EFFECTS = [
3580
+ /** The engine on fire: `engine_smoke` at its worst condition (black smoke, flames in the hood, a light). */
3581
+ aliasOf(ENGINE_SMOKE, "vehicle_fire", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 3 }, loop: 12 }),
3582
+ /** The exhaust of a car under way: `engine_smoke` in the best condition, for half a second so a moving car asks again. */
3583
+ aliasOf(ENGINE_SMOKE, "vehicle_exhaust", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 0 }, loop: 0.5 }),
3584
+ /** A hole in the tank: the page's own petrol leak (drips, pool, sparks, fire running to the tank). */
3585
+ aliasOf(FUEL_LEAK, "vehicle_leak", {})
3586
+ ];
3587
+ 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
3588
  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])) };
3589
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
3211
3590
  var LEHMER_MULTIPLIER = 16807;
3212
3591
  var LEHMER_MODULUS = 2147483647;
3213
3592
  var HASH_MIX = 2654435761;
@@ -3303,7 +3682,7 @@ var GameFluidsThree = (() => {
3303
3682
  }
3304
3683
  var COLUMNS = 6;
3305
3684
  var ROWS = 5;
3306
- var ORIGIN2 = [-2.2, 0, 2.6];
3685
+ var ORIGIN3 = [-2.2, 0, 2.6];
3307
3686
  var BLOCK_SIZE = [0.5, 0.44, 0.25];
3308
3687
  var OBJECT_ID_DRAWS2 = 4;
3309
3688
  var FLOOR2 = 0.13;
@@ -3346,7 +3725,7 @@ var GameFluidsThree = (() => {
3346
3725
  const chipScale = random.between(0.8, 1.3) * scale2;
3347
3726
  for (let draw = 0; draw < OBJECT_ID_DRAWS2; draw++) random.next();
3348
3727
  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);
3728
+ impact(host.engine, "concrete", [ORIGIN3[0] + x, y, ORIGIN3[2] + 0.13], [0, 0, 1], 0.8);
3350
3729
  } else if (action === "collapse") {
3351
3730
  const { x, y } = args;
3352
3731
  for (const block of blocks) {
@@ -3396,7 +3775,7 @@ var GameFluidsThree = (() => {
3396
3775
  }
3397
3776
  };
3398
3777
  }
3399
- var ORIGIN3 = [5.6, 0, 0.8];
3778
+ var ORIGIN4 = [5.6, 0, 0.8];
3400
3779
  var MAX_TILT = 0.25;
3401
3780
  function createCarBehavior() {
3402
3781
  let y = 0;
@@ -3428,8 +3807,13 @@ var GameFluidsThree = (() => {
3428
3807
  }
3429
3808
  }
3430
3809
  },
3431
- query(_host, name) {
3432
- if (name === "position") return [ORIGIN3[0], y, ORIGIN3[2]];
3810
+ query(_host, name, args) {
3811
+ if (name === "position") return [ORIGIN4[0], y, ORIGIN4[2]];
3812
+ if (name === "point") {
3813
+ const [x, py, z] = args;
3814
+ const point = carPoint(x, py, z);
3815
+ return [point[0], point[1] + y, point[2]];
3816
+ }
3433
3817
  if (name === "roll") return roll;
3434
3818
  throw new Error(`car: unknown query ${name}`);
3435
3819
  },
@@ -3629,6 +4013,29 @@ var GameFluidsThree = (() => {
3629
4013
  }
3630
4014
  };
3631
4015
  }
4016
+ function createRainBehavior() {
4017
+ return {
4018
+ reset() {
4019
+ },
4020
+ act(host, action, args) {
4021
+ const { engine, camera: camera2 } = host;
4022
+ if (action === "init") {
4023
+ const { count, wind } = args;
4024
+ engine.rain.init(count, wind);
4025
+ } else if (action === "update") {
4026
+ const { dt, splash } = args;
4027
+ engine.rain.update(dt, splash, camera2.position);
4028
+ }
4029
+ },
4030
+ query(host, name) {
4031
+ if (name === "count") return host.engine.rain.count;
4032
+ if (name === "on") return host.engine.rain.on;
4033
+ throw new Error(`rain: unknown query ${name}`);
4034
+ },
4035
+ update() {
4036
+ }
4037
+ };
4038
+ }
3632
4039
  var WALKER_COUNT = 10;
3633
4040
  var ARRIVAL_DISTANCE = 0.05;
3634
4041
  var SWAY_AMPLITUDE = 0.04;
@@ -3694,7 +4101,8 @@ var GameFluidsThree = (() => {
3694
4101
  blocks: () => createBlocksBehavior(),
3695
4102
  plate: () => createPlateBehavior(),
3696
4103
  cylinder: () => createCylinderBehavior(),
3697
- car: () => createCarBehavior()
4104
+ car: () => createCarBehavior(),
4105
+ rain: () => createRainBehavior()
3698
4106
  };
3699
4107
  var NOOP = () => {
3700
4108
  };
@@ -3765,6 +4173,8 @@ var GameFluidsThree = (() => {
3765
4173
  return Math.abs(asNumber(first));
3766
4174
  case "sqrt":
3767
4175
  return Math.sqrt(asNumber(first));
4176
+ case "exp":
4177
+ return Math.exp(asNumber(first));
3768
4178
  case "sin":
3769
4179
  return Math.sin(asNumber(first));
3770
4180
  case "cos":
@@ -3883,7 +4293,7 @@ var GameFluidsThree = (() => {
3883
4293
  return evaluateCall(value.$, args.map((entry) => resolve(entry, context)));
3884
4294
  }
3885
4295
  }
3886
- var UP11 = [0, 1, 0];
4296
+ var UP12 = [0, 1, 0];
3887
4297
  var ZERO = [0, 0, 0];
3888
4298
  var FULL_TURN_DRAWN = 6.283;
3889
4299
  function scaledSizes(range2, factor) {
@@ -3896,7 +4306,7 @@ var GameFluidsThree = (() => {
3896
4306
  p: centre,
3897
4307
  r: radius,
3898
4308
  flat: true,
3899
- dir: options.dir ?? UP11,
4309
+ dir: options.dir ?? UP12,
3900
4310
  cone: 0.35,
3901
4311
  sp: [0.4, 1.4],
3902
4312
  v: wind,
@@ -3913,7 +4323,7 @@ var GameFluidsThree = (() => {
3913
4323
  p: centre,
3914
4324
  r: radius * 0.6,
3915
4325
  flat: true,
3916
- dir: UP11,
4326
+ dir: UP12,
3917
4327
  cone: 0.3,
3918
4328
  sp: [0.3, 0.8],
3919
4329
  life: [0.2, 0.4],
@@ -3928,14 +4338,14 @@ var GameFluidsThree = (() => {
3928
4338
  if (engine.quality < 2) {
3929
4339
  engine.emit(`${key}e`, "streak", 10 * factor, dt, () => {
3930
4340
  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 };
4341
+ 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
4342
  });
3933
4343
  }
3934
4344
  if (options.noSmoke) return;
3935
4345
  engine.emit(`${key}s`, "normal", (options.smoke || 14) * factor, dt, () => ({
3936
4346
  p: plus(centre, 0, 0.8 * sqrtFactor, 0),
3937
4347
  r: radius * 0.7,
3938
- dir: UP11,
4348
+ dir: UP12,
3939
4349
  cone: 0.35,
3940
4350
  sp: [0.6, 1.4],
3941
4351
  v: wind,
@@ -3975,15 +4385,15 @@ var GameFluidsThree = (() => {
3975
4385
  engine.screen.shake += 0.7 * scale2;
3976
4386
  engine.screen.white += 0.18 * scale2;
3977
4387
  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 });
4388
+ 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
4389
  engine.ring([plus(point, 0, 0.05, 0), 0.3, 7 * scale2, 0.55, 16767136]);
3980
4390
  const toCamera = normalized([cameraPosition[0] - point[0], cameraPosition[1] - point[1], cameraPosition[2] - point[2]]);
3981
4391
  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 });
4392
+ 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
4393
  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 });
4394
+ 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
4395
  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 });
4396
+ 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
4397
  }
3988
4398
  engine.decal([point[0], engine.context.groundAt(point[0], point[2]), point[2], 3 * scale2, 657930, { grow: 0.15, life: 60 }]);
3989
4399
  }
@@ -4126,6 +4536,7 @@ var GameFluidsThree = (() => {
4126
4536
  if (!def) return;
4127
4537
  this.schedule.clear();
4128
4538
  this.variables.clear();
4539
+ for (const [name, value] of Object.entries(this.setup.values ?? {})) this.variables.set(name, value);
4129
4540
  for (const behavior2 of this.deps.behaviors()) behavior2.reset(this.host);
4130
4541
  this.time = 0;
4131
4542
  this.within(() => {
@@ -4184,6 +4595,10 @@ var GameFluidsThree = (() => {
4184
4595
  return engine.spawn(step.system, this.value(step.spawn));
4185
4596
  case "emit":
4186
4597
  return engine.emit(step.key, step.system, this.value(step.rate), this.dt, () => this.value(step.spawn));
4598
+ case "mesh": {
4599
+ engine.mesh(this.value(step.spawn));
4600
+ return;
4601
+ }
4187
4602
  case "debris":
4188
4603
  return engine.debrisPiece(this.value(step.spawn));
4189
4604
  case "decal":
@@ -4265,7 +4680,7 @@ var GameFluidsThree = (() => {
4265
4680
  this.index = previous;
4266
4681
  }
4267
4682
  };
4268
- var ORIGIN4 = [0, 0, 0];
4683
+ var ORIGIN5 = [0, 0, 0];
4269
4684
  var DEFAULT_HEADING = [0, 0, -1];
4270
4685
  var DEFAULT_MAX_INSTANCES = 48;
4271
4686
  var ENDLESS_SECONDS = 60;
@@ -4310,7 +4725,7 @@ var GameFluidsThree = (() => {
4310
4725
  }
4311
4726
  function placementOf(entry) {
4312
4727
  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 };
4728
+ return { origin, anchor: entry.anchor, yaw: entry.yaw, ...entry.orientation ? { orientation: entry.orientation } : {} };
4314
4729
  }
4315
4730
  function drawing(entry, action) {
4316
4731
  const context = engine.context;
@@ -4334,8 +4749,9 @@ var GameFluidsThree = (() => {
4334
4749
  random: playOptions.seed !== void 0 ? streamOf(playOptions.seed) : void 0,
4335
4750
  follow: playOptions.follow,
4336
4751
  origin: point,
4337
- anchor: def.anchor ?? ORIGIN4,
4752
+ anchor: def.anchor ?? ORIGIN5,
4338
4753
  yaw: yawOf(heading, playOptions.direction),
4754
+ orientation: playOptions.orientation,
4339
4755
  seconds: playOptions.seconds ?? (def.loop && def.loop > 0 ? def.loop : ENDLESS_SECONDS)
4340
4756
  };
4341
4757
  playing.push(entry);
@@ -4344,7 +4760,8 @@ var GameFluidsThree = (() => {
4344
4760
  () => instance.begin(def, paramIndex(def, playOptions.param), {
4345
4761
  placement: placementOf(entry),
4346
4762
  ...playOptions.direction ? { direction: playOptions.direction } : {},
4347
- ...playOptions.normal ? { normal: playOptions.normal } : {}
4763
+ ...playOptions.normal ? { normal: playOptions.normal } : {},
4764
+ ...playOptions.values ? { values: playOptions.values } : {}
4348
4765
  })
4349
4766
  );
4350
4767
  return entry.handle;
@@ -4604,15 +5021,15 @@ var GameFluidsThree = (() => {
4604
5021
  return shock;
4605
5022
  }
4606
5023
  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;
5024
+ for (const puff3 of puffs) {
5025
+ if (puff3.ring || puff3.t > PUSH_AGE_LIMIT) continue;
5026
+ const dx = puff3.x - shock.x;
5027
+ const dz = puff3.z - shock.z;
4611
5028
  const distance2 = Math.hypot(dx, dz) + 1e-3;
4612
- if (Math.abs(distance2 - reach) < PUSH_BAND && puff2.y < PUSH_CEILING) {
5029
+ if (Math.abs(distance2 - reach) < PUSH_BAND && puff3.y < PUSH_CEILING) {
4613
5030
  const strength = 4 * dt * (1 - age);
4614
- puff2.vx += dx / distance2 * strength * 6;
4615
- puff2.vz += dz / distance2 * strength * 6;
5031
+ puff3.vx += dx / distance2 * strength * 6;
5032
+ puff3.vz += dz / distance2 * strength * 6;
4616
5033
  }
4617
5034
  }
4618
5035
  }
@@ -4912,10 +5329,10 @@ var GameFluidsThree = (() => {
4912
5329
  const life = random.between(ring2.life * 0.75, ring2.life);
4913
5330
  const h0 = random.between(1, 1.25);
4914
5331
  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;
5332
+ 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 });
5333
+ puff3.ring = ring2;
5334
+ puff3.ph = random.next() * Math.PI * 2;
5335
+ puff3.th = random.next() * Math.PI * 2;
4919
5336
  }
4920
5337
  const stemCount = Math.round((spec.stem || 18) * Math.max(lod, 0.4));
4921
5338
  for (let index = 0; index < stemCount; index++) {
@@ -5716,7 +6133,7 @@ var GameFluidsThree = (() => {
5716
6133
  };
5717
6134
  var TWO_PI_ROUNDED = 6.2832;
5718
6135
  var WIDE_CONE = 3.1;
5719
- var UP12 = [0, 1, 0];
6136
+ var UP13 = [0, 1, 0];
5720
6137
  function drawRange(random, range2) {
5721
6138
  if (Array.isArray(range2)) return random.between(range2[0], range2[1]);
5722
6139
  return range2 || 0;
@@ -5744,7 +6161,7 @@ var GameFluidsThree = (() => {
5744
6161
  }
5745
6162
  function coneDirection(random, direction, cone, out) {
5746
6163
  if (cone >= WIDE_CONE) return randomUnit(random, out);
5747
- const [dx, dy, dz] = direction ?? UP12;
6164
+ const [dx, dy, dz] = direction ?? UP13;
5748
6165
  const cosTilt = 1 - random.next() * (1 - Math.cos(cone));
5749
6166
  const sinTilt = Math.sqrt(1 - cosTilt * cosTilt);
5750
6167
  const phase = random.next() * TWO_PI_ROUNDED;
@@ -5864,12 +6281,12 @@ var GameFluidsThree = (() => {
5864
6281
  }
5865
6282
  };
5866
6283
  var CAPACITY2 = 400;
5867
- var UP13 = [0, 1, 0];
6284
+ var UP14 = [0, 1, 0];
5868
6285
  var DEFAULT_LIFE2 = 60;
5869
6286
  var DEFAULT_GROW = 1.2;
5870
6287
  var EVAPORATION = { fuel: [6, 5], water: [40, 20] };
5871
6288
  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 };
6289
+ 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
6290
  }
5874
6291
  var DecalPool = class {
5875
6292
  constructor(context) {
@@ -5882,7 +6299,7 @@ var GameFluidsThree = (() => {
5882
6299
  const random = this.context.random;
5883
6300
  const index = this.next++ % CAPACITY2;
5884
6301
  const decal2 = this.decals[index];
5885
- const normal = options.n ?? UP13;
6302
+ const normal = options.n ?? UP14;
5886
6303
  random.between(0, 6.28);
5887
6304
  const offset = 4e-3 + index % 40 * 4e-4;
5888
6305
  decal2.on = true;
@@ -5923,6 +6340,94 @@ var GameFluidsThree = (() => {
5923
6340
  }
5924
6341
  }
5925
6342
  };
6343
+ var CAPACITY3 = 400;
6344
+ var IDENTITY = [0, 0, 0, 1];
6345
+ function multiply(a, b) {
6346
+ const [ax, ay, az, aw] = a;
6347
+ const [bx, by, bz, bw] = b;
6348
+ 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];
6349
+ }
6350
+ var aboutX = (angle) => [Math.sin(angle / 2), 0, 0, Math.cos(angle / 2)];
6351
+ var MeshPool = class {
6352
+ constructor(capacity = CAPACITY3) {
6353
+ __publicField2(this, "capacity", capacity);
6354
+ __publicField2(this, "particles", []);
6355
+ __publicField2(this, "serial", 0);
6356
+ }
6357
+ get count() {
6358
+ return this.particles.length;
6359
+ }
6360
+ spawn(options, frame = IDENTITY) {
6361
+ const velocity = options.v ?? [0, 0, 0];
6362
+ const world = options.wv ?? [0, 0, 0];
6363
+ const particle = {
6364
+ id: ++this.serial,
6365
+ shape: options.shape,
6366
+ radius: options.r,
6367
+ height: options.h ?? 0,
6368
+ segments: options.seg ?? 0,
6369
+ segments2: options.seg2 ?? 0,
6370
+ color: options.c0,
6371
+ additive: options.add === true,
6372
+ lit: options.lit === true,
6373
+ x: options.p[0],
6374
+ y: options.p[1],
6375
+ z: options.p[2],
6376
+ frame: options.roll ? multiply(frame, aboutX(options.roll)) : frame,
6377
+ at: options.at ?? [0, 0, 0],
6378
+ rx: options.rot?.[0] ?? 0,
6379
+ ry: options.rot?.[1] ?? 0,
6380
+ rz: options.rot?.[2] ?? 0,
6381
+ scale: 1,
6382
+ opacity: options.a,
6383
+ age: 0,
6384
+ life: options.life,
6385
+ vx: velocity[0] + world[0],
6386
+ vy: velocity[1] + world[1],
6387
+ vz: velocity[2] + world[2],
6388
+ spin: options.spin ?? [0, 0, 0],
6389
+ gravity: options.g ?? 0,
6390
+ drag: options.drag ?? 0,
6391
+ grow: options.grow ?? 0,
6392
+ ramp: options.ramp,
6393
+ fade: options.fade === true,
6394
+ startOpacity: options.a
6395
+ };
6396
+ this.particles.push(particle);
6397
+ if (this.particles.length > this.capacity) this.particles.shift();
6398
+ return particle;
6399
+ }
6400
+ update(dt) {
6401
+ const live = this.particles;
6402
+ for (let index = 0; index < live.length; index++) {
6403
+ const particle = live[index];
6404
+ particle.age += dt;
6405
+ if (particle.ramp ? particle.age >= particle.life : particle.age > particle.life) {
6406
+ live.splice(index--, 1);
6407
+ continue;
6408
+ }
6409
+ if (particle.fade) particle.opacity = Math.max(0, particle.startOpacity * (1 - particle.age / particle.life));
6410
+ particle.vy -= particle.gravity * dt;
6411
+ if (particle.drag) {
6412
+ const keep = Math.max(0, 1 - particle.drag * dt);
6413
+ particle.vx *= keep;
6414
+ particle.vy *= keep;
6415
+ particle.vz *= keep;
6416
+ }
6417
+ particle.x += particle.vx * dt;
6418
+ particle.y += particle.vy * dt;
6419
+ particle.z += particle.vz * dt;
6420
+ particle.rx += particle.spin[0] * dt;
6421
+ particle.ry += particle.spin[1] * dt;
6422
+ particle.rz += particle.spin[2] * dt;
6423
+ if (particle.grow) particle.scale *= 1 + particle.grow * dt;
6424
+ if (particle.ramp) particle.scale = particle.ramp[0] + Math.min(1, particle.age / particle.life) * particle.ramp[1];
6425
+ }
6426
+ }
6427
+ clear() {
6428
+ this.particles.length = 0;
6429
+ }
6430
+ };
5926
6431
  var DEFAULT_FADE_IN = 0.1;
5927
6432
  var FLOATS_PER_VECTOR = 3;
5928
6433
  var BYTE_MAX2 = 255;
@@ -6212,11 +6717,11 @@ var GameFluidsThree = (() => {
6212
6717
  };
6213
6718
  var RING_CAPACITY = 120;
6214
6719
  var LIGHT_CAPACITY = 4;
6215
- var UP14 = [0, 1, 0];
6720
+ var UP15 = [0, 1, 0];
6216
6721
  var DEFAULT_LIGHT_DISTANCE = 14;
6217
6722
  var WaveRingPool = class {
6218
6723
  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 })));
6724
+ __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
6725
  __publicField2(this, "count", 0);
6221
6726
  __publicField2(this, "next", 0);
6222
6727
  }
@@ -6229,7 +6734,7 @@ var GameFluidsThree = (() => {
6229
6734
  ring2.t = 0;
6230
6735
  ring2.duration = duration;
6231
6736
  ring2.color = color;
6232
- ring2.normal = normal ?? UP14;
6737
+ ring2.normal = normal ?? UP15;
6233
6738
  ring2.hold = hold || 0;
6234
6739
  }
6235
6740
  clear() {
@@ -6249,7 +6754,7 @@ var GameFluidsThree = (() => {
6249
6754
  var FlashLightPool = class {
6250
6755
  constructor(context) {
6251
6756
  __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 })));
6757
+ __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
6758
  __publicField2(this, "fire", [
6254
6759
  { x: 0, y: 0, z: 0, intensity: 0 },
6255
6760
  { x: 0, y: 0, z: 0, intensity: 0 }
@@ -6259,7 +6764,7 @@ var GameFluidsThree = (() => {
6259
6764
  get lit() {
6260
6765
  return this.lights.filter((light) => light.intensity > 0).length;
6261
6766
  }
6262
- flash(point, color, intensity, duration, distance2, force) {
6767
+ flash(point, color, intensity, duration, distance2, force, steady) {
6263
6768
  const { level } = this.context.quality;
6264
6769
  if (level >= 1 && !force) return;
6265
6770
  if (level >= 2 && force !== "boom") return;
@@ -6272,6 +6777,7 @@ var GameFluidsThree = (() => {
6272
6777
  light.peak = intensity;
6273
6778
  light.duration = duration;
6274
6779
  light.t = 0;
6780
+ light.steady = steady === true;
6275
6781
  light.intensity = intensity;
6276
6782
  }
6277
6783
  clear() {
@@ -6283,7 +6789,7 @@ var GameFluidsThree = (() => {
6283
6789
  if (light.intensity <= 0) continue;
6284
6790
  light.t += dt;
6285
6791
  const age = light.t / light.duration;
6286
- light.intensity = age >= 1 ? 0 : light.peak * (1 - age) * (1 - age);
6792
+ light.intensity = age >= 1 ? 0 : light.steady ? light.peak : light.peak * (1 - age) * (1 - age);
6287
6793
  }
6288
6794
  }
6289
6795
  };
@@ -6291,6 +6797,14 @@ var GameFluidsThree = (() => {
6291
6797
  var FIRE_FLICKER_SWAY = 0.25;
6292
6798
  var FIRE_FLICKER_JITTER = 0.08;
6293
6799
  var LENS_FADE = 0.16;
6800
+ function rotate(q, v) {
6801
+ const [qx, qy, qz, qw] = q;
6802
+ const ix = qw * v[0] + qy * v[2] - qz * v[1];
6803
+ const iy = qw * v[1] + qz * v[0] - qx * v[2];
6804
+ const iz = qw * v[2] + qx * v[1] - qy * v[0];
6805
+ const iw = -qx * v[0] - qy * v[1] - qz * v[2];
6806
+ 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];
6807
+ }
6294
6808
  function workshopGround(x, z) {
6295
6809
  return z < -2.9 && z > -4.5 && Math.abs(x) < 13 ? 0.12 : 0;
6296
6810
  }
@@ -6304,6 +6818,8 @@ var GameFluidsThree = (() => {
6304
6818
  __publicField2(this, "decals");
6305
6819
  __publicField2(this, "rings", new WaveRingPool());
6306
6820
  __publicField2(this, "lights");
6821
+ __publicField2(this, "rain", new RainField(this));
6822
+ __publicField2(this, "meshes", new MeshPool());
6307
6823
  __publicField2(this, "screen", createScreenState());
6308
6824
  __publicField2(this, "lens", []);
6309
6825
  __publicField2(this, "post", {});
@@ -6336,7 +6852,11 @@ var GameFluidsThree = (() => {
6336
6852
  place(point) {
6337
6853
  const placement = this.placement;
6338
6854
  if (!placement) return point;
6339
- const { anchor, origin, yaw } = placement;
6855
+ const { anchor, origin, yaw, orientation } = placement;
6856
+ if (orientation) {
6857
+ const turned = rotate(orientation, [point[0] - anchor[0], point[1] - anchor[1], point[2] - anchor[2]]);
6858
+ return [origin[0] + turned[0], origin[1] + turned[1], origin[2] + turned[2]];
6859
+ }
6340
6860
  const x = point[0] - anchor[0];
6341
6861
  const z = point[2] - anchor[2];
6342
6862
  const cos2 = Math.cos(yaw);
@@ -6347,6 +6867,7 @@ var GameFluidsThree = (() => {
6347
6867
  turn(vector) {
6348
6868
  const placement = this.placement;
6349
6869
  if (!placement) return vector;
6870
+ if (placement.orientation) return rotate(placement.orientation, vector);
6350
6871
  const cos2 = Math.cos(placement.yaw);
6351
6872
  const sin2 = Math.sin(placement.yaw);
6352
6873
  return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
@@ -6375,6 +6896,14 @@ var GameFluidsThree = (() => {
6375
6896
  }
6376
6897
  this.accumulators.set(key, carried);
6377
6898
  }
6899
+ /** 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. */
6900
+ mesh(recipe) {
6901
+ const placement = this.placement;
6902
+ const options = placement ? { ...recipe, p: this.place(recipe.p), ...recipe.v ? { v: this.turn(recipe.v) } : {} } : recipe;
6903
+ this.context.trace?.("mesh", [options]);
6904
+ const grouped = recipe.at !== void 0 || recipe.roll !== void 0;
6905
+ return this.meshes.spawn(options, placement && grouped ? placement.orientation ?? [0, Math.sin(placement.yaw / 2), 0, Math.cos(placement.yaw / 2)] : void 0);
6906
+ }
6378
6907
  debrisPiece(recipe) {
6379
6908
  const options = this.placed(recipe);
6380
6909
  this.context.trace?.("debris", [options]);
@@ -6395,10 +6924,11 @@ var GameFluidsThree = (() => {
6395
6924
  this.context.trace?.("ring", args);
6396
6925
  this.rings.add(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6397
6926
  }
6927
+ /** `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
6928
  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]);
6929
+ const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5], recipe[6]] : recipe;
6930
+ this.context.trace?.("flash", args.slice(0, 6));
6931
+ this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
6402
6932
  }
6403
6933
  /** `fireLight` (W400): sways with the clock and flickers with one draw. */
6404
6934
  fireLight(slot, recipe, intensity, time) {
@@ -6423,6 +6953,7 @@ var GameFluidsThree = (() => {
6423
6953
  this.debris.update(dt);
6424
6954
  this.decals.update(dt);
6425
6955
  this.rings.update(dt);
6956
+ this.meshes.update(dt);
6426
6957
  this.lights.update(dt);
6427
6958
  updateScreen(this.screen, dt);
6428
6959
  for (let index = this.lens.length - 1; index >= 0; index--) {
@@ -6439,7 +6970,9 @@ var GameFluidsThree = (() => {
6439
6970
  this.debris.clear();
6440
6971
  this.decals.clear();
6441
6972
  this.rings.clear();
6973
+ this.meshes.clear();
6442
6974
  this.lights.clear();
6975
+ this.rain.clear();
6443
6976
  this.accumulators.clear();
6444
6977
  resetScreenState(this.screen);
6445
6978
  this.lens.length = 0;
@@ -6585,16 +7118,16 @@ var GameFluidsThree = (() => {
6585
7118
  },
6586
7119
  push(point, radius, speed) {
6587
7120
  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];
7121
+ for (const puff3 of core.puffs.items) {
7122
+ const dx = puff3.x - point[0];
7123
+ const dy = puff3.y - point[1];
7124
+ const dz = puff3.z - point[2];
6592
7125
  const distance2 = Math.sqrt(dx * dx + dy * dy + dz * dz);
6593
7126
  if (distance2 >= radius) continue;
6594
7127
  const strength = speed * (1 - distance2 / radius) / (distance2 + 1e-3);
6595
- puff2.vx += dx * strength;
6596
- puff2.vy += dy * strength;
6597
- puff2.vz += dz * strength;
7128
+ puff3.vx += dx * strength;
7129
+ puff3.vy += dy * strength;
7130
+ puff3.vz += dz * strength;
6598
7131
  }
6599
7132
  },
6600
7133
  stop(handle) {
@@ -6611,6 +7144,12 @@ var GameFluidsThree = (() => {
6611
7144
  camera2.y = position[1];
6612
7145
  camera2.z = position[2];
6613
7146
  },
7147
+ setWind(next) {
7148
+ wind.x = next[0];
7149
+ wind.y = next[1];
7150
+ wind.z = next[2];
7151
+ },
7152
+ wind: () => [wind.x, wind.y, wind.z],
6614
7153
  forceLod(scale2) {
6615
7154
  core.clock.forcedLod = scale2;
6616
7155
  },
@@ -6792,11 +7331,11 @@ void main(){
6792
7331
  geometry.setAttribute("aC", attributes[2]);
6793
7332
  geometry.instanceCount = 0;
6794
7333
  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;
7334
+ const mesh2 = new THREE.Mesh(geometry, material);
7335
+ mesh2.name = "vfx-puffs";
7336
+ mesh2.frustumCulled = false;
6798
7337
  return {
6799
- mesh,
7338
+ mesh: mesh2,
6800
7339
  sync() {
6801
7340
  let n = 0;
6802
7341
  for (const p of pool.items) {
@@ -6837,12 +7376,12 @@ void main(){
6837
7376
  geometry.setAttribute("aE", attributes[2]);
6838
7377
  geometry.instanceCount = 0;
6839
7378
  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;
7379
+ const mesh2 = new THREE.Mesh(geometry, material);
7380
+ mesh2.name = "vfx-sparks";
7381
+ mesh2.frustumCulled = false;
7382
+ mesh2.renderOrder = 5;
6844
7383
  return {
6845
- mesh,
7384
+ mesh: mesh2,
6846
7385
  sync() {
6847
7386
  let n = 0;
6848
7387
  for (const s of pool.items) {
@@ -6880,12 +7419,12 @@ void main(){
6880
7419
  geometry.setAttribute("aC", attributes[1]);
6881
7420
  geometry.instanceCount = 0;
6882
7421
  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;
7422
+ const mesh2 = new THREE.Mesh(geometry, material);
7423
+ mesh2.name = "vfx-glows";
7424
+ mesh2.frustumCulled = false;
7425
+ mesh2.renderOrder = 6;
6887
7426
  return {
6888
- mesh,
7427
+ mesh: mesh2,
6889
7428
  sync() {
6890
7429
  let n = 0;
6891
7430
  for (const g of pool.items) {
@@ -6914,14 +7453,14 @@ void main(){
6914
7453
  const rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.85, flatShading: true });
6915
7454
  const plateMaterial = rockMaterial.clone();
6916
7455
  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;
7456
+ const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
7457
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
7458
+ mesh2.castShadow = true;
7459
+ mesh2.receiveShadow = true;
7460
+ mesh2.frustumCulled = false;
7461
+ mesh2.count = 0;
7462
+ mesh2.setColorAt(0, new THREE.Color(1, 1, 1));
7463
+ return mesh2;
6925
7464
  };
6926
7465
  const rocks = make(shapes.rock, rockMaterial);
6927
7466
  const plates = make(shapes.plate, plateMaterial);
@@ -6940,20 +7479,20 @@ void main(){
6940
7479
  euler.set(chunk.rx, chunk.ry, chunk.rz);
6941
7480
  quaternion.setFromEuler(euler);
6942
7481
  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;
7482
+ const mesh2 = chunk.plate ? plates : rocks;
6944
7483
  const index = chunk.plate ? plateCount++ : rockCount++;
6945
- mesh.setMatrixAt(index, matrix);
7484
+ mesh2.setMatrixAt(index, matrix);
6946
7485
  const color = colorOf(chunk);
6947
- const colors = mesh.instanceColor.array;
7486
+ const colors = mesh2.instanceColor.array;
6948
7487
  colors[index * 3] = color[0];
6949
7488
  colors[index * 3 + 1] = color[1];
6950
7489
  colors[index * 3 + 2] = color[2];
6951
7490
  }
6952
7491
  rocks.count = rockCount;
6953
7492
  plates.count = plateCount;
6954
- for (const mesh of [rocks, plates]) {
6955
- mesh.instanceMatrix.needsUpdate = true;
6956
- mesh.instanceColor.needsUpdate = true;
7493
+ for (const mesh2 of [rocks, plates]) {
7494
+ mesh2.instanceMatrix.needsUpdate = true;
7495
+ mesh2.instanceColor.needsUpdate = true;
6957
7496
  }
6958
7497
  return rockCount + plateCount;
6959
7498
  },
@@ -7137,28 +7676,28 @@ void main(){
7137
7676
  position.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
7138
7677
  }
7139
7678
  geometry.computeVertexNormals();
7140
- const mesh = new THREE.Mesh(geometry, material);
7141
- mesh.receiveShadow = true;
7142
- return mesh;
7679
+ const mesh2 = new THREE.Mesh(geometry, material);
7680
+ mesh2.receiveShadow = true;
7681
+ return mesh2;
7143
7682
  }
7144
7683
  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;
7684
+ const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
7685
+ mesh2.position.set(mark.x, mark.y, mark.z);
7686
+ mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
7687
+ mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
7688
+ mesh2.castShadow = true;
7689
+ mesh2.receiveShadow = true;
7690
+ return mesh2;
7152
7691
  }
7153
7692
  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;
7693
+ const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
7694
+ mesh2.position.set(mark.x, mark.y, mark.z);
7695
+ mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
7696
+ mesh2.rotation.y = mark.rotation;
7697
+ mesh2.receiveShadow = true;
7698
+ mesh2.scale.multiplyScalar(0.05);
7699
+ blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
7700
+ return mesh2;
7162
7701
  }
7163
7702
  function build(mark) {
7164
7703
  if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
@@ -7369,6 +7908,73 @@ void main() {
7369
7908
  }
7370
7909
  };
7371
7910
  }
7911
+ var SPHERE_DEFAULT_WIDTH = 8;
7912
+ var SPHERE_DEFAULT_HEIGHT = 6;
7913
+ function geometryOf(THREE, particle) {
7914
+ switch (particle.shape) {
7915
+ case "cone":
7916
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
7917
+ case "cone_open":
7918
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
7919
+ case "sphere":
7920
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
7921
+ case "icosahedron":
7922
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
7923
+ }
7924
+ }
7925
+ function materialOf(THREE, particle) {
7926
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
7927
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
7928
+ }
7929
+ function createMeshLayer(THREE, pool) {
7930
+ const group = new THREE.Group();
7931
+ group.name = "workshop-meshes";
7932
+ const drawn = /* @__PURE__ */ new Map();
7933
+ const frame = new THREE.Quaternion();
7934
+ const euler = new THREE.Euler();
7935
+ const turn = new THREE.Quaternion();
7936
+ const offset = new THREE.Vector3();
7937
+ function release(mesh2) {
7938
+ mesh2.removeFromParent();
7939
+ mesh2.geometry.dispose();
7940
+ mesh2.material.dispose();
7941
+ }
7942
+ return {
7943
+ group,
7944
+ sync() {
7945
+ const live = /* @__PURE__ */ new Set();
7946
+ for (const particle of pool.particles) {
7947
+ live.add(particle.id);
7948
+ let mesh2 = drawn.get(particle.id);
7949
+ if (!mesh2) {
7950
+ mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
7951
+ mesh2.frustumCulled = false;
7952
+ mesh2.renderOrder = particle.additive ? 5 : 3;
7953
+ drawn.set(particle.id, mesh2);
7954
+ group.add(mesh2);
7955
+ }
7956
+ frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
7957
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
7958
+ mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
7959
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
7960
+ mesh2.quaternion.copy(frame).multiply(turn);
7961
+ mesh2.scale.setScalar(particle.scale);
7962
+ mesh2.material.opacity = particle.opacity;
7963
+ }
7964
+ for (const [id, mesh2] of drawn) {
7965
+ if (live.has(id)) continue;
7966
+ release(mesh2);
7967
+ drawn.delete(id);
7968
+ }
7969
+ return pool.count;
7970
+ },
7971
+ dispose() {
7972
+ for (const mesh2 of drawn.values()) release(mesh2);
7973
+ drawn.clear();
7974
+ group.removeFromParent();
7975
+ }
7976
+ };
7977
+ }
7372
7978
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
7373
7979
  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
7980
  var WORKSHOP_SPRITE_FRAGMENT = `uniform float uSoft;varying vec3 vC;varying float vA;
@@ -7444,13 +8050,37 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7444
8050
  }
7445
8051
  };
7446
8052
  }
8053
+ function rainLayer(THREE, rain) {
8054
+ const geometry = new THREE.BufferGeometry();
8055
+ const position = new THREE.BufferAttribute(rain.positions, 3).setUsage(THREE.DynamicDrawUsage);
8056
+ geometry.setAttribute("position", position);
8057
+ geometry.setDrawRange(0, 0);
8058
+ const material = new THREE.LineBasicMaterial({ color: 10466255, transparent: true, opacity: 0.35, depthWrite: false });
8059
+ const lines = new THREE.LineSegments(geometry, material);
8060
+ lines.frustumCulled = false;
8061
+ lines.name = "workshop-rain";
8062
+ return {
8063
+ object: lines,
8064
+ sync() {
8065
+ const drawn = rain.on && rain.count > 0;
8066
+ lines.visible = drawn;
8067
+ geometry.setDrawRange(0, drawn ? rain.count * 2 : 0);
8068
+ position.needsUpdate = true;
8069
+ return drawn;
8070
+ },
8071
+ dispose() {
8072
+ geometry.dispose();
8073
+ material.dispose();
8074
+ }
8075
+ };
8076
+ }
7447
8077
  function debrisLayer(THREE, pool) {
7448
8078
  const geometry = new THREE.BoxGeometry(1, 1, 1);
7449
8079
  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";
8080
+ const mesh2 = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
8081
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8082
+ mesh2.frustumCulled = false;
8083
+ mesh2.name = "workshop-debris";
7454
8084
  const euler = new THREE.Euler();
7455
8085
  const quaternion = new THREE.Quaternion();
7456
8086
  const position = new THREE.Vector3();
@@ -7458,16 +8088,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7458
8088
  const matrix = new THREE.Matrix4();
7459
8089
  const colour = new THREE.Color();
7460
8090
  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));
8091
+ for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh2.setMatrixAt(index, hidden);
8092
+ mesh2.setColorAt(0, colour.setHex(16777215));
7463
8093
  return {
7464
- object: mesh,
8094
+ object: mesh2,
7465
8095
  sync() {
7466
8096
  let drawn = false;
7467
8097
  for (let index = 0; index < DEBRIS_CAPACITY; index++) {
7468
8098
  const piece = pool.pieces[index];
7469
8099
  if (!piece.on) {
7470
- mesh.setMatrixAt(index, hidden);
8100
+ mesh2.setMatrixAt(index, hidden);
7471
8101
  continue;
7472
8102
  }
7473
8103
  drawn = true;
@@ -7475,11 +8105,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7475
8105
  euler.set(piece.rx, piece.ry, piece.rz);
7476
8106
  quaternion.setFromEuler(euler);
7477
8107
  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));
8108
+ mesh2.setMatrixAt(index, matrix);
8109
+ mesh2.setColorAt(index, colour.setHex(piece.color));
7480
8110
  }
7481
- mesh.instanceMatrix.needsUpdate = true;
7482
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8111
+ mesh2.instanceMatrix.needsUpdate = true;
8112
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
7483
8113
  return drawn;
7484
8114
  },
7485
8115
  dispose() {
@@ -7536,11 +8166,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7536
8166
  const dryMesh = new THREE.InstancedMesh(geometry, dry, DECAL_CAPACITY);
7537
8167
  const group = new THREE.Group();
7538
8168
  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);
8169
+ for (const mesh2 of [wetMesh, dryMesh]) {
8170
+ mesh2.frustumCulled = false;
8171
+ mesh2.renderOrder = 1;
8172
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8173
+ group.add(mesh2);
7544
8174
  }
7545
8175
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
7546
8176
  const colour = new THREE.Color();
@@ -7577,13 +8207,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7577
8207
  quaternion.multiply(turn.setFromAxisAngle(forward, index * 2.399963));
7578
8208
  const offset = 4e-3 + index % 40 * 4e-4;
7579
8209
  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);
8210
+ const mesh2 = look.dried ? dryMesh : wetMesh;
8211
+ mesh2.setMatrixAt(index, matrix);
8212
+ mesh2.setColorAt(index, colour);
7583
8213
  }
7584
- for (const mesh of [wetMesh, dryMesh]) {
7585
- mesh.instanceMatrix.needsUpdate = true;
7586
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8214
+ for (const mesh2 of [wetMesh, dryMesh]) {
8215
+ mesh2.instanceMatrix.needsUpdate = true;
8216
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
7587
8217
  }
7588
8218
  return drawn;
7589
8219
  },
@@ -7597,10 +8227,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7597
8227
  function ringLayer(THREE, pool) {
7598
8228
  const geometry = new THREE.RingGeometry(0.86, 1, 40);
7599
8229
  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";
8230
+ const mesh2 = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
8231
+ mesh2.frustumCulled = false;
8232
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
8233
+ mesh2.name = "workshop-rings";
7604
8234
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
7605
8235
  const colour = new THREE.Color();
7606
8236
  const normal = new THREE.Vector3();
@@ -7609,16 +8239,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7609
8239
  const position = new THREE.Vector3();
7610
8240
  const scale2 = new THREE.Vector3();
7611
8241
  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);
8242
+ mesh2.setColorAt(0, colour.setHex(0));
8243
+ for (let index = 0; index < RING_CAPACITY3; index++) mesh2.setMatrixAt(index, hidden);
7614
8244
  return {
7615
- object: mesh,
8245
+ object: mesh2,
7616
8246
  sync() {
7617
8247
  let drawn = false;
7618
8248
  for (let index = 0; index < RING_CAPACITY3; index++) {
7619
8249
  const ring2 = pool.rings[index];
7620
8250
  if (!ring2.on) {
7621
- mesh.setMatrixAt(index, hidden);
8251
+ mesh2.setMatrixAt(index, hidden);
7622
8252
  continue;
7623
8253
  }
7624
8254
  drawn = true;
@@ -7626,11 +8256,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7626
8256
  const radius = ring2.from + (ring2.to - ring2.from) * (1 - Math.pow(1 - k, 2));
7627
8257
  quaternion.setFromUnitVectors(forward, normal.set(ring2.normal[0], ring2.normal[1], ring2.normal[2]));
7628
8258
  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));
8259
+ mesh2.setMatrixAt(index, matrix);
8260
+ mesh2.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
7631
8261
  }
7632
- mesh.instanceMatrix.needsUpdate = true;
7633
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
8262
+ mesh2.instanceMatrix.needsUpdate = true;
8263
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
7634
8264
  return drawn;
7635
8265
  },
7636
8266
  dispose() {
@@ -7646,11 +8276,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7646
8276
  spriteLayer(THREE, engine.add, true, 1, pointScale),
7647
8277
  spriteLayer(THREE, engine.normal, false, 0.55, pointScale),
7648
8278
  streakLayer(THREE, engine.streaks),
8279
+ rainLayer(THREE, engine.rain),
7649
8280
  debrisLayer(THREE, engine.debris),
7650
8281
  decalLayer(THREE, engine.decals),
7651
8282
  ringLayer(THREE, engine.rings)
7652
8283
  ];
7653
8284
  for (const layer2 of layers) group.add(layer2.object);
8285
+ const meshes = createMeshLayer(THREE, engine.meshes);
8286
+ group.add(meshes.group);
7654
8287
  const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
7655
8288
  const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
7656
8289
  for (const light of [...flashLights, ...fireLights]) group.add(light);
@@ -7659,6 +8292,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7659
8292
  sync() {
7660
8293
  let drawCalls = 0;
7661
8294
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
8295
+ drawCalls += meshes.sync();
7662
8296
  engine.lights.lights.forEach((source, index) => {
7663
8297
  const light = flashLights[index];
7664
8298
  light.position.set(source.x, source.y, source.z);
@@ -7675,6 +8309,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7675
8309
  },
7676
8310
  dispose() {
7677
8311
  for (const layer2 of layers) layer2.dispose();
8312
+ meshes.dispose();
7678
8313
  group.clear();
7679
8314
  }
7680
8315
  };
@@ -7790,8 +8425,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
7790
8425
  return world.play(request.effect, request.point, {
7791
8426
  ...request.direction ? { direction: request.direction } : {},
7792
8427
  ...request.normal ? { normal: request.normal } : {},
8428
+ ...request.orientation ? { orientation: request.orientation } : {},
7793
8429
  ...request.seed !== void 0 ? { seed: request.seed } : {},
7794
- ...request.param !== void 0 ? { param: request.param } : {}
8430
+ ...request.param !== void 0 ? { param: request.param } : {},
8431
+ ...request.values ? { values: request.values } : {}
7795
8432
  });
7796
8433
  },
7797
8434
  stop: (handle) => world.stop(handle),
@@ -8605,7 +9242,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8605
9242
  if (lastRow < 0) return;
8606
9243
  const profile = profileOf2();
8607
9244
  const rainScale = 1 - options.weather().rain;
8608
- const seconds = dryingSeconds * rainScale;
9245
+ const seconds = dryingSeconds * rainScale * (options.weather().drying ?? 1);
8609
9246
  for (let row = firstRow; row <= lastRow; row++) {
8610
9247
  const offset = row * width;
8611
9248
  for (let column = firstColumn; column <= lastColumn; column++) {
@@ -8729,6 +9366,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8729
9366
  }
8730
9367
  var DEFAULT_STEP_RATE = 180;
8731
9368
  var DEFAULT_MAX_STEPS = 6;
9369
+ var WASH_BELOW_DRYNESS = 0.5;
8732
9370
  function createFilm(options) {
8733
9371
  const { layout, random } = options;
8734
9372
  const cellCount = layout.width * layout.height;
@@ -8739,7 +9377,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8739
9377
  dryness: new Float32Array(cellCount),
8740
9378
  dampness: new Float32Array(cellCount)
8741
9379
  };
8742
- let weatherState = { rain: 0 };
9380
+ let weatherState = { rain: 0, drying: 1 };
8743
9381
  let currentProfile = options.profile;
8744
9382
  const context = { field, random, profile: () => currentProfile };
8745
9383
  const drops = createDropPool(layout, context);
@@ -8777,9 +9415,29 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8777
9415
  simulator.advance({ dt, dryingSeconds });
8778
9416
  },
8779
9417
  setWeather(weather) {
8780
- weatherState = { rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)) };
9418
+ weatherState = {
9419
+ rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)),
9420
+ drying: Math.max(0, weather.drying ?? weatherState.drying ?? 1)
9421
+ };
8781
9422
  },
8782
9423
  weather: () => ({ ...weatherState }),
9424
+ wash(maxDryness = WASH_BELOW_DRYNESS) {
9425
+ simulator.refreshBounds();
9426
+ const { firstRow, lastRow, firstColumn, lastColumn } = simulator.bounds;
9427
+ let cleared = 0;
9428
+ for (let row = firstRow; row <= lastRow; row++) {
9429
+ for (let column = firstColumn; column <= lastColumn; column++) {
9430
+ const index = row * layout.width + column;
9431
+ if (field.thickness[index] > 0 && field.dryness[index] < maxDryness) {
9432
+ field.thickness[index] = 0;
9433
+ field.dryness[index] = 0;
9434
+ field.dampness[index] = 0;
9435
+ cleared++;
9436
+ }
9437
+ }
9438
+ }
9439
+ return cleared;
9440
+ },
8783
9441
  clear() {
8784
9442
  field.thickness.fill(0);
8785
9443
  field.dryness.fill(0);
@@ -9292,11 +9950,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9292
9950
  const index = [];
9293
9951
  for (let i = 0; i < this.ring; i++) index.push(0, 1 + i, 1 + (i + 1) % this.ring);
9294
9952
  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 };
9953
+ const mesh2 = new T.Mesh(geometry);
9954
+ mesh2.name = name;
9955
+ mesh2.frustumCulled = false;
9956
+ mesh2.renderOrder = order;
9957
+ return { mesh: mesh2, position, color };
9300
9958
  }
9301
9959
  assign(def, materials) {
9302
9960
  this.outer.mesh.material = materials.stainFilm(def);
@@ -10036,12 +10694,12 @@ void main(){
10036
10694
  this.group = new THREE.Group();
10037
10695
  this.group.name = "film_grid";
10038
10696
  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);
10697
+ const mesh2 = new THREE.Mesh(createStripGeometry(THREE, layout, region), this.material.material);
10698
+ mesh2.frustumCulled = false;
10699
+ mesh2.renderOrder = 3;
10700
+ mesh2.name = `film_${region.id}`;
10701
+ this.strips.push(mesh2);
10702
+ this.group.add(mesh2);
10045
10703
  }
10046
10704
  this.dropsView = createFilmDrops(THREE, layout.rules.dropCapacity, profile);
10047
10705
  this.group.add(this.dropsView.drops, this.dropsView.bead);
@@ -10086,6 +10744,19 @@ void main(){
10086
10744
  this.pending = this.union(this.pending, rect);
10087
10745
  return this.takePending();
10088
10746
  }
10747
+ /** Clears the cells less than `maxDryness` dry and sends the rectangle that held them up with the next update. Returns how many it cleared. */
10748
+ wash(maxDryness) {
10749
+ const cleared = this.film.wash(maxDryness);
10750
+ if (cleared > 0) {
10751
+ const bounds = this.film.simulator.bounds;
10752
+ if (bounds.lastRow >= 0) {
10753
+ const rect = { firstRow: bounds.firstRow, lastRow: bounds.lastRow, firstColumn: bounds.firstColumn, lastColumn: bounds.lastColumn };
10754
+ this.encode(rect);
10755
+ this.pending = this.union(this.pending, rect);
10756
+ }
10757
+ }
10758
+ return cleared;
10759
+ }
10089
10760
  takePending() {
10090
10761
  const rect = this.pending;
10091
10762
  this.pending = null;
@@ -10141,7 +10812,7 @@ void main(){
10141
10812
  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
10813
  }
10143
10814
  dispose() {
10144
- for (const mesh of this.strips) mesh.geometry.dispose();
10815
+ for (const mesh2 of this.strips) mesh2.geometry.dispose();
10145
10816
  this.material.dispose();
10146
10817
  this.dropsView.dispose();
10147
10818
  this.texture.dispose();
@@ -10157,11 +10828,12 @@ void main(){
10157
10828
  var DEFAULT_GRID_METRES = 2;
10158
10829
  var DEFAULT_CELL2 = 0.0125;
10159
10830
  var BAKE_RANGE_FACTOR = 1.25;
10831
+ var DEFAULT_WASHABLE = ["blood", "old_blood"];
10160
10832
  var EDGE_MARGIN = 0.35;
10161
10833
  var FLOOR_REACH = 0.3;
10162
10834
  var DROP_HEIGHT = 2.5;
10163
10835
  var DEFAULT_DROP_RADIUS = 3;
10164
- var FALL_SPEED = 2;
10836
+ var FALL_SPEED2 = 2;
10165
10837
  var VISIBLE_THICKNESS3 = 0.035;
10166
10838
  var DEFAULT_HALF = 50;
10167
10839
  var FREE_AFTER_EMPTY = 2;
@@ -10182,6 +10854,7 @@ void main(){
10182
10854
  __publicField(this, "staging", /* @__PURE__ */ new Map());
10183
10855
  __publicField(this, "now");
10184
10856
  __publicField(this, "rain", 0);
10857
+ __publicField(this, "drying", 1);
10185
10858
  __publicField(this, "time", 0);
10186
10859
  __publicField(this, "bakedCount", 0);
10187
10860
  __publicField(this, "last", { grids: 0, drops: 0, wetCells: 0, simMs: 0, starved: 0, baked: 0, uploadedTexels: 0 });
@@ -10201,13 +10874,24 @@ void main(){
10201
10874
  }
10202
10875
  setWeather(weather) {
10203
10876
  this.rain = Math.min(1, Math.max(0, weather.rain));
10204
- for (const grid of this.grids) grid.film.setWeather({ rain: this.rain });
10877
+ if (weather.drying !== void 0) this.drying = Math.max(0, weather.drying);
10878
+ for (const grid of this.grids) grid.film.setWeather({ rain: this.rain, drying: this.drying });
10879
+ }
10880
+ /**
10881
+ * 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
10882
+ * cells that are less than `maxDryness` dry (default 0.5) are cleared. Returns how many cells it cleared.
10883
+ */
10884
+ wash(maxDryness) {
10885
+ const washable = this.options.washable ?? DEFAULT_WASHABLE;
10886
+ let cleared = 0;
10887
+ for (const grid of this.grids) if (washable.includes(grid.profile.id)) cleared += grid.wash(maxDryness);
10888
+ return cleared;
10205
10889
  }
10206
10890
  /** 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
10891
  launch(request) {
10208
10892
  const grid = this.gridFor(request.fluid, request.point, request.surfaces, request.tint);
10209
10893
  if (!grid) return null;
10210
- const velocity = request.velocity ?? [0, -FALL_SPEED, 0];
10894
+ const velocity = request.velocity ?? [0, -FALL_SPEED2, 0];
10211
10895
  grid.film.launch(request.point, velocity, request.radius ?? DEFAULT_DROP_RADIUS);
10212
10896
  return grid;
10213
10897
  }
@@ -10232,7 +10916,7 @@ void main(){
10232
10916
  const layout = layoutFromSurfaces({ ...found, center: point, columns: this.columns(), rows: this.columns(), cellSize: this.options.cellSize ?? DEFAULT_CELL2 });
10233
10917
  if (this.grids.length >= (this.options.maxGrids ?? DEFAULT_MAX_GRIDS)) this.evict(point);
10234
10918
  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 });
10919
+ grid.film.setWeather({ rain: this.rain, drying: this.drying });
10236
10920
  this.grids.push(grid);
10237
10921
  this.group.add(grid.group);
10238
10922
  return grid;
@@ -10309,7 +10993,7 @@ void main(){
10309
10993
  /** Whether `point` is over the grid, away from its edge, so a splash there does not fall off the strip. */
10310
10994
  covers(grid, point) {
10311
10995
  const layout = grid.film.layout;
10312
- const wide = layout.width * layout.cellSize;
10996
+ const wide2 = layout.width * layout.cellSize;
10313
10997
  for (const region of layout.regions) {
10314
10998
  const local2 = [point[0] - region.origin[0], point[1] - region.origin[1], point[2] - region.origin[2]];
10315
10999
  const above2 = local2[0] * region.normal[0] + local2[1] * region.normal[1] + local2[2] * region.normal[2];
@@ -10317,7 +11001,7 @@ void main(){
10317
11001
  const sideways = local2[0] * region.columnAxis[0] + local2[1] * region.columnAxis[1] + local2[2] * region.columnAxis[2];
10318
11002
  const down = local2[0] * region.rowAxis[0] + local2[1] * region.rowAxis[1] + local2[2] * region.rowAxis[2];
10319
11003
  const length2 = region.rowCount * layout.cellSize;
10320
- if (sideways > EDGE_MARGIN && sideways < wide - EDGE_MARGIN && down > EDGE_MARGIN && down < length2 - EDGE_MARGIN) return true;
11004
+ if (sideways > EDGE_MARGIN && sideways < wide2 - EDGE_MARGIN && down > EDGE_MARGIN && down < length2 - EDGE_MARGIN) return true;
10321
11005
  }
10322
11006
  return false;
10323
11007
  }
@@ -10470,6 +11154,7 @@ void main(){
10470
11154
  return landed;
10471
11155
  },
10472
11156
  setWeather: (weather) => manager.setWeather(weather),
11157
+ wash: (maxDryness) => manager.wash(maxDryness),
10473
11158
  update(dt) {
10474
11159
  if (leaks.size === 0) return;
10475
11160
  const streams = new Map(fluids.streams().map((stream) => [stream.id, stream]));