fix: persistent mute toggle, 25%/floor shield regen, halved pressure relief, shielded hits no relief, drop Scavenger perk (0.9.2)
This commit is contained in:
1 parent
683e7a47ec
commit
c41ce5fdb0
16 files changed
+105
-71
No files matched your search
+4
-4
@@ -178,7 +178,7 @@ function drawHelpPerkList(ctx, px, py) {
|
||||
let ty = py + HELP_BODY;
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillStyle = '#d7a934'; ctx.font = hfont(9, true);
|
||||
ctx.fillText('23 PERKS · DRAFT 1 OF 3 PER LEVEL · XP FOR NEXT LEVEL: 8 + LEVEL × 4', x, ty);
|
||||
ctx.fillText(PERKS.length + ' PERKS · DRAFT 1 OF 3 PER LEVEL · XP FOR NEXT LEVEL: 8 + LEVEL × 4', x, ty);
|
||||
ty += 11;
|
||||
ctx.fillStyle = '#7d6a68'; ctx.font = hfont(8, false);
|
||||
ctx.fillText('REROLL RIB GRANTS TOKENS (R TO REDRAW) · AUTO-DRAFT (T) PICKS RANDOMLY', x, ty + 11);
|
||||
@@ -304,11 +304,11 @@ function drawHelpPressure(ctx, px, py) {
|
||||
'ALL SCORE IS MULTIPLIED BY LIVE PRESSURE',
|
||||
'DIAL ' + (d > 0 ? '+' : '') + d + ' — RISE ' + gain.toFixed(1) + '/10 PER CLEAN ROOM · RELIEF ' + drop.toFixed(1) + '/10 ON HIT',
|
||||
'+' + pressureGain().toFixed(3) + ' FOR EVERY ROOM CLEARED WITHOUT TAKING ANY DAMAGE',
|
||||
'RELIEF ON HIT = ' + (0.03 * scale).toFixed(3) + ' × SEVERITY × DESPERATION × CHURN',
|
||||
'RELIEF ON HIT = ' + (PRESSURE_DROP_BASE * PRESSURE_UNIT * PRESSURE_RELIEF_MUL * scale).toFixed(3) + ' × SEVERITY × DESPERATION × CHURN',
|
||||
' SEVERITY = HIT / (MAX HP × 0.25), CLAMPED 0.4 – 2',
|
||||
' DESPERATION = 1 + 2 × (1 − HP FRACTION) — MORE RELIEF WHEN HURT',
|
||||
' CHURN = 1 + 0.35 × HITS IN LAST 12S (CAP +4) — MORE WHEN SWARMED',
|
||||
'DECAY: ' + (0.02 * scale).toFixed(3) + '/S BELOW 35% HP · ' + (0.008 * scale).toFixed(3) + '/S AFTER 90S IN ONE ROOM',
|
||||
'DECAY: ' + (0.02 * scale * PRESSURE_RELIEF_MUL).toFixed(3) + '/S BELOW 35% HP · ' + (0.008 * scale * PRESSURE_RELIEF_MUL).toFixed(3) + '/S AFTER 90S IN ONE ROOM',
|
||||
'LIVING ENEMIES KEEP THEIR STATS — ONLY NEW SPAWNS CHANGE',
|
||||
'STREAK = CONSECUTIVE FLAWLESS CLEARS (COSMETIC)',
|
||||
] },
|
||||
@@ -316,7 +316,7 @@ function drawHelpPressure(ctx, px, py) {
|
||||
'ARMOR = BLOCK CHANCE: RATING / (1 + RATING), HARD CAP 75%',
|
||||
' THICK HIDE +1/24 RATING · TANNED HIDE +0.08/0.92 (DIMINISHING)',
|
||||
'BLOCKED HIT: 0 DAMAGE, 0.2S I-FRAMES, STREAK PRESERVED',
|
||||
'SHIELD HEARTS ABSORB FIRST, REFILL EACH FLOOR · 0.9S I-FRAMES ON HIT',
|
||||
'SHIELD HEARTS ABSORB FIRST, REGEN 25% EACH FLOOR · SHIELDED HITS NO RELIEF · 0.9S I-FRAMES ON HIT',
|
||||
'CRIT: 5% BASE CHANCE, ×2 DAMAGE (CRITMUL STACKS)',
|
||||
] },
|
||||
], HELP_PANEL.w - 80);
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ const ITEMS = {
|
||||
orbitalknives: { name: 'Orbital Knives', desc: '+1 circling knife', rarity: 'rare', cap: 3, apply: s => { s.orbitals += 1; } },
|
||||
volatilebile: { name: 'Volatile Bile', desc: 'Kills explode (bigger per tier)', rarity: 'rare', cap: 3, apply: s => { s.explodeOnKill = (s.explodeOnKill || 0) + 1; } },
|
||||
deadmanswitch: { name: "Dead Man's Switch", desc: 'Kills trigger a fire-rate frenzy', rarity: 'rare', cap: 3, apply: s => { s.frenzy += 0.12; } },
|
||||
boneplate: { name: 'Bone Plate', desc: '+½ shield heart each floor', rarity: 'rare', cap: 3, apply: (s, p) => { s.shieldPerk = (s.shieldPerk || 0) + 1; p.shieldHp += 1; } },
|
||||
boneplate: { name: 'Bone Plate', desc: '+½ max shield heart (regens 25% per floor)', rarity: 'rare', cap: 3, apply: (s, p) => { s.shieldPerk = (s.shieldPerk || 0) + 1; p.shieldHp += 1; } },
|
||||
wormgut: { name: 'Worm Gut', desc: 'Heal ½ heart after combat rooms', rarity: 'rare', cap: 3, apply: s => { s.roomHeal += 1; } },
|
||||
secondstomach: { name: 'Second Stomach', desc: '+½ heart and excess healing shields', rarity: 'rare', cap: 3, apply: (s, p) => { s.maxHp += 1; s.overShield += 1; p.hp = Math.min(s.maxHp, p.hp + 1); } },
|
||||
twinhearts: { name: 'Twin Hearts', desc: '+1 max heart, heal to full', rarity: 'rare', cap: 3, apply: (s, p) => { s.maxHp += 2; p.hp = s.maxHp; } },
|
||||
|
||||
+5
-1
@@ -50,6 +50,7 @@ function init() {
|
||||
if (Number.isFinite(sv)) G.sfxVol = clamp(sv, 0, 1);
|
||||
if (Number.isFinite(mv)) G.musicVol = clamp(mv, 0, 1);
|
||||
G.autoPerk = localStorage.getItem('meatslicer_autoperk') === '1';
|
||||
G.muted = localStorage.getItem('meatslicer_muted') === '1';
|
||||
const pd = parseFloat(localStorage.getItem('meatslicer_pressure_dial'));
|
||||
if (Number.isFinite(pd)) G.pressureDial = clamp(Math.round(pd), PRESSURE_DIAL_MIN, PRESSURE_DIAL_MAX);
|
||||
const ha = parseFloat(localStorage.getItem('meatslicer_hud_alpha'));
|
||||
@@ -67,6 +68,9 @@ function toggleMute() {
|
||||
G.muted = !G.muted;
|
||||
Music.setMuted(G.muted);
|
||||
Sfx.setMuted(G.muted);
|
||||
try { localStorage.setItem('meatslicer_muted', G.muted ? '1' : '0'); } catch (e) {}
|
||||
addToast(G.muted ? 'AUDIO MUTED' : 'AUDIO UNMUTED', G.muted ? 'all sound is silenced' : 'sound restored');
|
||||
if (!G.muted) Sfx.menu();
|
||||
}
|
||||
|
||||
function setHudAlpha(v) {
|
||||
@@ -559,7 +563,7 @@ function drawPause(ctx) {
|
||||
const menuConfirm = G.confirmAction === 'menu';
|
||||
const desktopExit = canQuitDesktop();
|
||||
const exitConfirm = desktopExit && G.confirmAction === 'desktop';
|
||||
drawPixelTag(ctx, '[M] MUTE AUDIO', gridX[0], 526, { width: 150, height: 28, color: muteHover ? '#f5e9d6' : '#bba9a1', accent: muteHover ? '#b5243a' : '#6e3843' });
|
||||
drawPixelTag(ctx, '[M] MUTE ' + (G.muted ? 'ON' : 'OFF'), gridX[0], 526, { width: 150, height: 28, color: G.muted ? '#79d9ca' : (muteHover ? '#f5e9d6' : '#bba9a1'), accent: G.muted ? '#378b80' : (muteHover ? '#b5243a' : '#6e3843') });
|
||||
drawPixelTag(ctx, menuConfirm ? '[Q] CONFIRM MENU' : '[Q] MAIN MENU', PAUSE_MENU_BUTTON.x, PAUSE_MENU_BUTTON.y, {
|
||||
width: PAUSE_MENU_BUTTON.w, height: PAUSE_MENU_BUTTON.h,
|
||||
color: menuConfirm ? '#ffd36a' : (menuHover ? '#f5e9d6' : '#bba9a1'),
|
||||
|
||||
+1
-2
@@ -7,8 +7,7 @@ const PERKS = [
|
||||
{ id: 'longbone', name: 'Long Bone', desc: '+5% range', apply: s => { s.rangeMul *= 1.05; } },
|
||||
{ id: 'bigheart', name: 'Big Heart', desc: '+½ max heart, heal ½ heart', apply: (s, p) => { s.maxHp += 1; p.hp = Math.min(s.maxHp, p.hp + 1); } },
|
||||
{ id: 'magnetb', name: 'Magnet Bile', desc: '+10% pickup radius', apply: s => { s.magnet *= 1.10; } },
|
||||
{ id: 'shieldheart', name: 'Shield Heart', desc: '+½ shield heart at the start of each floor', apply: (s, p) => { s.shieldPerk = (s.shieldPerk || 0) + 1; p.shieldHp = (p.shieldHp || 0) + 1; } },
|
||||
{ id: 'scavenge', name: 'Scavenger', desc: '+4% drop luck', apply: s => { s.luck += 0.04; } },
|
||||
{ id: 'shieldheart', name: 'Shield Heart', desc: '+½ max shield heart (regens 25% per floor)', apply: (s, p) => { s.shieldPerk = (s.shieldPerk || 0) + 1; p.shieldHp = (p.shieldHp || 0) + 1; } },
|
||||
{ id: 'bloodrush',name: 'Bloodrush', desc: '+5% XP gain, bonus crystal chance', apply: s => { s.xpMul *= 1.05; } },
|
||||
{ id: 'deadeye', name: 'Deadeye', desc: '+5% shot speed', apply: s => { s.shotSpeedMul *= 1.05; } },
|
||||
{ id: 'critbone', name: 'Bone Splitter', desc: '+2% critical chance', apply: s => { s.crit += 0.02; } },
|
||||
|
||||
+4
-3
@@ -305,7 +305,7 @@ function pressureRelief(dmgTaken) {
|
||||
const severity = clamp(dmgTaken / Math.max(2, p.stats.maxHp * 0.25), 0.4, 2);
|
||||
const desperation = 1 + (1 - hpFrac) * 2;
|
||||
const churn = 1 + Math.min(G.recentHits.length, 4) * 0.35;
|
||||
return PRESSURE_DROP_BASE * PRESSURE_UNIT * severity * desperation * churn * pressureDropScale();
|
||||
return PRESSURE_DROP_BASE * PRESSURE_UNIT * severity * desperation * churn * pressureDropScale() * PRESSURE_RELIEF_MUL;
|
||||
}
|
||||
|
||||
function hurtPlayer(dmg, ang, attacker) {
|
||||
@@ -333,8 +333,9 @@ function hurtPlayer(dmg, ang, attacker) {
|
||||
// Meat Grinder: aura damages you for +1 per hit taken
|
||||
if (p.stats.meatGrinder > 0) reduced += p.stats.meatGrinder;
|
||||
// shield hearts absorb first (from the Shield Heart perk)
|
||||
let absorbed = 0;
|
||||
if (p.shieldHp > 0) {
|
||||
const absorbed = Math.min(p.shieldHp, reduced);
|
||||
absorbed = Math.min(p.shieldHp, reduced);
|
||||
p.shieldHp -= absorbed;
|
||||
reduced -= absorbed;
|
||||
if (p.shieldHp <= 0) { spawnText(p.x, p.y - 20, 'SHIELD DOWN', '#3bc9e0'); Sfx.shieldBreak(); }
|
||||
@@ -352,7 +353,7 @@ function hurtPlayer(dmg, ang, attacker) {
|
||||
G.roomDamaged = true;
|
||||
G.recentHits = (G.recentHits || []).filter(t => G.time - t < 12);
|
||||
G.recentHits.push(G.time);
|
||||
applyPressureDelta(-pressureRelief(reduced));
|
||||
if (absorbed === 0) applyPressureDelta(-pressureRelief(reduced));
|
||||
G.streak = 0;
|
||||
}
|
||||
if (attacker && attacker.hp > 0 && p.stats.thorns > 0) {
|
||||
|
||||
+7
-4
@@ -202,7 +202,7 @@ function updateRoom(dt) {
|
||||
const stalled = G.time - G.roomEnterT > 90;
|
||||
if (lowHealth || stalled) {
|
||||
const decay = lowHealth ? 0.02 : 0.008;
|
||||
applyPressureDelta(-decay * dt * pressureDropScale());
|
||||
applyPressureDelta(-decay * dt * pressureDropScale() * PRESSURE_RELIEF_MUL);
|
||||
}
|
||||
}
|
||||
G.recentHits = (G.recentHits || []).filter(t => G.time - t < 12);
|
||||
@@ -304,9 +304,12 @@ function nextFloor() {
|
||||
Sfx.stairs();
|
||||
genFloor(G.floor);
|
||||
enterRoom(0, 0);
|
||||
// Shield Heart perk: fresh shield hearts each floor
|
||||
G.player.shieldHp = G.player.stats.shieldPerk || 0;
|
||||
if (G.player.shieldHp > 0) { spawnText(G.player.x, G.player.y - 20, 'SHIELD UP', '#3bc9e0'); Sfx.shieldUp(); }
|
||||
// Shield Heart: regen 25% of max shields per floor, rounded up to the next
|
||||
// whole shield (1 shield = 2 shieldHp). Full shields only at run start.
|
||||
const maxShield = G.player.stats.shieldPerk || 0;
|
||||
const shieldBefore = G.player.shieldHp;
|
||||
G.player.shieldHp = Math.min(maxShield, G.player.shieldHp + 2 * Math.ceil(maxShield / 8));
|
||||
if (G.player.shieldHp > shieldBefore) { spawnText(G.player.x, G.player.y - 20, 'SHIELD UP', '#3bc9e0'); Sfx.shieldUp(); }
|
||||
// legendary once-per-floor flags reset
|
||||
G.player.secondSkinUsed = false;
|
||||
G.player.lastCutActive = false;
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ const SfxBank = {
|
||||
if (this.buffers.has(name) || this.pending.has(name) || this.failed.has(name)) return;
|
||||
const file = this.FILES[name];
|
||||
if (!file || !this.ctx || typeof fetch !== 'function') return;
|
||||
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=57')
|
||||
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=58')
|
||||
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.arrayBuffer(); })
|
||||
.then(bytes => this.ctx.decodeAudioData(bytes))
|
||||
.then(buffer => this.buffers.set(name, this.trim(buffer)))
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ const SPRITE_MANIFEST = [
|
||||
'decal_blood1', 'decal_blood2', 'decal_blood3', 'decal_blood4',
|
||||
// level-up perk emblems
|
||||
'perk_adrenal', 'perk_sharpen', 'perk_quick', 'perk_longbone', 'perk_bigheart',
|
||||
'perk_magnetb', 'perk_shieldheart', 'perk_scavenge', 'perk_bloodrush', 'perk_deadeye',
|
||||
'perk_magnetb', 'perk_shieldheart', 'perk_bloodrush', 'perk_deadeye',
|
||||
'perk_critbone', 'perk_critmeat', 'perk_flensing', 'perk_ember', 'perk_frostbile',
|
||||
'perk_heavyhand', 'perk_thickhide', 'perk_secondwind', 'perk_scrapfeed', 'perk_boneknit',
|
||||
'perk_spiteflesh', 'perk_carrion', 'perk_sinew',
|
||||
@@ -88,10 +88,10 @@ const Sprites = {
|
||||
const finish = () => { if (--pending === 0) G.imagesLoaded = true; };
|
||||
img.onload = () => { this.imgs[name] = img; if (typeof img.decode === 'function') img.decode().then(finish, finish); else finish(); };
|
||||
img.onerror = () => {
|
||||
if (hd && !img._sdRetry) { img._sdRetry = true; img.src = 'assets/' + name + '.png?v=57'; return; }
|
||||
if (hd && !img._sdRetry) { img._sdRetry = true; img.src = 'assets/' + name + '.png?v=58'; return; }
|
||||
finish();
|
||||
};
|
||||
img.src = hd ? 'assets/hd/' + name + '.webp?v=57' : 'assets/' + name + '.png?v=57';
|
||||
img.src = hd ? 'assets/hd/' + name + '.webp?v=58' : 'assets/' + name + '.png?v=58';
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ const WEAPON_DROP_LOCKOUT = 2.5;
|
||||
const PRESSURE_UNIT = 0.01; // one "point" on the dial scale
|
||||
const PRESSURE_GAIN = PRESSURE_UNIT; // retained: dial 0 == 1 point
|
||||
const PRESSURE_DROP_BASE = 3; // dial 0 == 0.03 relief base
|
||||
const PRESSURE_RELIEF_MUL = 0.5; // global brake: all pressure drops (hit relief + passive decay) are halved
|
||||
const DEATH_LOCK = 3;
|
||||
const PRESSURE_DIAL_MIN = -10, PRESSURE_DIAL_MAX = 10;
|
||||
|
||||
|
||||
Reference in new issue
Block a user