feat(traps): activation VFX/SFX, explosion effect, and help section

Wire mimic/pit/arrow/laser media on activate, poison/explosion via effects, and document the scene editor and traps in Instructions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-25 12:35:22 +08:00
parent 02d73ddf81
commit fed5674468
36 changed files with 697 additions and 34 deletions
@@ -57,6 +57,9 @@ function vfxPacksForTool(tool: EffectToolType): readonly VfxFramePack[] {
return ['sunbeam'];
case 'poisonCloud':
return ['poisonCloud'];
case 'explosion':
// Визуал — HTML `<video>` с альфой (ExplosionVideoOverlay), не кадровый Pixi-pack.
return [];
default:
return [];
}
@@ -78,6 +81,8 @@ function vfxPacksForInstanceType(type: EffectInstanceType): readonly VfxFramePac
return ['sunbeam'];
case 'poisonCloud':
return ['poisonCloud'];
case 'explosion':
return [];
default:
return [];
}
@@ -769,6 +774,13 @@ function createInstanceNode(
redrawPoisonCloud(cont, inst, viewport, 0, Math.max(1, inst.lifetimeMs));
return cont;
}
if (inst.type === 'explosion') {
// Визуал рисует ExplosionVideoOverlay (WebM с альфой). Pixi-нода — только placeholder.
const cont = new pixi.Container();
cont.visible = false;
(cont as any).__fx = { id: inst.id, type: inst.type };
return cont;
}
if (inst.type === 'freeze') {
const tex = getFreezeScreenTexture(pixi, inst.seed, viewport);
const s = new pixi.Sprite(tex);
@@ -988,6 +1000,12 @@ function animateNodes(
redrawPoisonCloud(cont, inst, viewport, t, life);
}
if (inst.type === 'explosion') {
// Визуал — ExplosionVideoOverlay; Pixi-placeholder остаётся скрытым.
node.visible = false;
continue;
}
if (inst.type === 'freeze') {
const s = node;
const life = Math.max(1, inst.lifetimeMs);
@@ -1776,6 +1794,9 @@ function instanceContentSig(inst: EffectInstance): string {
if (inst.type === 'poisonCloud') {
return `pc:${Math.round(inst.at.x * 1000)}:${Math.round(inst.at.y * 1000)}:${Math.round(inst.radiusN * 1000)}`;
}
if (inst.type === 'explosion') {
return `ex:${Math.round(inst.at.x * 1000)}:${Math.round(inst.at.y * 1000)}:${Math.round(inst.radiusN * 1000)}`;
}
if (inst.type === 'freeze') {
return `fr:${Math.round(inst.at.x * 1000)}:${Math.round(inst.at.y * 1000)}:${Math.round(inst.intensity * 1000)}`;
}
@@ -1913,6 +1934,11 @@ function relayoutInstanceNode(
return true;
}
if (inst.type === 'explosion') {
node.visible = false;
return true;
}
if (inst.type === 'freeze') {
const fx = (node as any).__fx ?? {};
if (fx.vw !== viewport.w || fx.vh !== viewport.h) {