@adatechnology/game-test-room 0.9.0 → 0.11.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.
@@ -4587,6 +4587,56 @@ var GameTestRoom = (() => {
4587
4587
  };
4588
4588
  }
4589
4589
  var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
4590
+ var ATTACK = 2e-3;
4591
+ function lengthOf(d, attack) {
4592
+ if (typeof d === "number") return attack + d;
4593
+ if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
4594
+ return { ...d, add: (d.add ?? 0) + attack };
4595
+ }
4596
+ var envelope = (attack) => [
4597
+ [0, 1e-4, "set", "fixed"],
4598
+ [attack, 1, "exp"],
4599
+ ["1d", 1e-4, "exp", "fixed"]
4600
+ ];
4601
+ function sceneNoise(o) {
4602
+ const a = o.a ?? ATTACK;
4603
+ const over = typeof o.d === "number" ? o.d : 0;
4604
+ return {
4605
+ filters: {
4606
+ mode: "serial",
4607
+ bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
4608
+ },
4609
+ duration: lengthOf(o.d, a),
4610
+ gain: o.vol,
4611
+ ...o.delay === void 0 ? {} : { at: o.delay },
4612
+ ...o.wet === void 0 ? {} : { wet: o.wet },
4613
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
4614
+ envelope: envelope(a)
4615
+ };
4616
+ }
4617
+ function sceneTone(o) {
4618
+ return {
4619
+ source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
4620
+ duration: lengthOf(o.d, ATTACK),
4621
+ gain: o.vol,
4622
+ ...o.delay === void 0 ? {} : { at: o.delay },
4623
+ ...o.wet === void 0 ? {} : { wet: o.wet },
4624
+ envelope: envelope(ATTACK)
4625
+ };
4626
+ }
4627
+ var sceneMany = (count, make) => Array.from({ length: count }, make);
4628
+ function sceneRecipe(id, layers, line, options = {}) {
4629
+ return {
4630
+ id,
4631
+ bus: options.bus ?? "world",
4632
+ pan: true,
4633
+ layers,
4634
+ falloff: DISTANCE_CURVES.art_scene,
4635
+ ...options.vars ? { vars: options.vars } : {},
4636
+ ...options.group ? { group: options.group } : {},
4637
+ sources: [`cena:${line}`]
4638
+ };
4639
+ }
4590
4640
  function fillNoise(random, length3) {
4591
4641
  const samples = new Float32Array(length3);
4592
4642
  for (let index = 0; index < length3; index++) samples[index] = random() * 2 - 1;
@@ -6859,77 +6909,77 @@ var GameTestRoom = (() => {
6859
6909
  }));
6860
6910
  }
6861
6911
  function spawnPuff(pool, random, init) {
6862
- const puff2 = pool.acquire();
6863
- puff2.x = init.x ?? 0;
6864
- puff2.y = init.y ?? 0;
6865
- puff2.z = init.z ?? 0;
6866
- puff2.vx = init.vx ?? 0;
6867
- puff2.vy = init.vy ?? 0;
6868
- puff2.vz = init.vz ?? 0;
6869
- puff2.s0 = init.s0 ?? 0.2;
6870
- puff2.s1 = init.s1 ?? 1;
6871
- puff2.sg = init.sg ?? 0.1;
6872
- puff2.gk = init.gk ?? 6;
6873
- puff2.life = init.life ?? 3;
6874
- puff2.t = 0;
6875
- puff2.h0 = init.h0 ?? 0;
6876
- puff2.hl = init.hl ?? 0.4;
6877
- puff2.drag = init.drag ?? 3;
6878
- puff2.buoy = init.buoy ?? 0.5;
6879
- puff2.grav = init.grav ?? 0;
6880
- puff2.kind = init.kind ?? 0;
6881
- puff2.seed = random.next();
6882
- puff2.wind = init.wind ?? 1;
6883
- puff2.color = init.color ?? SMOKE_COLORS.grey;
6884
- puff2.ring = init.ring ?? null;
6885
- puff2.ph = init.ph ?? 0;
6886
- puff2.th = init.th ?? 0;
6887
- puff2.rng = random;
6888
- puff2.age = 0;
6889
- puff2.heat = 0;
6890
- puff2.size = 0;
6891
- return puff2;
6892
- }
6893
- function ageOrKeep2(puff2, dt) {
6894
- puff2.t += dt;
6895
- return puff2.t < puff2.life;
6896
- }
6897
- function moveOnRing(puff2, ring2, wind) {
6912
+ const puff3 = pool.acquire();
6913
+ puff3.x = init.x ?? 0;
6914
+ puff3.y = init.y ?? 0;
6915
+ puff3.z = init.z ?? 0;
6916
+ puff3.vx = init.vx ?? 0;
6917
+ puff3.vy = init.vy ?? 0;
6918
+ puff3.vz = init.vz ?? 0;
6919
+ puff3.s0 = init.s0 ?? 0.2;
6920
+ puff3.s1 = init.s1 ?? 1;
6921
+ puff3.sg = init.sg ?? 0.1;
6922
+ puff3.gk = init.gk ?? 6;
6923
+ puff3.life = init.life ?? 3;
6924
+ puff3.t = 0;
6925
+ puff3.h0 = init.h0 ?? 0;
6926
+ puff3.hl = init.hl ?? 0.4;
6927
+ puff3.drag = init.drag ?? 3;
6928
+ puff3.buoy = init.buoy ?? 0.5;
6929
+ puff3.grav = init.grav ?? 0;
6930
+ puff3.kind = init.kind ?? 0;
6931
+ puff3.seed = random.next();
6932
+ puff3.wind = init.wind ?? 1;
6933
+ puff3.color = init.color ?? SMOKE_COLORS.grey;
6934
+ puff3.ring = init.ring ?? null;
6935
+ puff3.ph = init.ph ?? 0;
6936
+ puff3.th = init.th ?? 0;
6937
+ puff3.rng = random;
6938
+ puff3.age = 0;
6939
+ puff3.heat = 0;
6940
+ puff3.size = 0;
6941
+ return puff3;
6942
+ }
6943
+ function ageOrKeep2(puff3, dt) {
6944
+ puff3.t += dt;
6945
+ return puff3.t < puff3.life;
6946
+ }
6947
+ function moveOnRing(puff3, ring2, wind) {
6898
6948
  const ringTime = ring2.t;
6899
6949
  const centerY = ring2.y0 + ring2.rise * (1 - Math.exp(-ringTime * ring2.rk));
6900
6950
  const majorRadius = ring2.R0 + (ring2.R1 - ring2.R0) * (1 - Math.exp(-ringTime * 1.6));
6901
- const theta = puff2.th + ringTime * ring2.roll;
6951
+ const theta = puff3.th + ringTime * ring2.roll;
6902
6952
  const minorRadius = ring2.r * (0.8 + 0.4 * Math.min(1, ringTime));
6903
6953
  const radius = majorRadius + minorRadius * Math.cos(theta);
6904
- puff2.x = ring2.x + Math.cos(puff2.ph) * radius + wind.x * ringTime * 0.6;
6905
- puff2.z = ring2.z + Math.sin(puff2.ph) * radius + wind.z * ringTime * 0.6;
6906
- puff2.y = centerY + minorRadius * Math.sin(theta);
6907
- }
6908
- function moveFree(puff2, dt, age, heat, wind) {
6909
- const damping = Math.exp(-puff2.drag * dt);
6910
- puff2.vx *= damping;
6911
- puff2.vy *= damping;
6912
- puff2.vz *= damping;
6913
- puff2.vy += (puff2.buoy * (heat > 0 ? 1.6 : 1) - puff2.grav) * dt;
6914
- puff2.x += (puff2.vx + wind.x * puff2.wind * age) * dt;
6915
- puff2.y += puff2.vy * dt;
6916
- puff2.z += (puff2.vz + wind.z * puff2.wind * age) * dt;
6917
- if (puff2.y < puff2.s0 * FLOOR_FRACTION) {
6918
- puff2.y = puff2.s0 * FLOOR_FRACTION;
6919
- if (puff2.vy < 0) puff2.vy *= FLOOR_BOUNCE;
6954
+ puff3.x = ring2.x + Math.cos(puff3.ph) * radius + wind.x * ringTime * 0.6;
6955
+ puff3.z = ring2.z + Math.sin(puff3.ph) * radius + wind.z * ringTime * 0.6;
6956
+ puff3.y = centerY + minorRadius * Math.sin(theta);
6957
+ }
6958
+ function moveFree(puff3, dt, age, heat, wind) {
6959
+ const damping = Math.exp(-puff3.drag * dt);
6960
+ puff3.vx *= damping;
6961
+ puff3.vy *= damping;
6962
+ puff3.vz *= damping;
6963
+ puff3.vy += (puff3.buoy * (heat > 0 ? 1.6 : 1) - puff3.grav) * dt;
6964
+ puff3.x += (puff3.vx + wind.x * puff3.wind * age) * dt;
6965
+ puff3.y += puff3.vy * dt;
6966
+ puff3.z += (puff3.vz + wind.z * puff3.wind * age) * dt;
6967
+ if (puff3.y < puff3.s0 * FLOOR_FRACTION) {
6968
+ puff3.y = puff3.s0 * FLOOR_FRACTION;
6969
+ if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
6920
6970
  }
6921
6971
  }
6922
6972
  function updatePuffs(pool, dt, wind) {
6923
6973
  pool.compact(ageOrKeep2, dt);
6924
- for (const puff2 of pool.items) {
6925
- const age = puff2.t / puff2.life;
6926
- const heat = puff2.h0 > 0 ? puff2.h0 * Math.pow(Math.max(0, 1 - puff2.t / puff2.hl), 1.25) : 0;
6927
- if (puff2.ring) moveOnRing(puff2, puff2.ring, wind);
6928
- else moveFree(puff2, dt, age, heat, wind);
6929
- const size = puff2.s0 + (puff2.s1 - puff2.s0) * (1 - Math.exp(-puff2.t * puff2.gk)) + puff2.sg * Math.min(puff2.t, GROWTH_CAP_SECONDS);
6930
- puff2.size = size * Math.min(1, puff2.t * ARRIVAL_FRAME + ARRIVAL_BASE);
6931
- puff2.age = age;
6932
- puff2.heat = heat;
6974
+ for (const puff3 of pool.items) {
6975
+ const age = puff3.t / puff3.life;
6976
+ const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
6977
+ if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
6978
+ else moveFree(puff3, dt, age, heat, wind);
6979
+ const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
6980
+ puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
6981
+ puff3.age = age;
6982
+ puff3.heat = heat;
6933
6983
  }
6934
6984
  }
6935
6985
  var GROUND_LIFT = 0.02;
@@ -6986,9 +7036,9 @@ var GameTestRoom = (() => {
6986
7036
  var HOT_GLOW_COLOR = [1, 0.45, 0.12];
6987
7037
  function addPuff(core, random, init) {
6988
7038
  core.spawned.puffs++;
6989
- const puff2 = spawnPuff(core.puffs, random, init);
6990
- core.trace?.puff?.(puff2);
6991
- return puff2;
7039
+ const puff3 = spawnPuff(core.puffs, random, init);
7040
+ core.trace?.puff?.(puff3);
7041
+ return puff3;
6992
7042
  }
6993
7043
  function addSpark(core, init) {
6994
7044
  core.spawned.sparks++;
@@ -7170,6 +7220,7 @@ var GameTestRoom = (() => {
7170
7220
  var neg = call("neg");
7171
7221
  var abs = call("abs");
7172
7222
  var sqrt = call("sqrt");
7223
+ var exp = call("exp");
7173
7224
  var sin = call("sin");
7174
7225
  var cos = call("cos");
7175
7226
  var atan2 = call("atan2");
@@ -7215,10 +7266,12 @@ var GameTestRoom = (() => {
7215
7266
  var INDEX = { $: "index" };
7216
7267
  var QUALITY = { $: "quality" };
7217
7268
  var FRESH = { $: "fresh" };
7269
+ var DIRECTION = { $: "direction" };
7218
7270
  var CAMERA_POSITION = { $: "cameraPosition" };
7219
7271
  var burst = (system, count, spawn2) => ({ op: "burst", system, count, spawn: spawn2 });
7220
7272
  var spawn = (system, options) => ({ op: "spawn", system, spawn: options });
7221
7273
  var emit = (key, system, rate, options) => ({ op: "emit", key, system, rate, spawn: options });
7274
+ var mesh = (options) => ({ op: "mesh", spawn: options });
7222
7275
  var debris = (options) => ({ op: "debris", spawn: options });
7223
7276
  var decal = (...args) => ({ op: "decal", args });
7224
7277
  var ring = (...args) => ({ op: "ring", args });
@@ -8230,6 +8283,241 @@ var GameTestRoom = (() => {
8230
8283
  )
8231
8284
  ]
8232
8285
  };
8286
+ var SPOTS_X = [-3, 1, 5];
8287
+ var SPOTS_Z = [-1, -2.4, 0.4];
8288
+ var FOG_AND_ASH = {
8289
+ id: "fog_and_ash",
8290
+ pageId: "neblina",
8291
+ team: "world",
8292
+ budget: { particles: 120, lights: 1 },
8293
+ loop: 0,
8294
+ view: { eye: [3, 1.8, 7.5], target: [-3, 1.2, -1] },
8295
+ setup: [
8296
+ cue("fog", { density: 0.075 }),
8297
+ cue("lamp_cone", { visible: true }),
8298
+ cue("zombie_visible", { index: -1, visible: false }),
8299
+ repeat(
8300
+ SPOTS_X.length,
8301
+ set("turn", rnd(-0.6, 0.6)),
8302
+ behavior("walkers", "place", { index: INDEX, x: pick(SPOTS_X, INDEX), z: pick(SPOTS_Z, INDEX), facing: variable("turn") })
8303
+ )
8304
+ ],
8305
+ timeline: [],
8306
+ tick: [
8307
+ 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 }),
8308
+ 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 })
8309
+ ]
8310
+ };
8311
+ var RAIN_CAPACITY = 4e3;
8312
+ var FALL_SPEED = 15;
8313
+ var WIND_PER_FALL = 0.07;
8314
+ var STREAK_SLANT = 0.04;
8315
+ var STREAK_LENGTH = 0.6;
8316
+ var BOX_X = [-15, 15];
8317
+ var BOX_Z = [-6, 16];
8318
+ var START_HEIGHT = [0, 16];
8319
+ var REENTRY_HEIGHT = [14, 17];
8320
+ var SPLASH_CHANCE = 0.35;
8321
+ var SPLASH_RANGE = 12;
8322
+ var SPRITE_CHANCE = 0.5;
8323
+ var UP9 = [0, 1, 0];
8324
+ var WORKSHOP_PUDDLES = [
8325
+ [-1.5, 2.2, 1.6, 0.9],
8326
+ [3, 3.6, 1.1, 0.7],
8327
+ [-6, 3.2, 1.3, 0.8],
8328
+ [1.4, -2.2, 0.9, 0.5]
8329
+ ];
8330
+ var inPuddle = (puddles, x, z) => puddles.some((p) => ((x - p[0]) / p[2]) ** 2 + ((z - p[1]) / p[3]) ** 2 < 1);
8331
+ var RainField = class {
8332
+ constructor(engine) {
8333
+ __publicField(this, "engine", engine);
8334
+ __publicField(this, "positions", new Float32Array(RAIN_CAPACITY * 6));
8335
+ __publicField(this, "count", 0);
8336
+ __publicField(this, "wind", 1);
8337
+ __publicField(this, "on", false);
8338
+ __publicField(this, "centerX", 0);
8339
+ __publicField(this, "centerZ", 0);
8340
+ __publicField(this, "puddles", WORKSHOP_PUDDLES);
8341
+ __publicField(this, "puddleAt");
8342
+ }
8343
+ /** `initRain` (W321): `count` streaks (thinned by the quality), each drawn at a random place in the box. */
8344
+ init(count, wind) {
8345
+ const { random } = this.engine;
8346
+ this.count = Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult));
8347
+ this.wind = wind;
8348
+ this.on = true;
8349
+ for (let index = 0; index < this.count; index++) {
8350
+ const offset = index * 6;
8351
+ const x = random.between(BOX_X[0], BOX_X[1]);
8352
+ const y = random.between(START_HEIGHT[0], START_HEIGHT[1]);
8353
+ const z = random.between(BOX_Z[0], BOX_Z[1]);
8354
+ this.set(offset, x, y, z);
8355
+ }
8356
+ }
8357
+ /**
8358
+ * 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
8359
+ * starts at a random place of the box, as `init` draws it) and the wind slants them. The golden never calls it.
8360
+ */
8361
+ drive(count, wind, centerX, centerZ) {
8362
+ const { random } = this.engine;
8363
+ const target = Math.max(0, Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult)));
8364
+ this.wind = wind;
8365
+ this.centerX = centerX;
8366
+ this.centerZ = centerZ;
8367
+ this.on = target > 0;
8368
+ for (let index = this.count; index < target; index++) {
8369
+ 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);
8370
+ }
8371
+ this.count = target;
8372
+ }
8373
+ /** `updateRain` (W322-329). `camera` is where the eye is; `splash` lets the drops that land leave a splash. */
8374
+ update(dt, splash, camera2) {
8375
+ if (!this.on) return;
8376
+ const { random, context } = this.engine;
8377
+ const positions = this.positions;
8378
+ const fall = FALL_SPEED * dt;
8379
+ const slant = this.wind * fall * WIND_PER_FALL;
8380
+ const reach = SPLASH_RANGE / (1 + context.quality.level);
8381
+ for (let index = 0; index < this.count; index++) {
8382
+ const o = index * 6;
8383
+ positions[o] += slant;
8384
+ positions[o + 3] += slant;
8385
+ positions[o + 1] -= fall;
8386
+ positions[o + 4] -= fall;
8387
+ const ground = context.groundAt(positions[o], positions[o + 2]);
8388
+ if (positions[o + 4] < ground) {
8389
+ const x = positions[o];
8390
+ const z = positions[o + 2];
8391
+ if (splash && random.next() < SPLASH_CHANCE) {
8392
+ const distance4 = Math.hypot(x - camera2[0], z - camera2[2]);
8393
+ if (distance4 < reach) {
8394
+ if (random.next() < SPRITE_CHANCE) {
8395
+ 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 });
8396
+ }
8397
+ if (this.puddleAt ? this.puddleAt(x, z) : inPuddle(this.puddles, x, z)) {
8398
+ this.engine.ring([[x, ground + 0.012, z], 0.01, random.between(0.12, 0.25), random.between(0.4, 0.6), 6978192]);
8399
+ }
8400
+ }
8401
+ }
8402
+ const nx = random.between(BOX_X[0], BOX_X[1]) + this.centerX;
8403
+ const ny = random.between(REENTRY_HEIGHT[0], REENTRY_HEIGHT[1]);
8404
+ const nz = random.between(BOX_Z[0], BOX_Z[1]) + this.centerZ;
8405
+ this.set(o, nx, ny, nz);
8406
+ }
8407
+ }
8408
+ }
8409
+ /** `rain.visible = false; RN.on = false` (W440): the field stops; the streaks stay where they were. */
8410
+ clear() {
8411
+ this.on = false;
8412
+ }
8413
+ set(offset, x, y, z) {
8414
+ const positions = this.positions;
8415
+ positions[offset] = x;
8416
+ positions[offset + 1] = y;
8417
+ positions[offset + 2] = z;
8418
+ positions[offset + 3] = x + this.wind * STREAK_SLANT;
8419
+ positions[offset + 4] = y - STREAK_LENGTH;
8420
+ positions[offset + 5] = z;
8421
+ }
8422
+ };
8423
+ var DROPS = [900, 2200, 4e3];
8424
+ var SLANT = [0.3, 1.2, 4.5];
8425
+ var STORM = 2;
8426
+ 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)));
8427
+ var RAIN_AND_PUDDLES = {
8428
+ id: "rain_and_puddles",
8429
+ pageId: "chuva",
8430
+ team: "world",
8431
+ params: { id: "intensity", options: ["drizzle", "rain", "storm"], default: 1 },
8432
+ budget: { particles: 4e3, decals: 4 },
8433
+ loop: 0,
8434
+ view: { eye: [2, 2, 7], target: [-1, 0.6, 1] },
8435
+ setup: [
8436
+ cue("lamp_cone", { visible: true }),
8437
+ ...WORKSHOP_PUDDLES.map(([x, z, radiusX, radiusZ]) => decal(x, 0, z, radiusX * 2, 790550, { grow: 0, life: 1e6, ar: radiusZ / radiusX }))
8438
+ ],
8439
+ timeline: [
8440
+ 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])) }),
8441
+ set("nextStrike", rnd(2, 4)),
8442
+ set("flash", -1)
8443
+ ],
8444
+ tick: [
8445
+ behavior("rain", "update", { dt: DELTA, splash: gt(PARAM, 0) }),
8446
+ when(eq(PARAM, STORM), [
8447
+ when(gt(TIME, variable("nextStrike")), [set("nextStrike", add(TIME, rnd(3.5, 6))), set("flash", 0)]),
8448
+ when(ge(variable("flash"), 0), [
8449
+ set("flash", add(variable("flash"), DELTA)),
8450
+ set("light", FLASH_STEPS),
8451
+ cue("lightning", { intensity: variable("light") }),
8452
+ screen({ max: { white: mul(variable("light"), 0.12) } }),
8453
+ when(gt(variable("flash"), 0.4), [set("flash", -1), cue("lightning", { intensity: 0 })])
8454
+ ]),
8455
+ 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 })
8456
+ ])
8457
+ ]
8458
+ };
8459
+ var CAR_ORIGIN2 = [5.6, 0, 0.8];
8460
+ var CAR_ROTATION2 = [0, -0.4, 0];
8461
+ var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
8462
+ var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
8463
+ var BEAM_FROM = carPoint(0, 0.74, 2.15);
8464
+ var BEAM_DIRECTION = carDirection(BEAM_FROM, carPoint(0, 0.74, 3.15));
8465
+ var BEAM_SIDE = [BEAM_DIRECTION[2], 0, -BEAM_DIRECTION[0]];
8466
+ var WALKERS = 6;
8467
+ var WALK_SPEED2 = 0.7;
8468
+ var DROPS2 = 1800;
8469
+ var SLANT2 = 0.8;
8470
+ var startWalking = [
8471
+ set("spot", query("car", "point", rnd(-2, 2), 0, rnd(8, 18))),
8472
+ set("goal", query("car", "point", rnd(-0.6, 0.6), 0, 2.6)),
8473
+ behavior("walkers", "place", { index: INDEX, x: pick(variable("spot"), 0), z: pick(variable("spot"), 2), facing: 0 }),
8474
+ behavior("walkers", "walk", { index: INDEX, to: variable("goal"), speed: WALK_SPEED2 })
8475
+ ];
8476
+ var HEADLIGHTS_IN_RAIN = {
8477
+ id: "headlights_in_rain",
8478
+ pageId: "farol",
8479
+ team: "vehicles",
8480
+ budget: { particles: 600, lights: 2 },
8481
+ loop: 0,
8482
+ view: { eye: [-2.5, 2, 5.5], target: [4, 1, 6] },
8483
+ setup: [
8484
+ cue("fog", { density: 0.05 }),
8485
+ cue("headlights", { on: true, intensity: 2.4, color: 16773584 }),
8486
+ behavior("rain", "init", { count: DROPS2, wind: SLANT2 }),
8487
+ repeat(WALKERS, ...startWalking)
8488
+ ],
8489
+ timeline: [],
8490
+ tick: [
8491
+ behavior("rain", "update", { dt: DELTA, splash: true }),
8492
+ emit(
8493
+ "lit",
8494
+ "streak",
8495
+ 600,
8496
+ local(
8497
+ "depth",
8498
+ rnd(1, 13),
8499
+ local(
8500
+ "spread",
8501
+ mul(variable("depth"), 0.33, sqrt(rand())),
8502
+ local(
8503
+ "turn",
8504
+ rnd(0, 6.283),
8505
+ local("base", vadd(BEAM_FROM, vscale(BEAM_DIRECTION, variable("depth")), vscale(BEAM_SIDE, mul(cos(variable("turn")), variable("spread")))), {
8506
+ p: vwith(variable("base"), 1, add(vy(variable("base")), mul(sin(variable("turn")), variable("spread"), 0.6))),
8507
+ v: [0.8, -14, 0],
8508
+ sp: 0,
8509
+ life: 0.06,
8510
+ len: 0.035,
8511
+ c0: 13622512,
8512
+ a: 0.9
8513
+ })
8514
+ )
8515
+ )
8516
+ )
8517
+ ),
8518
+ repeat(WALKERS, when(not(query("walkers", "is_walking", INDEX)), startWalking))
8519
+ ]
8520
+ };
8233
8521
  var YAW = -Math.PI / 2;
8234
8522
  var COS = Math.cos(YAW);
8235
8523
  var SIN = Math.sin(YAW);
@@ -8257,10 +8545,6 @@ var GameTestRoom = (() => {
8257
8545
  fireLight(0, [vx(variable("contact")), 0.34, vz(variable("contact"))], 1.6, mul(TIME, 3))
8258
8546
  ]
8259
8547
  };
8260
- var CAR_ORIGIN2 = [5.6, 0, 0.8];
8261
- var CAR_ROTATION2 = [0, -0.4, 0];
8262
- var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
8263
- var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
8264
8548
  var HOOD = carPoint(0, 1.02, 1.4);
8265
8549
  var EXHAUST = carPoint(0.5, 0.3, -2.2);
8266
8550
  var EXHAUST_DIRECTION = carDirection(EXHAUST, carPoint(0.5, 0.3, -3));
@@ -8294,7 +8578,7 @@ var GameTestRoom = (() => {
8294
8578
  ], [fireLightOff(0)])
8295
8579
  ]
8296
8580
  };
8297
- var UP9 = [0, 1, 0];
8581
+ var UP10 = [0, 1, 0];
8298
8582
  var doorVelocity = (side) => {
8299
8583
  const out = carPoint(side * 4, 3, 0.3);
8300
8584
  const centre = carPoint(0, 0, 0.3);
@@ -8304,7 +8588,7 @@ var GameTestRoom = (() => {
8304
8588
  call2("explode", vadd(query("car", "position"), [0, 0.6, 0]), 1.8, 2759188),
8305
8589
  cue("car_burnt"),
8306
8590
  behavior("car", "launch", { rise: 4.5, spin: 0.9 }),
8307
- 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 })),
8591
+ 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 })),
8308
8592
  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 }),
8309
8593
  ...[-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 })),
8310
8594
  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 })
@@ -8474,7 +8758,7 @@ var GameTestRoom = (() => {
8474
8758
  ])
8475
8759
  ]
8476
8760
  };
8477
- var WALKERS = 6;
8761
+ var WALKERS2 = 6;
8478
8762
  var NEAR = 9;
8479
8763
  var RANGE = 7;
8480
8764
  var RESTING_BPM = 70;
@@ -8490,7 +8774,7 @@ var GameTestRoom = (() => {
8490
8774
  setup: [
8491
8775
  cue("zombie_visible", { index: -1, visible: false }),
8492
8776
  repeat(
8493
- WALKERS,
8777
+ WALKERS2,
8494
8778
  set("startX", rnd(-4, 4)),
8495
8779
  set("startZ", sub(rnd(-4, -2), mul(INDEX, 0.6))),
8496
8780
  behavior("walkers", "place", { index: INDEX, x: variable("startX"), z: variable("startZ"), facing: 0 }),
@@ -8500,7 +8784,7 @@ var GameTestRoom = (() => {
8500
8784
  timeline: [],
8501
8785
  tick: [
8502
8786
  set("nearest", 99),
8503
- repeat(WALKERS, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
8787
+ repeat(WALKERS2, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
8504
8788
  set("fear", clamp3(div(sub(NEAR, variable("nearest")), RANGE), 0, 1)),
8505
8789
  set("bpm", add(RESTING_BPM, mul(BPM_RISE, variable("fear")))),
8506
8790
  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"))) } })]),
@@ -8567,7 +8851,7 @@ var GameTestRoom = (() => {
8567
8851
  ]
8568
8852
  };
8569
8853
  var FACING4 = [0, 0, 1];
8570
- var UP10 = [0, 1, 0];
8854
+ var UP11 = [0, 1, 0];
8571
8855
  var ORIGIN = [0, 0, 0];
8572
8856
  var FORWARD4 = [0, 0, -1];
8573
8857
  var IMPACT_MATERIALS = ["concrete", "metal", "wood", "glass", "flesh", "water"];
@@ -8697,12 +8981,148 @@ var GameTestRoom = (() => {
8697
8981
  team: "destruction",
8698
8982
  budget: { particles: 20, debris: 8 },
8699
8983
  loop: 2,
8700
- timeline: [call2("chips", ORIGIN, UP10, 8, 0.9), call2("rust_at", ORIGIN, 6)]
8984
+ timeline: [call2("chips", ORIGIN, UP11, 8, 0.9), call2("rust_at", ORIGIN, 6)]
8701
8985
  }
8702
8986
  ];
8703
- 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];
8987
+ var PI = Math.PI;
8988
+ var SMOKE = { color: 9079430, width: 10, height: 8, gravity: -0.08, drag: 2.2, growth: 2.1 };
8989
+ var FLASH_SECONDS = 0.055;
8990
+ var FLASH_RAMP = [0.6, 0.9];
8991
+ var FLASH_OPACITY = 0.95;
8992
+ var FLAME_COLORS = [16742946, 16760896, 13121050, 16773296];
8993
+ var FLAME_ADDITIVE = 3;
8994
+ var ORIGIN2 = [0, 0, 0];
8995
+ var SIZE = variable("size");
8996
+ var wide = (width) => mul(sub(rand(), 0.5), width);
8997
+ var puff2 = (point, velocity, world, radius, life, opacity) => mesh({
8998
+ shape: "sphere",
8999
+ p: point,
9000
+ v: velocity,
9001
+ wv: world,
9002
+ r: radius,
9003
+ seg: SMOKE.width,
9004
+ seg2: SMOKE.height,
9005
+ life,
9006
+ c0: SMOKE.color,
9007
+ a: opacity,
9008
+ fade: true,
9009
+ lit: true,
9010
+ g: SMOKE.gravity,
9011
+ drag: SMOKE.drag,
9012
+ grow: SMOKE.growth
9013
+ });
9014
+ var flameBit = (point, velocity, world, radius, life) => [
9015
+ set("bitPoint", point),
9016
+ set("bitVelocity", velocity),
9017
+ set("bitWorld", world),
9018
+ set("bitRadius", radius),
9019
+ set("bitLife", life),
9020
+ set("bitColour", floor(mul(rand(), FLAME_COLORS.length))),
9021
+ mesh({
9022
+ shape: "icosahedron",
9023
+ p: variable("bitPoint"),
9024
+ v: variable("bitVelocity"),
9025
+ wv: variable("bitWorld"),
9026
+ r: variable("bitRadius"),
9027
+ seg: 1,
9028
+ rot: [mul(rand(), 3), mul(rand(), 3), 0],
9029
+ spin: [3, 4, 2],
9030
+ life: variable("bitLife"),
9031
+ c0: pick(FLAME_COLORS, variable("bitColour")),
9032
+ add: eq(variable("bitColour"), FLAME_ADDITIVE),
9033
+ a: 0.9,
9034
+ fade: true,
9035
+ g: -1.4,
9036
+ drag: 1.4,
9037
+ grow: 2.2
9038
+ })
9039
+ ];
9040
+ 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 });
9041
+ var MUZZLE_FLASH2 = {
9042
+ id: "viewmodel_muzzle_flash",
9043
+ pageId: "event:viewmodel_muzzle_flash",
9044
+ team: "weapons",
9045
+ budget: { particles: 12, lights: 1 },
9046
+ loop: 0.1,
9047
+ timeline: [
9048
+ set("angles", []),
9049
+ repeat(4, append("angles", add(div(mul(INDEX, PI), 2), mul(rand(), 0.6)))),
9050
+ set("roll", mul(rand(), PI)),
9051
+ flash(ORIGIN2, 16756832, mul(4, SIZE), FLASH_SECONDS, mul(2, SIZE), true, true),
9052
+ flashPiece("cone_open", mul(0.013, SIZE), mul(0.085, SIZE), 10, [mul(0.042, SIZE), 0, 0], [0, 0, -PI / 2], 16757575),
9053
+ repeat(
9054
+ 4,
9055
+ set("angle", pick(variable("angles"), INDEX)),
9056
+ flashPiece(
9057
+ "cone",
9058
+ mul(5e-3, SIZE),
9059
+ mul(0.04, SIZE),
9060
+ 6,
9061
+ [mul(0.012, SIZE), mul(cos(variable("angle")), 0.016, SIZE), mul(sin(variable("angle")), 0.016, SIZE)],
9062
+ [variable("angle"), 0, -PI / 2],
9063
+ 16757575
9064
+ )
9065
+ ),
9066
+ flashPiece("sphere", mul(0.016, SIZE), void 0, 12, [mul(8e-3, SIZE), 0, 0], [0, 0, 0], 16773568),
9067
+ repeat(
9068
+ 6,
9069
+ puff2(
9070
+ ORIGIN2,
9071
+ [add(0.15, mul(rand(), 0.35)), add(0.03, mul(rand(), 0.12)), mul(sub(rand(), 0.5), 0.14)],
9072
+ [0, 0, 0],
9073
+ mul(0.013, SIZE),
9074
+ add(1.3, mul(rand(), 0.6)),
9075
+ 0.45
9076
+ )
9077
+ )
9078
+ ]
9079
+ };
9080
+ var SMOKE_PUFF = {
9081
+ id: "weapon_smoke_puff",
9082
+ pageId: "event:weapon_smoke_puff",
9083
+ team: "weapons",
9084
+ budget: { particles: 1 },
9085
+ loop: 0.1,
9086
+ timeline: [puff2(ORIGIN2, void 0, [variable("vx"), variable("vy"), variable("vz")], variable("radius"), variable("life"), variable("opacity"))]
9087
+ };
9088
+ var BACKBLAST = {
9089
+ id: "launcher_backblast",
9090
+ pageId: "event:launcher_backblast",
9091
+ team: "weapons",
9092
+ budget: { particles: 40 },
9093
+ loop: 0.1,
9094
+ timeline: [
9095
+ 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)),
9096
+ repeat(14, ...flameBit(ORIGIN2, [neg(add(2, mul(rand(), 2))), 0, 0], [wide(1), wide(0.6), wide(1)], 0.02, 0.25))
9097
+ ]
9098
+ };
9099
+ var FLAME_JET = {
9100
+ id: "flame_jet",
9101
+ pageId: "event:flame_jet",
9102
+ team: "weapons",
9103
+ budget: { particles: 30 },
9104
+ loop: 0.1,
9105
+ timeline: [
9106
+ repeat(
9107
+ floor(mul(420, variable("dt"), variable("scale"))),
9108
+ set("speed", mul(add(2.2, mul(rand(), 1.1)), variable("scale"))),
9109
+ ...flameBit(
9110
+ [mul(rand(), variable("speed"), min(variable("dt"), 0.3)), 0, 0],
9111
+ [variable("speed"), 0, 0],
9112
+ [wide(0.35), mul(sub(rand(), 0.3), 0.3), wide(0.35)],
9113
+ add(0.02, mul(rand(), 0.022)),
9114
+ add(0.4, mul(rand(), 0.35))
9115
+ )
9116
+ ),
9117
+ when(lt(rand(), 0.5), [
9118
+ 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)
9119
+ ])
9120
+ ]
9121
+ };
9122
+ var VIEWMODEL_EFFECTS = [MUZZLE_FLASH2, SMOKE_PUFF, BACKBLAST, FLAME_JET];
9123
+ 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];
8704
9124
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
8705
- var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries(EVENT_EFFECTS.map((effect) => [effect.id, effect])) };
9125
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS].map((effect) => [effect.id, effect])) };
8706
9126
  var LEHMER_MULTIPLIER = 16807;
8707
9127
  var LEHMER_MODULUS = 2147483647;
8708
9128
  var HASH_MIX = 2654435761;
@@ -8798,7 +9218,7 @@ var GameTestRoom = (() => {
8798
9218
  }
8799
9219
  var COLUMNS = 6;
8800
9220
  var ROWS = 5;
8801
- var ORIGIN2 = [-2.2, 0, 2.6];
9221
+ var ORIGIN3 = [-2.2, 0, 2.6];
8802
9222
  var BLOCK_SIZE = [0.5, 0.44, 0.25];
8803
9223
  var OBJECT_ID_DRAWS2 = 4;
8804
9224
  var FLOOR2 = 0.13;
@@ -8841,7 +9261,7 @@ var GameTestRoom = (() => {
8841
9261
  const chipScale = random.between(0.8, 1.3) * scale2;
8842
9262
  for (let draw2 = 0; draw2 < OBJECT_ID_DRAWS2; draw2++) random.next();
8843
9263
  best.holes.push({ x: offsetX, y: offsetY, scale: scale2, chipTurn, chipScale });
8844
- impact(host.engine, "concrete", [ORIGIN2[0] + x, y, ORIGIN2[2] + 0.13], [0, 0, 1], 0.8);
9264
+ impact(host.engine, "concrete", [ORIGIN3[0] + x, y, ORIGIN3[2] + 0.13], [0, 0, 1], 0.8);
8845
9265
  } else if (action === "collapse") {
8846
9266
  const { x, y } = args;
8847
9267
  for (const block of blocks) {
@@ -8891,7 +9311,7 @@ var GameTestRoom = (() => {
8891
9311
  }
8892
9312
  };
8893
9313
  }
8894
- var ORIGIN3 = [5.6, 0, 0.8];
9314
+ var ORIGIN4 = [5.6, 0, 0.8];
8895
9315
  var MAX_TILT = 0.25;
8896
9316
  function createCarBehavior() {
8897
9317
  let y = 0;
@@ -8923,8 +9343,13 @@ var GameTestRoom = (() => {
8923
9343
  }
8924
9344
  }
8925
9345
  },
8926
- query(_host, name) {
8927
- if (name === "position") return [ORIGIN3[0], y, ORIGIN3[2]];
9346
+ query(_host, name, args) {
9347
+ if (name === "position") return [ORIGIN4[0], y, ORIGIN4[2]];
9348
+ if (name === "point") {
9349
+ const [x, py, z] = args;
9350
+ const point = carPoint(x, py, z);
9351
+ return [point[0], point[1] + y, point[2]];
9352
+ }
8928
9353
  if (name === "roll") return roll;
8929
9354
  throw new Error(`car: unknown query ${name}`);
8930
9355
  },
@@ -9124,6 +9549,29 @@ var GameTestRoom = (() => {
9124
9549
  }
9125
9550
  };
9126
9551
  }
9552
+ function createRainBehavior() {
9553
+ return {
9554
+ reset() {
9555
+ },
9556
+ act(host, action, args) {
9557
+ const { engine, camera: camera2 } = host;
9558
+ if (action === "init") {
9559
+ const { count, wind } = args;
9560
+ engine.rain.init(count, wind);
9561
+ } else if (action === "update") {
9562
+ const { dt, splash } = args;
9563
+ engine.rain.update(dt, splash, camera2.position);
9564
+ }
9565
+ },
9566
+ query(host, name) {
9567
+ if (name === "count") return host.engine.rain.count;
9568
+ if (name === "on") return host.engine.rain.on;
9569
+ throw new Error(`rain: unknown query ${name}`);
9570
+ },
9571
+ update() {
9572
+ }
9573
+ };
9574
+ }
9127
9575
  var WALKER_COUNT = 10;
9128
9576
  var ARRIVAL_DISTANCE = 0.05;
9129
9577
  var SWAY_AMPLITUDE = 0.04;
@@ -9189,7 +9637,8 @@ var GameTestRoom = (() => {
9189
9637
  blocks: () => createBlocksBehavior(),
9190
9638
  plate: () => createPlateBehavior(),
9191
9639
  cylinder: () => createCylinderBehavior(),
9192
- car: () => createCarBehavior()
9640
+ car: () => createCarBehavior(),
9641
+ rain: () => createRainBehavior()
9193
9642
  };
9194
9643
  var NOOP = () => {
9195
9644
  };
@@ -9260,6 +9709,8 @@ var GameTestRoom = (() => {
9260
9709
  return Math.abs(asNumber(first));
9261
9710
  case "sqrt":
9262
9711
  return Math.sqrt(asNumber(first));
9712
+ case "exp":
9713
+ return Math.exp(asNumber(first));
9263
9714
  case "sin":
9264
9715
  return Math.sin(asNumber(first));
9265
9716
  case "cos":
@@ -9378,7 +9829,7 @@ var GameTestRoom = (() => {
9378
9829
  return evaluateCall(value.$, args.map((entry) => resolve(entry, context)));
9379
9830
  }
9380
9831
  }
9381
- var UP11 = [0, 1, 0];
9832
+ var UP12 = [0, 1, 0];
9382
9833
  var ZERO = [0, 0, 0];
9383
9834
  var FULL_TURN_DRAWN = 6.283;
9384
9835
  function scaledSizes(range2, factor) {
@@ -9391,7 +9842,7 @@ var GameTestRoom = (() => {
9391
9842
  p: centre,
9392
9843
  r: radius,
9393
9844
  flat: true,
9394
- dir: options.dir ?? UP11,
9845
+ dir: options.dir ?? UP12,
9395
9846
  cone: 0.35,
9396
9847
  sp: [0.4, 1.4],
9397
9848
  v: wind,
@@ -9408,7 +9859,7 @@ var GameTestRoom = (() => {
9408
9859
  p: centre,
9409
9860
  r: radius * 0.6,
9410
9861
  flat: true,
9411
- dir: UP11,
9862
+ dir: UP12,
9412
9863
  cone: 0.3,
9413
9864
  sp: [0.3, 0.8],
9414
9865
  life: [0.2, 0.4],
@@ -9423,14 +9874,14 @@ var GameTestRoom = (() => {
9423
9874
  if (engine.quality < 2) {
9424
9875
  engine.emit(`${key}e`, "streak", 10 * factor, dt, () => {
9425
9876
  const jitter = plus(wind, engine.random.between(-0.4, 0.4), 0, engine.random.between(-0.4, 0.4));
9426
- 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 };
9877
+ 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 };
9427
9878
  });
9428
9879
  }
9429
9880
  if (options.noSmoke) return;
9430
9881
  engine.emit(`${key}s`, "normal", (options.smoke || 14) * factor, dt, () => ({
9431
9882
  p: plus(centre, 0, 0.8 * sqrtFactor, 0),
9432
9883
  r: radius * 0.7,
9433
- dir: UP11,
9884
+ dir: UP12,
9434
9885
  cone: 0.35,
9435
9886
  sp: [0.6, 1.4],
9436
9887
  v: wind,
@@ -9470,15 +9921,15 @@ var GameTestRoom = (() => {
9470
9921
  engine.screen.shake += 0.7 * scale2;
9471
9922
  engine.screen.white += 0.18 * scale2;
9472
9923
  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 });
9473
- 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 });
9924
+ 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 });
9474
9925
  engine.ring([plus(point, 0, 0.05, 0), 0.3, 7 * scale2, 0.55, 16767136]);
9475
9926
  const toCamera = normalized([cameraPosition[0] - point[0], cameraPosition[1] - point[1], cameraPosition[2] - point[2]]);
9476
9927
  engine.ring([plus(point, 0, 1, 0), 0.3, 5 * scale2, 0.35, 16773328, toCamera]);
9477
- 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 });
9928
+ 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 });
9478
9929
  ringOfDust(engine, point, scale2);
9479
- 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 });
9930
+ 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 });
9480
9931
  for (let index = 0; index < engine.scaled(16 * scale2); index++) {
9481
- 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 });
9932
+ 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 });
9482
9933
  }
9483
9934
  engine.decal([point[0], engine.context.groundAt(point[0], point[2]), point[2], 3 * scale2, 657930, { grow: 0.15, life: 60 }]);
9484
9935
  }
@@ -9621,6 +10072,7 @@ var GameTestRoom = (() => {
9621
10072
  if (!def) return;
9622
10073
  this.schedule.clear();
9623
10074
  this.variables.clear();
10075
+ for (const [name, value] of Object.entries(this.setup.values ?? {})) this.variables.set(name, value);
9624
10076
  for (const behavior2 of this.deps.behaviors()) behavior2.reset(this.host);
9625
10077
  this.time = 0;
9626
10078
  this.within(() => {
@@ -9679,6 +10131,10 @@ var GameTestRoom = (() => {
9679
10131
  return engine.spawn(step.system, this.value(step.spawn));
9680
10132
  case "emit":
9681
10133
  return engine.emit(step.key, step.system, this.value(step.rate), this.dt, () => this.value(step.spawn));
10134
+ case "mesh": {
10135
+ engine.mesh(this.value(step.spawn));
10136
+ return;
10137
+ }
9682
10138
  case "debris":
9683
10139
  return engine.debrisPiece(this.value(step.spawn));
9684
10140
  case "decal":
@@ -9760,7 +10216,7 @@ var GameTestRoom = (() => {
9760
10216
  this.index = previous;
9761
10217
  }
9762
10218
  };
9763
- var ORIGIN4 = [0, 0, 0];
10219
+ var ORIGIN5 = [0, 0, 0];
9764
10220
  var DEFAULT_HEADING = [0, 0, -1];
9765
10221
  var DEFAULT_MAX_INSTANCES = 48;
9766
10222
  var ENDLESS_SECONDS = 60;
@@ -9805,7 +10261,7 @@ var GameTestRoom = (() => {
9805
10261
  }
9806
10262
  function placementOf(entry) {
9807
10263
  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;
9808
- return { origin, anchor: entry.anchor, yaw: entry.yaw };
10264
+ return { origin, anchor: entry.anchor, yaw: entry.yaw, ...entry.orientation ? { orientation: entry.orientation } : {} };
9809
10265
  }
9810
10266
  function drawing(entry, action) {
9811
10267
  const context = engine.context;
@@ -9829,8 +10285,9 @@ var GameTestRoom = (() => {
9829
10285
  random: playOptions.seed !== void 0 ? streamOf(playOptions.seed) : void 0,
9830
10286
  follow: playOptions.follow,
9831
10287
  origin: point,
9832
- anchor: def.anchor ?? ORIGIN4,
10288
+ anchor: def.anchor ?? ORIGIN5,
9833
10289
  yaw: yawOf(heading, playOptions.direction),
10290
+ orientation: playOptions.orientation,
9834
10291
  seconds: playOptions.seconds ?? (def.loop && def.loop > 0 ? def.loop : ENDLESS_SECONDS)
9835
10292
  };
9836
10293
  playing.push(entry);
@@ -9839,7 +10296,8 @@ var GameTestRoom = (() => {
9839
10296
  () => instance.begin(def, paramIndex(def, playOptions.param), {
9840
10297
  placement: placementOf(entry),
9841
10298
  ...playOptions.direction ? { direction: playOptions.direction } : {},
9842
- ...playOptions.normal ? { normal: playOptions.normal } : {}
10299
+ ...playOptions.normal ? { normal: playOptions.normal } : {},
10300
+ ...playOptions.values ? { values: playOptions.values } : {}
9843
10301
  })
9844
10302
  );
9845
10303
  return entry.handle;
@@ -10099,15 +10557,15 @@ var GameTestRoom = (() => {
10099
10557
  return shock;
10100
10558
  }
10101
10559
  function pushPuffs(puffs, shock, reach, dt, age) {
10102
- for (const puff2 of puffs) {
10103
- if (puff2.ring || puff2.t > PUSH_AGE_LIMIT) continue;
10104
- const dx = puff2.x - shock.x;
10105
- const dz = puff2.z - shock.z;
10560
+ for (const puff3 of puffs) {
10561
+ if (puff3.ring || puff3.t > PUSH_AGE_LIMIT) continue;
10562
+ const dx = puff3.x - shock.x;
10563
+ const dz = puff3.z - shock.z;
10106
10564
  const distance4 = Math.hypot(dx, dz) + 1e-3;
10107
- if (Math.abs(distance4 - reach) < PUSH_BAND && puff2.y < PUSH_CEILING) {
10565
+ if (Math.abs(distance4 - reach) < PUSH_BAND && puff3.y < PUSH_CEILING) {
10108
10566
  const strength = 4 * dt * (1 - age);
10109
- puff2.vx += dx / distance4 * strength * 6;
10110
- puff2.vz += dz / distance4 * strength * 6;
10567
+ puff3.vx += dx / distance4 * strength * 6;
10568
+ puff3.vz += dz / distance4 * strength * 6;
10111
10569
  }
10112
10570
  }
10113
10571
  }
@@ -10407,10 +10865,10 @@ var GameTestRoom = (() => {
10407
10865
  const life = random.between(ring2.life * 0.75, ring2.life);
10408
10866
  const h0 = random.between(1, 1.25);
10409
10867
  const hl = random.between(0.9, 1.6);
10410
- 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 });
10411
- puff2.ring = ring2;
10412
- puff2.ph = random.next() * Math.PI * 2;
10413
- puff2.th = random.next() * Math.PI * 2;
10868
+ 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 });
10869
+ puff3.ring = ring2;
10870
+ puff3.ph = random.next() * Math.PI * 2;
10871
+ puff3.th = random.next() * Math.PI * 2;
10414
10872
  }
10415
10873
  const stemCount = Math.round((spec.stem || 18) * Math.max(lod, 0.4));
10416
10874
  for (let index = 0; index < stemCount; index++) {
@@ -11211,7 +11669,7 @@ var GameTestRoom = (() => {
11211
11669
  };
11212
11670
  var TWO_PI_ROUNDED = 6.2832;
11213
11671
  var WIDE_CONE = 3.1;
11214
- var UP12 = [0, 1, 0];
11672
+ var UP13 = [0, 1, 0];
11215
11673
  function drawRange(random, range2) {
11216
11674
  if (Array.isArray(range2)) return random.between(range2[0], range2[1]);
11217
11675
  return range2 || 0;
@@ -11239,7 +11697,7 @@ var GameTestRoom = (() => {
11239
11697
  }
11240
11698
  function coneDirection(random, direction, cone, out) {
11241
11699
  if (cone >= WIDE_CONE) return randomUnit(random, out);
11242
- const [dx, dy, dz] = direction ?? UP12;
11700
+ const [dx, dy, dz] = direction ?? UP13;
11243
11701
  const cosTilt = 1 - random.next() * (1 - Math.cos(cone));
11244
11702
  const sinTilt = Math.sqrt(1 - cosTilt * cosTilt);
11245
11703
  const phase = random.next() * TWO_PI_ROUNDED;
@@ -11359,12 +11817,12 @@ var GameTestRoom = (() => {
11359
11817
  }
11360
11818
  };
11361
11819
  var CAPACITY2 = 400;
11362
- var UP13 = [0, 1, 0];
11820
+ var UP14 = [0, 1, 0];
11363
11821
  var DEFAULT_LIFE2 = 60;
11364
11822
  var DEFAULT_GROW = 1.2;
11365
11823
  var EVAPORATION = { fuel: [6, 5], water: [40, 20] };
11366
11824
  function createDecal() {
11367
- 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 };
11825
+ 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 };
11368
11826
  }
11369
11827
  var DecalPool = class {
11370
11828
  constructor(context) {
@@ -11377,7 +11835,7 @@ var GameTestRoom = (() => {
11377
11835
  const random = this.context.random;
11378
11836
  const index = this.next++ % CAPACITY2;
11379
11837
  const decal2 = this.decals[index];
11380
- const normal = options.n ?? UP13;
11838
+ const normal = options.n ?? UP14;
11381
11839
  random.between(0, 6.28);
11382
11840
  const offset = 4e-3 + index % 40 * 4e-4;
11383
11841
  decal2.on = true;
@@ -11418,6 +11876,94 @@ var GameTestRoom = (() => {
11418
11876
  }
11419
11877
  }
11420
11878
  };
11879
+ var CAPACITY3 = 400;
11880
+ var IDENTITY2 = [0, 0, 0, 1];
11881
+ function multiply(a, b) {
11882
+ const [ax, ay, az, aw] = a;
11883
+ const [bx, by, bz, bw] = b;
11884
+ 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];
11885
+ }
11886
+ var aboutX = (angle) => [Math.sin(angle / 2), 0, 0, Math.cos(angle / 2)];
11887
+ var MeshPool = class {
11888
+ constructor(capacity = CAPACITY3) {
11889
+ __publicField(this, "capacity", capacity);
11890
+ __publicField(this, "particles", []);
11891
+ __publicField(this, "serial", 0);
11892
+ }
11893
+ get count() {
11894
+ return this.particles.length;
11895
+ }
11896
+ spawn(options, frame3 = IDENTITY2) {
11897
+ const velocity = options.v ?? [0, 0, 0];
11898
+ const world = options.wv ?? [0, 0, 0];
11899
+ const particle = {
11900
+ id: ++this.serial,
11901
+ shape: options.shape,
11902
+ radius: options.r,
11903
+ height: options.h ?? 0,
11904
+ segments: options.seg ?? 0,
11905
+ segments2: options.seg2 ?? 0,
11906
+ color: options.c0,
11907
+ additive: options.add === true,
11908
+ lit: options.lit === true,
11909
+ x: options.p[0],
11910
+ y: options.p[1],
11911
+ z: options.p[2],
11912
+ frame: options.roll ? multiply(frame3, aboutX(options.roll)) : frame3,
11913
+ at: options.at ?? [0, 0, 0],
11914
+ rx: options.rot?.[0] ?? 0,
11915
+ ry: options.rot?.[1] ?? 0,
11916
+ rz: options.rot?.[2] ?? 0,
11917
+ scale: 1,
11918
+ opacity: options.a,
11919
+ age: 0,
11920
+ life: options.life,
11921
+ vx: velocity[0] + world[0],
11922
+ vy: velocity[1] + world[1],
11923
+ vz: velocity[2] + world[2],
11924
+ spin: options.spin ?? [0, 0, 0],
11925
+ gravity: options.g ?? 0,
11926
+ drag: options.drag ?? 0,
11927
+ grow: options.grow ?? 0,
11928
+ ramp: options.ramp,
11929
+ fade: options.fade === true,
11930
+ startOpacity: options.a
11931
+ };
11932
+ this.particles.push(particle);
11933
+ if (this.particles.length > this.capacity) this.particles.shift();
11934
+ return particle;
11935
+ }
11936
+ update(dt) {
11937
+ const live = this.particles;
11938
+ for (let index = 0; index < live.length; index++) {
11939
+ const particle = live[index];
11940
+ particle.age += dt;
11941
+ if (particle.ramp ? particle.age >= particle.life : particle.age > particle.life) {
11942
+ live.splice(index--, 1);
11943
+ continue;
11944
+ }
11945
+ if (particle.fade) particle.opacity = Math.max(0, particle.startOpacity * (1 - particle.age / particle.life));
11946
+ particle.vy -= particle.gravity * dt;
11947
+ if (particle.drag) {
11948
+ const keep = Math.max(0, 1 - particle.drag * dt);
11949
+ particle.vx *= keep;
11950
+ particle.vy *= keep;
11951
+ particle.vz *= keep;
11952
+ }
11953
+ particle.x += particle.vx * dt;
11954
+ particle.y += particle.vy * dt;
11955
+ particle.z += particle.vz * dt;
11956
+ particle.rx += particle.spin[0] * dt;
11957
+ particle.ry += particle.spin[1] * dt;
11958
+ particle.rz += particle.spin[2] * dt;
11959
+ if (particle.grow) particle.scale *= 1 + particle.grow * dt;
11960
+ if (particle.ramp) particle.scale = particle.ramp[0] + Math.min(1, particle.age / particle.life) * particle.ramp[1];
11961
+ }
11962
+ }
11963
+ clear() {
11964
+ this.particles.length = 0;
11965
+ }
11966
+ };
11421
11967
  var DEFAULT_FADE_IN = 0.1;
11422
11968
  var FLOATS_PER_VECTOR = 3;
11423
11969
  var BYTE_MAX2 = 255;
@@ -11707,11 +12253,11 @@ var GameTestRoom = (() => {
11707
12253
  };
11708
12254
  var RING_CAPACITY = 120;
11709
12255
  var LIGHT_CAPACITY = 4;
11710
- var UP14 = [0, 1, 0];
12256
+ var UP15 = [0, 1, 0];
11711
12257
  var DEFAULT_LIGHT_DISTANCE = 14;
11712
12258
  var WaveRingPool = class {
11713
12259
  constructor() {
11714
- __publicField(this, "rings", Array.from({ length: RING_CAPACITY }, () => ({ on: false, point: UP14, from: 0, to: 0, t: 0, duration: 1, color: 0, normal: UP14, hold: 0 })));
12260
+ __publicField(this, "rings", Array.from({ length: RING_CAPACITY }, () => ({ on: false, point: UP15, from: 0, to: 0, t: 0, duration: 1, color: 0, normal: UP15, hold: 0 })));
11715
12261
  __publicField(this, "count", 0);
11716
12262
  __publicField(this, "next", 0);
11717
12263
  }
@@ -11724,7 +12270,7 @@ var GameTestRoom = (() => {
11724
12270
  ring2.t = 0;
11725
12271
  ring2.duration = duration;
11726
12272
  ring2.color = color;
11727
- ring2.normal = normal ?? UP14;
12273
+ ring2.normal = normal ?? UP15;
11728
12274
  ring2.hold = hold || 0;
11729
12275
  }
11730
12276
  clear() {
@@ -11744,7 +12290,7 @@ var GameTestRoom = (() => {
11744
12290
  var FlashLightPool = class {
11745
12291
  constructor(context) {
11746
12292
  __publicField(this, "context", context);
11747
- __publicField(this, "lights", Array.from({ length: LIGHT_CAPACITY }, () => ({ x: 0, y: 0, z: 0, color: 16760944, distance: DEFAULT_LIGHT_DISTANCE, peak: 0, duration: 1, t: 0, intensity: 0 })));
12293
+ __publicField(this, "lights", Array.from({ length: LIGHT_CAPACITY }, () => ({ x: 0, y: 0, z: 0, color: 16760944, distance: DEFAULT_LIGHT_DISTANCE, peak: 0, duration: 1, t: 0, intensity: 0, steady: false })));
11748
12294
  __publicField(this, "fire", [
11749
12295
  { x: 0, y: 0, z: 0, intensity: 0 },
11750
12296
  { x: 0, y: 0, z: 0, intensity: 0 }
@@ -11754,7 +12300,7 @@ var GameTestRoom = (() => {
11754
12300
  get lit() {
11755
12301
  return this.lights.filter((light) => light.intensity > 0).length;
11756
12302
  }
11757
- flash(point, color, intensity, duration, distance4, force) {
12303
+ flash(point, color, intensity, duration, distance4, force, steady) {
11758
12304
  const { level } = this.context.quality;
11759
12305
  if (level >= 1 && !force) return;
11760
12306
  if (level >= 2 && force !== "boom") return;
@@ -11767,6 +12313,7 @@ var GameTestRoom = (() => {
11767
12313
  light.peak = intensity;
11768
12314
  light.duration = duration;
11769
12315
  light.t = 0;
12316
+ light.steady = steady === true;
11770
12317
  light.intensity = intensity;
11771
12318
  }
11772
12319
  clear() {
@@ -11778,7 +12325,7 @@ var GameTestRoom = (() => {
11778
12325
  if (light.intensity <= 0) continue;
11779
12326
  light.t += dt;
11780
12327
  const age = light.t / light.duration;
11781
- light.intensity = age >= 1 ? 0 : light.peak * (1 - age) * (1 - age);
12328
+ light.intensity = age >= 1 ? 0 : light.steady ? light.peak : light.peak * (1 - age) * (1 - age);
11782
12329
  }
11783
12330
  }
11784
12331
  };
@@ -11786,6 +12333,14 @@ var GameTestRoom = (() => {
11786
12333
  var FIRE_FLICKER_SWAY = 0.25;
11787
12334
  var FIRE_FLICKER_JITTER = 0.08;
11788
12335
  var LENS_FADE = 0.16;
12336
+ function rotate(q, v) {
12337
+ const [qx, qy, qz, qw] = q;
12338
+ const ix = qw * v[0] + qy * v[2] - qz * v[1];
12339
+ const iy = qw * v[1] + qz * v[0] - qx * v[2];
12340
+ const iz = qw * v[2] + qx * v[1] - qy * v[0];
12341
+ const iw = -qx * v[0] - qy * v[1] - qz * v[2];
12342
+ 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];
12343
+ }
11789
12344
  function workshopGround(x, z) {
11790
12345
  return z < -2.9 && z > -4.5 && Math.abs(x) < 13 ? 0.12 : 0;
11791
12346
  }
@@ -11799,6 +12354,8 @@ var GameTestRoom = (() => {
11799
12354
  __publicField(this, "decals");
11800
12355
  __publicField(this, "rings", new WaveRingPool());
11801
12356
  __publicField(this, "lights");
12357
+ __publicField(this, "rain", new RainField(this));
12358
+ __publicField(this, "meshes", new MeshPool());
11802
12359
  __publicField(this, "screen", createScreenState());
11803
12360
  __publicField(this, "lens", []);
11804
12361
  __publicField(this, "post", {});
@@ -11831,7 +12388,11 @@ var GameTestRoom = (() => {
11831
12388
  place(point) {
11832
12389
  const placement = this.placement;
11833
12390
  if (!placement) return point;
11834
- const { anchor, origin, yaw } = placement;
12391
+ const { anchor, origin, yaw, orientation } = placement;
12392
+ if (orientation) {
12393
+ const turned = rotate(orientation, [point[0] - anchor[0], point[1] - anchor[1], point[2] - anchor[2]]);
12394
+ return [origin[0] + turned[0], origin[1] + turned[1], origin[2] + turned[2]];
12395
+ }
11835
12396
  const x = point[0] - anchor[0];
11836
12397
  const z = point[2] - anchor[2];
11837
12398
  const cos2 = Math.cos(yaw);
@@ -11842,6 +12403,7 @@ var GameTestRoom = (() => {
11842
12403
  turn(vector) {
11843
12404
  const placement = this.placement;
11844
12405
  if (!placement) return vector;
12406
+ if (placement.orientation) return rotate(placement.orientation, vector);
11845
12407
  const cos2 = Math.cos(placement.yaw);
11846
12408
  const sin2 = Math.sin(placement.yaw);
11847
12409
  return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
@@ -11870,6 +12432,14 @@ var GameTestRoom = (() => {
11870
12432
  }
11871
12433
  this.accumulators.set(key, carried);
11872
12434
  }
12435
+ /** 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. */
12436
+ mesh(recipe) {
12437
+ const placement = this.placement;
12438
+ const options = placement ? { ...recipe, p: this.place(recipe.p), ...recipe.v ? { v: this.turn(recipe.v) } : {} } : recipe;
12439
+ this.context.trace?.("mesh", [options]);
12440
+ const grouped = recipe.at !== void 0 || recipe.roll !== void 0;
12441
+ return this.meshes.spawn(options, placement && grouped ? placement.orientation ?? [0, Math.sin(placement.yaw / 2), 0, Math.cos(placement.yaw / 2)] : void 0);
12442
+ }
11873
12443
  debrisPiece(recipe) {
11874
12444
  const options = this.placed(recipe);
11875
12445
  this.context.trace?.("debris", [options]);
@@ -11890,10 +12460,11 @@ var GameTestRoom = (() => {
11890
12460
  this.context.trace?.("ring", args);
11891
12461
  this.rings.add(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
11892
12462
  }
12463
+ /** `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. */
11893
12464
  flash(recipe) {
11894
- const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5]] : recipe;
11895
- this.context.trace?.("flash", args);
11896
- this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5]);
12465
+ const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5], recipe[6]] : recipe;
12466
+ this.context.trace?.("flash", args.slice(0, 6));
12467
+ this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
11897
12468
  }
11898
12469
  /** `fireLight` (W400): sways with the clock and flickers with one draw. */
11899
12470
  fireLight(slot, recipe, intensity, time) {
@@ -11918,6 +12489,7 @@ var GameTestRoom = (() => {
11918
12489
  this.debris.update(dt);
11919
12490
  this.decals.update(dt);
11920
12491
  this.rings.update(dt);
12492
+ this.meshes.update(dt);
11921
12493
  this.lights.update(dt);
11922
12494
  updateScreen(this.screen, dt);
11923
12495
  for (let index = this.lens.length - 1; index >= 0; index--) {
@@ -11934,7 +12506,9 @@ var GameTestRoom = (() => {
11934
12506
  this.debris.clear();
11935
12507
  this.decals.clear();
11936
12508
  this.rings.clear();
12509
+ this.meshes.clear();
11937
12510
  this.lights.clear();
12511
+ this.rain.clear();
11938
12512
  this.accumulators.clear();
11939
12513
  resetScreenState(this.screen);
11940
12514
  this.lens.length = 0;
@@ -12080,16 +12654,16 @@ var GameTestRoom = (() => {
12080
12654
  },
12081
12655
  push(point, radius, speed) {
12082
12656
  spriteSystem.push(point, radius, speed);
12083
- for (const puff2 of core.puffs.items) {
12084
- const dx = puff2.x - point[0];
12085
- const dy = puff2.y - point[1];
12086
- const dz = puff2.z - point[2];
12657
+ for (const puff3 of core.puffs.items) {
12658
+ const dx = puff3.x - point[0];
12659
+ const dy = puff3.y - point[1];
12660
+ const dz = puff3.z - point[2];
12087
12661
  const distance4 = Math.sqrt(dx * dx + dy * dy + dz * dz);
12088
12662
  if (distance4 >= radius) continue;
12089
12663
  const strength = speed * (1 - distance4 / radius) / (distance4 + 1e-3);
12090
- puff2.vx += dx * strength;
12091
- puff2.vy += dy * strength;
12092
- puff2.vz += dz * strength;
12664
+ puff3.vx += dx * strength;
12665
+ puff3.vy += dy * strength;
12666
+ puff3.vz += dz * strength;
12093
12667
  }
12094
12668
  },
12095
12669
  stop(handle) {
@@ -12106,6 +12680,12 @@ var GameTestRoom = (() => {
12106
12680
  camera2.y = position[1];
12107
12681
  camera2.z = position[2];
12108
12682
  },
12683
+ setWind(next) {
12684
+ wind.x = next[0];
12685
+ wind.y = next[1];
12686
+ wind.z = next[2];
12687
+ },
12688
+ wind: () => [wind.x, wind.y, wind.z],
12109
12689
  forceLod(scale2) {
12110
12690
  core.clock.forcedLod = scale2;
12111
12691
  },
@@ -12374,11 +12954,11 @@ void main(){
12374
12954
  geometry.setAttribute("aC", attributes[2]);
12375
12955
  geometry.instanceCount = 0;
12376
12956
  const material = new THREE.ShaderMaterial({ uniforms, vertexShader: PUFF_VERTEX, fragmentShader: PUFF_FRAGMENT });
12377
- const mesh = new THREE.Mesh(geometry, material);
12378
- mesh.name = "vfx-puffs";
12379
- mesh.frustumCulled = false;
12957
+ const mesh2 = new THREE.Mesh(geometry, material);
12958
+ mesh2.name = "vfx-puffs";
12959
+ mesh2.frustumCulled = false;
12380
12960
  return {
12381
- mesh,
12961
+ mesh: mesh2,
12382
12962
  sync() {
12383
12963
  let n = 0;
12384
12964
  for (const p of pool.items) {
@@ -12419,12 +12999,12 @@ void main(){
12419
12999
  geometry.setAttribute("aE", attributes[2]);
12420
13000
  geometry.instanceCount = 0;
12421
13001
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: SPARK_VERTEX, fragmentShader: SPARK_FRAGMENT });
12422
- const mesh = new THREE.Mesh(geometry, material);
12423
- mesh.name = "vfx-sparks";
12424
- mesh.frustumCulled = false;
12425
- mesh.renderOrder = 5;
13002
+ const mesh2 = new THREE.Mesh(geometry, material);
13003
+ mesh2.name = "vfx-sparks";
13004
+ mesh2.frustumCulled = false;
13005
+ mesh2.renderOrder = 5;
12426
13006
  return {
12427
- mesh,
13007
+ mesh: mesh2,
12428
13008
  sync() {
12429
13009
  let n = 0;
12430
13010
  for (const s of pool.items) {
@@ -12462,12 +13042,12 @@ void main(){
12462
13042
  geometry.setAttribute("aC", attributes[1]);
12463
13043
  geometry.instanceCount = 0;
12464
13044
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: GLOW_VERTEX, fragmentShader: GLOW_FRAGMENT });
12465
- const mesh = new THREE.Mesh(geometry, material);
12466
- mesh.name = "vfx-glows";
12467
- mesh.frustumCulled = false;
12468
- mesh.renderOrder = 6;
13045
+ const mesh2 = new THREE.Mesh(geometry, material);
13046
+ mesh2.name = "vfx-glows";
13047
+ mesh2.frustumCulled = false;
13048
+ mesh2.renderOrder = 6;
12469
13049
  return {
12470
- mesh,
13050
+ mesh: mesh2,
12471
13051
  sync() {
12472
13052
  let n = 0;
12473
13053
  for (const g of pool.items) {
@@ -12496,14 +13076,14 @@ void main(){
12496
13076
  const rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.85, flatShading: true });
12497
13077
  const plateMaterial = rockMaterial.clone();
12498
13078
  const make = (geometry, material) => {
12499
- const mesh = new THREE.InstancedMesh(geometry, material, capacity);
12500
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
12501
- mesh.castShadow = true;
12502
- mesh.receiveShadow = true;
12503
- mesh.frustumCulled = false;
12504
- mesh.count = 0;
12505
- mesh.setColorAt(0, new THREE.Color(1, 1, 1));
12506
- return mesh;
13079
+ const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
13080
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13081
+ mesh2.castShadow = true;
13082
+ mesh2.receiveShadow = true;
13083
+ mesh2.frustumCulled = false;
13084
+ mesh2.count = 0;
13085
+ mesh2.setColorAt(0, new THREE.Color(1, 1, 1));
13086
+ return mesh2;
12507
13087
  };
12508
13088
  const rocks = make(shapes.rock, rockMaterial);
12509
13089
  const plates = make(shapes.plate, plateMaterial);
@@ -12522,20 +13102,20 @@ void main(){
12522
13102
  euler.set(chunk.rx, chunk.ry, chunk.rz);
12523
13103
  quaternion.setFromEuler(euler);
12524
13104
  matrix.compose(position.set(chunk.x, chunk.y, chunk.z), quaternion, scale2.set(chunk.sx, chunk.sy, chunk.sz));
12525
- const mesh = chunk.plate ? plates : rocks;
13105
+ const mesh2 = chunk.plate ? plates : rocks;
12526
13106
  const index = chunk.plate ? plateCount++ : rockCount++;
12527
- mesh.setMatrixAt(index, matrix);
13107
+ mesh2.setMatrixAt(index, matrix);
12528
13108
  const color = colorOf2(chunk);
12529
- const colors = mesh.instanceColor.array;
13109
+ const colors = mesh2.instanceColor.array;
12530
13110
  colors[index * 3] = color[0];
12531
13111
  colors[index * 3 + 1] = color[1];
12532
13112
  colors[index * 3 + 2] = color[2];
12533
13113
  }
12534
13114
  rocks.count = rockCount;
12535
13115
  plates.count = plateCount;
12536
- for (const mesh of [rocks, plates]) {
12537
- mesh.instanceMatrix.needsUpdate = true;
12538
- mesh.instanceColor.needsUpdate = true;
13116
+ for (const mesh2 of [rocks, plates]) {
13117
+ mesh2.instanceMatrix.needsUpdate = true;
13118
+ mesh2.instanceColor.needsUpdate = true;
12539
13119
  }
12540
13120
  return rockCount + plateCount;
12541
13121
  },
@@ -12719,28 +13299,28 @@ void main(){
12719
13299
  position.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
12720
13300
  }
12721
13301
  geometry.computeVertexNormals();
12722
- const mesh = new THREE.Mesh(geometry, material);
12723
- mesh.receiveShadow = true;
12724
- return mesh;
13302
+ const mesh2 = new THREE.Mesh(geometry, material);
13303
+ mesh2.receiveShadow = true;
13304
+ return mesh2;
12725
13305
  }
12726
13306
  function addRock(mark) {
12727
- const mesh = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
12728
- mesh.position.set(mark.x, mark.y, mark.z);
12729
- mesh.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
12730
- mesh.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
12731
- mesh.castShadow = true;
12732
- mesh.receiveShadow = true;
12733
- return mesh;
13307
+ const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
13308
+ mesh2.position.set(mark.x, mark.y, mark.z);
13309
+ mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
13310
+ mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
13311
+ mesh2.castShadow = true;
13312
+ mesh2.receiveShadow = true;
13313
+ return mesh2;
12734
13314
  }
12735
13315
  function addBlood(mark) {
12736
- const mesh = new THREE.Mesh(bloodGeometry, bloodMaterial);
12737
- mesh.position.set(mark.x, mark.y, mark.z);
12738
- mesh.scale.set(mark.scaleX, 1, mark.scaleZ);
12739
- mesh.rotation.y = mark.rotation;
12740
- mesh.receiveShadow = true;
12741
- mesh.scale.multiplyScalar(0.05);
12742
- blood.push({ mesh, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
12743
- return mesh;
13316
+ const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
13317
+ mesh2.position.set(mark.x, mark.y, mark.z);
13318
+ mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
13319
+ mesh2.rotation.y = mark.rotation;
13320
+ mesh2.receiveShadow = true;
13321
+ mesh2.scale.multiplyScalar(0.05);
13322
+ blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
13323
+ return mesh2;
12744
13324
  }
12745
13325
  function build(mark) {
12746
13326
  if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
@@ -12951,6 +13531,73 @@ void main() {
12951
13531
  }
12952
13532
  };
12953
13533
  }
13534
+ var SPHERE_DEFAULT_WIDTH = 8;
13535
+ var SPHERE_DEFAULT_HEIGHT = 6;
13536
+ function geometryOf(THREE, particle) {
13537
+ switch (particle.shape) {
13538
+ case "cone":
13539
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
13540
+ case "cone_open":
13541
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
13542
+ case "sphere":
13543
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
13544
+ case "icosahedron":
13545
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
13546
+ }
13547
+ }
13548
+ function materialOf(THREE, particle) {
13549
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
13550
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
13551
+ }
13552
+ function createMeshLayer(THREE, pool) {
13553
+ const group = new THREE.Group();
13554
+ group.name = "workshop-meshes";
13555
+ const drawn = /* @__PURE__ */ new Map();
13556
+ const frame3 = new THREE.Quaternion();
13557
+ const euler = new THREE.Euler();
13558
+ const turn = new THREE.Quaternion();
13559
+ const offset = new THREE.Vector3();
13560
+ function release(mesh2) {
13561
+ mesh2.removeFromParent();
13562
+ mesh2.geometry.dispose();
13563
+ mesh2.material.dispose();
13564
+ }
13565
+ return {
13566
+ group,
13567
+ sync() {
13568
+ const live = /* @__PURE__ */ new Set();
13569
+ for (const particle of pool.particles) {
13570
+ live.add(particle.id);
13571
+ let mesh2 = drawn.get(particle.id);
13572
+ if (!mesh2) {
13573
+ mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
13574
+ mesh2.frustumCulled = false;
13575
+ mesh2.renderOrder = particle.additive ? 5 : 3;
13576
+ drawn.set(particle.id, mesh2);
13577
+ group.add(mesh2);
13578
+ }
13579
+ frame3.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
13580
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame3);
13581
+ mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
13582
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
13583
+ mesh2.quaternion.copy(frame3).multiply(turn);
13584
+ mesh2.scale.setScalar(particle.scale);
13585
+ mesh2.material.opacity = particle.opacity;
13586
+ }
13587
+ for (const [id, mesh2] of drawn) {
13588
+ if (live.has(id)) continue;
13589
+ release(mesh2);
13590
+ drawn.delete(id);
13591
+ }
13592
+ return pool.count;
13593
+ },
13594
+ dispose() {
13595
+ for (const mesh2 of drawn.values()) release(mesh2);
13596
+ drawn.clear();
13597
+ group.removeFromParent();
13598
+ }
13599
+ };
13600
+ }
12954
13601
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
12955
13602
  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;}`;
12956
13603
  var WORKSHOP_SPRITE_FRAGMENT = `uniform float uSoft;varying vec3 vC;varying float vA;
@@ -13026,13 +13673,37 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13026
13673
  }
13027
13674
  };
13028
13675
  }
13676
+ function rainLayer(THREE, rain) {
13677
+ const geometry = new THREE.BufferGeometry();
13678
+ const position = new THREE.BufferAttribute(rain.positions, 3).setUsage(THREE.DynamicDrawUsage);
13679
+ geometry.setAttribute("position", position);
13680
+ geometry.setDrawRange(0, 0);
13681
+ const material = new THREE.LineBasicMaterial({ color: 10466255, transparent: true, opacity: 0.35, depthWrite: false });
13682
+ const lines = new THREE.LineSegments(geometry, material);
13683
+ lines.frustumCulled = false;
13684
+ lines.name = "workshop-rain";
13685
+ return {
13686
+ object: lines,
13687
+ sync() {
13688
+ const drawn = rain.on && rain.count > 0;
13689
+ lines.visible = drawn;
13690
+ geometry.setDrawRange(0, drawn ? rain.count * 2 : 0);
13691
+ position.needsUpdate = true;
13692
+ return drawn;
13693
+ },
13694
+ dispose() {
13695
+ geometry.dispose();
13696
+ material.dispose();
13697
+ }
13698
+ };
13699
+ }
13029
13700
  function debrisLayer(THREE, pool) {
13030
13701
  const geometry = new THREE.BoxGeometry(1, 1, 1);
13031
13702
  const material = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.45, metalness: 0.25, flatShading: true });
13032
- const mesh = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
13033
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13034
- mesh.frustumCulled = false;
13035
- mesh.name = "workshop-debris";
13703
+ const mesh2 = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
13704
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13705
+ mesh2.frustumCulled = false;
13706
+ mesh2.name = "workshop-debris";
13036
13707
  const euler = new THREE.Euler();
13037
13708
  const quaternion = new THREE.Quaternion();
13038
13709
  const position = new THREE.Vector3();
@@ -13040,16 +13711,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13040
13711
  const matrix = new THREE.Matrix4();
13041
13712
  const colour = new THREE.Color();
13042
13713
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
13043
- for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh.setMatrixAt(index, hidden);
13044
- mesh.setColorAt(0, colour.setHex(16777215));
13714
+ for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh2.setMatrixAt(index, hidden);
13715
+ mesh2.setColorAt(0, colour.setHex(16777215));
13045
13716
  return {
13046
- object: mesh,
13717
+ object: mesh2,
13047
13718
  sync() {
13048
13719
  let drawn = false;
13049
13720
  for (let index = 0; index < DEBRIS_CAPACITY; index++) {
13050
13721
  const piece = pool.pieces[index];
13051
13722
  if (!piece.on) {
13052
- mesh.setMatrixAt(index, hidden);
13723
+ mesh2.setMatrixAt(index, hidden);
13053
13724
  continue;
13054
13725
  }
13055
13726
  drawn = true;
@@ -13057,11 +13728,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13057
13728
  euler.set(piece.rx, piece.ry, piece.rz);
13058
13729
  quaternion.setFromEuler(euler);
13059
13730
  matrix.compose(position.set(piece.x, piece.y, piece.z), quaternion, scale2.set(piece.sx * shrink, piece.sy * shrink, piece.sz * shrink));
13060
- mesh.setMatrixAt(index, matrix);
13061
- mesh.setColorAt(index, colour.setHex(piece.color));
13731
+ mesh2.setMatrixAt(index, matrix);
13732
+ mesh2.setColorAt(index, colour.setHex(piece.color));
13062
13733
  }
13063
- mesh.instanceMatrix.needsUpdate = true;
13064
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
13734
+ mesh2.instanceMatrix.needsUpdate = true;
13735
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
13065
13736
  return drawn;
13066
13737
  },
13067
13738
  dispose() {
@@ -13118,11 +13789,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13118
13789
  const dryMesh = new THREE.InstancedMesh(geometry, dry, DECAL_CAPACITY);
13119
13790
  const group = new THREE.Group();
13120
13791
  group.name = "workshop-decals";
13121
- for (const mesh of [wetMesh, dryMesh]) {
13122
- mesh.frustumCulled = false;
13123
- mesh.renderOrder = 1;
13124
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13125
- group.add(mesh);
13792
+ for (const mesh2 of [wetMesh, dryMesh]) {
13793
+ mesh2.frustumCulled = false;
13794
+ mesh2.renderOrder = 1;
13795
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13796
+ group.add(mesh2);
13126
13797
  }
13127
13798
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
13128
13799
  const colour = new THREE.Color();
@@ -13159,13 +13830,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13159
13830
  quaternion.multiply(turn.setFromAxisAngle(forward, index * 2.399963));
13160
13831
  const offset = 4e-3 + index % 40 * 4e-4;
13161
13832
  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));
13162
- const mesh = look.dried ? dryMesh : wetMesh;
13163
- mesh.setMatrixAt(index, matrix);
13164
- mesh.setColorAt(index, colour);
13833
+ const mesh2 = look.dried ? dryMesh : wetMesh;
13834
+ mesh2.setMatrixAt(index, matrix);
13835
+ mesh2.setColorAt(index, colour);
13165
13836
  }
13166
- for (const mesh of [wetMesh, dryMesh]) {
13167
- mesh.instanceMatrix.needsUpdate = true;
13168
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
13837
+ for (const mesh2 of [wetMesh, dryMesh]) {
13838
+ mesh2.instanceMatrix.needsUpdate = true;
13839
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
13169
13840
  }
13170
13841
  return drawn;
13171
13842
  },
@@ -13179,10 +13850,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13179
13850
  function ringLayer(THREE, pool) {
13180
13851
  const geometry = new THREE.RingGeometry(0.86, 1, 40);
13181
13852
  const material = new THREE.MeshBasicMaterial({ color: 16777215, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide });
13182
- const mesh = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
13183
- mesh.frustumCulled = false;
13184
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13185
- mesh.name = "workshop-rings";
13853
+ const mesh2 = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
13854
+ mesh2.frustumCulled = false;
13855
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13856
+ mesh2.name = "workshop-rings";
13186
13857
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
13187
13858
  const colour = new THREE.Color();
13188
13859
  const normal = new THREE.Vector3();
@@ -13191,16 +13862,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13191
13862
  const position = new THREE.Vector3();
13192
13863
  const scale2 = new THREE.Vector3();
13193
13864
  const matrix = new THREE.Matrix4();
13194
- mesh.setColorAt(0, colour.setHex(0));
13195
- for (let index = 0; index < RING_CAPACITY3; index++) mesh.setMatrixAt(index, hidden);
13865
+ mesh2.setColorAt(0, colour.setHex(0));
13866
+ for (let index = 0; index < RING_CAPACITY3; index++) mesh2.setMatrixAt(index, hidden);
13196
13867
  return {
13197
- object: mesh,
13868
+ object: mesh2,
13198
13869
  sync() {
13199
13870
  let drawn = false;
13200
13871
  for (let index = 0; index < RING_CAPACITY3; index++) {
13201
13872
  const ring2 = pool.rings[index];
13202
13873
  if (!ring2.on) {
13203
- mesh.setMatrixAt(index, hidden);
13874
+ mesh2.setMatrixAt(index, hidden);
13204
13875
  continue;
13205
13876
  }
13206
13877
  drawn = true;
@@ -13208,11 +13879,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13208
13879
  const radius = ring2.from + (ring2.to - ring2.from) * (1 - Math.pow(1 - k, 2));
13209
13880
  quaternion.setFromUnitVectors(forward, normal.set(ring2.normal[0], ring2.normal[1], ring2.normal[2]));
13210
13881
  matrix.compose(position.set(ring2.point[0], ring2.point[1], ring2.point[2]), quaternion, scale2.set(radius, radius, 1));
13211
- mesh.setMatrixAt(index, matrix);
13212
- mesh.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
13882
+ mesh2.setMatrixAt(index, matrix);
13883
+ mesh2.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
13213
13884
  }
13214
- mesh.instanceMatrix.needsUpdate = true;
13215
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
13885
+ mesh2.instanceMatrix.needsUpdate = true;
13886
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
13216
13887
  return drawn;
13217
13888
  },
13218
13889
  dispose() {
@@ -13228,11 +13899,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13228
13899
  spriteLayer(THREE, engine.add, true, 1, pointScale),
13229
13900
  spriteLayer(THREE, engine.normal, false, 0.55, pointScale),
13230
13901
  streakLayer(THREE, engine.streaks),
13902
+ rainLayer(THREE, engine.rain),
13231
13903
  debrisLayer(THREE, engine.debris),
13232
13904
  decalLayer(THREE, engine.decals),
13233
13905
  ringLayer(THREE, engine.rings)
13234
13906
  ];
13235
13907
  for (const layer2 of layers) group.add(layer2.object);
13908
+ const meshes = createMeshLayer(THREE, engine.meshes);
13909
+ group.add(meshes.group);
13236
13910
  const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
13237
13911
  const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
13238
13912
  for (const light of [...flashLights, ...fireLights]) group.add(light);
@@ -13241,6 +13915,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13241
13915
  sync() {
13242
13916
  let drawCalls = 0;
13243
13917
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
13918
+ drawCalls += meshes.sync();
13244
13919
  engine.lights.lights.forEach((source, index) => {
13245
13920
  const light = flashLights[index];
13246
13921
  light.position.set(source.x, source.y, source.z);
@@ -13257,6 +13932,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13257
13932
  },
13258
13933
  dispose() {
13259
13934
  for (const layer2 of layers) layer2.dispose();
13935
+ meshes.dispose();
13260
13936
  group.clear();
13261
13937
  }
13262
13938
  };
@@ -13372,8 +14048,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13372
14048
  return world.play(request.effect, request.point, {
13373
14049
  ...request.direction ? { direction: request.direction } : {},
13374
14050
  ...request.normal ? { normal: request.normal } : {},
14051
+ ...request.orientation ? { orientation: request.orientation } : {},
13375
14052
  ...request.seed !== void 0 ? { seed: request.seed } : {},
13376
- ...request.param !== void 0 ? { param: request.param } : {}
14053
+ ...request.param !== void 0 ? { param: request.param } : {},
14054
+ ...request.values ? { values: request.values } : {}
13377
14055
  });
13378
14056
  },
13379
14057
  stop: (handle) => world.stop(handle),
@@ -13438,8 +14116,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13438
14116
  const uv = geometry.attributes.uv;
13439
14117
  for (let index = 0; index < position.count; index++) uv.setXY(index, position.getX(index) / metres, position.getZ(index) / metres);
13440
14118
  const material = options.material ?? new THREE.MeshStandardMaterial({ color: 2829359, roughness: 0.93, flatShading: true });
13441
- const mesh = new THREE.Mesh(geometry, material);
13442
- mesh.receiveShadow = true;
14119
+ const mesh2 = new THREE.Mesh(geometry, material);
14120
+ mesh2.receiveShadow = true;
13443
14121
  let version = -1;
13444
14122
  function sync() {
13445
14123
  if (field.version === version) return false;
@@ -13450,7 +14128,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13450
14128
  return true;
13451
14129
  }
13452
14130
  return {
13453
- mesh,
14131
+ mesh: mesh2,
13454
14132
  sync,
13455
14133
  dispose() {
13456
14134
  geometry.dispose();
@@ -13564,11 +14242,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13564
14242
  * Bullet holes that stay on a fragment (`Fragment.marks`): dark discs that are children of its mesh, so they fall, spin and settle with it. The mesh is
13565
14243
  * a unit box scaled to the fragment, so each disc is scaled back; exact for a mark on a face, which is where a bullet hole is.
13566
14244
  */
13567
- addMarks(mesh, f) {
14245
+ addMarks(mesh2, f) {
13568
14246
  if (!f.marks?.length) return;
13569
14247
  this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
13570
14248
  this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
13571
- const sc = mesh.scale;
14249
+ const sc = mesh2.scale;
13572
14250
  for (const mark of f.marks) {
13573
14251
  const disc = new this.THREE.Mesh(this.markGeometry, this.markMaterial);
13574
14252
  const radius = Math.max(4e-3, mark.diameter) * 0.6;
@@ -13576,22 +14254,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13576
14254
  disc.position.set(mark.point[0] / sc.x + n.x * 2e-3 / sc.x, mark.point[1] / sc.y + n.y * 2e-3 / sc.y, mark.point[2] / sc.z + n.z * 2e-3 / sc.z);
13577
14255
  disc.quaternion.setFromUnitVectors(this.w.set(0, 0, 1), n);
13578
14256
  disc.scale.set(radius / sc.x, radius / sc.y, radius / sc.z);
13579
- mesh.add(disc);
14257
+ mesh2.add(disc);
13580
14258
  }
13581
14259
  }
13582
14260
  /** Create the mesh of a fragment and start its motion. `maxAllowed` is the live budget. */
13583
14261
  spawn(f, material, home, maxAllowed) {
13584
14262
  const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
13585
- const mesh = new this.THREE.Mesh(geo, material);
13586
- mesh.position.set(f.center[0], f.center[1], f.center[2]);
13587
- mesh.quaternion.set(f.rotation[0], f.rotation[1], f.rotation[2], f.rotation[3]).normalize();
14263
+ const mesh2 = new this.THREE.Mesh(geo, material);
14264
+ mesh2.position.set(f.center[0], f.center[1], f.center[2]);
14265
+ mesh2.quaternion.set(f.rotation[0], f.rotation[1], f.rotation[2], f.rotation[3]).normalize();
13588
14266
  const sz = f.size;
13589
14267
  const sc = new this.THREE.Vector3(Math.max(5e-3, sz[0]), Math.max(5e-3, sz[1]), Math.max(3e-3, sz[2]));
13590
- mesh.scale.copy(sc);
13591
- mesh.castShadow = true;
13592
- this.o.scene.add(mesh);
14268
+ mesh2.scale.copy(sc);
14269
+ mesh2.castShadow = true;
14270
+ this.o.scene.add(mesh2);
13593
14271
  const live = {
13594
- mesh,
14272
+ mesh: mesh2,
13595
14273
  home,
13596
14274
  vel: new this.THREE.Vector3(f.velocity[0], f.velocity[1], f.velocity[2]),
13597
14275
  ang: new this.THREE.Vector3(f.angularVelocity[0], f.angularVelocity[1], f.angularVelocity[2]),
@@ -13612,8 +14290,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13612
14290
  alive--;
13613
14291
  }
13614
14292
  }
13615
- this.addMarks(mesh, f);
13616
- return mesh;
14293
+ this.addMarks(mesh2, f);
14294
+ return mesh2;
13617
14295
  }
13618
14296
  /** Add velocity to every fragment within `radius` of a point (blasts). */
13619
14297
  blast(point, radius, speed) {
@@ -13652,24 +14330,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13652
14330
  }
13653
14331
  }
13654
14332
  step(f, dt, g) {
13655
- const mesh = f.mesh;
14333
+ const mesh2 = f.mesh;
13656
14334
  f.vel.y -= g * dt;
13657
- mesh.position.addScaledVector(f.vel, dt);
14335
+ mesh2.position.addScaledVector(f.vel, dt);
13658
14336
  const w = f.ang.length();
13659
14337
  if (w > 1e-5) {
13660
14338
  this.dq.setFromAxisAngle(this.v.copy(f.ang).multiplyScalar(1 / w), w * dt);
13661
- mesh.quaternion.premultiply(this.dq).normalize();
14339
+ mesh2.quaternion.premultiply(this.dq).normalize();
13662
14340
  }
13663
- this.m.makeRotationFromQuaternion(mesh.quaternion);
14341
+ this.m.makeRotationFromQuaternion(mesh2.quaternion);
13664
14342
  const e = this.m.elements;
13665
14343
  const h = f.half;
13666
14344
  const sc = f.dying ? f.fade : 1;
13667
14345
  const ext = (Math.abs(e[1]) * h.x + Math.abs(e[5]) * h.y + Math.abs(e[9]) * h.z) * sc;
13668
- const gy = this.o.groundAt(mesh.position.x, mesh.position.z);
14346
+ const gy = this.o.groundAt(mesh2.position.x, mesh2.position.z);
13669
14347
  let contact = false;
13670
- if (mesh.position.y - ext < gy) {
14348
+ if (mesh2.position.y - ext < gy) {
13671
14349
  contact = true;
13672
- mesh.position.y = gy + ext;
14350
+ mesh2.position.y = gy + ext;
13673
14351
  if (f.vel.y < -0.6) {
13674
14352
  f.vel.y = -f.vel.y * 0.25;
13675
14353
  f.vel.x *= 0.7;
@@ -13702,8 +14380,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13702
14380
  }
13703
14381
  /** Tip the fragment towards lying on its flattest face. */
13704
14382
  settle(f, dt) {
13705
- const mesh = f.mesh;
13706
- this.m.makeRotationFromQuaternion(mesh.quaternion);
14383
+ const mesh2 = f.mesh;
14384
+ this.m.makeRotationFromQuaternion(mesh2.quaternion);
13707
14385
  const e = this.m.elements;
13708
14386
  const up = [e[1], e[5], e[9]];
13709
14387
  const half = [f.half.x, f.half.y, f.half.z];
@@ -13718,8 +14396,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13718
14396
  this.v.set(e[axis2 * 4], e[axis2 * 4 + 1], e[axis2 * 4 + 2]).multiplyScalar(sign);
13719
14397
  this.w.set(0, 1, 0);
13720
14398
  this.dq.setFromUnitVectors(this.v, this.w);
13721
- this.q.copy(this.dq).multiply(mesh.quaternion);
13722
- mesh.quaternion.slerp(this.q, clamp6(dt * 7, 0, 1));
14399
+ this.q.copy(this.dq).multiply(mesh2.quaternion);
14400
+ mesh2.quaternion.slerp(this.q, clamp6(dt * 7, 0, 1));
13723
14401
  }
13724
14402
  /** Remove the fragments of one object (after `restore`). */
13725
14403
  removeHome(home) {
@@ -13789,13 +14467,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13789
14467
  }
13790
14468
  }
13791
14469
  while (this.spots.length >= this.cap) this.remove(this.spots[0]);
13792
- const mesh = new this.THREE.Mesh(this.geometry, this.material(kind, color));
14470
+ const mesh2 = new this.THREE.Mesh(this.geometry, this.material(kind, color));
13793
14471
  const y = this.groundAt(x, z) + 4e-3 + this.spots.length % 8 * 4e-4;
13794
- mesh.position.set(x, y, z);
13795
- mesh.scale.setScalar(1e-3);
13796
- mesh.renderOrder = 1;
13797
- this.scene.add(mesh);
13798
- this.spots.push({ mesh, kind, home, x, z, radius: 1e-3, target: Math.min(MAX_RADIUS, radius) });
14472
+ mesh2.position.set(x, y, z);
14473
+ mesh2.scale.setScalar(1e-3);
14474
+ mesh2.renderOrder = 1;
14475
+ this.scene.add(mesh2);
14476
+ this.spots.push({ mesh: mesh2, kind, home, x, z, radius: 1e-3, target: Math.min(MAX_RADIUS, radius) });
13799
14477
  }
13800
14478
  remove(s) {
13801
14479
  const i = this.spots.indexOf(s);
@@ -14171,7 +14849,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14171
14849
  /** Bars drawn now, over every part. */
14172
14850
  get count() {
14173
14851
  let n = 0;
14174
- for (const mesh of this.cages.values()) n += mesh.count;
14852
+ for (const mesh2 of this.cages.values()) n += mesh2.count;
14175
14853
  return n;
14176
14854
  }
14177
14855
  /** Meshes of the live cages (tests and picking). */
@@ -14182,25 +14860,25 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14182
14860
  set(key, bars) {
14183
14861
  this.remove(key);
14184
14862
  if (bars.length === 0) return;
14185
- const mesh = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
14863
+ const mesh2 = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
14186
14864
  bars.forEach(([a, b], i) => {
14187
14865
  this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
14188
14866
  const length3 = this.dir.length();
14189
14867
  this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
14190
14868
  this.q.setFromUnitVectors(this.up, length3 > 1e-9 ? this.dir.divideScalar(length3) : this.up);
14191
14869
  this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length3, 1e-4), 1));
14192
- mesh.setMatrixAt(i, this.m);
14870
+ mesh2.setMatrixAt(i, this.m);
14193
14871
  });
14194
- mesh.instanceMatrix.needsUpdate = true;
14195
- mesh.castShadow = true;
14196
- this.scene.add(mesh);
14197
- this.cages.set(key, mesh);
14872
+ mesh2.instanceMatrix.needsUpdate = true;
14873
+ mesh2.castShadow = true;
14874
+ this.scene.add(mesh2);
14875
+ this.cages.set(key, mesh2);
14198
14876
  }
14199
14877
  remove(key) {
14200
- const mesh = this.cages.get(key);
14201
- if (!mesh) return;
14202
- this.scene.remove(mesh);
14203
- mesh.dispose();
14878
+ const mesh2 = this.cages.get(key);
14879
+ if (!mesh2) return;
14880
+ this.scene.remove(mesh2);
14881
+ mesh2.dispose();
14204
14882
  this.cages.delete(key);
14205
14883
  }
14206
14884
  /** Remove the cages of every part of `object` (keys are `object|part`). */
@@ -14484,25 +15162,25 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14484
15162
  /**
14485
15163
  * @param range vertex range `[v0, v1)` of a merged mesh this part is drawn by (see `MeshRange`); `hosts` caches the shared state per mesh for one `bind`.
14486
15164
  */
14487
- makeMeshVis(mesh, home, range2, hosts) {
15165
+ makeMeshVis(mesh2, home, range2, hosts) {
14488
15166
  let host = null;
14489
15167
  if (range2) {
14490
- host = hosts?.get(mesh) ?? null;
15168
+ host = hosts?.get(mesh2) ?? null;
14491
15169
  if (!host) {
14492
- host = { mesh, origGeometry: mesh.geometry, origMaterial: mesh.material, ownGeo: false, work: null, ownMat: false, sharers: [] };
14493
- hosts?.set(mesh, host);
15170
+ host = { mesh: mesh2, origGeometry: mesh2.geometry, origMaterial: mesh2.material, ownGeo: false, work: null, ownMat: false, sharers: [] };
15171
+ hosts?.set(mesh2, host);
14494
15172
  }
14495
15173
  }
14496
15174
  const mv = {
14497
- mesh,
15175
+ mesh: mesh2,
14498
15176
  home,
14499
- origGeometry: mesh.geometry,
14500
- origMaterial: mesh.material,
14501
- origVisible: mesh.visible,
14502
- origParent: mesh.parent,
14503
- origPosition: mesh.position.clone(),
14504
- origQuaternion: mesh.quaternion.clone(),
14505
- origScale: mesh.scale.clone(),
15177
+ origGeometry: mesh2.geometry,
15178
+ origMaterial: mesh2.material,
15179
+ origVisible: mesh2.visible,
15180
+ origParent: mesh2.parent,
15181
+ origPosition: mesh2.position.clone(),
15182
+ origQuaternion: mesh2.quaternion.clone(),
15183
+ origScale: mesh2.scale.clone(),
14506
15184
  ownMat: false,
14507
15185
  ownGeo: false,
14508
15186
  work: null,
@@ -14627,15 +15305,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14627
15305
  * Returns false when nothing was changed (too far, low quality on a heavy mesh, or no editable positions).
14628
15306
  */
14629
15307
  applyDent(mv, worldPoint, worldNormal, dent) {
14630
- const mesh = mv.mesh;
15308
+ const mesh2 = mv.mesh;
14631
15309
  if (this.lowQuality && this.vertexCount(mv) > 2e4) return false;
14632
- mesh.updateWorldMatrix(true, false);
14633
- const s = mesh.matrixWorld.getMaxScaleOnAxis() || 1;
14634
- const lp = mesh.worldToLocal(this.a.copy(worldPoint));
14635
- const ln = this.b.copy(worldNormal).transformDirection(this.m4.copy(mesh.matrixWorld).invert()).normalize();
15310
+ mesh2.updateWorldMatrix(true, false);
15311
+ const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
15312
+ const lp = mesh2.worldToLocal(this.a.copy(worldPoint));
15313
+ const ln = this.b.copy(worldNormal).transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
14636
15314
  const r = dent.radius / s;
14637
15315
  const depth = dent.depth / s;
14638
- const geo = mesh.geometry;
15316
+ const geo = mesh2.geometry;
14639
15317
  let bb;
14640
15318
  if (mv.host) bb = this.localBox(mv, this.lbox);
14641
15319
  else {
@@ -14645,7 +15323,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14645
15323
  if (bb && !bb.isEmpty() && bb.distanceToPoint(lp) > r) return false;
14646
15324
  if (!this.ownGeometry(mv)) return false;
14647
15325
  const work = mv.host ? mv.host.work : mv.work;
14648
- const pos = positionAttr(mesh.geometry);
15326
+ const pos = positionAttr(mesh2.geometry);
14649
15327
  const r2 = r * r;
14650
15328
  let touched = false;
14651
15329
  const px = lp.x;
@@ -14711,11 +15389,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14711
15389
  // Decals
14712
15390
  // ------------------------------------------------------------------------------------------------------------------------------------
14713
15391
  /** Decal pose in mesh space: position and a rotation taking +Z to the surface normal. */
14714
- decalPose(mesh, worldPoint, worldNormal, lift) {
14715
- mesh.updateWorldMatrix(true, false);
14716
- const s = mesh.matrixWorld.getMaxScaleOnAxis() || 1;
14717
- const ln = worldNormal.clone().transformDirection(this.m4.copy(mesh.matrixWorld).invert()).normalize();
14718
- const pos = mesh.worldToLocal(worldPoint.clone()).addScaledVector(ln, lift / s);
15392
+ decalPose(mesh2, worldPoint, worldNormal, lift) {
15393
+ mesh2.updateWorldMatrix(true, false);
15394
+ const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
15395
+ const ln = worldNormal.clone().transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
15396
+ const pos = mesh2.worldToLocal(worldPoint.clone()).addScaledVector(ln, lift / s);
14719
15397
  const quat2 = new this.THREE.Quaternion().setFromUnitVectors(new this.THREE.Vector3(0, 0, 1), ln);
14720
15398
  return { s, pos, quat: quat2 };
14721
15399
  }
@@ -14851,7 +15529,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14851
15529
  if (Math.abs(m - pv.meltApplied) < 0.01) return;
14852
15530
  pv.meltApplied = m;
14853
15531
  for (const mv of pv.meshes) {
14854
- const mesh = mv.mesh;
15532
+ const mesh2 = mv.mesh;
14855
15533
  if (mv.host) {
14856
15534
  mv.meltApplied = m;
14857
15535
  mv.touched = true;
@@ -14859,27 +15537,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14859
15537
  continue;
14860
15538
  }
14861
15539
  if (mv.meltApplied === 0 && m > 0) {
14862
- mesh.updateWorldMatrix(true, false);
14863
- mv.bottomY = this.box.setFromObject(mesh).min.y;
15540
+ mesh2.updateWorldMatrix(true, false);
15541
+ mv.bottomY = this.box.setFromObject(mesh2).min.y;
14864
15542
  }
14865
15543
  mv.meltApplied = m;
14866
15544
  mv.moved = true;
14867
15545
  const sy = lerp5(1, 0.25, m);
14868
15546
  const sxz = 1 + 0.18 * m;
14869
- mesh.scale.set(mv.origScale.x * sxz, mv.origScale.y * sy, mv.origScale.z * sxz);
14870
- mesh.position.copy(mv.origPosition);
14871
- mesh.updateWorldMatrix(true, false);
14872
- const bottom = this.box.setFromObject(mesh).min.y;
15547
+ mesh2.scale.set(mv.origScale.x * sxz, mv.origScale.y * sy, mv.origScale.z * sxz);
15548
+ mesh2.position.copy(mv.origPosition);
15549
+ mesh2.updateWorldMatrix(true, false);
15550
+ const bottom = this.box.setFromObject(mesh2).min.y;
14873
15551
  const dy = mv.bottomY - bottom;
14874
- if (Math.abs(dy) > 1e-6 && mesh.parent) {
14875
- mesh.parent.updateWorldMatrix(true, false);
14876
- const inv = this.m4.copy(mesh.parent.matrixWorld).invert();
15552
+ if (Math.abs(dy) > 1e-6 && mesh2.parent) {
15553
+ mesh2.parent.updateWorldMatrix(true, false);
15554
+ const inv = this.m4.copy(mesh2.parent.matrixWorld).invert();
14877
15555
  this.a.set(0, dy, 0).applyMatrix4(inv);
14878
15556
  this.b.set(0, 0, 0).applyMatrix4(inv);
14879
- mesh.position.copy(mv.origPosition).add(this.a.sub(this.b));
15557
+ mesh2.position.copy(mv.origPosition).add(this.a.sub(this.b));
14880
15558
  }
14881
15559
  if (!(this.lowQuality && this.vertexCount(mv) > 2e4) && this.ownGeometry(mv)) {
14882
- this.writePositions(mv, positionAttr(mesh.geometry));
15560
+ this.writePositions(mv, positionAttr(mesh2.geometry));
14883
15561
  }
14884
15562
  }
14885
15563
  }
@@ -15152,30 +15830,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
15152
15830
  this.restoreRanged(mv);
15153
15831
  return;
15154
15832
  }
15155
- const mesh = mv.mesh;
15833
+ const mesh2 = mv.mesh;
15156
15834
  for (const d of mv.decals) this.disposeDecal(d);
15157
15835
  mv.decals.length = 0;
15158
15836
  if (mv.ownGeo) {
15159
- mesh.geometry.dispose();
15160
- mesh.geometry = mv.origGeometry;
15837
+ mesh2.geometry.dispose();
15838
+ mesh2.geometry = mv.origGeometry;
15161
15839
  mv.ownGeo = false;
15162
15840
  mv.work = null;
15163
15841
  }
15164
15842
  if (mv.ownMat) {
15165
- for (const m of materialsOf(mesh.material)) m.dispose();
15166
- mesh.material = mv.origMaterial;
15843
+ for (const m of materialsOf(mesh2.material)) m.dispose();
15844
+ mesh2.material = mv.origMaterial;
15167
15845
  mv.ownMat = false;
15168
15846
  mv.baseColors = null;
15169
15847
  }
15170
- mesh.visible = mv.origVisible;
15171
- if (mv.origParent && mesh.parent !== mv.origParent) {
15172
- mv.origParent.add(mesh);
15848
+ mesh2.visible = mv.origVisible;
15849
+ if (mv.origParent && mesh2.parent !== mv.origParent) {
15850
+ mv.origParent.add(mesh2);
15173
15851
  mv.moved = true;
15174
15852
  }
15175
15853
  if (mv.moved) {
15176
- mesh.position.copy(mv.origPosition);
15177
- mesh.quaternion.copy(mv.origQuaternion);
15178
- mesh.scale.copy(mv.origScale);
15854
+ mesh2.position.copy(mv.origPosition);
15855
+ mesh2.quaternion.copy(mv.origQuaternion);
15856
+ mesh2.scale.copy(mv.origScale);
15179
15857
  mv.moved = false;
15180
15858
  }
15181
15859
  mv.meltApplied = 0;
@@ -16223,88 +16901,42 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
16223
16901
  this.disposed = true;
16224
16902
  }
16225
16903
  };
16226
- var ATTACK = 2e-3;
16227
- var lengthOf = (d, attack) => {
16228
- if (typeof d === "number") return attack + d;
16229
- if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
16230
- return { ...d, add: (d.add ?? 0) + attack };
16231
- };
16232
- var envelope = (attack) => [
16233
- [0, 1e-4, "set", "fixed"],
16234
- [attack, 1, "exp"],
16235
- ["1d", 1e-4, "exp", "fixed"]
16236
- ];
16237
- function noise(o) {
16238
- const a = o.a ?? ATTACK;
16239
- const over = typeof o.d === "number" ? o.d : 0;
16240
- return {
16241
- filters: {
16242
- mode: "serial",
16243
- bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
16244
- },
16245
- duration: lengthOf(o.d, a),
16246
- gain: o.vol,
16247
- ...o.delay === void 0 ? {} : { at: o.delay },
16248
- ...o.wet === void 0 ? {} : { wet: o.wet },
16249
- source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
16250
- envelope: envelope(a)
16251
- };
16252
- }
16253
- function tone(o) {
16254
- return {
16255
- source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
16256
- duration: lengthOf(o.d, ATTACK),
16257
- gain: o.vol,
16258
- ...o.delay === void 0 ? {} : { at: o.delay },
16259
- ...o.wet === void 0 ? {} : { wet: o.wet },
16260
- envelope: envelope(ATTACK)
16261
- };
16262
- }
16263
- var many = (count, make) => Array.from({ length: count }, make);
16264
- var scene = (id, layers, line, vars) => ({
16265
- id,
16266
- bus: "world",
16267
- pan: true,
16268
- layers,
16269
- falloff: DISTANCE_CURVES.art_scene,
16270
- ...vars ? { vars } : {},
16271
- sources: [`cena:${line}`]
16272
- });
16904
+ var scene = (id, layers, line, vars) => sceneRecipe(id, layers, line, vars ? { vars } : {});
16273
16905
  var boomLayers = (big) => [
16274
- noise({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
16275
- tone({ f: 65, f2: 24, d: 1, vol: 1.8 }),
16276
- noise({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
16277
- ...big ? [noise({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
16278
- ...many(8, () => tone({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
16906
+ sceneNoise({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
16907
+ sceneTone({ f: 65, f2: 24, d: 1, vol: 1.8 }),
16908
+ sceneNoise({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
16909
+ ...big ? [sceneNoise({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
16910
+ ...sceneMany(8, () => sceneTone({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
16279
16911
  ];
16280
16912
  var SCENE_SOUNDS = [
16281
16913
  scene(
16282
16914
  "glass_shatter",
16283
- [noise({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...many(12, () => tone({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
16915
+ [sceneNoise({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...sceneMany(12, () => sceneTone({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
16284
16916
  "L1359 glass"
16285
16917
  ),
16286
16918
  scene(
16287
16919
  "wood_hit",
16288
- [noise({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), tone({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), noise({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
16920
+ [sceneNoise({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), sceneTone({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), sceneNoise({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
16289
16921
  "L1360 wood"
16290
16922
  ),
16291
- scene("thunk", [noise({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
16292
- scene("ping", [tone({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
16293
- scene("clang", [tone({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), tone({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), noise({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
16294
- scene("chip", [noise({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
16923
+ scene("thunk", [sceneNoise({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
16924
+ scene("ping", [sceneTone({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
16925
+ scene("clang", [sceneTone({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), sceneTone({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), sceneNoise({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
16926
+ scene("chip", [sceneNoise({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
16295
16927
  scene(
16296
16928
  "crumble",
16297
- [noise({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...many(5, () => noise({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
16929
+ [sceneNoise({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...sceneMany(5, () => sceneNoise({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
16298
16930
  "L1365 crumble"
16299
16931
  ),
16300
16932
  // `thud(x, z, v)`: `v` is the `scale` param (default 1)
16301
- scene("thud", [tone({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), noise({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
16933
+ scene("thud", [sceneTone({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), sceneNoise({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
16302
16934
  // `hiss(x, z, d)`: `seconds` is the param `d` (default 1.2)
16303
- scene("hiss", [noise({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
16935
+ scene("hiss", [sceneNoise({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
16304
16936
  scene("boom", boomLayers(false), "L1367 boom"),
16305
16937
  scene("boom_big", boomLayers(true), "L1367 boom (big)"),
16306
- scene("burst", [noise({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), noise({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
16307
- scene("crack", [noise({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), tone({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
16938
+ scene("burst", [sceneNoise({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), sceneNoise({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
16939
+ scene("crack", [sceneNoise({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), sceneTone({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
16308
16940
  ];
16309
16941
  var EXPLOSION_BOOM = {
16310
16942
  id: "explosion_boom",
@@ -32046,7 +32678,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32046
32678
  if (lastRow < 0) return;
32047
32679
  const profile = profileOf2();
32048
32680
  const rainScale = 1 - options.weather().rain;
32049
- const seconds = dryingSeconds * rainScale;
32681
+ const seconds = dryingSeconds * rainScale * (options.weather().drying ?? 1);
32050
32682
  for (let row = firstRow; row <= lastRow; row++) {
32051
32683
  const offset = row * width;
32052
32684
  for (let column = firstColumn; column <= lastColumn; column++) {
@@ -32170,6 +32802,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32170
32802
  }
32171
32803
  var DEFAULT_STEP_RATE = 180;
32172
32804
  var DEFAULT_MAX_STEPS = 6;
32805
+ var WASH_BELOW_DRYNESS = 0.5;
32173
32806
  function createFilm(options) {
32174
32807
  const { layout, random } = options;
32175
32808
  const cellCount = layout.width * layout.height;
@@ -32180,7 +32813,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32180
32813
  dryness: new Float32Array(cellCount),
32181
32814
  dampness: new Float32Array(cellCount)
32182
32815
  };
32183
- let weatherState = { rain: 0 };
32816
+ let weatherState = { rain: 0, drying: 1 };
32184
32817
  let currentProfile = options.profile;
32185
32818
  const context = { field, random, profile: () => currentProfile };
32186
32819
  const drops = createDropPool(layout, context);
@@ -32218,9 +32851,29 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32218
32851
  simulator.advance({ dt, dryingSeconds });
32219
32852
  },
32220
32853
  setWeather(weather) {
32221
- weatherState = { rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)) };
32854
+ weatherState = {
32855
+ rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)),
32856
+ drying: Math.max(0, weather.drying ?? weatherState.drying ?? 1)
32857
+ };
32222
32858
  },
32223
32859
  weather: () => ({ ...weatherState }),
32860
+ wash(maxDryness = WASH_BELOW_DRYNESS) {
32861
+ simulator.refreshBounds();
32862
+ const { firstRow, lastRow, firstColumn, lastColumn } = simulator.bounds;
32863
+ let cleared = 0;
32864
+ for (let row = firstRow; row <= lastRow; row++) {
32865
+ for (let column = firstColumn; column <= lastColumn; column++) {
32866
+ const index = row * layout.width + column;
32867
+ if (field.thickness[index] > 0 && field.dryness[index] < maxDryness) {
32868
+ field.thickness[index] = 0;
32869
+ field.dryness[index] = 0;
32870
+ field.dampness[index] = 0;
32871
+ cleared++;
32872
+ }
32873
+ }
32874
+ }
32875
+ return cleared;
32876
+ },
32224
32877
  clear() {
32225
32878
  field.thickness.fill(0);
32226
32879
  field.dryness.fill(0);
@@ -32324,8 +32977,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32324
32977
  bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
32325
32978
  roughness = 0.55;
32326
32979
  }
32327
- const noise2 = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
32328
- bed[index] = bedHeight + (noise2 - 0.5) * roughness * 2;
32980
+ const noise = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
32981
+ bed[index] = bedHeight + (noise - 0.5) * roughness * 2;
32329
32982
  vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
32330
32983
  pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, row * 0.21) * 0.5 + hashCell(column, row) * 0.5) * (region === 3 ? 1.25 : 1);
32331
32984
  }
@@ -32482,8 +33135,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32482
33135
  for (let column = 0; column < columns; column++) {
32483
33136
  const index = (region.firstRow + row) * columns + column;
32484
33137
  const centre = add2(add2(region.origin, scale(columnAxis, (column + 0.5) * cell)), scale(region.rowAxis, (row + 0.5) * cell));
32485
- const noise2 = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
32486
- bed[index] = centre[1] * 1e3 + (noise2 - 0.5) * strip.roughness * 2;
33138
+ const noise = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
33139
+ bed[index] = centre[1] * 1e3 + (noise - 0.5) * strip.roughness * 2;
32487
33140
  vertical[index] = region.isVertical ? 0.95 : 1;
32488
33141
  pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, (region.firstRow + row) * 0.21) * 0.5 + valueNoise(column * 0.9, row * 0.9) * 0.5);
32489
33142
  }
@@ -33150,11 +33803,11 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
33150
33803
  const index = [];
33151
33804
  for (let i = 0; i < this.ring; i++) index.push(0, 1 + i, 1 + (i + 1) % this.ring);
33152
33805
  geometry.setIndex(index);
33153
- const mesh = new T.Mesh(geometry);
33154
- mesh.name = name;
33155
- mesh.frustumCulled = false;
33156
- mesh.renderOrder = order;
33157
- return { mesh, position, color };
33806
+ const mesh2 = new T.Mesh(geometry);
33807
+ mesh2.name = name;
33808
+ mesh2.frustumCulled = false;
33809
+ mesh2.renderOrder = order;
33810
+ return { mesh: mesh2, position, color };
33158
33811
  }
33159
33812
  assign(def, materials) {
33160
33813
  this.outer.mesh.material = materials.stainFilm(def);
@@ -33877,12 +34530,12 @@ void main(){
33877
34530
  this.group = new THREE.Group();
33878
34531
  this.group.name = "film_grid";
33879
34532
  for (const region of layout.regions) {
33880
- const mesh = new THREE.Mesh(createStripGeometry(THREE, layout, region), this.material.material);
33881
- mesh.frustumCulled = false;
33882
- mesh.renderOrder = 3;
33883
- mesh.name = `film_${region.id}`;
33884
- this.strips.push(mesh);
33885
- this.group.add(mesh);
34533
+ const mesh2 = new THREE.Mesh(createStripGeometry(THREE, layout, region), this.material.material);
34534
+ mesh2.frustumCulled = false;
34535
+ mesh2.renderOrder = 3;
34536
+ mesh2.name = `film_${region.id}`;
34537
+ this.strips.push(mesh2);
34538
+ this.group.add(mesh2);
33886
34539
  }
33887
34540
  this.dropsView = createFilmDrops(THREE, layout.rules.dropCapacity, profile);
33888
34541
  this.group.add(this.dropsView.drops, this.dropsView.bead);
@@ -33927,6 +34580,19 @@ void main(){
33927
34580
  this.pending = this.union(this.pending, rect);
33928
34581
  return this.takePending();
33929
34582
  }
34583
+ /** Clears the cells less than `maxDryness` dry and sends the rectangle that held them up with the next update. Returns how many it cleared. */
34584
+ wash(maxDryness) {
34585
+ const cleared = this.film.wash(maxDryness);
34586
+ if (cleared > 0) {
34587
+ const bounds = this.film.simulator.bounds;
34588
+ if (bounds.lastRow >= 0) {
34589
+ const rect = { firstRow: bounds.firstRow, lastRow: bounds.lastRow, firstColumn: bounds.firstColumn, lastColumn: bounds.lastColumn };
34590
+ this.encode(rect);
34591
+ this.pending = this.union(this.pending, rect);
34592
+ }
34593
+ }
34594
+ return cleared;
34595
+ }
33930
34596
  takePending() {
33931
34597
  const rect = this.pending;
33932
34598
  this.pending = null;
@@ -33982,7 +34648,7 @@ void main(){
33982
34648
  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) };
33983
34649
  }
33984
34650
  dispose() {
33985
- for (const mesh of this.strips) mesh.geometry.dispose();
34651
+ for (const mesh2 of this.strips) mesh2.geometry.dispose();
33986
34652
  this.material.dispose();
33987
34653
  this.dropsView.dispose();
33988
34654
  this.texture.dispose();
@@ -33996,11 +34662,12 @@ void main(){
33996
34662
  var DEFAULT_GRID_METRES = 2;
33997
34663
  var DEFAULT_CELL2 = 0.0125;
33998
34664
  var BAKE_RANGE_FACTOR = 1.25;
34665
+ var DEFAULT_WASHABLE = ["blood", "old_blood"];
33999
34666
  var EDGE_MARGIN = 0.35;
34000
34667
  var FLOOR_REACH = 0.3;
34001
34668
  var DROP_HEIGHT = 2.5;
34002
34669
  var DEFAULT_DROP_RADIUS = 3;
34003
- var FALL_SPEED = 2;
34670
+ var FALL_SPEED2 = 2;
34004
34671
  var VISIBLE_THICKNESS22 = 0.035;
34005
34672
  var DEFAULT_HALF = 50;
34006
34673
  var FREE_AFTER_EMPTY = 2;
@@ -34021,6 +34688,7 @@ void main(){
34021
34688
  __publicField5(this, "staging", /* @__PURE__ */ new Map());
34022
34689
  __publicField5(this, "now");
34023
34690
  __publicField5(this, "rain", 0);
34691
+ __publicField5(this, "drying", 1);
34024
34692
  __publicField5(this, "time", 0);
34025
34693
  __publicField5(this, "bakedCount", 0);
34026
34694
  __publicField5(this, "last", { grids: 0, drops: 0, wetCells: 0, simMs: 0, starved: 0, baked: 0, uploadedTexels: 0 });
@@ -34040,13 +34708,24 @@ void main(){
34040
34708
  }
34041
34709
  setWeather(weather) {
34042
34710
  this.rain = Math.min(1, Math.max(0, weather.rain));
34043
- for (const grid of this.grids) grid.film.setWeather({ rain: this.rain });
34711
+ if (weather.drying !== void 0) this.drying = Math.max(0, weather.drying);
34712
+ for (const grid of this.grids) grid.film.setWeather({ rain: this.rain, drying: this.drying });
34713
+ }
34714
+ /**
34715
+ * 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
34716
+ * cells that are less than `maxDryness` dry (default 0.5) are cleared. Returns how many cells it cleared.
34717
+ */
34718
+ wash(maxDryness) {
34719
+ const washable = this.options.washable ?? DEFAULT_WASHABLE;
34720
+ let cleared = 0;
34721
+ for (const grid of this.grids) if (washable.includes(grid.profile.id)) cleared += grid.wash(maxDryness);
34722
+ return cleared;
34044
34723
  }
34045
34724
  /** 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. */
34046
34725
  launch(request) {
34047
34726
  const grid = this.gridFor(request.fluid, request.point, request.surfaces, request.tint);
34048
34727
  if (!grid) return null;
34049
- const velocity = request.velocity ?? [0, -FALL_SPEED, 0];
34728
+ const velocity = request.velocity ?? [0, -FALL_SPEED2, 0];
34050
34729
  grid.film.launch(request.point, velocity, request.radius ?? DEFAULT_DROP_RADIUS);
34051
34730
  return grid;
34052
34731
  }
@@ -34071,7 +34750,7 @@ void main(){
34071
34750
  const layout = layoutFromSurfaces({ ...found, center: point, columns: this.columns(), rows: this.columns(), cellSize: this.options.cellSize ?? DEFAULT_CELL2 });
34072
34751
  if (this.grids.length >= (this.options.maxGrids ?? DEFAULT_MAX_GRIDS)) this.evict(point);
34073
34752
  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 } });
34074
- grid.film.setWeather({ rain: this.rain });
34753
+ grid.film.setWeather({ rain: this.rain, drying: this.drying });
34075
34754
  this.grids.push(grid);
34076
34755
  this.group.add(grid.group);
34077
34756
  return grid;
@@ -34148,7 +34827,7 @@ void main(){
34148
34827
  /** Whether `point` is over the grid, away from its edge, so a splash there does not fall off the strip. */
34149
34828
  covers(grid, point) {
34150
34829
  const layout = grid.film.layout;
34151
- const wide = layout.width * layout.cellSize;
34830
+ const wide2 = layout.width * layout.cellSize;
34152
34831
  for (const region of layout.regions) {
34153
34832
  const local2 = [point[0] - region.origin[0], point[1] - region.origin[1], point[2] - region.origin[2]];
34154
34833
  const above2 = local2[0] * region.normal[0] + local2[1] * region.normal[1] + local2[2] * region.normal[2];
@@ -34156,7 +34835,7 @@ void main(){
34156
34835
  const sideways = local2[0] * region.columnAxis[0] + local2[1] * region.columnAxis[1] + local2[2] * region.columnAxis[2];
34157
34836
  const down = local2[0] * region.rowAxis[0] + local2[1] * region.rowAxis[1] + local2[2] * region.rowAxis[2];
34158
34837
  const length3 = region.rowCount * layout.cellSize;
34159
- if (sideways > EDGE_MARGIN && sideways < wide - EDGE_MARGIN && down > EDGE_MARGIN && down < length3 - EDGE_MARGIN) return true;
34838
+ if (sideways > EDGE_MARGIN && sideways < wide2 - EDGE_MARGIN && down > EDGE_MARGIN && down < length3 - EDGE_MARGIN) return true;
34160
34839
  }
34161
34840
  return false;
34162
34841
  }
@@ -34307,6 +34986,7 @@ void main(){
34307
34986
  return landed;
34308
34987
  },
34309
34988
  setWeather: (weather) => manager.setWeather(weather),
34989
+ wash: (maxDryness) => manager.wash(maxDryness),
34310
34990
  update(dt) {
34311
34991
  if (leaks.size === 0) return;
34312
34992
  const streams = new Map(fluids.streams().map((stream) => [stream.id, stream]));
@@ -34399,15 +35079,15 @@ void main(){
34399
35079
  if (s.kind === "box") geo = new THREE.BoxGeometry(s.size[0], s.size[1], s.size[2]);
34400
35080
  else if (s.kind === "cylinder") geo = new THREE.CylinderGeometry(s.radius, s.radius, s.height, seg);
34401
35081
  else geo = new THREE.SphereGeometry(s.radius, seg, Math.max(6, seg - 4));
34402
- const mesh = new THREE.Mesh(geo, materialFor(p));
34403
- mesh.name = p.id;
34404
- mesh.userData.destructionPart = p.id;
34405
- mesh.userData.destructionMaterial = p.material;
34406
- mesh.position.set(p.position[0], p.position[1], p.position[2]);
34407
- if (p.rotation) mesh.rotation.set(p.rotation[0], p.rotation[1], p.rotation[2], "XYZ");
34408
- mesh.castShadow = shadows && !TRANSPARENT[p.material];
34409
- mesh.receiveShadow = shadows;
34410
- root.add(mesh);
35082
+ const mesh2 = new THREE.Mesh(geo, materialFor(p));
35083
+ mesh2.name = p.id;
35084
+ mesh2.userData.destructionPart = p.id;
35085
+ mesh2.userData.destructionMaterial = p.material;
35086
+ mesh2.position.set(p.position[0], p.position[1], p.position[2]);
35087
+ if (p.rotation) mesh2.rotation.set(p.rotation[0], p.rotation[1], p.rotation[2], "XYZ");
35088
+ mesh2.castShadow = shadows && !TRANSPARENT[p.material];
35089
+ mesh2.receiveShadow = shadows;
35090
+ root.add(mesh2);
34411
35091
  }
34412
35092
  return root;
34413
35093
  }
@@ -35732,16 +36412,16 @@ void main(){
35732
36412
  const up = new THREE.Vector3(0, 1, 0);
35733
36413
  const clear = () => {
35734
36414
  projectiles.length = 0;
35735
- for (const mesh of meshes) mesh.removeFromParent();
36415
+ for (const mesh2 of meshes) mesh2.removeFromParent();
35736
36416
  meshes.length = 0;
35737
36417
  };
35738
36418
  const addMesh = (geometry, color, metalness, at3) => {
35739
- const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness }));
35740
- mesh.castShadow = true;
35741
- mesh.position.set(at3[0], at3[1], at3[2]);
35742
- scene2.add(mesh);
35743
- meshes.push(mesh);
35744
- return mesh;
36419
+ const mesh2 = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness }));
36420
+ mesh2.castShadow = true;
36421
+ mesh2.position.set(at3[0], at3[1], at3[2]);
36422
+ scene2.add(mesh2);
36423
+ meshes.push(mesh2);
36424
+ return mesh2;
35745
36425
  };
35746
36426
  const remove = (projectile) => {
35747
36427
  for (const extra of projectile.attached ?? []) {
@@ -35759,9 +36439,9 @@ void main(){
35759
36439
  const isMolotov = !!use.fire;
35760
36440
  const isRocket = use.trigger === "impact" && (use.armDistance ?? 0) > 0;
35761
36441
  const geometry = isRocket ? new THREE.CylinderGeometry(0.04, 0.04, 0.5, 8) : isMolotov ? new THREE.CylinderGeometry(0.035, 0.04, 0.2, 8) : new THREE.SphereGeometry(0.045, 8, 6);
35762
- const mesh = addMesh(geometry, isMolotov ? 3897915 : isRocket ? 7041116 : 4936480, isMolotov ? 0 : 0.4, use.origin);
36442
+ const mesh2 = addMesh(geometry, isMolotov ? 3897915 : isRocket ? 7041116 : 4936480, isMolotov ? 0 : 0.4, use.origin);
35763
36443
  projectiles.push({
35764
- mesh,
36444
+ mesh: mesh2,
35765
36445
  pos: new THREE.Vector3(use.origin[0], use.origin[1], use.origin[2]),
35766
36446
  vel: new THREE.Vector3(use.velocity[0], use.velocity[1], use.velocity[2]),
35767
36447
  weapon: context.toolId(),
@@ -35812,11 +36492,11 @@ void main(){
35812
36492
  const placed = { ...use, origin: at3 };
35813
36493
  const body = createExplosiveBody(placed, arsenal.explosives, arsenal.rules.explosives);
35814
36494
  if (!body) return;
35815
- const mesh = addMesh(new THREE.BoxGeometry(0.18, 0.06, 0.1), use.explosive === "claymore" ? 5597999 : 12757112, 0.1, at3);
35816
- mesh.rotation.y = Math.atan2(use.direction[0], use.direction[2]);
36495
+ const mesh2 = addMesh(new THREE.BoxGeometry(0.18, 0.06, 0.1), use.explosive === "claymore" ? 5597999 : 12757112, 0.1, at3);
36496
+ mesh2.rotation.y = Math.atan2(use.direction[0], use.direction[2]);
35817
36497
  const attached = [markerAt(at3)];
35818
36498
  const segment = tripwireSegment(placed);
35819
- const projectile = { mesh, pos: new THREE.Vector3(at3[0], at3[1], at3[2]), vel: new THREE.Vector3(), weapon: context.toolId(), fuse: 0, burstOnImpact: false, energy: use.energy, radius: use.radius, age: 0, body, placed: true, attached };
36499
+ const projectile = { mesh: mesh2, pos: new THREE.Vector3(at3[0], at3[1], at3[2]), vel: new THREE.Vector3(), weapon: context.toolId(), fuse: 0, burstOnImpact: false, energy: use.energy, radius: use.radius, age: 0, body, placed: true, attached };
35820
36500
  if (segment) {
35821
36501
  const wire = { from: segment.from, to: segment.to, letThrough: /* @__PURE__ */ new Set() };
35822
36502
  for (const id of touching(wire)) wire.letThrough.add(id);
@@ -36493,13 +37173,13 @@ void main(){
36493
37173
  if (!g || g.done) break;
36494
37174
  g.done = true;
36495
37175
  const [a, b] = ev.parts;
36496
- const many2 = g.list.length > 1;
37176
+ const many = g.list.length > 1;
36497
37177
  c.log.add({
36498
37178
  channel: "event",
36499
37179
  type: "detach",
36500
37180
  objectId: ev.object,
36501
37181
  itemId: c.itemIdOf(ev.object),
36502
- text: many2 ? t(`ev.detach.many.${ev.by === "tool" ? "tool" : "break"}`, { obj: c.nameOf(ev.object), joints: joints(g.list.length), pieces: pieces(g.groups) }) : t("ev.detach", { obj: c.nameOf(ev.object), a, b, by: t(`by.${ev.by}`), pieces: pieces(g.groups) }),
37182
+ text: many ? t(`ev.detach.many.${ev.by === "tool" ? "tool" : "break"}`, { obj: c.nameOf(ev.object), joints: joints(g.list.length), pieces: pieces(g.groups) }) : t("ev.detach", { obj: c.nameOf(ev.object), a, b, by: t(`by.${ev.by}`), pieces: pieces(g.groups) }),
36503
37183
  data: { joint: ev.joint, joints: g.list.map((j) => j.joint), parts: ev.parts, by: ev.by, groups: g.groups }
36504
37184
  });
36505
37185
  break;
@@ -36783,6 +37463,9 @@ void main(){
36783
37463
  const blocked = (x, z) => {
36784
37464
  if (Math.abs(x) > o.limit - PLAYER_RADIUS || Math.abs(z) > o.limit - PLAYER_RADIUS) return true;
36785
37465
  for (const c of o.colliders) if (x > c[0] - PLAYER_RADIUS && x < c[2] + PLAYER_RADIUS && z > c[1] - PLAYER_RADIUS && z < c[3] + PLAYER_RADIUS) return true;
37466
+ if (o.blockers) {
37467
+ for (const blocked2 of o.blockers) if (blocked2(x, z, PLAYER_RADIUS)) return true;
37468
+ }
36786
37469
  return false;
36787
37470
  };
36788
37471
  const move = (dx, dz) => {
@@ -38431,10 +39114,12 @@ void main(){
38431
39114
  log.setBeforeRead(flush);
38432
39115
  let useHeld = false;
38433
39116
  let useFirst = false;
39117
+ const blockers = /* @__PURE__ */ new Set();
38434
39118
  const player = createPlayer(THREE, {
38435
39119
  camera: camera2,
38436
39120
  dom,
38437
39121
  colliders: arena.colliders,
39122
+ blockers,
38438
39123
  limit: arenaSize,
38439
39124
  start: arena.playerStart,
38440
39125
  hooks: {
@@ -38617,8 +39302,10 @@ void main(){
38617
39302
  film.clear();
38618
39303
  fluids.setTap(TAP_ID, false);
38619
39304
  };
39305
+ const clearListeners = /* @__PURE__ */ new Set();
38620
39306
  const clear = () => {
38621
39307
  clearEverything();
39308
+ for (const listener of [...clearListeners]) listener();
38622
39309
  restingTops.length = 0;
38623
39310
  pending = [];
38624
39311
  action("clear", t("act.clear"));
@@ -39817,6 +40504,14 @@ void main(){
39817
40504
  },
39818
40505
  log(text, data) {
39819
40506
  action("module_note", text, { data });
40507
+ },
40508
+ addBlocker(blocked) {
40509
+ blockers.add(blocked);
40510
+ return () => blockers.delete(blocked);
40511
+ },
40512
+ onClear(listener) {
40513
+ clearListeners.add(listener);
40514
+ return () => clearListeners.delete(listener);
39820
40515
  }
39821
40516
  };
39822
40517
  const lendBench = (store) => ({
@@ -40021,6 +40716,8 @@ void main(){
40021
40716
  camera: camera2,
40022
40717
  arsenal,
40023
40718
  fluids,
40719
+ film: filmBridge,
40720
+ vfx: vfx.world,
40024
40721
  fluidRenderer: fluidView,
40025
40722
  audio,
40026
40723
  environment,