feat: add auto_sleep and fall_protection modes#702
feat: add auto_sleep and fall_protection modes#702atiweb wants to merge 2 commits intomindcraft-bots:developfrom
Conversation
Add two new survival modes: - fall_protection: Uses water bucket (MLG water) when falling from height >10 blocks. Checks for ground proximity before activating. High priority mode that interrupts all actions. - auto_sleep: Automatically finds and sleeps in nearby beds when night falls (13000-23000 ticks). Checks every 30 seconds to avoid spamming. Uses the existing goToBed skill which handles all bed types. Handles occupied bed errors gracefully. Both modes use English narration and follow the existing mode patterns (say/execute helpers, proper active/interrupts flags).
There was a problem hiding this comment.
Pull request overview
This PR adds two new autonomous survival modes to improve the bot's ability to survive without manual intervention: fall_protection for MLG water bucket falls and auto_sleep for automatic bed usage at night.
Changes:
- Added
fall_protectionmode that detects long falls (>10 blocks) and uses MLG water bucket technique to prevent fall damage - Added
auto_sleepmode that automatically finds and sleeps in beds during nighttime (13000-23000 ticks) to skip the night and avoid monsters
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/agent/modes.js
Outdated
| const fallDistance = this.fallStartY - pos.y; | ||
|
|
||
| // If falling more than 10 blocks and accelerating, try MLG water | ||
| if (fallDistance > 10 && vel.y < -0.8) { |
There was a problem hiding this comment.
The check vel.y < -0.8 on line 111 is more restrictive than the initial falling check vel.y < -0.5 on line 103. This means the fallStartY can be set during a slower fall, but won't trigger MLG water placement. Consider using the same velocity threshold for both checks, or resetting fallStartY when velocity is between -0.5 and -0.8 to ensure accurate fall distance calculation.
| if (fallDistance > 10 && vel.y < -0.8) { | |
| if (fallDistance > 10 && vel.y < -0.5) { |
src/agent/modes.js
Outdated
| if (e.message && e.message.includes('occupied')) { | ||
| say(agent, 'The bed is occupied.'); |
There was a problem hiding this comment.
The error handling only checks for 'occupied' in the error message, but silently swallows all other errors. If the bed is too far, blocks are in the way, or other pathfinding issues occur, the bot will fail silently without informing the user. Consider either logging all errors or handling specific common error cases (e.g., pathfinding failures, missing beds after the search).
| if (e.message && e.message.includes('occupied')) { | |
| say(agent, 'The bed is occupied.'); | |
| if (e && e.message && e.message.includes('occupied')) { | |
| say(agent, 'The bed is occupied.'); | |
| } else { | |
| console.error('Failed to sleep in bed:', e); | |
| const errorMessage = e && e.message ? e.message : 'unknown reason'; | |
| say(agent, `I couldn\'t reach the bed to sleep (${errorMessage}).`); |
src/agent/modes.js
Outdated
| await bot.equip(waterBucket, 'hand'); | ||
| await bot.lookAt(pos.offset(0, -3, 0)); | ||
| bot.activateItem(); | ||
| await new Promise(r => setTimeout(r, 500)); |
There was a problem hiding this comment.
The 500ms wait after placing water may not be sufficient for the bot to land in the water and stop falling. Depending on fall speed and distance, the bot might still be falling when it tries to pick up the water block. This could result in the bot not landing safely or picking up the water too early. Consider waiting for the bot to actually land (checking velocity or ground contact) before attempting to pick up the water.
| await new Promise(r => setTimeout(r, 500)); | |
| // Wait until the bot has actually landed or a timeout is reached | |
| await new Promise(resolve => { | |
| const start = Date.now(); | |
| const checkLanding = () => { | |
| const entity = bot.entity; | |
| // Consider landed if onGround or vertical velocity is no longer negative | |
| if (entity.onGround || entity.velocity.y >= 0) { | |
| return resolve(); | |
| } | |
| // Safety timeout to avoid waiting forever (e.g., in unusual situations) | |
| if (Date.now() - start > 2000) { | |
| return resolve(); | |
| } | |
| setTimeout(checkLanding, 50); | |
| }; | |
| checkLanding(); | |
| }); |
src/agent/modes.js
Outdated
| say(agent, 'MLG water!'); | ||
| try { | ||
| await bot.equip(waterBucket, 'hand'); | ||
| await bot.lookAt(pos.offset(0, -3, 0)); |
There was a problem hiding this comment.
The position variable pos is captured from outside the execute callback at line 99, but by the time the async function executes, the bot will have fallen significantly. The lookAt target position pos.offset(0, -3, 0) will be based on the bot's position from when the check started, not the current position during execution. This could cause the bot to place water at an incorrect location. Consider capturing the bot's current position inside the execute callback instead.
| say(agent, 'MLG water!'); | |
| try { | |
| await bot.equip(waterBucket, 'hand'); | |
| await bot.lookAt(pos.offset(0, -3, 0)); | |
| const currentPos = bot.entity.position.clone(); | |
| say(agent, 'MLG water!'); | |
| try { | |
| await bot.equip(waterBucket, 'hand'); | |
| await bot.lookAt(currentPos.offset(0, -3, 0)); |
src/agent/modes.js
Outdated
| // Check if it's nighttime (13000-23000 ticks) and we're not already sleeping | ||
| const time = bot.time.timeOfDay; | ||
| const isNight = time >= 13000 && time <= 23000; |
There was a problem hiding this comment.
The night time check uses time >= 13000 && time <= 23000, but Minecraft's time cycle wraps from 23999 to 0. This means times from 23001-23999 (about 50 seconds of night) will be missed. In Minecraft, you can sleep in a bed during times 12542-23459, and monsters spawn when time >= 13000. Consider changing to time >= 13000 without an upper bound check, since the bot will stop checking once it's sleeping, or add proper wrap-around handling with (time >= 13000 && time < 24000) || time < 1000.
| // Check if it's nighttime (13000-23000 ticks) and we're not already sleeping | |
| const time = bot.time.timeOfDay; | |
| const isNight = time >= 13000 && time <= 23000; | |
| // Check if it's nighttime (time >= 13000) and we're not already sleeping | |
| const time = bot.time.timeOfDay; | |
| const isNight = time >= 13000; |
| // Look for a bed within 32 blocks using block name matching (beds are named like 'white_bed', 'red_bed', etc.) | ||
| const beds = bot.findBlocks({ | ||
| matching: (block) => block.name.includes('bed'), | ||
| maxDistance: 32, | ||
| count: 1 | ||
| }); | ||
| if (beds.length > 0) { | ||
| execute(this, agent, async () => { | ||
| say(agent, 'It\'s getting dark, I should sleep.'); | ||
| try { | ||
| await skills.goToBed(bot); | ||
| } catch (e) { | ||
| if (e.message && e.message.includes('occupied')) { | ||
| say(agent, 'The bed is occupied.'); | ||
| } | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
The bed finding logic here duplicates the same search that skills.goToBed() performs (see skills.js lines 1551-1557). This means the bed search happens twice: once here to check if a bed exists, and again inside skills.goToBed(). Consider removing the bed search here and letting skills.goToBed() handle it, or check the return value from skills.goToBed() to determine if a bed was found.
| // Look for a bed within 32 blocks using block name matching (beds are named like 'white_bed', 'red_bed', etc.) | |
| const beds = bot.findBlocks({ | |
| matching: (block) => block.name.includes('bed'), | |
| maxDistance: 32, | |
| count: 1 | |
| }); | |
| if (beds.length > 0) { | |
| execute(this, agent, async () => { | |
| say(agent, 'It\'s getting dark, I should sleep.'); | |
| try { | |
| await skills.goToBed(bot); | |
| } catch (e) { | |
| if (e.message && e.message.includes('occupied')) { | |
| say(agent, 'The bed is occupied.'); | |
| } | |
| } | |
| }); | |
| } | |
| // Let skills.goToBed handle finding a suitable bed | |
| execute(this, agent, async () => { | |
| try { | |
| await skills.goToBed(bot); | |
| say(agent, 'It\'s getting dark, I should sleep.'); | |
| } catch (e) { | |
| if (e && e.message && e.message.includes('occupied')) { | |
| say(agent, 'The bed is occupied.'); | |
| } | |
| } | |
| }); |
- Unify velocity threshold to -0.5 for consistent fall detection - Capture current position inside execute callback to avoid stale coords - Poll bot.entity.onGround instead of fixed 500ms wait for landing - Fix night time range: use >= 13000 (wraps from 23999 to 0) - Add null guard on error object in auto_sleep catch - Log non-occupied sleep errors instead of swallowing them silently
Summary
Adds two new survival modes that improve the bot's autonomous survival capabilities.
New Modes
fall_protection(priority: afterself_preservation)interrupts: ['all']) since fall death is instantauto_sleep(priority: afterself_defense, beforehunting)block.name.includes('bed')to match all bed colorsskills.goToBed()which handles navigation, sleeping, and waitinginterrupts: ['action:followPlayer']) so it won't interrupt combatDesign Decisions
say()messages are in English to match the rest of the codebaseincludes('bed')) so it works with all current and future bed colorsmodes.js, no new dependenciessay()/execute()helpers, properactive/interruptsflags, cooldown timersmineflayer-auto-eatplugin already loaded inagent.js) or armor equipping (handled bymineflayer-armor-managerplugin)Relation to Previous PR
This is a focused subset of the changes from #693, resubmitted as a smaller PR addressing all review feedback from @uukelele-scratch. The previous PR was too large and contained hardcoded lists, Spanish narration, and duplicate plugin functionality. This PR fixes all of those issues.