Phase 5: active items — room-clear charged, dedicated pedestals, Space to use

- ACTIVES table with 10 actives (Bone Nova, Offal Bomb, Blood Transfusion,
  Cleaver Storm, Butcher's Bell, Marrow Draught, Slaughter Time, Panic Room,
  Skinner's Coin, Gut Reroll); charges accrue +1/combat room, +2/boss
- Dedicated 'active' pickup with weapon-style swap-drop; 30% boss / 15% item-room
  bonus pedestal spawns; flat +2 powerScore while held
- HUD active slot with charge pips + READY state; ACTIVES help page (12 pages);
  teal medallion frame variant in draw_sprites.py; 10 generated icons
- Procedural Sfx.active()/activeEmpty() (real clips deferred to audio batch)
- Fix: toast panel width now fits the wider of title/subtitle and clamps to screen
- Bump asset cache to v=40
This commit is contained in:
MeatSlicer Developer committed 2026-07-30 17:14:25 +10:00
1 parent f71d202850
commit 0ec12cd72c
27 files changed
+337 -37

No files matched your search

+77
View File
@@ -187,3 +187,80 @@ function spawnItemPedestal(x, y, iid, source) {
spawnPickup('itemspot', x, y);
spawnPickup('item', x, y, { iid: iid || rollItemId(source || 'room', G.floor) });
}
// ---- active items: room-clear charged, dedicated pedestal pickups ----
// Actives live in their own table so p.items (tier math + powerScore) stays clean.
// charges are granted by recordRoomClear: +1 per combat room, +2 per boss.
const ACTIVES = {
bonenova: { name: 'Bone Nova', desc: 'Damage ring + hard knockback', cost: 2, use(p) {
areaDamage(p.x, p.y, 150, 30 * p.stats.dmgMul, true, { source: 'player' });
for (const e of G.enemies) { const a = angleTo(p.x, p.y, e.x, e.y); e.vx += Math.cos(a) * 500; e.vy += Math.sin(a) * 500; }
spawnShockwave(p.x, p.y, 150, '#e8dcc2', 0.8); addShake(8);
} },
offalbomb: { name: 'Offal Bomb', desc: 'Gore bomb at the cursor', cost: 1, use(p) {
explodeAt(Input.mx, Input.my, 95, 25 * p.stats.dmgMul, true);
} },
bloodtransfusion:{ name: 'Blood Transfusion', desc: 'Heal 2 hearts, lose 25% current XP', cost: 2, use(p) {
p.xp = Math.floor(p.xp * 0.75);
healPlayer(4); spawnText(p.x, p.y - 16, '+2 HEARTS', '#d92038');
} },
cleaverstorm: { name: 'Cleaver Storm', desc: '12 orbiting cleavers for 6s', cost: 2, use(p) {
p.cleaverStormT = 6;
} },
butchersbell: { name: "Butcher's Bell", desc: 'Pull every enemy in and stun 1s', cost: 2, use(p) {
for (const e of G.enemies) {
if (e.boss) continue;
const a = angleTo(e.x, e.y, p.x, p.y);
e.vx += Math.cos(a) * 700; e.vy += Math.sin(a) * 700;
e.stunT = Math.max(e.stunT || 0, 1);
}
spawnShockwave(p.x, p.y, 320, '#c9a227', 0.7); addShake(6);
} },
marrowdraught: { name: 'Marrow Draught', desc: '+100% fire rate and free ammo for 5s', cost: 3, use(p) {
p.marrowDraughtT = 5;
} },
slaughtertime: { name: 'Slaughter Time', desc: 'Enemies at 25% speed for 5s', cost: 3, use(p) {
for (const e of G.enemies) e.slowT = Math.max(e.slowT || 0, 5);
} },
panicroom: { name: 'Panic Room', desc: '2.5s invulnerable, can\'t fire', cost: 3, use(p) {
p.panicRoomT = 2.5;
p.invT = Math.max(p.invT, 2.5);
} },
skinnerscoin: { name: "Skinner's Coin", desc: 'Clear all enemy bullets into gems', cost: 3, use(p) {
for (const b of G.ebullets) spawnPickup('gem', b.x, b.y, { v: 1 });
G.ebullets = [];
spawnShockwave(p.x, p.y, 480, '#55f5dc', 0.5);
} },
gutreroll: { name: 'Gut Reroll', desc: 'Reroll the pedestal you\'re standing on', cost: 1, use(p) {
for (const k of G.pickups) {
if (k.type === 'item' && dist2(k.x, k.y, p.x, p.y) < 120 * 120) {
k.iid = rollItemId('room', G.floor);
spawnText(k.x, k.y - 40, 'REROLLED', '#e2472f');
return;
}
}
addToast('NO PEDESTAL', 'stand on an item pedestal to reroll it');
} },
};
function useActive() {
const p = G.player;
if (!p.active || G.mode !== 'play') return;
const a = ACTIVES[p.active.iid];
if (!a) return;
if (p.active.charges < a.cost) {
addToast(a.name, 'not ready — clear ' + (a.cost - p.active.charges) + ' more room' + (a.cost - p.active.charges > 1 ? 's' : ''));
Sfx.activeEmpty();
return;
}
p.active.charges = 0;
a.use(p);
spawnText(p.x, p.y - 22, a.name.toUpperCase(), '#e2472f');
Sfx.active();
}
// spawn an active pedestal (boss / item-room bonus drops)
function spawnActivePedestal(x, y) {
spawnPickup('itemspot', x, y);
spawnPickup('active', x, y, { aid: choice(Object.keys(ACTIVES)) });
}