feat: QoL weapon/UI batch — door fix, Red Right Hand + flamethrower right-offset, fire slowdown, literal armor dodge, persistent pedestals, menu shot-lock
This commit is contained in:
1 parent
1131ed1e0b
commit
b41dcd8ebb
9 files changed
+153
-45
No files matched your search
+1
-1
@@ -23,7 +23,7 @@ const ITEMS = {
|
||||
hollowbones: { name: 'Hollow Bones', desc: '+14% move speed', rarity: 'common', cap: 6, apply: s => { s.speedMul *= 1.14; } },
|
||||
brassmagazine: { name: 'Brass Magazine', desc: '+10% ammo efficiency, +20% ammo found', rarity: 'common', cap: 5, apply: s => { s.ammoEff += 0.10; s.ammoPickupMul *= 1.20; } },
|
||||
ironstomach: { name: 'Iron Stomach', desc: '+½ heart container, heal ½ heart', rarity: 'common', cap: 8, apply: (s, p) => { s.maxHp += 1; p.hp = Math.min(s.maxHp, p.hp + 1); } },
|
||||
tannedhide: { name: 'Tanned Hide', desc: '+8% chance to ignore damage', rarity: 'common', cap: 6, apply: s => { s.armor += 0.08 / 0.92; } },
|
||||
tannedhide: { name: 'Tanned Hide', desc: '+8% chance to ignore damage', rarity: 'common', cap: 6, apply: s => { s.armor += 0.08; } },
|
||||
bloodlust: { name: 'Bloodlust', desc: 'Kills may drop bonus XP', rarity: 'common', cap: 5, apply: s => { s.bloodlust = (s.bloodlust || 0) + 0.12; } },
|
||||
|
||||
// ---- uncommon ----
|
||||
|
||||
+4
-5
@@ -101,7 +101,7 @@ function resetRun() {
|
||||
G.confirmAction = null; G.confirmT = 0;
|
||||
G.deathLockT = 0;
|
||||
G.pressure = 1; G.streak = 0; G.roomDamaged = false; G.roomEnterT = 0; G.recentHits = [];
|
||||
G.shake = 0; G.flash = 0;
|
||||
G.shake = 0; G.flash = 0; G.shotLockT = 0;
|
||||
G.debugUsed = false; G.debugFlags = {}; G.debugTimescale = 1; G.debugFrameStep = false;
|
||||
initPlayer();
|
||||
genFloor(1);
|
||||
@@ -169,6 +169,7 @@ function loop(now) {
|
||||
}
|
||||
|
||||
function update(dt) {
|
||||
const menuWas = G.mode !== 'play';
|
||||
Music.update(dt);
|
||||
if (G.deathLockT > 0) G.deathLockT = Math.max(0, G.deathLockT - dt);
|
||||
if (G.confirmT > 0) {
|
||||
@@ -221,10 +222,6 @@ function update(dt) {
|
||||
break;
|
||||
case 'levelup':
|
||||
updateLevelup(dt);
|
||||
// A click that installed a perk must not carry the held button into play;
|
||||
// updatePlayer fires on Input.mdown, so the very next frame would shoot.
|
||||
// Same mask pattern as the pinned debug console below.
|
||||
if (G.mode !== 'levelup') Input.mdown = false;
|
||||
updateParticles(dt);
|
||||
break;
|
||||
case 'debug':
|
||||
@@ -291,6 +288,8 @@ function update(dt) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (menuWas && G.mode === 'play') { Input.mdown = false; G.shotLockT = 0.05; }
|
||||
G.shotLockT = Math.max(0, (G.shotLockT || 0) - dt);
|
||||
if (G.shake > 0) G.shake = Math.max(0, G.shake - dt * 30);
|
||||
if (G.flash > 0) G.flash = Math.max(0, G.flash - dt);
|
||||
if (G.transition > 0) G.transition = Math.max(0, G.transition - dt);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ const PERKS = [
|
||||
{ id: 'ember', name: 'Ember Hands', desc: '8% chance to ignite', apply: s => { s.igniteChance += 0.08; } },
|
||||
{ id: 'frostbile',name: 'Chill Bile', desc: '10% chance to slow', apply: s => { s.slowOnHit += 0.10; } },
|
||||
{ id: 'heavyhand',name: 'Heavy Hand', desc: '+20% knockback', apply: s => { s.knockbackMul *= 1.20; } },
|
||||
{ id: 'thickhide',name: 'Thick Hide', desc: '+4% chance to ignore damage', apply: s => { s.armor += 1 / 24; } },
|
||||
{ id: 'thickhide',name: 'Thick Hide', desc: '+4% chance to ignore damage', apply: s => { s.armor += 0.04; } },
|
||||
{ id: 'secondwind',name: 'Second Wind', desc: '+0.15s hurt immunity', apply: s => { s.invBonus += 0.15; } },
|
||||
{ id: 'scrapfeed',name: 'Scrap Feed', desc: '5% less ammo (diminishing)', apply: s => { s.ammoEff += 0.05; } },
|
||||
{ id: 'boneknit', name: 'Bone Knit', desc: '3% chance to heal ½ heart after combat rooms', apply: s => { s.roomHealChance += 0.03; } },
|
||||
|
||||
+1
-3
@@ -59,7 +59,7 @@ function updatePickups(dt) {
|
||||
k.x += Math.cos(a) * sp * dt; k.y += Math.sin(a) * sp * dt;
|
||||
}
|
||||
|
||||
if (d < p.r + 12 && !(k.delay > 0)) {
|
||||
if (d < p.r + 12 && !(k.delay > 0) && k.type !== 'itemspot') {
|
||||
collectPickup(k);
|
||||
G.pickups.splice(i, 1);
|
||||
}
|
||||
@@ -141,7 +141,6 @@ function collectPickup(k) {
|
||||
}
|
||||
case 'item': {
|
||||
giveItem(k.iid);
|
||||
for (const q of G.pickups) if (q.type === 'itemspot') q.dead = true; // remove pedestal base
|
||||
break;
|
||||
}
|
||||
case 'active': {
|
||||
@@ -156,7 +155,6 @@ function collectPickup(k) {
|
||||
addToast(def.name, def.desc + ' — [SPACE] when charged', 5.5);
|
||||
spawnText(p.x, p.y - 14, def.name.toUpperCase(), '#55f5dc');
|
||||
Sfx.item();
|
||||
for (const q of G.pickups) if (q.type === 'itemspot') q.dead = true; // remove pedestal base
|
||||
break;
|
||||
}
|
||||
case 'stairs':
|
||||
|
||||
+9
-8
@@ -65,6 +65,8 @@ function swapWeapon(silent) {
|
||||
function updatePlayer(dt) {
|
||||
const p = G.player;
|
||||
const st = p.stats;
|
||||
const w = WEAPONS[p.weapon.id];
|
||||
const held = Input.mdown && !(G.shotLockT > 0); // fire allowed; locked for 50ms after a menu closes
|
||||
if (p.invT > 0) p.invT -= dt;
|
||||
if (p.hitT > 0) p.hitT -= dt;
|
||||
if (p.frenzyT > 0) p.frenzyT -= dt;
|
||||
@@ -79,7 +81,7 @@ function updatePlayer(dt) {
|
||||
if (keyDown('d', 'arrowright')) mx += 1;
|
||||
if (mx || my) {
|
||||
const len = Math.hypot(mx, my);
|
||||
const spd = 178 * st.speedMul;
|
||||
const spd = 178 * st.speedMul * (held ? (w.id === 'bonepopper' ? 0.95 : 0.85) : 1);
|
||||
p.x += (mx / len) * spd * dt;
|
||||
p.y += (my / len) * spd * dt;
|
||||
p.step += dt * 9 * Math.min(st.speedMul, 1.8);
|
||||
@@ -106,9 +108,8 @@ function updatePlayer(dt) {
|
||||
}
|
||||
|
||||
// firing
|
||||
const w = WEAPONS[p.weapon.id];
|
||||
const sustained = ['cone', 'flame', 'saw', 'beam'].includes(w.behavior);
|
||||
Sfx.syncWeaponLoop(w, sustained && Input.mdown && p.weapon.ammo > 0);
|
||||
Sfx.syncWeaponLoop(w, sustained && held && p.weapon.ammo > 0);
|
||||
if (p.fireT > 0) p.fireT -= dt;
|
||||
const frenzyMul = p.frenzyT > 0 ? 1 + st.frenzy : 1;
|
||||
const draughtMul = p.marrowDraughtT > 0 ? 2 : 1;
|
||||
@@ -116,7 +117,7 @@ function updatePlayer(dt) {
|
||||
const rate = w.interval / effectiveRate;
|
||||
|
||||
if (w.behavior === 'saw') {
|
||||
if (Input.mdown && p.weapon.ammo > 0) {
|
||||
if (held && p.weapon.ammo > 0) {
|
||||
sawTick(p, w, p.weapon, dt);
|
||||
if (!(p.marrowDraughtT > 0)) p.weapon.ammo -= dt * (w.drain || 18) / st.ammoEff;
|
||||
p.muzzleT = 0.04; p.recoil = 0.18 + Math.sin(G.time * 45) * 0.05;
|
||||
@@ -126,7 +127,7 @@ function updatePlayer(dt) {
|
||||
else p.fireT = 0;
|
||||
} else if (w.drain) {
|
||||
// cone / flame streams: projectiles are free, ammo is time-denominated
|
||||
if (Input.mdown && p.weapon.ammo > 0 && !(p.panicRoomT > 0)) {
|
||||
if (held && p.weapon.ammo > 0 && !(p.panicRoomT > 0)) {
|
||||
if (p.fireT <= 0) {
|
||||
fireWeapon(p, w);
|
||||
p.recoil = Math.min(1, p.recoil + 0.45);
|
||||
@@ -139,7 +140,7 @@ function updatePlayer(dt) {
|
||||
}
|
||||
else p.fireT = 0;
|
||||
} else if (w.behavior === 'beam') {
|
||||
if (Input.mdown && p.weapon.ammo > 0) {
|
||||
if (held && p.weapon.ammo > 0) {
|
||||
p.charge = Math.min(p.charge + dt * effectiveRate * Math.sqrt(st.shotSpeedMul), w.chargeTime);
|
||||
} else if (Input.mreleased && p.charge > 0) {
|
||||
if (p.charge >= w.chargeTime * 0.4) fireBeam(p, w, p.charge / w.chargeTime);
|
||||
@@ -148,7 +149,7 @@ function updatePlayer(dt) {
|
||||
p.charge = Math.max(0, p.charge - dt * 2);
|
||||
}
|
||||
} else {
|
||||
if (Input.mdown && p.fireT <= 0 && p.weapon.ammo > 0 && !(p.panicRoomT > 0)) {
|
||||
if (held && p.fireT <= 0 && p.weapon.ammo > 0 && !(p.panicRoomT > 0)) {
|
||||
fireWeapon(p, w);
|
||||
// Twin Sidearm: the Bone Popper double-taps
|
||||
if (st.twinSidearm > 0 && p.weapon.id === 'bonepopper') fireWeapon(p, w);
|
||||
@@ -282,7 +283,7 @@ function updatePlayer(dt) {
|
||||
}
|
||||
|
||||
function armorBlockChance(rating) {
|
||||
return Math.min(0.75, Math.max(0, rating) / (1 + Math.max(0, rating)));
|
||||
return Math.min(0.75, Math.max(0, rating));
|
||||
}
|
||||
|
||||
function pressureRelief(dmgTaken) {
|
||||
|
||||
+4
-4
@@ -239,10 +239,10 @@ function updateRoom(dt) {
|
||||
enterRoom(r.gx + dx, r.gy + dy);
|
||||
const next = G.arena;
|
||||
// place player at the opposite door in the destination's own shape
|
||||
if (dir === 'n') p.y = next.y1 - p.r - 8;
|
||||
if (dir === 's') p.y = next.y0 + p.r + 8;
|
||||
if (dir === 'e') p.x = next.x0 + p.r + 8;
|
||||
if (dir === 'w') p.x = next.x1 - p.r - 8;
|
||||
if (dir === 'n') { p.y = next.y1 - p.r - 8; p.x = next.cx; }
|
||||
if (dir === 's') { p.y = next.y0 + p.r + 8; p.x = next.cx; }
|
||||
if (dir === 'e') { p.x = next.x0 + p.r + 8; p.y = next.cy; }
|
||||
if (dir === 'w') { p.x = next.x1 - p.r - 8; p.y = next.cy; }
|
||||
p.x = clamp(p.x, next.x0 + p.r, next.x1 - p.r);
|
||||
p.y = clamp(p.y, next.y0 + p.r, next.y1 - p.r);
|
||||
if (G.entryFresh) { separateEntryWave(); G.entryFresh = false; }
|
||||
|
||||
@@ -28,6 +28,7 @@ const G = {
|
||||
pressure: 1,
|
||||
pressureDial: 0,
|
||||
streak: 0,
|
||||
shotLockT: 0,
|
||||
roomDamaged: false,
|
||||
roomEnterT: 0,
|
||||
recentHits: [],
|
||||
|
||||
+11
-4
@@ -47,7 +47,7 @@ function fireWeapon(p, w) {
|
||||
const st = p.stats;
|
||||
const ang = p.aim;
|
||||
const muzzle = w.muzzle || 35;
|
||||
const bx = p.x + Math.cos(ang) * muzzle, by = p.y + Math.sin(ang) * muzzle;
|
||||
let bx = p.x + Math.cos(ang) * muzzle, by = p.y + Math.sin(ang) * muzzle;
|
||||
const dmgMul = st.dmgMul * st.dmgLiveMul;
|
||||
const spdMul = st.shotSpeedMul;
|
||||
spawnMuzzleFx(bx, by, ang, w.behavior);
|
||||
@@ -86,6 +86,10 @@ function fireWeapon(p, w) {
|
||||
break;
|
||||
}
|
||||
case 'cone': case 'flame': {
|
||||
if (w.behavior === 'flame') {
|
||||
const rh = ang + Math.PI / 2;
|
||||
bx += Math.cos(rh) * 26; by += Math.sin(rh) * 26; // right-hand hold: origin right of the body
|
||||
}
|
||||
for (const center of volley) {
|
||||
for (let i = 0; i < 2; i++) mk(center + rand(-w.spread, w.spread), { pierce: 2 + st.pierce, r: 6 * st.sizeMul });
|
||||
}
|
||||
@@ -144,9 +148,12 @@ function sawTick(p, def, inst, dt) {
|
||||
const inert = 1 + (st.pierce + st.bounce + st.homing) * 0.06;
|
||||
let hitAny = false;
|
||||
for (const a of angles) {
|
||||
const reach = 30 * st.rangeMul * Math.sqrt(st.shotSpeedMul);
|
||||
const cx = p.x + Math.cos(a) * reach, cy = p.y + Math.sin(a) * reach;
|
||||
const radius = 34 * Math.sqrt(st.sizeMul);
|
||||
const reach = 58 * st.rangeMul * Math.sqrt(st.shotSpeedMul); // out to the blade (was 30)
|
||||
const rh = p.aim + Math.PI / 2; // right-hand side, perpendicular to aim
|
||||
const rho = 36 * Math.sqrt(st.sizeMul); // right-hand offset distance
|
||||
const cx = p.x + Math.cos(a) * reach + Math.cos(rh) * rho;
|
||||
const cy = p.y + Math.sin(a) * reach + Math.sin(rh) * rho;
|
||||
const radius = 38 * Math.sqrt(st.sizeMul); // full blade (was 34)
|
||||
for (const e of G.enemies) {
|
||||
if (dist2(cx, cy, e.x, e.y) < (radius + e.r) * (radius + e.r)) {
|
||||
const tickScale = dt / Math.max(0.01, def.interval || 0.1);
|
||||
|
||||
Reference in new issue
Block a user