From 8cf99329992923763ced134c2a28c031ab1afb4d Mon Sep 17 00:00:00 2001 From: MeatSlicer Developer Date: Wed, 29 Jul 2026 20:50:08 +1000 Subject: [PATCH] Make Bone Knit a chance-based room heal The perk now grants a 3% roll per stack to mend half a heart on combat room clears instead of a guaranteed heal. Worm Gut's guaranteed room heal is unchanged; the new roomHealChance stat is separate. --- js/perks.js | 2 +- js/player.js | 2 +- js/rooms.js | 7 +++++++ test/smoke.js | 21 +++++++++++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/js/perks.js b/js/perks.js index 28f2629..88a8973 100644 --- a/js/perks.js +++ b/js/perks.js @@ -20,7 +20,7 @@ const PERKS = [ { id: 'thickhide',name: 'Thick Hide', desc: '+4% chance to ignore damage', apply: s => { s.armor += 1 / 24; } }, { 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 consumed', apply: s => { s.ammoEff *= 1.05; } }, - { id: 'boneknit', name: 'Bone Knit', desc: 'Heal ½ heart after each combat room', apply: s => { s.roomHeal += 1; } }, + { id: 'boneknit', name: 'Bone Knit', desc: '3% chance to heal ½ heart after combat rooms', apply: s => { s.roomHealChance += 0.03; } }, { id: 'spiteflesh',name: 'Spite Flesh', desc: 'Contact attackers take damage', apply: s => { s.thorns += 4; } }, { id: 'carrion', name: 'Carrion Sense', desc: '+8% pickup radius and pull speed', apply: s => { s.magnet *= 1.08; s.magnetPull *= 1.08; } }, { id: 'sinew', name: 'Sinew Weave', desc: '+3% move speed and range', apply: s => { s.speedMul *= 1.03; s.rangeMul *= 1.03; } }, diff --git a/js/player.js b/js/player.js index 00b6649..65baf07 100644 --- a/js/player.js +++ b/js/player.js @@ -19,7 +19,7 @@ function initPlayer() { armor: 0, luck: 0, magnet: 1, magnetPull: 1, xpMul: 1, crit: 0.05, critMul: 2, bloodlust: 0, bleed: 0, igniteChance: 0, slowOnHit: 0, stunOnHit: 0, acidOnHit: 0, pullOnHit: 0, chain: 0, knockbackMul: 1, invBonus: 0, ammoEff: 1, ammoPickupMul: 1, - roomHeal: 0, thorns: 0, retaliate: 0, sizeMul: 1, mortar: 0, frenzy: 0, + roomHeal: 0, roomHealChance: 0, thorns: 0, retaliate: 0, sizeMul: 1, mortar: 0, frenzy: 0, orbSpeedMul: 1, orbDmgMul: 1, overShield: 0, rerolls: 0, rerollPerLevel: 0, maxHp: 6, diff --git a/js/rooms.js b/js/rooms.js index 5682389..48a84b3 100644 --- a/js/rooms.js +++ b/js/rooms.js @@ -180,6 +180,13 @@ function recordRoomClear(room) { healPlayer(G.player.stats.roomHeal); if (G.player.hp > before) spawnText(G.player.x, G.player.y - 18, 'ROOM KNIT +' + (G.player.hp - before), '#d98991'); } + // Bone Knit perk: small chance of a half-heart mend on each combat clear + if (room.type === 'combat' && G.player.stats.roomHealChance > 0 && G.player.hp < G.player.stats.maxHp && + chance(Math.min(0.9, G.player.stats.roomHealChance))) { + const before = G.player.hp; + healPlayer(1); + if (G.player.hp > before) spawnText(G.player.x, G.player.y - 30, 'BONE KNIT +' + (G.player.hp - before), '#d98991'); + } } function nextFloor() { diff --git a/test/smoke.js b/test/smoke.js index 706953a..3085fbd 100644 --- a/test/smoke.js +++ b/test/smoke.js @@ -770,6 +770,27 @@ console.log('== adaptive clean-room pressure =='); check('new runs reset pressure and streak', ctx.G.pressure === 1 && ctx.G.streak === 0); } +console.log('== boneknit is a chance-based room heal =='); +{ + const p = ctx.G.player; + const savedHeal = p.stats.roomHeal, savedChance = p.stats.roomHealChance; + p.stats.roomHeal = 0; p.stats.roomHealChance = 0; + ctx.PERKS.find(k => k.id === 'boneknit').apply(p.stats, p); + check('boneknit grants a 3% roll per stack', Math.abs(p.stats.roomHealChance - 0.03) < 0.0001); + p.hp = 1; p.stats.maxHp = 10; ctx.G.roomDamaged = true; // dirty room: no pressure side-effects + const realChance = ctx.chance; + ctx.__smokeRealChance = realChance; + vm.runInContext('chance = () => true', ctx); + ctx.recordRoomClear({ type: 'combat' }); + check('successful boneknit roll heals half a heart', p.hp === 2); + vm.runInContext('chance = () => false', ctx); + ctx.recordRoomClear({ type: 'combat' }); + check('failed boneknit roll heals nothing', p.hp === 2); + vm.runInContext('chance = __smokeRealChance', ctx); + delete ctx.__smokeRealChance; + p.stats.roomHeal = savedHeal; p.stats.roomHealChance = savedChance; +} + console.log('== weapon swap (R) =='); { const p = ctx.G.player;