Spaces:
Running
Running
Phase 5: wire emotes into the Game + settings toggle + asset
Browse filesPass the emote sheet URL into comboBattler, fire a level-up heart via heroEmote, add
a Gameplay > 'Show character emotes' toggle, copy the emote sheet to web/assets
(served at /sprites/emotes.png) and add it to build.sh. Rebuilt comboBattler bundle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- build.sh +3 -0
- web/assets/emotes.png +0 -0
- web/comboBattler.js +162 -1
- web/settingsPanel.js +13 -0
- web/tiny.js +2 -2
build.sh
CHANGED
|
@@ -65,6 +65,9 @@ cp "$AB/public/assets/characters.json" web/assets/characters.json
|
|
| 65 |
cp "$AB/public/assets/effects.json" web/assets/effects.json
|
| 66 |
cp "$AB/public/classes.json" web/assets/classes.json
|
| 67 |
cp "$AB/public/enemies.json" web/assets/enemies.json
|
|
|
|
|
|
|
|
|
|
| 68 |
# Map assets: dump the manifest (MAP_ASSET_URLS — assembled from the map renderers + configs in
|
| 69 |
# src/render/mapConfigs.js so it can't drift) and curate exactly those PNGs (tilesets / props /
|
| 70 |
# premade-scene layers / interior+tower skins, ~105 files). No whole-pack copy.
|
|
|
|
| 65 |
cp "$AB/public/assets/effects.json" web/assets/effects.json
|
| 66 |
cp "$AB/public/classes.json" web/assets/classes.json
|
| 67 |
cp "$AB/public/enemies.json" web/assets/enemies.json
|
| 68 |
+
# Emote sheet (Minifantasy UI Overhaul "Character_Emotions") for the on-map condition emotes.
|
| 69 |
+
# Served at /sprites/emotes.png; comboBattler slices it as an 8×8 grid (src/render/emotes.js).
|
| 70 |
+
cp "$AB/public/assets/minifantasy/Minifantasy_UI _Overhaul_v1.0/_Minifantasy_UI_Overhaul_Assets/_General_UI_Resources/Character_Emotions/_Emotions.png" web/assets/emotes.png
|
| 71 |
# Map assets: dump the manifest (MAP_ASSET_URLS — assembled from the map renderers + configs in
|
| 72 |
# src/render/mapConfigs.js so it can't drift) and curate exactly those PNGs (tilesets / props /
|
| 73 |
# premade-scene layers / interior+tower skins, ~105 files). No whole-pack copy.
|
web/assets/emotes.png
ADDED
|
web/comboBattler.js
CHANGED
|
@@ -5903,6 +5903,103 @@ function step(b, dt) {
|
|
| 5903 |
}
|
| 5904 |
}
|
| 5905 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5906 |
// ../auto-battler/src/render/comboBattler.js
|
| 5907 |
var TILE8 = 8;
|
| 5908 |
var SPRITE_TILES = 0.95;
|
|
@@ -5958,6 +6055,9 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 5958 |
return { forgottenPlains: e, orc: e, necropolis: e };
|
| 5959 |
})();
|
| 5960 |
let battle = null, R = null, combatRoot = null, rings = null, markers = null, navDbg = null, spawnFn = null, dead = false;
|
|
|
|
|
|
|
|
|
|
| 5961 |
const depthScale = { v: 2 };
|
| 5962 |
let offTick = null, keyHandlers = null, tapHandlers = null, controlsEl = null, alive = true;
|
| 5963 |
const keys = { x: 0, y: 0 };
|
|
@@ -6213,11 +6313,51 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6213 |
R.updateFloats(dtMS);
|
| 6214 |
R.drawProjectiles(battle);
|
| 6215 |
R.processLog(battle, cursor);
|
|
|
|
| 6216 |
drawRings();
|
| 6217 |
drawMarkers();
|
| 6218 |
drawNav();
|
| 6219 |
emit();
|
| 6220 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6221 |
function drawRings() {
|
| 6222 |
const p = pa();
|
| 6223 |
rings.clear();
|
|
@@ -6292,6 +6432,12 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6292 |
} catch {
|
| 6293 |
}
|
| 6294 |
navDbg = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6295 |
try {
|
| 6296 |
combatRoot?.destroy({ children: true });
|
| 6297 |
} catch {
|
|
@@ -6365,6 +6511,20 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6365 |
R = await createCombatRenderer({ pixi, defsById, layers: { units, fx, projLayer: proj }, coords: { mapX: (x) => x, mapY: (y) => y, depthOf: () => depthScale.v }, getBattle: () => battle });
|
| 6366 |
if (!alive) return;
|
| 6367 |
for (const a of battle.actors) a.attackTimer = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6368 |
let foeN = 0;
|
| 6369 |
const deadAt = /* @__PURE__ */ new Map();
|
| 6370 |
const spawnAcc = { t: SPAWN_INTERVAL };
|
|
@@ -6389,6 +6549,7 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6389 |
if (!a.alive) {
|
| 6390 |
if (!deadAt.has(a.id)) {
|
| 6391 |
deadAt.set(a.id, battle.t);
|
|
|
|
| 6392 |
try {
|
| 6393 |
opts.onEnemyDefeated?.({ id: a.id, name: a.name, profession: a.profession, tier: a.tier, x: a.x, y: a.y, t: battle.t });
|
| 6394 |
} catch {
|
|
@@ -6488,7 +6649,7 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6488 |
const p = pa();
|
| 6489 |
return p ? { name: p.name, profession: p.profession, hp: Math.round(p.hp), maxHp: Math.round(p.maxHp), skills: (p.bar || []).map((s) => s.name) } : null;
|
| 6490 |
}
|
| 6491 |
-
const ctrl = { ready, selectHero, getSpawnWorld, getSnapshot, getHero, resize, onChange, destroy, map, walkable: (wx, wy) => roamWalkable(wx, wy) };
|
| 6492 |
if (typeof window !== "undefined") {
|
| 6493 |
window.__comboSnap = () => ctrl.getSnapshot();
|
| 6494 |
window.__combo = ctrl;
|
|
|
|
| 5903 |
}
|
| 5904 |
}
|
| 5905 |
|
| 5906 |
+
// ../auto-battler/src/render/emotes.js
|
| 5907 |
+
var CELL = {
|
| 5908 |
+
happy: [6, 0],
|
| 5909 |
+
// smile — killed a foe / level-up payoff
|
| 5910 |
+
angry: [4, 2],
|
| 5911 |
+
// grr — took a hit
|
| 5912 |
+
surprised: [2, 3],
|
| 5913 |
+
// o-mouth
|
| 5914 |
+
dizzy: [5, 3],
|
| 5915 |
+
// red X eyes — stunned / dead
|
| 5916 |
+
sick: [2, 4],
|
| 5917 |
+
// queasy — bleeding/poison/burning
|
| 5918 |
+
sweat: [2, 5],
|
| 5919 |
+
// nervous drop — low HP
|
| 5920 |
+
love: [6, 5],
|
| 5921 |
+
// ❤ — level-up
|
| 5922 |
+
sleepy: [7, 5]
|
| 5923 |
+
// ♪ — idle, out of combat
|
| 5924 |
+
};
|
| 5925 |
+
async function createEmoteLayer(pixi, parent, { url, unit, sizeTiles = 1.3, headTiles = 1.15 }) {
|
| 5926 |
+
await pixi.Assets.load(url);
|
| 5927 |
+
const base = pixi.Texture.from(url);
|
| 5928 |
+
try {
|
| 5929 |
+
base.source.scaleMode = "nearest";
|
| 5930 |
+
} catch {
|
| 5931 |
+
}
|
| 5932 |
+
const sub4 = (c, r) => new pixi.Texture({ source: base.source, frame: new pixi.Rectangle(8 + 16 * c, 8 + 16 * r, 8, 8) });
|
| 5933 |
+
const tex = {};
|
| 5934 |
+
for (const k in CELL) tex[k] = sub4(CELL[k][0], CELL[k][1]);
|
| 5935 |
+
const layer = new pixi.Container();
|
| 5936 |
+
layer.zIndex = 5e8;
|
| 5937 |
+
parent.addChild(layer);
|
| 5938 |
+
const scale = unit * sizeTiles / 8;
|
| 5939 |
+
const active = /* @__PURE__ */ new Map();
|
| 5940 |
+
const enabled = () => {
|
| 5941 |
+
try {
|
| 5942 |
+
return localStorage.getItem("tinyarmy.emotes") !== "0";
|
| 5943 |
+
} catch {
|
| 5944 |
+
return true;
|
| 5945 |
+
}
|
| 5946 |
+
};
|
| 5947 |
+
function show(actorId, key, now, secs = 1.4) {
|
| 5948 |
+
if (!enabled() || !tex[key]) return;
|
| 5949 |
+
let e = active.get(actorId);
|
| 5950 |
+
if (!e) {
|
| 5951 |
+
const spr = new pixi.Sprite(tex[key]);
|
| 5952 |
+
spr.anchor.set(0.5, 1);
|
| 5953 |
+
layer.addChild(spr);
|
| 5954 |
+
e = { spr };
|
| 5955 |
+
active.set(actorId, e);
|
| 5956 |
+
}
|
| 5957 |
+
e.spr.texture = tex[key];
|
| 5958 |
+
e.until = now + secs;
|
| 5959 |
+
e.born = now;
|
| 5960 |
+
}
|
| 5961 |
+
function update(now, posOf) {
|
| 5962 |
+
for (const [id, e] of [...active]) {
|
| 5963 |
+
if (now >= e.until) {
|
| 5964 |
+
e.spr.destroy();
|
| 5965 |
+
active.delete(id);
|
| 5966 |
+
continue;
|
| 5967 |
+
}
|
| 5968 |
+
const p = posOf(id);
|
| 5969 |
+
if (!p) {
|
| 5970 |
+
e.spr.visible = false;
|
| 5971 |
+
continue;
|
| 5972 |
+
}
|
| 5973 |
+
const age = now - e.born, pop = Math.min(1, age / 0.1);
|
| 5974 |
+
e.spr.visible = true;
|
| 5975 |
+
e.spr.scale.set(scale * (0.5 + 0.5 * pop));
|
| 5976 |
+
e.spr.x = p.x;
|
| 5977 |
+
e.spr.y = p.y - headTiles * unit - Math.sin(Math.min(age, 0.25) / 0.25 * Math.PI) * 0.3 * unit;
|
| 5978 |
+
}
|
| 5979 |
+
}
|
| 5980 |
+
function clear(actorId) {
|
| 5981 |
+
const e = active.get(actorId);
|
| 5982 |
+
if (e) {
|
| 5983 |
+
e.spr.destroy();
|
| 5984 |
+
active.delete(actorId);
|
| 5985 |
+
}
|
| 5986 |
+
}
|
| 5987 |
+
function destroy() {
|
| 5988 |
+
for (const [, e] of active) {
|
| 5989 |
+
try {
|
| 5990 |
+
e.spr.destroy();
|
| 5991 |
+
} catch {
|
| 5992 |
+
}
|
| 5993 |
+
}
|
| 5994 |
+
active.clear();
|
| 5995 |
+
try {
|
| 5996 |
+
layer.destroy({ children: true });
|
| 5997 |
+
} catch {
|
| 5998 |
+
}
|
| 5999 |
+
}
|
| 6000 |
+
return { show, update, clear, destroy, keys: Object.keys(CELL) };
|
| 6001 |
+
}
|
| 6002 |
+
|
| 6003 |
// ../auto-battler/src/render/comboBattler.js
|
| 6004 |
var TILE8 = 8;
|
| 6005 |
var SPRITE_TILES = 0.95;
|
|
|
|
| 6055 |
return { forgottenPlains: e, orc: e, necropolis: e };
|
| 6056 |
})();
|
| 6057 |
let battle = null, R = null, combatRoot = null, rings = null, markers = null, navDbg = null, spawnFn = null, dead = false;
|
| 6058 |
+
let emoteLayer = null;
|
| 6059 |
+
const emoPrev = /* @__PURE__ */ new Map();
|
| 6060 |
+
let idleEmoAcc = 0;
|
| 6061 |
const depthScale = { v: 2 };
|
| 6062 |
let offTick = null, keyHandlers = null, tapHandlers = null, controlsEl = null, alive = true;
|
| 6063 |
const keys = { x: 0, y: 0 };
|
|
|
|
| 6313 |
R.updateFloats(dtMS);
|
| 6314 |
R.drawProjectiles(battle);
|
| 6315 |
R.processLog(battle, cursor);
|
| 6316 |
+
updateEmotes(dt);
|
| 6317 |
drawRings();
|
| 6318 |
drawMarkers();
|
| 6319 |
drawNav();
|
| 6320 |
emit();
|
| 6321 |
}
|
| 6322 |
+
const AFFLICT = ["bleeding", "poison", "burning", "deepWound"];
|
| 6323 |
+
function updateEmotes(dt) {
|
| 6324 |
+
if (!emoteLayer || !battle) return;
|
| 6325 |
+
const now = battle.t;
|
| 6326 |
+
for (const a of battle.actors) {
|
| 6327 |
+
const prev = emoPrev.get(a.id) || { hp: a.hp, low: false, conds: /* @__PURE__ */ new Set(), kd: 0, alive: true };
|
| 6328 |
+
if (a.alive) {
|
| 6329 |
+
if (a.hp < prev.hp - 0.5) emoteLayer.show(a.id, "angry", now, 0.85);
|
| 6330 |
+
const low = a.maxHp ? a.hp / a.maxHp < 0.33 : false;
|
| 6331 |
+
if (low && !prev.low) emoteLayer.show(a.id, "sweat", now, 1.4);
|
| 6332 |
+
const conds = new Set((a.conds || []).map((c) => c.type));
|
| 6333 |
+
for (const t of conds) if (!prev.conds.has(t) && AFFLICT.includes(t)) {
|
| 6334 |
+
emoteLayer.show(a.id, "sick", now, 1.2);
|
| 6335 |
+
break;
|
| 6336 |
+
}
|
| 6337 |
+
if (a.kd > now && a.kd > prev.kd) emoteLayer.show(a.id, "dizzy", now, 1);
|
| 6338 |
+
emoPrev.set(a.id, { hp: a.hp, low, conds, kd: a.kd, alive: true });
|
| 6339 |
+
} else {
|
| 6340 |
+
if (prev.alive) emoteLayer.show(a.id, "dizzy", now, 1.3);
|
| 6341 |
+
emoPrev.set(a.id, { ...prev, alive: false, hp: a.hp, conds: /* @__PURE__ */ new Set() });
|
| 6342 |
+
}
|
| 6343 |
+
}
|
| 6344 |
+
const p = pa();
|
| 6345 |
+
if (p && p.alive && nearestFoeDist() === Infinity) {
|
| 6346 |
+
idleEmoAcc += dt;
|
| 6347 |
+
if (idleEmoAcc > 6) {
|
| 6348 |
+
emoteLayer.show(p.id, "sleepy", now, 1.6);
|
| 6349 |
+
idleEmoAcc = 0;
|
| 6350 |
+
}
|
| 6351 |
+
} else idleEmoAcc = 0;
|
| 6352 |
+
emoteLayer.update(now, (id) => {
|
| 6353 |
+
const a = battle.actors.find((x) => x.id === id);
|
| 6354 |
+
return a ? { x: a.x, y: a.y } : null;
|
| 6355 |
+
});
|
| 6356 |
+
}
|
| 6357 |
+
function heroEmote(key) {
|
| 6358 |
+
const p = pa();
|
| 6359 |
+
if (emoteLayer && p && battle) emoteLayer.show(p.id, key, battle.t, 1.6);
|
| 6360 |
+
}
|
| 6361 |
function drawRings() {
|
| 6362 |
const p = pa();
|
| 6363 |
rings.clear();
|
|
|
|
| 6432 |
} catch {
|
| 6433 |
}
|
| 6434 |
navDbg = null;
|
| 6435 |
+
try {
|
| 6436 |
+
emoteLayer?.destroy();
|
| 6437 |
+
} catch {
|
| 6438 |
+
}
|
| 6439 |
+
emoteLayer = null;
|
| 6440 |
+
emoPrev.clear();
|
| 6441 |
try {
|
| 6442 |
combatRoot?.destroy({ children: true });
|
| 6443 |
} catch {
|
|
|
|
| 6511 |
R = await createCombatRenderer({ pixi, defsById, layers: { units, fx, projLayer: proj }, coords: { mapX: (x) => x, mapY: (y) => y, depthOf: () => depthScale.v }, getBattle: () => battle });
|
| 6512 |
if (!alive) return;
|
| 6513 |
for (const a of battle.actors) a.attackTimer = 0;
|
| 6514 |
+
emoPrev.clear();
|
| 6515 |
+
idleEmoAcc = 0;
|
| 6516 |
+
if (opts.emoteSheetUrl) {
|
| 6517 |
+
try {
|
| 6518 |
+
emoteLayer = await createEmoteLayer(pixi, combatRoot, { url: opts.emoteSheetUrl, unit: TILE8 / G });
|
| 6519 |
+
} catch {
|
| 6520 |
+
emoteLayer = null;
|
| 6521 |
+
}
|
| 6522 |
+
}
|
| 6523 |
+
if (!alive) {
|
| 6524 |
+
emoteLayer?.destroy();
|
| 6525 |
+
emoteLayer = null;
|
| 6526 |
+
return;
|
| 6527 |
+
}
|
| 6528 |
let foeN = 0;
|
| 6529 |
const deadAt = /* @__PURE__ */ new Map();
|
| 6530 |
const spawnAcc = { t: SPAWN_INTERVAL };
|
|
|
|
| 6549 |
if (!a.alive) {
|
| 6550 |
if (!deadAt.has(a.id)) {
|
| 6551 |
deadAt.set(a.id, battle.t);
|
| 6552 |
+
heroEmote("happy");
|
| 6553 |
try {
|
| 6554 |
opts.onEnemyDefeated?.({ id: a.id, name: a.name, profession: a.profession, tier: a.tier, x: a.x, y: a.y, t: battle.t });
|
| 6555 |
} catch {
|
|
|
|
| 6649 |
const p = pa();
|
| 6650 |
return p ? { name: p.name, profession: p.profession, hp: Math.round(p.hp), maxHp: Math.round(p.maxHp), skills: (p.bar || []).map((s) => s.name) } : null;
|
| 6651 |
}
|
| 6652 |
+
const ctrl = { ready, selectHero, getSpawnWorld, getSnapshot, getHero, heroEmote, resize, onChange, destroy, map, walkable: (wx, wy) => roamWalkable(wx, wy) };
|
| 6653 |
if (typeof window !== "undefined") {
|
| 6654 |
window.__comboSnap = () => ctrl.getSnapshot();
|
| 6655 |
window.__combo = ctrl;
|
web/settingsPanel.js
CHANGED
|
@@ -37,6 +37,16 @@ function injectSection(sample, id, title, intro, mountFn) {
|
|
| 37 |
mountFn(host)
|
| 38 |
}
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
export function mountSettingsPanel() {
|
| 41 |
const tryInject = () => {
|
| 42 |
const sample = [...document.querySelectorAll('.banner-wrap')].find((e) => /Display Theme/i.test(e.textContent))
|
|
@@ -64,6 +74,9 @@ export function mountSettingsPanel() {
|
|
| 64 |
'Nemotron 3 Nano (NVIDIA) runs via NVIDIA NIM; Mellum2 (JetBrains) runs as a ' +
|
| 65 |
'ZeroGPU sidecar and falls back to Nemotron (NIM) if its sidecar is unavailable.',
|
| 66 |
mountCodingModelBar)
|
|
|
|
|
|
|
|
|
|
| 67 |
}
|
| 68 |
new MutationObserver(tryInject).observe(document.body, { childList: true, subtree: true })
|
| 69 |
tryInject()
|
|
|
|
| 37 |
mountFn(host)
|
| 38 |
}
|
| 39 |
|
| 40 |
+
// Gameplay toggles. Currently just emotes on/off, persisted to the same localStorage flag the
|
| 41 |
+
// emote layer reads ('tinyarmy.emotes', default on → only '0' disables).
|
| 42 |
+
function mountGameplayBar(host) {
|
| 43 |
+
const cb = el('input', { type: 'checkbox' })
|
| 44 |
+
try { cb.checked = localStorage.getItem('tinyarmy.emotes') !== '0' } catch { cb.checked = true }
|
| 45 |
+
cb.addEventListener('change', () => { try { localStorage.setItem('tinyarmy.emotes', cb.checked ? '1' : '0') } catch { /* ignore */ } })
|
| 46 |
+
const lab = el('label', { class: 'tac-set-row', style: 'display:flex;align-items:center;gap:8px;cursor:pointer' }, [cb, 'Show character emotes'])
|
| 47 |
+
host.append(lab)
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
export function mountSettingsPanel() {
|
| 51 |
const tryInject = () => {
|
| 52 |
const sample = [...document.querySelectorAll('.banner-wrap')].find((e) => /Display Theme/i.test(e.textContent))
|
|
|
|
| 74 |
'Nemotron 3 Nano (NVIDIA) runs via NVIDIA NIM; Mellum2 (JetBrains) runs as a ' +
|
| 75 |
'ZeroGPU sidecar and falls back to Nemotron (NIM) if its sidecar is unavailable.',
|
| 76 |
mountCodingModelBar)
|
| 77 |
+
injectSection(sample, 'tac-gameplay-settings', 'Gameplay',
|
| 78 |
+
'On-map flourishes during the Game. Emotes pop little reaction bubbles above heroes ' +
|
| 79 |
+
'and enemies when they take a hit, get afflicted, drop low, or score a kill.', mountGameplayBar)
|
| 80 |
}
|
| 81 |
new MutationObserver(tryInject).observe(document.body, { childList: true, subtree: true })
|
| 82 |
tryInject()
|
web/tiny.js
CHANGED
|
@@ -352,7 +352,7 @@ whenEl('battle-stage', async (el) => {
|
|
| 352 |
// Award XP to the active hero when it defeats a foe (the loop fires this once per kill).
|
| 353 |
// Declared as a closure so it always reads the *current* hero + freshest stored progression.
|
| 354 |
const onEnemyDefeated = (enemy) => { try { awardKill(enemy) } catch { /* never break combat */ } }
|
| 355 |
-
comboCtrl = mountComboBattler(PIXI, el, { seed: 1, rosters, onEnemyDefeated })
|
| 356 |
await comboCtrl.ready
|
| 357 |
const FALLBACK = { name: 'Fighter', unitClass: 'Warrior' }
|
| 358 |
const OVERVIEW_ZOOM = 0.45, GAMEPLAY_ZOOM = 2.5
|
|
@@ -382,7 +382,7 @@ whenEl('battle-stage', async (el) => {
|
|
| 382 |
if (res.leveledUp) events = appendEvent(events, 'level_up', { level: prog.level, ts: Date.now() })
|
| 383 |
patchPersona(id, { progression: prog, events })
|
| 384 |
currentHero = getPersona(id)
|
| 385 |
-
if (res.leveledUp) showLevelUp(prog)
|
| 386 |
if (sheetOpen) renderSheet()
|
| 387 |
}
|
| 388 |
// A brief level-up banner over the canvas (mobile-friendly: centered, auto-dismiss).
|
|
|
|
| 352 |
// Award XP to the active hero when it defeats a foe (the loop fires this once per kill).
|
| 353 |
// Declared as a closure so it always reads the *current* hero + freshest stored progression.
|
| 354 |
const onEnemyDefeated = (enemy) => { try { awardKill(enemy) } catch { /* never break combat */ } }
|
| 355 |
+
comboCtrl = mountComboBattler(PIXI, el, { seed: 1, rosters, onEnemyDefeated, emoteSheetUrl: '/sprites/emotes.png' })
|
| 356 |
await comboCtrl.ready
|
| 357 |
const FALLBACK = { name: 'Fighter', unitClass: 'Warrior' }
|
| 358 |
const OVERVIEW_ZOOM = 0.45, GAMEPLAY_ZOOM = 2.5
|
|
|
|
| 382 |
if (res.leveledUp) events = appendEvent(events, 'level_up', { level: prog.level, ts: Date.now() })
|
| 383 |
patchPersona(id, { progression: prog, events })
|
| 384 |
currentHero = getPersona(id)
|
| 385 |
+
if (res.leveledUp) { showLevelUp(prog); comboCtrl?.heroEmote?.('love') }
|
| 386 |
if (sheetOpen) renderSheet()
|
| 387 |
}
|
| 388 |
// A brief level-up banner over the canvas (mobile-friendly: centered, auto-dismiss).
|