Spaces:
Running
Running
Trickle spawns + remove dead code (attribute-point spend UI, level-up toast)
Browse filesRebuild comboBattler for the 1-2/wave trickle. Remove the now-unused attribute-point
spend flow (openSpend, spendPoint, the spend button) and the old level-up toast —
superseded by the roguelike perk draft.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- web/comboBattler.js +8 -9
- web/progression.js +0 -11
- web/tiny.js +1 -34
web/comboBattler.js
CHANGED
|
@@ -6036,9 +6036,7 @@ var STEP = 0.05;
|
|
| 6036 |
var SPAWN_TILES = 22;
|
| 6037 |
var SPAWN_JITTER = 6;
|
| 6038 |
var DESPAWN_TILES = 52;
|
| 6039 |
-
var
|
| 6040 |
-
var MAX_FOES = 26;
|
| 6041 |
-
var SPAWN_INTERVAL = 0.3;
|
| 6042 |
var DEATH_LINGER = 1.6;
|
| 6043 |
async function contentHeight(url) {
|
| 6044 |
try {
|
|
@@ -6782,7 +6780,7 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6782 |
}
|
| 6783 |
let foeN = 0;
|
| 6784 |
const deadAt = /* @__PURE__ */ new Map();
|
| 6785 |
-
const spawnAcc = { t:
|
| 6786 |
let nextEliteAt = 8, nextBossAt = 24;
|
| 6787 |
runState = { time: 0, kills: 0, diff: 1 };
|
| 6788 |
const TIER = {
|
|
@@ -6842,12 +6840,13 @@ function mountComboBattler(pixi, host, opts = {}) {
|
|
| 6842 |
}
|
| 6843 |
}
|
| 6844 |
spawnAcc.t += dt;
|
| 6845 |
-
if (spawnAcc.t < SPAWN_INTERVAL) return;
|
| 6846 |
-
spawnAcc.t = 0;
|
| 6847 |
const diff = runState.diff;
|
| 6848 |
-
const
|
| 6849 |
-
|
| 6850 |
-
|
|
|
|
|
|
|
|
|
|
| 6851 |
const ang = Math.random() * Math.PI * 2;
|
| 6852 |
const rr = (SPAWN_TILES + (Math.random() * 2 - 1) * SPAWN_JITTER) * FSTEP;
|
| 6853 |
const pos = snapField(p.x + Math.cos(ang) * rr, p.y + Math.sin(ang) * rr);
|
|
|
|
| 6036 |
var SPAWN_TILES = 22;
|
| 6037 |
var SPAWN_JITTER = 6;
|
| 6038 |
var DESPAWN_TILES = 52;
|
| 6039 |
+
var MAX_FOES = 12;
|
|
|
|
|
|
|
| 6040 |
var DEATH_LINGER = 1.6;
|
| 6041 |
async function contentHeight(url) {
|
| 6042 |
try {
|
|
|
|
| 6780 |
}
|
| 6781 |
let foeN = 0;
|
| 6782 |
const deadAt = /* @__PURE__ */ new Map();
|
| 6783 |
+
const spawnAcc = { t: 99 };
|
| 6784 |
let nextEliteAt = 8, nextBossAt = 24;
|
| 6785 |
runState = { time: 0, kills: 0, diff: 1 };
|
| 6786 |
const TIER = {
|
|
|
|
| 6840 |
}
|
| 6841 |
}
|
| 6842 |
spawnAcc.t += dt;
|
|
|
|
|
|
|
| 6843 |
const diff = runState.diff;
|
| 6844 |
+
const waveInterval = Math.max(0.8, 3 / diff);
|
| 6845 |
+
if (spawnAcc.t < waveInterval) return;
|
| 6846 |
+
spawnAcc.t = 0;
|
| 6847 |
+
if (ea().filter((a) => a.alive).length >= MAX_FOES) return;
|
| 6848 |
+
const count = 1 + (Math.random() < Math.min(0.7, (diff - 1) / 3) ? 1 : 0);
|
| 6849 |
+
for (let i = 0; i < count; i++) {
|
| 6850 |
const ang = Math.random() * Math.PI * 2;
|
| 6851 |
const rr = (SPAWN_TILES + (Math.random() * 2 - 1) * SPAWN_JITTER) * FSTEP;
|
| 6852 |
const pos = snapField(p.x + Math.cos(ang) * rr, p.y + Math.sin(ang) * rr);
|
web/progression.js
CHANGED
|
@@ -76,17 +76,6 @@ export function statBonuses(progression) {
|
|
| 76 |
}
|
| 77 |
}
|
| 78 |
|
| 79 |
-
// Spend one attribute point into a stat ('hp' | 'dmg'). Returns a NEW progression object;
|
| 80 |
-
// no-op if no points or an unknown stat.
|
| 81 |
-
export function spendPoint(progression, stat) {
|
| 82 |
-
const p = { level: 1, xp: 0, kills: 0, attributePoints: 0, ...(progression || {}) }
|
| 83 |
-
p.spent = { hp: 0, dmg: 0, ...(p.spent || {}) }
|
| 84 |
-
if ((p.attributePoints || 0) <= 0 || !['hp', 'dmg'].includes(stat)) return p
|
| 85 |
-
p.attributePoints -= 1
|
| 86 |
-
p.spent[stat] = (p.spent[stat] || 0) + 1
|
| 87 |
-
return p
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
// Append a capped, structured event to a persona's log (newest last). Used for the chat
|
| 91 |
// "what's happening to you" context (woid-style perception log) and the level-up feed.
|
| 92 |
const MAX_EVENTS = 60
|
|
|
|
| 76 |
}
|
| 77 |
}
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
// Append a capped, structured event to a persona's log (newest last). Used for the chat
|
| 80 |
// "what's happening to you" context (woid-style perception log) and the level-up feed.
|
| 81 |
const MAX_EVENTS = 60
|
web/tiny.js
CHANGED
|
@@ -16,7 +16,7 @@ import { mountComboBattler } from '/web/comboBattler.js'
|
|
| 16 |
import { mountPersonaPanel, CLASS_SLUG } from '/web/personaPanel.js'
|
| 17 |
import { mountHeroCreator, animateIdleIcon } from '/web/heroCreator.js'
|
| 18 |
import { listPersonas, onRosterChange, getPortrait, getPersona, patchPersona, setActiveHeroId } from '/web/personaStore.js'
|
| 19 |
-
import { applyXp, enemyXpValue, levelProgress, xpToNext, appendEvent, statBonuses
|
| 20 |
import { getSkillIcon, getSkillArt, putSkillArt } from '/web/personaStore.js'
|
| 21 |
import { effectSummary, resolveEquipped } from '/web/skillSchema.js'
|
| 22 |
import { generatePortrait } from '/web/imagen.js'
|
|
@@ -433,16 +433,6 @@ whenEl('battle-stage', async (el) => {
|
|
| 433 |
}
|
| 434 |
showNext()
|
| 435 |
}
|
| 436 |
-
// A brief level-up banner over the canvas (mobile-friendly: centered, auto-dismiss).
|
| 437 |
-
let levelToast = null
|
| 438 |
-
const showLevelUp = (prog) => {
|
| 439 |
-
levelToast?.remove()
|
| 440 |
-
const t = document.createElement('div')
|
| 441 |
-
t.textContent = `⚔️ ${currentHero?.name || 'Hero'} reached level ${prog.level}!`
|
| 442 |
-
t.style.cssText = 'position:absolute;left:50%;top:18%;transform:translateX(-50%);z-index:9;padding:12px 18px;border-radius:12px;border:1px solid #c9a227;background:rgba(20,24,33,.94);color:#ffe082;font:700 15px var(--tac-font,system-ui);box-shadow:0 6px 24px rgba(0,0,0,.5);pointer-events:none;text-align:center;max-width:86vw'
|
| 443 |
-
el.append(t); levelToast = t
|
| 444 |
-
setTimeout(() => { t.style.transition = 'opacity .5s'; t.style.opacity = '0'; setTimeout(() => t.remove(), 520) }, 2200)
|
| 445 |
-
}
|
| 446 |
// Picking a hero opens its detail page (portrait/about/quote + Select); Select confirms → spawnWithFly.
|
| 447 |
const onPick = (p) => openHeroDetail(el, p, chars, () => spawnWithFly(p))
|
| 448 |
const buildPicker = () => {
|
|
@@ -603,28 +593,6 @@ whenEl('battle-stage', async (el) => {
|
|
| 603 |
} catch (e) { status.textContent = 'Forge failed: ' + (e && e.message ? e.message : e); busy = false; go.disabled = false; ta.disabled = false }
|
| 604 |
})
|
| 605 |
}
|
| 606 |
-
// Spend attribute points (earned on level-up) into +HP or +Damage. Applies on next spawn.
|
| 607 |
-
const openSpend = () => {
|
| 608 |
-
const { sh } = openToolSheet('Spend attribute points')
|
| 609 |
-
const render = () => {
|
| 610 |
-
sh.querySelectorAll('.spend-dyn').forEach((n) => n.remove())
|
| 611 |
-
const pr = getPersona(currentHero.id).progression
|
| 612 |
-
const sp = pr.spent || { hp: 0, dmg: 0 }
|
| 613 |
-
const info = document.createElement('div'); info.className = 'spend-dyn'; info.style.cssText = 'color:#9aa4b2;font-size:12px;margin-bottom:10px'
|
| 614 |
-
info.textContent = `${pr.attributePoints} point${pr.attributePoints !== 1 ? 's' : ''} to spend · current: +${Math.round((sp.hp || 0) * 5)}% HP, +${Math.round((sp.dmg || 0) * 5)}% DMG`
|
| 615 |
-
sh.append(info)
|
| 616 |
-
const mk = (label, stat) => {
|
| 617 |
-
const b = document.createElement('button'); b.className = 'spend-dyn'; b.type = 'button'; b.textContent = label
|
| 618 |
-
const none = pr.attributePoints <= 0; b.disabled = none
|
| 619 |
-
b.style.cssText = `width:100%;padding:12px;border-radius:10px;border:1px solid #2a3340;background:#1a2230;color:#e8e8e8;font:600 13px var(--tac-font,system-ui);cursor:${none ? 'default' : 'pointer'};margin-bottom:8px;opacity:${none ? 0.5 : 1}`
|
| 620 |
-
b.addEventListener('click', () => { const np = spendPoint(getPersona(currentHero.id).progression, stat); patchPersona(currentHero.id, { progression: np }); currentHero = getPersona(currentHero.id); render(); renderSheet() })
|
| 621 |
-
sh.append(b)
|
| 622 |
-
}
|
| 623 |
-
mk('+5% Max HP', 'hp'); mk('+5% Damage', 'dmg')
|
| 624 |
-
const note = document.createElement('div'); note.className = 'spend-dyn'; note.style.cssText = 'color:#6b7686;font-size:11px;margin-top:4px'; note.textContent = 'Takes full effect on your next spawn.'; sh.append(note)
|
| 625 |
-
}
|
| 626 |
-
render()
|
| 627 |
-
}
|
| 628 |
const renderSheet = () => {
|
| 629 |
sheet.innerHTML = ''
|
| 630 |
const close = document.createElement('button'); close.type = 'button'; close.textContent = '✕'
|
|
@@ -649,7 +617,6 @@ whenEl('battle-stage', async (el) => {
|
|
| 649 |
const meta = document.createElement('div'); meta.style.cssText = 'display:flex;justify-content:space-between;font-size:11px;color:#9aa4b2;margin-bottom:10px'
|
| 650 |
const kills = document.createElement('span'); kills.textContent = `⚔ ${prog.kills} kills`
|
| 651 |
meta.append(kills)
|
| 652 |
-
if (prog.attributePoints > 0) { const pts = document.createElement('button'); pts.type = 'button'; pts.textContent = `★ ${prog.attributePoints} point${prog.attributePoints > 1 ? 's' : ''} — spend`; pts.style.cssText = 'background:none;border:none;color:#ffe082;font:inherit;cursor:pointer;text-decoration:underline'; pts.addEventListener('click', openSpend); meta.append(pts) }
|
| 653 |
sheet.append(meta)
|
| 654 |
if (prog.best && prog.best.kills) { const bst = document.createElement('div'); bst.textContent = `🏆 Best run: ${prog.best.kills} foes · threat ${prog.best.threat || 1}×`; bst.style.cssText = 'font-size:11px;color:#c9a227;margin:-4px 0 10px'; sheet.append(bst) }
|
| 655 |
}
|
|
|
|
| 16 |
import { mountPersonaPanel, CLASS_SLUG } from '/web/personaPanel.js'
|
| 17 |
import { mountHeroCreator, animateIdleIcon } from '/web/heroCreator.js'
|
| 18 |
import { listPersonas, onRosterChange, getPortrait, getPersona, patchPersona, setActiveHeroId } from '/web/personaStore.js'
|
| 19 |
+
import { applyXp, enemyXpValue, levelProgress, xpToNext, appendEvent, statBonuses } from '/web/progression.js'
|
| 20 |
import { getSkillIcon, getSkillArt, putSkillArt } from '/web/personaStore.js'
|
| 21 |
import { effectSummary, resolveEquipped } from '/web/skillSchema.js'
|
| 22 |
import { generatePortrait } from '/web/imagen.js'
|
|
|
|
| 433 |
}
|
| 434 |
showNext()
|
| 435 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
// Picking a hero opens its detail page (portrait/about/quote + Select); Select confirms → spawnWithFly.
|
| 437 |
const onPick = (p) => openHeroDetail(el, p, chars, () => spawnWithFly(p))
|
| 438 |
const buildPicker = () => {
|
|
|
|
| 593 |
} catch (e) { status.textContent = 'Forge failed: ' + (e && e.message ? e.message : e); busy = false; go.disabled = false; ta.disabled = false }
|
| 594 |
})
|
| 595 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 596 |
const renderSheet = () => {
|
| 597 |
sheet.innerHTML = ''
|
| 598 |
const close = document.createElement('button'); close.type = 'button'; close.textContent = '✕'
|
|
|
|
| 617 |
const meta = document.createElement('div'); meta.style.cssText = 'display:flex;justify-content:space-between;font-size:11px;color:#9aa4b2;margin-bottom:10px'
|
| 618 |
const kills = document.createElement('span'); kills.textContent = `⚔ ${prog.kills} kills`
|
| 619 |
meta.append(kills)
|
|
|
|
| 620 |
sheet.append(meta)
|
| 621 |
if (prog.best && prog.best.kills) { const bst = document.createElement('div'); bst.textContent = `🏆 Best run: ${prog.best.kills} foes · threat ${prog.best.threat || 1}×`; bst.style.cssText = 'font-size:11px;color:#c9a227;margin:-4px 0 10px'; sheet.append(bst) }
|
| 622 |
}
|