Spaces:
Running
Running
Rebuild comboBattler: procedural skill VFX
Browse filesCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- web/comboBattler.js +143 -0
web/comboBattler.js
CHANGED
|
@@ -6036,6 +6036,106 @@ async function createEmoteLayer(pixi, parent, { url, unit, sizeTiles = 1.3, head
|
|
| 6036 |
return { show, update, clear, destroy, keys: Object.keys(CELL) };
|
| 6037 |
}
|
| 6038 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6039 |
// ../auto-battler/src/render/comboBattler.js
|
| 6040 |
var TILE8 = 8;
|
| 6041 |
var SKILL_YELLOW = 16769357;
|
|
@@ -6092,6 +6192,7 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6092 |
})();
|
| 6093 |
let battle = null, R = null, combatRoot = null, rings = null, markers = null, navDbg = null, spawnFn = null, dead = false;
|
| 6094 |
let emoteLayer = null;
|
|
|
|
| 6095 |
const emoPrev = /* @__PURE__ */ new Map();
|
| 6096 |
let idleEmoAcc = 0;
|
| 6097 |
let runState = { time: 0, kills: 0, diff: 1 };
|
|
@@ -6417,6 +6518,18 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6417 |
for (const e of ea()) if (e.alive) m = Math.min(m, dist3(p, e));
|
| 6418 |
return m;
|
| 6419 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6420 |
function tick(ticker) {
|
| 6421 |
if (!battle || !R) return;
|
| 6422 |
if (paused) return;
|
|
@@ -6504,6 +6617,23 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6504 |
R.updateFloats(dtMS);
|
| 6505 |
R.drawProjectiles(battle);
|
| 6506 |
R.processLog(battle, cursor, {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6507 |
onHit: (e) => {
|
| 6508 |
const p2 = pa();
|
| 6509 |
if (!p2) return;
|
|
@@ -6539,6 +6669,7 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6539 |
}
|
| 6540 |
}
|
| 6541 |
updateEmotes(dt);
|
|
|
|
| 6542 |
updateHud();
|
| 6543 |
drawRings();
|
| 6544 |
drawMarkers();
|
|
@@ -6693,6 +6824,11 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6693 |
}
|
| 6694 |
emoteLayer = null;
|
| 6695 |
emoPrev.clear();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6696 |
hitstopT = 0;
|
| 6697 |
shake.t = 0;
|
| 6698 |
shake.mag = 0;
|
|
@@ -6782,9 +6918,16 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6782 |
emoteLayer = null;
|
| 6783 |
}
|
| 6784 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6785 |
if (!alive) {
|
| 6786 |
emoteLayer?.destroy();
|
| 6787 |
emoteLayer = null;
|
|
|
|
|
|
|
| 6788 |
return;
|
| 6789 |
}
|
| 6790 |
let foeN = 0;
|
|
|
|
| 6036 |
return { show, update, clear, destroy, keys: Object.keys(CELL) };
|
| 6037 |
}
|
| 6038 |
|
| 6039 |
+
// ../auto-battler/src/render/skillVfx.js
|
| 6040 |
+
function createSkillVfx(pixi, parent, { gScale = 1 } = {}) {
|
| 6041 |
+
const { Container, Graphics } = pixi;
|
| 6042 |
+
const layer = new Container();
|
| 6043 |
+
layer.zIndex = 6e8;
|
| 6044 |
+
parent.addChild(layer);
|
| 6045 |
+
const active = [];
|
| 6046 |
+
const hex = (s) => {
|
| 6047 |
+
const n = parseInt(String(s || "").replace("#", ""), 16);
|
| 6048 |
+
return Number.isFinite(n) ? n : 16777215;
|
| 6049 |
+
};
|
| 6050 |
+
const lwOf = (base) => base / gScale;
|
| 6051 |
+
function play(visual, x, y, radius, now, { aim } = {}) {
|
| 6052 |
+
const g = new Graphics();
|
| 6053 |
+
layer.addChild(g);
|
| 6054 |
+
active.push({ g, born: now, dur: 0.45 + (visual?.intensity || 2) * 0.13, shape: visual?.shape || "nova", color: hex(visual?.color), intensity: visual?.intensity || 2, x, y, radius: radius || 120, aim });
|
| 6055 |
+
}
|
| 6056 |
+
function update(now) {
|
| 6057 |
+
for (let i = active.length - 1; i >= 0; i--) {
|
| 6058 |
+
const e = active[i];
|
| 6059 |
+
const t = (now - e.born) / e.dur;
|
| 6060 |
+
if (t >= 1 || t < 0) {
|
| 6061 |
+
try {
|
| 6062 |
+
e.g.destroy();
|
| 6063 |
+
} catch {
|
| 6064 |
+
}
|
| 6065 |
+
active.splice(i, 1);
|
| 6066 |
+
continue;
|
| 6067 |
+
}
|
| 6068 |
+
draw(e, t);
|
| 6069 |
+
}
|
| 6070 |
+
}
|
| 6071 |
+
function draw(e, t) {
|
| 6072 |
+
const g = e.g;
|
| 6073 |
+
g.clear();
|
| 6074 |
+
const ease = 1 - Math.pow(1 - t, 2);
|
| 6075 |
+
const a = 1 - t;
|
| 6076 |
+
const lw = lwOf(2 + e.intensity);
|
| 6077 |
+
const c = e.color, R = e.radius;
|
| 6078 |
+
switch (e.shape) {
|
| 6079 |
+
case "ring":
|
| 6080 |
+
case "shockwave": {
|
| 6081 |
+
const r = R * ease;
|
| 6082 |
+
g.circle(e.x, e.y, r).stroke({ width: lw * 1.6, color: c, alpha: a * 0.95 });
|
| 6083 |
+
if (e.shape === "shockwave") g.circle(e.x, e.y, r * 0.66).stroke({ width: lw, color: c, alpha: a * 0.5 });
|
| 6084 |
+
break;
|
| 6085 |
+
}
|
| 6086 |
+
case "beam": {
|
| 6087 |
+
const aim = e.aim || { x: e.x + R, y: e.y };
|
| 6088 |
+
g.moveTo(e.x, e.y).lineTo(aim.x, aim.y).stroke({ width: lw * 2.4 * (1 - t * 0.5), color: c, alpha: a });
|
| 6089 |
+
g.circle(aim.x, aim.y, R * 0.18 * ease).fill({ color: c, alpha: a * 0.7 });
|
| 6090 |
+
break;
|
| 6091 |
+
}
|
| 6092 |
+
case "pillar": {
|
| 6093 |
+
const w = R * 0.5 * (1 - t * 0.3), h = R * 1.5;
|
| 6094 |
+
g.rect(e.x - w / 2, e.y - h, w, h).fill({ color: c, alpha: a * 0.32 });
|
| 6095 |
+
g.rect(e.x - w / 2, e.y - h, w, h).stroke({ width: lw, color: c, alpha: a * 0.85 });
|
| 6096 |
+
break;
|
| 6097 |
+
}
|
| 6098 |
+
case "projectile": {
|
| 6099 |
+
const r = R * ease;
|
| 6100 |
+
g.circle(e.x, e.y, Math.max(lwOf(4), R * 0.16)).fill({ color: c, alpha: a });
|
| 6101 |
+
g.circle(e.x, e.y, r).stroke({ width: lw, color: c, alpha: a * 0.4 });
|
| 6102 |
+
break;
|
| 6103 |
+
}
|
| 6104 |
+
case "slash": {
|
| 6105 |
+
const r = R * (0.6 + ease * 0.5);
|
| 6106 |
+
g.arc(e.x, e.y, r, -0.8 + t * 1.2, 0.8 + t * 1.2).stroke({ width: lw * 2, color: c, alpha: a });
|
| 6107 |
+
break;
|
| 6108 |
+
}
|
| 6109 |
+
case "nova":
|
| 6110 |
+
default: {
|
| 6111 |
+
const r = R * ease;
|
| 6112 |
+
g.circle(e.x, e.y, r).fill({ color: c, alpha: a * 0.22 });
|
| 6113 |
+
g.circle(e.x, e.y, r).stroke({ width: lw * 1.5, color: c, alpha: a * 0.9 });
|
| 6114 |
+
const spokes = e.intensity * 4;
|
| 6115 |
+
for (let k = 0; k < spokes; k++) {
|
| 6116 |
+
const ang = k / spokes * Math.PI * 2;
|
| 6117 |
+
g.moveTo(e.x, e.y).lineTo(e.x + Math.cos(ang) * r, e.y + Math.sin(ang) * r).stroke({ width: lw * 0.6, color: c, alpha: a * 0.5 });
|
| 6118 |
+
}
|
| 6119 |
+
break;
|
| 6120 |
+
}
|
| 6121 |
+
}
|
| 6122 |
+
}
|
| 6123 |
+
function destroy() {
|
| 6124 |
+
for (const e of active) {
|
| 6125 |
+
try {
|
| 6126 |
+
e.g.destroy();
|
| 6127 |
+
} catch {
|
| 6128 |
+
}
|
| 6129 |
+
}
|
| 6130 |
+
active.length = 0;
|
| 6131 |
+
try {
|
| 6132 |
+
layer.destroy({ children: true });
|
| 6133 |
+
} catch {
|
| 6134 |
+
}
|
| 6135 |
+
}
|
| 6136 |
+
return { play, update, destroy };
|
| 6137 |
+
}
|
| 6138 |
+
|
| 6139 |
// ../auto-battler/src/render/comboBattler.js
|
| 6140 |
var TILE8 = 8;
|
| 6141 |
var SKILL_YELLOW = 16769357;
|
|
|
|
| 6192 |
})();
|
| 6193 |
let battle = null, R = null, combatRoot = null, rings = null, markers = null, navDbg = null, spawnFn = null, dead = false;
|
| 6194 |
let emoteLayer = null;
|
| 6195 |
+
let skillVfxLayer = null;
|
| 6196 |
const emoPrev = /* @__PURE__ */ new Map();
|
| 6197 |
let idleEmoAcc = 0;
|
| 6198 |
let runState = { time: 0, kills: 0, diff: 1 };
|
|
|
|
| 6518 |
for (const e of ea()) if (e.alive) m = Math.min(m, dist3(p, e));
|
| 6519 |
return m;
|
| 6520 |
};
|
| 6521 |
+
const nearestFoeTo = (a) => {
|
| 6522 |
+
if (!a) return null;
|
| 6523 |
+
let m = Infinity, best = null;
|
| 6524 |
+
for (const e of ea()) if (e.alive) {
|
| 6525 |
+
const d = dist3(a, e);
|
| 6526 |
+
if (d < m) {
|
| 6527 |
+
m = d;
|
| 6528 |
+
best = e;
|
| 6529 |
+
}
|
| 6530 |
+
}
|
| 6531 |
+
return best;
|
| 6532 |
+
};
|
| 6533 |
function tick(ticker) {
|
| 6534 |
if (!battle || !R) return;
|
| 6535 |
if (paused) return;
|
|
|
|
| 6617 |
R.updateFloats(dtMS);
|
| 6618 |
R.drawProjectiles(battle);
|
| 6619 |
R.processLog(battle, cursor, {
|
| 6620 |
+
onCast: (e) => {
|
| 6621 |
+
const caster = battle.actors.find((x) => x.id === e.who);
|
| 6622 |
+
const skill = caster?.bar?.find((s) => s && s.id === e.skillId);
|
| 6623 |
+
if (skill?.visual && skillVfxLayer) {
|
| 6624 |
+
const aoe = (skill.effects || []).find((x) => x.scope === "area_around_caster");
|
| 6625 |
+
const radius = aoe?.range || 110;
|
| 6626 |
+
let aim = null;
|
| 6627 |
+
if (skill.visual.shape === "beam" || skill.visual.shape === "projectile") {
|
| 6628 |
+
const f = nearestFoeTo(caster);
|
| 6629 |
+
if (f) aim = { x: f.x, y: f.y };
|
| 6630 |
+
}
|
| 6631 |
+
skillVfxLayer.play(skill.visual, caster.x, caster.y, radius, battle.t, { aim });
|
| 6632 |
+
const inten = skill.visual.intensity || 2;
|
| 6633 |
+
addShake(0.1 + inten * 0.05, 2 + inten * 1.5);
|
| 6634 |
+
}
|
| 6635 |
+
return {};
|
| 6636 |
+
},
|
| 6637 |
onHit: (e) => {
|
| 6638 |
const p2 = pa();
|
| 6639 |
if (!p2) return;
|
|
|
|
| 6669 |
}
|
| 6670 |
}
|
| 6671 |
updateEmotes(dt);
|
| 6672 |
+
skillVfxLayer?.update(battle.t);
|
| 6673 |
updateHud();
|
| 6674 |
drawRings();
|
| 6675 |
drawMarkers();
|
|
|
|
| 6824 |
}
|
| 6825 |
emoteLayer = null;
|
| 6826 |
emoPrev.clear();
|
| 6827 |
+
try {
|
| 6828 |
+
skillVfxLayer?.destroy();
|
| 6829 |
+
} catch {
|
| 6830 |
+
}
|
| 6831 |
+
skillVfxLayer = null;
|
| 6832 |
hitstopT = 0;
|
| 6833 |
shake.t = 0;
|
| 6834 |
shake.mag = 0;
|
|
|
|
| 6918 |
emoteLayer = null;
|
| 6919 |
}
|
| 6920 |
}
|
| 6921 |
+
try {
|
| 6922 |
+
skillVfxLayer = createSkillVfx(pixi, combatRoot, { gScale: G });
|
| 6923 |
+
} catch {
|
| 6924 |
+
skillVfxLayer = null;
|
| 6925 |
+
}
|
| 6926 |
if (!alive) {
|
| 6927 |
emoteLayer?.destroy();
|
| 6928 |
emoteLayer = null;
|
| 6929 |
+
skillVfxLayer?.destroy();
|
| 6930 |
+
skillVfxLayer = null;
|
| 6931 |
return;
|
| 6932 |
}
|
| 6933 |
let foeN = 0;
|