From e801c02ef6e2eb5d52716882558150daa82a3e16 Mon Sep 17 00:00:00 2001 From: Quintine Date: Sun, 9 Aug 2026 13:56:41 +1000 Subject: [PATCH] feat: 2.5s lockout before dropped weapons can be re-picked --- js/pickups.js | 2 +- js/state.js | 2 ++ package-lock.json | 4 ++-- test/smoke.js | 24 ++++++++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/js/pickups.js b/js/pickups.js index 17abca1..95a977f 100644 --- a/js/pickups.js +++ b/js/pickups.js @@ -117,7 +117,7 @@ function collectPickup(k) { if (old) { const a = rand(0, TAU); spawnPickup('weapon', p.x + Math.cos(a) * 34, p.y + Math.sin(a) * 34, - { wid: old.id, ammo: old.ammo, delay: 0.6 }); + { wid: old.id, ammo: old.ammo, delay: WEAPON_DROP_LOCKOUT }); } p.weapon = { id: w.id, ammo: k.ammo !== undefined ? k.ammo : w.ammo }; p.holstered = null; diff --git a/js/state.js b/js/state.js index 4d59a2b..27b3580 100644 --- a/js/state.js +++ b/js/state.js @@ -59,6 +59,8 @@ const PERK_POWER_WEIGHT = 0.5; // ammo economy: global dial for drop frequency (refill size is per-weapon) const AMMO_DROP_SCALE = 0.75; const PRESSURE_MIN = 0.60, PRESSURE_MAX = 2.00; +// seconds a player-dropped weapon stays un-pickable (prevents instant swap loops) +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 diff --git a/package-lock.json b/package-lock.json index e0a3cd7..a6de9dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "meatslicer", - "version": "0.1.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "meatslicer", - "version": "0.1.0", + "version": "0.5.0", "devDependencies": { "electron": "^40.10.2", "electron-builder": "^25.1.8" diff --git a/test/smoke.js b/test/smoke.js index a0408d0..2b25494 100644 --- a/test/smoke.js +++ b/test/smoke.js @@ -324,6 +324,30 @@ console.log('== upgrade card click does not fire =='); check('card click fires no bullet (mdown not carried into play)', ctx.G.bullets.length === 0); } +console.log('== dropped weapon lockout =='); +{ + ctx.startRun(); step(3, 16); // clean, armed player in play mode + const p = ctx.G.player; + p.weapon = { id: 'cleaver', ammo: 60 }; p.holstered = null; + ctx.G.pickups.length = 0; + ctx.spawnPickup('weapon', p.x, p.y, { wid: 'repeater', ammo: 30 }); + ctx.updatePickups(0.016); ctx.updatePickups(0.016); // pick up the repeater + check('weapon swap happens instantly on fresh loot', p.weapon.id === 'repeater'); + const drop = ctx.G.pickups.find(k => k.type === 'weapon'); + check('old weapon dropped with the lockout delay', !!drop && Math.abs(drop.delay - ctx.WEAPON_DROP_LOCKOUT) < 0.1); + if (drop) { drop.vx = 0; drop.vy = 0; } // hold it still for the timing checks + // standing on the drop, it must stay uncollectible for ~70% of the lockout + const blockedFor = Math.floor(ctx.WEAPON_DROP_LOCKOUT * 0.7 / 0.016); + for (let i = 0; i < blockedFor; i++) { p.x = drop.x; p.y = drop.y; ctx.updatePickups(0.016); } + const stillBlocked = ctx.G.pickups.some(k => k.type === 'weapon') && p.weapon.id === 'repeater'; + check('dropped weapon stays un-pickable inside the lockout window', stillBlocked); + // the remaining ~30% (plus margin) lets it be re-picked + const collectFor = Math.ceil(ctx.WEAPON_DROP_LOCKOUT / 0.016) + 10; + for (let i = 0; i < collectFor; i++) { p.x = drop.x; p.y = drop.y; ctx.updatePickups(0.016); } + check('dropped weapon re-pickable after the lockout', p.weapon.id === 'cleaver'); + check('lockout constant is within the 2-3s request', ctx.WEAPON_DROP_LOCKOUT >= 2 && ctx.WEAPON_DROP_LOCKOUT <= 3); +} + autoDraft = true; // later sections must not get stuck in the perk draft console.log('== shield heart perk ==');