feat: 🎉 init new repo

Dump of current working bot.

Warning: somewhat messy code! Lints haven't been run, no tests, etc.
This commit is contained in:
jay 2020-12-21 21:08:38 +05:00
commit 4f1e510386
12 changed files with 1386 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
/lib/**/*.old
/lib/**/*.bak
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor
*.swp
*.swo

29
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,29 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "protospace",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/lib/index.js",
"args": ["games.protospace.ca"]
},
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/lib/index.js",
// port may need to be changed for each session
"args": ["localhost", "56901"]
}
]
}

123
lib/index.js Normal file
View File

@ -0,0 +1,123 @@
// TODO reload
const fs = require('fs');
let cfg = {
admin: "Applezaus",
mods: ["Applezaus", "tanner6", "Angram42", "[WEB] Angram42", "[WEB] Applezaus"],
stateMachines: {}
}
const mineflayer = require("mineflayer");
// const { createGetAccessor } = require('typescript');
const bot =
!isNaN(parseInt(process.argv[3])) && parseInt(process.argv[3]) > 1e2 ?
mineflayer.createBot({
host: process.argv[2] || process.env.MINECRAFT_HOST || 'localhost', // Change this to the ip you want.
port: parseInt(process.argv[3]) || process.env.MINECRAFT_PORT // || 58471,
})
:
mineflayer.createBot({
host: process.argv[2] || process.env.MINECRAFT_HOST || 'localhost', // Change this to the ip you want.
username: process.argv[3] || process.env.MINECRAFT_USER,
password: process.argv[4] || process.env.MINECRAFT_PASS,
// port: process.argv[5] || process.env.MINECRAFT_PORT || 58471,
})
let plugins = {}
function loadplugin(pluginname, pluginpath) {
try {
plugins[pluginname] = require(pluginpath)
plugins[pluginname]?.load(cfg)
} catch (error) {
if (error.code == 'MODULE_NOT_FOUND') {
console.warn('plugin not used:', pluginpath)
} else {
console.error(error)
}
}
}
function unloadplugin(pluginname, pluginpath) {
pluginpath = pluginpath ? pluginpath : './plugins/' + pluginname
const plugin = require.resolve(pluginpath)
try {
if (plugin && require.cache[plugin]) {
require.cache[plugin].exports?.unload()
delete plugins[pluginname]
delete require.cache[plugin]
}
} catch (error) {
console.error(error)
}
}
reloadplugin = (event, filename, pluginpath) => {
if (!/\.js$/.test(filename)) { return }
if (!cfg.fsTimeout) {
console.info(event, filename)
pluginpath = (pluginpath ? pluginpath : './plugins/') + filename
const check = Object.keys(cfg.plugins)
console.info(`reload file:`, pluginpath)
const plugin = require.resolve(pluginpath)
if (plugin && require.cache[plugin]) {
// console.debug(Object.keys(cfg.plugins))
unloadplugin(filename.split(".js")[0], pluginpath)
console.assert(Object.keys(cfg.plugins).length == check.length - 1, "plugin not removed, potential memory leak")
}
loadplugin(filename.split(".js")[0], pluginpath)
if (Object.keys(cfg.plugins).length != check.length) {
// If left < right :
// - new plugin that's not registered in cfg.plugins, so added
// - rename, so old wasn't removed
// If left > right :
// - error in file (syntax, missing load()), so was not reloaded
console.warn("plugin count mismatch", check.length, Object.keys(cfg.plugins).length, Object.keys(cfg.plugins))
}
cfg.fsTimeout = setTimeout(function () { cfg.fsTimeout = null }, 2000) // give 2 seconds for multiple events
}
// console.log('file.js %s event', event)
}
fs.watch('./lib/plugins', reloadplugin)
cfg.bot = bot
cfg.botAddress = new RegExp(`^${bot.username} (!.+)`)
cfg.quiet = true
// == actually do stuff
bot.once("spawn", () => {
plugins = {
command: require('./plugins/command'),
sleeper: require('./plugins/sleeper'),
armor: require('./plugins/armor'),
// mover: require('./plugins/mover'),
guard: require('./plugins/guard'),
inventory: require('./plugins/inventory'),
// eater: require('./plugins/eater'),
finder: require('./plugins/finder'),
miner: require('./plugins/miner'),
// statemachine: require('./plugins/statemachine'),
}
cfg.plugins = plugins
for (const plugin of Object.values(plugins)) {
try {
plugin.load(cfg)
} catch (error) {
console.warn(error)
}
}
})
bot.on("death", () => {
bot.pathfinder && bot.pathfinder.setGoal(null)
// plugins.guard.unload()
})
// bot.on("respawn", () => {
// // setTimeout(plugins.guard.load, 2000)
// })

16
lib/plugins/armor.js Normal file
View File

@ -0,0 +1,16 @@
const armorManager = require('mineflayer-armor-manager')
const mineflayer = require('mineflayer');
let cfg = {}
const load = (config) => {
cfg = config
bot = cfg.bot
bot.loadPlugin(armorManager);
}
const unload = () => { console.warn("armour: may not properly unload") }
module.exports = { load, unload }

323
lib/plugins/command.js Normal file
View File

@ -0,0 +1,323 @@
const v = require('vec3')
let mcData = require('minecraft-data')
let cfg = {}
let bot = {}
// bot.chatAddPattern(new RegExp(`^hi|hello ${bot.username}`, "i"), 'greet', "General greeting")
function todo() {
bot.chat("not implemented yet")
}
function checkBlockExists(name) {
if (mcData.blocksByName[name] === undefined) {
bot.chat(`${name} is not a block name`)
return false
} else {
return true
}
}
function checkItemExists(name) {
if (mcData.itemsByName[name] === undefined) {
bot.chat(`${name} is not an item name`)
return false
} else {
return true
}
}
const events = {
whisper: function command_whisper(username, message) {
if ([bot.username, "me"].includes(username)) return
if (/^gossip/.test(message)) return
// if (/^!/.test(message) && username === cfg.admin){
if (username === cfg.admin) {
message = message.replace("\\", "@")
console.info("whispered command", message)
bot.chat("/" + message)
} else {
bot.whisper(cfg.admin, `gossip ${username}: ${message}`)
console.info(username, "whispered", message)
}
}
, chat: command
, kicked: (reason, loggedIn) => console.warn(reason, loggedIn)
}
const events_registered = []
function command(username, message) {
if (username === bot.username && !message.startsWith("!")) return
const player = bot.players[username] ? bot.players[username].entity : null
if (message.startsWith("zzz")) {
bot.chat("/afk")
cfg.plugins.sleeper.sleep()
// if(cfg)
return
}
switch (message) {
case "hi":
case "hello":
case "howdy":
case "yo":
bot.swingArm()
return
case "69":
Math.random() > 0.5 && bot.chat("nice!")
return
case "awesome":
case "cool":
case "superb":
case "nice":
Math.random() > 0.1 && setTimeout(() => bot.chat("very nice!"), 1000)
return
default:
break;
}
if (message.startsWith("!") || cfg.botAddress.test(message)) {
message = cfg.botAddress.test(message) ? cfg.botAddress.exec(message)[1] : message
console.log(message)
message = message.slice(1)
message_parts = message.split(/\s+/)
switch (message_parts[0]) {
// case "follow":
// // if(username === cfg.admin)
// cfg.stateMachines.follow.transitions[4].trigger()
// // cfg.stateMachines.follow.transitions[1].trigger()
// break;
// case "stay":
// // if(username === cfg.admin)
// cfg.stateMachines.follow.transitions[3].trigger()
// break;
case "echo":
// bot.chat(message_parts[1])
// bot.chat(message.slice(1))
bot.chat(message)
break;
case "autosleep":
case "sleep":
message_parts[1] = message_parts[0] == "autosleep" ? "auto" : message_parts[1]
switch (message_parts[1]) {
case "auto":
cfg.sleep.auto = !cfg.sleep.auto
bot.chat(`ok, ${cfg.sleep.auto ? "" : "not "}auto sleeping `)
bot.chat("/afk")
break;
case "silent":
case "quiet":
cfg.sleep.quiet = !cfg.sleep.quiet
bot.chat(`ok, ${cfg.sleep.quiet ? "" : "not "}sleeping quietly`)
break;
case "timeout":
if (!message_parts[2]) {
bot.chat(`sleeping every ${Math.round(cfg.sleep.timeout / 60 / 1000)}mins`)
return
}
const timeout = parseFloat(message_parts[2], 10)
if (timeout) {
cfg.sleep.timeout = timeout * 60 * 1000
cfg.sleep.timeoutFn = null
cfg.plugins.sleeper.sleep(true) //reset timer
bot.chat(`ok, next sleep attempt in ${timeout}mins`)
break;
}
default:
bot.chat(`usage: !sleep [auto | quiet | timeout <mins>], or zzz for manual sleep`)
break;
}
break; //todo; needed?
case "zzz":
cfg.plugins.sleeper.sleep()
case "afk":
bot.chat("/afk")
break;
case "awaken":
case "wake":
case "wakeup":
cfg.plugins.sleeper.wake()
break;
case "attack":
case "rage":
case "ragemode":
case "guard":
switch (message_parts.length) {
case 1:
if (!player) {
bot.chat("can't see you.")
return
}
bot.chat('for glory!')
cfg.plugins.guard.guardArea(player.position)
break
case 2:
switch (message_parts[1]) {
case "self":
cfg.guard.self = !cfg.guard.self
bot.chat(`ok, ${cfg.guard.self?"no longer being altruistic":"self sacrifice!"}`)
return;
case "stop":
cfg.plugins.guard.stopGuarding()
bot.chat('no longer guarding this area.')
return;
default:
break;
}
default:
bot.chat("don't know wym")
}
// entity = new BehaviorGetClosestEntity(bot, bot.nearestEntity(), a => EntityFilters().MobsOnly(a))
// if (entity) {
// bot.attack(entity, true)
// } else {
// bot.chat('no nearby entities')
// }
// // bot.attack(new BehaviorGetClosestEntity(bot, {}, EntityFilters().MobsOnly()))
break;
case "find":
switch (message_parts.length) {
case 2:
cfg.plugins.finder.findBlocks(message_parts[1])
break
default:
break;
}
break
case "lookat":
// const coords = v(message_parts.splice(1))
switch (message_parts.length) {
case 2:
case 3:
todo()
// bot.lookAt({})
break
case 1:
bot.lookAt(bot.nearestEntity().position)
break
case 4:
bot.lookAt(v(message_parts.splice(1)))
break
default:
break
}
break
case "ride":
case "mount":
bot.mount(bot.nearestEntity())
break
case "getoff":
case "unmount":
case "dismount":
bot.dismount()
break
case "go":
bot.moveVehicle(0, 10)
break
// case "use":
// bot.useOn(bot.nearestEntity())
// break;
// // TODO move all inventory related tasks into own module
// case "give":
// // switch (message_parts[1]) {
// // case "hand":
// // case "right":
// // break;
// // case "left":
// // break;
// // // case "slot":
// // default:
// // let slot = parseInt(message_parts[1])
// // if (!isNaN(slot)) {
// // } else {
// // }
// // break;
// // }
// // break;
// case "take":
// // TODO take only what's requested, then throw all the rest
// // TODO take all
// case "toss":
// case "drop":
// if (!message_parts[1]) { return false } // FIXME, works but ugly
// if (!checkItemExists(message_parts[1])) { return false }
// switch (message_parts.length) {
// case 2:
// bot.toss(mcData.blocksByName[message_parts[1]].id)
// break
// case 3:
// bot.tossStack(
// mcData.itemsByName[message_parts[1]].id,
// (err) => {
// if (err) {
// console.log(err)
// bot.chat(err)
// }
// }
// )
// break
// default:
// break
// }
// break;
case "location":
switch (message_parts[1]) {
case "add":
case "record":
break;
default:
break;
}
case "where":
case "where?":
// TODO put in /lib/location
console.log(bot.entity.position)
bot.chat(bot.entity.position.floored().toString())
break;
case "warp":
// if(message_parts[1] == "spawn")
bot.chat("/" + message)
break;
default:
if (cfg.mods.includes(username))
bot.chat("/" + message)
break;
}
}
}
const load = (config) => {
cfg = config
bot = cfg.bot
mcData = mcData(bot.version)
for (const [key, fn] of Object.entries(events)) {
events_registered.push(
bot.on(key, fn)
)
}
mcData = require('minecraft-data')(bot.version)
}
const unload = () => {
console.log("events_registered:", events_registered.length, "of", bot._eventsCount)
for (const [key, fn] of Object.entries(events)) {
bot.off(key, fn)
events_registered.shift()
}
console.log("events_registered:", bot._eventsCount)
}
module.exports = { load, unload, events_registered, command }

53
lib/plugins/finder.js Normal file
View File

@ -0,0 +1,53 @@
// const performance = require('performance-now')
const {Vec3} = require('vec3')
let mcData = require('minecraft-data')
let cfg = {}
let bot = {}
function findBlocks(name){
// const name = message.split(' ')[1]
if (mcData.blocksByName[name] === undefined) {
bot.chat(`${name} is not a block name`)
return
} else if (["ancient_debris", "diamond_ore", "emerald_ore"].includes(name)) {
return
}
const ids = [mcData.blocksByName[name].id]
// const startTime = performance.now()
const blocks = bot.findBlocks({ matching: ids, maxDistance: 128, count: 10 })
// const time = (performance.now() - startTime).toFixed(2)
// TODO check if toString() is really needed
if (blocks.length === 0){
bot.chat("not here (around 128 blocks)")
return
}
const pos = minmax(blocks).map(x => x.toString())
// bot.chat(`I found ${blocks.length} ${name} blocks in ${time} ms`)
bot.chat(`I found ${blocks.length} ${name} blocks at ${pos}`)
}
function minmax(coordsArray){
const min = coordsArray.reduce((x,y) => x.min(y))
const max = coordsArray.reduce((x,y) => x.max(y))
return [min, max]
}
const load = (config) => {
cfg = config
bot = cfg.bot
mcData = mcData(bot.version)
}
const unload = () => {
// bot.off("time", autoSleep)
}
module.exports = { load, unload, findBlocks}

147
lib/plugins/guard.js Normal file
View File

@ -0,0 +1,147 @@
const mineflayer = require('mineflayer')
const { pathfinder, Movements, goals } = require('mineflayer-pathfinder')
const pvp = require('mineflayer-pvp').plugin
let cfg = {}
let bot = {}
let guardPos
const filterallMobs = e => e.type === 'mob'
// const filterPlayers = e => e.type === 'player' && e.username !== 'Applezaus'
// const filterPassiveMobs = e => e.kind === 'Passive mobs'
const filterCreeper = e => e.mobType === 'Creeper'
const filterHostileMobs = e => e.kind === 'Hostile mobs'
// // not needed if detecting by kind
// && e.mobType !== 'Armor Stand' // Mojang classifies armor stands as mobs for some reason?
// Assign the given location to be guarded
function guardArea(pos) {
if (pos) {
cfg.guard.pos = guardPos = pos
// Check for new enemies to attack
// bot.off('physicTick', lookForMobs)
bot.off('time', lookForMobs)
bot.on('time', lookForMobs)
}
// We are not currently in combat, move to the guard pos
if (!bot.pvp.target && guardPos) {
moveToGuardPos()
}
}
// Cancel all pathfinder and combat
function stopGuarding() {
guardPos = cfg.guard.pos = null
bot.pvp.stop()
bot.pathfinder.setGoal(null)
bot.off('time', lookForMobs)
// bot.off('physicTick', lookForMobs)
}
// Pathfinder to the guard position
function moveToGuardPos() {
const mcData = require('minecraft-data')(bot.version)
bot.pathfinder.setMovements(new Movements(bot, mcData))
bot.pathfinder.setGoal(new goals.GoalBlock(guardPos.x, guardPos.y, guardPos.z))
}
// Called when the bot has killed it's target.
// () => {
// if (guardPos) {
// moveToGuardPos()
// }
// })
function lookForMobs() {
if (!guardPos) return // Do nothing if bot is not guarding anything
// Only look for mobs within 16 blocks
const filter = e => e.position.distanceTo(bot.entity.position) < 16 && filterHostileMobs(e)
const entityEnemy = bot.nearestEntity(filter)
if (entityEnemy) {
if(filterCreeper(entityEnemy))
// Start attacking
// bot.off('time', lookForMobs)
// bot.on('physicTick', lookForMobs)
bot.pvp.attack(entityEnemy)
}
}
function guardSelf(entity) {
if (!cfg.guard.self || entity !== bot.entity || guardPos) return // Do nothing
// bot.chat("")
bot.chat((
() => { const a = ["ouch!", "oww!", "help", "", "", ""]; return a[Math.floor(Math.random() * a.length)] }
)())
console.info(bot.nearestEntity(filterallMobs))
// Only look for mobs within 10 blocks
const filter = e => e.position.distanceTo(bot.entity.position) < 10 && filterHostileMobs(e)
const entityEnemy = bot.nearestEntity(filter)
if (entityEnemy && !filterCreeper(entityEnemy)) {
// Start attacking
// bot.off('time', lookForMobs)
// bot.on('physicTick', lookForMobs)
bot.pvp.attack(entityEnemy)
}
}
// bot.on('physicTick', lookForMobs)
// Listen for player commands
// bot.on('chat', (username, message) => {
// // Guard the location the player is standing
// if (message === 'guard') {
// const player = bot.players[username]
// if (!player) {
// bot.chat("I can't see you.")
// return
// }
// bot.chat('I will guard that location.')
// guardArea(player.entity.position)
// }
// // Stop guarding
// if (message === 'stop') {
// bot.chat('I will no longer guard this area.')
// stopGuarding()
// }
// })
const load = (config) => {
cfg = config
bot = cfg.bot
cfg.guard = {
pos: null,
auto: true,
self: true,
}
guardPos = cfg.guard.pos
bot.loadPlugin(pathfinder)
bot.loadPlugin(pvp)
bot.on('stoppedAttacking', guardArea)
bot.on('entityHurt', guardSelf)
// bot.on("time", guardArea)
}
const unload = () => {
stopGuarding()
bot.off('time', lookForMobs)
// bot.off('physicTick', lookForMobs)
bot.off('stoppedAttacking', guardArea)
bot.off('entityHurt', guardSelf)
// bot.off("time", guardArea)
}
module.exports = { load, unload, guardArea, guardSelf, stopGuarding }

190
lib/plugins/inventory.js Normal file
View File

@ -0,0 +1,190 @@
/*
* Using the inventory is one of the first things you learn in Minecraft,
* now it's time to teach your bot the same skill.
*
* Command your bot with chat messages and make him toss, equip, use items
* and even craft new items using the built-in recipe book.
*
* To learn more about the recipe system and how crafting works
* remember to read the API documentation!
*/
const mineflayer = require('mineflayer')
// if (process.argv.length < 4 || process.argv.length > 6) {
// console.log('Usage : node inventory.js <host> <port> [<name>] [<password>]')
// process.exit(1)
// }
// const bot = mineflayer.createBot({
// host: process.argv[2],
// port: parseInt(process.argv[3]),
// username: process.argv[4] ? process.argv[4] : 'inventory',
// password: process.argv[5]
// })
let cfg = {}
let bot = {}
// let mcd
function inventory(username, message) {
if (username === bot.username) return
const command = message.split(' ')
switch (true) {
// case message === 'loaded':
// bot.waitForChunksToLoad(() => {
// bot.chat('Ready!')
// })
// break
case /^list$/.test(message):
sayItems()
break
case /^toss \d+ \w+$/.test(message):
// toss amount name
// ex: toss 64 diamond
tossItem(command[2], command[1])
break
case /^toss \w+$/.test(message):
// toss name
// ex: toss diamond
tossItem(command[1])
break
case /^equip \w+ \w+$/.test(message):
// equip destination name
// ex: equip hand diamond
equipItem(command[2], command[1], quiet = cfg.quiet)
break
case /^unequip \w+$/.test(message):
// unequip testination
// ex: unequip hand
unequipItem(command[1])
break
case /^use$/.test(message):
useEquippedItem()
break
case /^craft \d+ \w+$/.test(message):
// craft amount item
// ex: craft 64 stick
craftItem(command[2], command[1])
break
}
}
function sayItems(items = bot.inventory.items()) {
const output = items.map(itemToString).join(', ')
if (output) {
console.info("inventory:", output)
!cfg.quiet && bot.chat(output)
} else {
!cfg.quiet && bot.chat('empty')
}
}
function tossItem(name, amount) {
amount = parseInt(amount, 10)
const item = itemByName(name)
if (!item) {
bot.chat(`I have no ${name}`)
} else if (amount) {
bot.toss(item.type, null, amount, checkIfTossed)
} else {
bot.tossStack(item, checkIfTossed)
}
function checkIfTossed(err) {
if (err) {
bot.chat(`unable to toss: ${err.message}`)
} else if (amount) {
bot.chat(`tossed ${amount} x ${name}`)
} else {
bot.chat(`tossed ${name}`)
}
}
}
function equipItem(name, destination, quiet = false) {
const item = itemByName(name)
if (item) {
bot.equip(item, destination, checkIfEquipped)
} else {
!quiet && bot.chat(`I have no ${name}`)
}
function checkIfEquipped(err) {
if (err) {
bot.chat(`cannot equip ${name}: ${err.message}`)
} else {
!quiet && bot.chat(`equipped ${name}`)
}
}
}
function unequipItem(destination) {
bot.unequip(destination, (err) => {
if (err) {
bot.chat(`cannot unequip: ${err.message}`)
} else {
bot.chat('unequipped')
}
})
}
function useEquippedItem() {
bot.chat('activating item')
bot.activateItem()
}
function craftItem(name, amount) {
amount = parseInt(amount, 10)
const item = require('minecraft-data')(bot.version).findItemOrBlockByName(name)
const craftingTable = bot.findBlock({
matching: 58
})
if (item) {
const recipe = bot.recipesFor(item.id, null, 1, craftingTable)[0]
if (recipe) {
bot.chat(`I can make ${name}`)
bot.craft(recipe, amount, craftingTable, (err) => {
if (err) {
bot.chat(`error making ${name}`)
} else {
bot.chat(`did the recipe for ${name} ${amount} times`)
}
})
} else {
bot.chat(`I cannot make ${name}`)
}
} else {
bot.chat(`unknown item: ${name}`)
}
}
function itemToString(item) {
if (item) {
return `${item.name} x ${item.count}`
} else {
return '(nothing)'
}
}
function itemByName(name) {
return bot.inventory.items().filter(item => item.name === name)[0]
}
const load = (config) => {
cfg = config
bot = cfg.bot
// cfg.inventory = {
// auto: true,
// quiet: false
// }
bot.on('chat', inventory)
}
const unload = () => {
bot.off('chat', inventory)
}
module.exports = { load, unload, equipItem }

190
lib/plugins/miner.js Normal file
View File

@ -0,0 +1,190 @@
const mineflayer = require('mineflayer')
const pathfinder = require('mineflayer-pathfinder').pathfinder
const {
gameplay,
MoveTo,
BreakBlock,
ObtainItems,
ObtainItem,
GiveTo,
Craft
} = require('prismarine-gameplay')
// const { Gameplay } = require('prismarine-gameplay/lib/gameplay')
// const { Vec3 } = require('vec3')
let mcData = require('minecraft-data')
let cfg = {}
let bot = {}
// if (process.argv.length < 4 || process.argv.length > 6) {
// console.log('Usage : node miner.js <host> <port> [<name>] [<password>]')
// process.exit(1)
// }
// const bot = mineflayer.createBot({
// host: process.argv[2],
// port: parseInt(process.argv[3]),
// username: process.argv[4] ? process.argv[4] : 'collect_items',
// // password: process.argv[5]
// })
// bot.on('spawn', () => bot.gameplay.debugText = true)
// bot.on('chat', (username, message) => mine(username, message))
function checkBlockExists(name){
if (mcData.blocksByName[name] === undefined) {
bot.chat(`${name} is not a block name`)
return false
} else {
return true
}
}
function miner(username, message) {
const player = bot.players[username] ? bot.players[username].entity : null
const command = message.split(' ')
switch (true) {
// case /^echo .*/.test(message):
// bot.chat(command.slice(1).join(" "))
// break
// case /^zz+/.test(message):
// bot.chat("/afk")
// break
case /^debug$/.test(message):
bot.gameplay.debugText = !!!bot.gameplay.debugText
break
case /^moveto -?[0-9]+ -?[0-9]+$/.test(message):
bot.gameplay.solveFor(
new MoveTo({
x: parseInt(command[1]),
z: parseInt(command[2])
}), logError)
break
case /^moveto -?[0-9]+ -?[0-9]+ -?[0-9]+$/.test(message):
bot.gameplay.solveFor(
new MoveTo({
x: parseInt(command[1]),
y: parseInt(command[2]),
z: parseInt(command[3])
}), logError)
break
case /^moveto \w+$/.test(message):
const player2 = bot.players[command[1]] ? bot.players[command[1]].entity : null
if (!player2) {
bot.chat(`can't see ${command[1]}..`)
} else {
bot.gameplay.solveFor(
new MoveTo({
x: player2.position.x,
y: player2.position.y,
z: player2.position.z
})
)
}
break
case /^comehere$/.test(message):
if (!player) {
bot.chat("can't see you..")
} else {
bot.gameplay.solveFor(
new MoveTo({
x: player.position.x,
y: player.position.y,
z: player.position.z
}), logError)
}
break
case /^break -?[0-9]+ -?[0-9]+ -?[0-9]+$/.test(message):
bot.gameplay.solveFor(
new BreakBlock({
position: new Vec3(
parseInt(command[1]),
parseInt(command[2]),
parseInt(command[3])
)
}), logError)
break
case /^collect [0-9]+ [a-zA-Z_]+$/.test(message):
if(!checkBlockExists(command[2])) {return false}
bot.gameplay.solveFor(
new ObtainItems({
itemType: command[2],
count: parseInt(command[1])
}), logError)
break
case /^collect [a-zA-Z_]+$/.test(message):
if(!checkBlockExists(command[2])) {return false}
bot.gameplay.solveFor(
new ObtainItem({
itemType: command[1]
}), logError)
break
case /^bringme [0-9]+ [a-zA-Z_]+$/.test(message):
if(!checkBlockExists(command[2])) {return false}
bot.gameplay.solveFor(
new GiveTo({
itemType: command[2],
count: parseInt(command[1]),
entity: player
}), logError)
break
case /^craft [0-9]+ [a-zA-Z_]+$/.test(message):
if(!checkBlockExists(command[2])) {return false}
bot.gameplay.solveFor(
new Craft({
itemType: command[2],
count: parseInt(command[1]),
entity: player
}), logError)
break
case /^stop$/.test(message):
bot.chat("♪♪ can't stop me now!! ♪♪")
// player = bot.player.entity
if (player) {
bot.gameplay.solveFor(
new MoveTo({
x: player.position.x,
y: player.position.y,
z: player.position.z
}), logError)
}
break
}
}
function logError(err) {
if (err)
bot.chat(`Failed task: ${err.message}`)
}
const load = (config) => {
cfg = config
bot = cfg.bot
mcData = mcData(bot.version)
bot.loadPlugin(pathfinder)
bot.loadPlugin(gameplay)
bot.on("chat", miner)
}
const unload = () => {
// bot.gameplay
bot.off("chat", miner)
}
module.exports = { load, unload }

115
lib/plugins/sleeper.js Normal file
View File

@ -0,0 +1,115 @@
const pathfinder = require('mineflayer-pathfinder').pathfinder
const { Bot } = require('mineflayer')
const {
gameplay,
MoveTo,
MoveToInteract,
ObtainItem,
// Craft
} = require('prismarine-gameplay')
let cfg = {}
let bot = {}
let inv
// cfg.autosleep = false
function sleep(quiet) {
quiet = quiet !== undefined ? quiet : cfg.sleep.quiet
if(bot.game.dimension !== "minecraft:overworld" || cfg.sleep.force){
!quiet && bot.chat("can't sleep, not in overworld now")
return
}
let bed = bot.findBlock({
matching: block => bot.isABed(block)
})
let bedstatus = bed && bot.parseBedMetadata(bed).occupied ? "n unoccupied" : ""
if(bed && bedstatus == "n unoccupied"){
bot.lookAt(bed.position)
bed = bot.findBlock({
matching: block => bot.isABed(block) && !bot.parseBedMetadata(block).occupied
}) || bed
bedstatus = bot.parseBedMetadata(bed).occupied ? "n unoccupied" : ""
}
if (bed && bedstatus == "") {
bot.lookAt(bed.position)
// const nearbed =
bot.gameplay.solveFor(
new MoveTo((bed.position.range = 2) && bed.position), (err) => {
// new MoveTo(bed.position), (err) => {
// new MoveToInteract(bed.position), (err) => {
if (err) {
!quiet && bot.chat(`can't reach bed: ${err.message}`)
} else {
bot.waitForChunksToLoad(() => {
bot.sleep(bed, (err) => {
if (err) {
!quiet && bot.chat(`can't sleep: ${err.message}`)
} else {
!quiet && bot.chat("zzz")
console.log("sleeping? ", bot.isSleeping)
// hack until this is fixed
// bot.isSleeping = bot.isSleeping ? bot.isSleeping : true
bot.isSleeping = true
}
})
})
}
})
// } else if (bed){
} else if (inv && inv.equipItem("red_bed", "hand", true)) {
// doesn't work fortunately
// FIXME: DONT IMPLEMENT until it is detected as NOT NETHER
bot.placeBlock()
} else {
bot.gameplay.solveFor(
new ObtainItem("bed"), (err) => {
if (err) {
!quiet && bot.chat(`need a${bedstatus} bed: may not see if just placed`)
}
}
)
// bot.chat('/afk')
}
}
function wake() {
bot.wake((err) => {
if (err) {
bot.chat(`can't wake up: ${err.message}`)
} else {
bot.chat('woke up')
}
})
}
function autoSleep() {
if (!bot.time.isDay && !cfg.sleep.timeoutFn && cfg.sleep.auto && !bot.isSleeping) {
sleep()
cfg.sleep.timeoutFn = setTimeout(() => { cfg.sleep.timeoutFn = null }, cfg.sleep.timeout) // give 2 seconds for multiple events
console.log("sleeping?", bot.isSleeping, bot.time)
}
}
const load = (config) => {
cfg = config
bot = cfg.bot
cfg.sleep = {
auto: true,
// timeout: 30 * 1000,
timeout: 2 * 60 * 1000,
quiet: false
}
bot.loadPlugin(pathfinder)
bot.loadPlugin(gameplay)
inv = cfg.plugins["inventory"]
bot.on("time", autoSleep)
}
const unload = () => {
bot.off("time", autoSleep)
}
module.exports = { load, unload, sleep, wake }

115
lib/plugins/statemachine.js Normal file
View File

@ -0,0 +1,115 @@
// Load your dependency plugins.
const {pathfinder} = require('mineflayer-pathfinder')
// bot.loadPlugin(require('prismarine-viewer').mineflayer)
// const mineflayerViewer = require('prismarine-viewer').mineflayer
// Import required behaviors.
const {
StateTransition,
BotStateMachine,
EntityFilters,
BehaviorFollowEntity,
BehaviorLookAtEntity,
BehaviorGetClosestEntity,
NestedStateMachine,
BehaviorIdle,
StateMachineWebserver,
} = require("mineflayer-statemachine");
// TODO chat
// wait for our bot to login.
function statemachineInit() {
cfg.botAddress = new RegExp(`^${bot.username} (!.+)`)
// This targets object is used to pass data between different states. It can be left empty.
const targets = new Object();
// Create our states
const getClosestPlayer = new BehaviorGetClosestEntity(
bot, targets, entity => EntityFilters().PlayersOnly(entity) && bot.entity.position.distanceTo(entity.position) <= 50 ); // && a.username !== bot.username);
const followPlayer = new BehaviorFollowEntity(bot, targets);
const lookAtPlayer = new BehaviorLookAtEntity(bot, targets);
const stay = new BehaviorIdle();
// Create our transitions
const transitions = [
// We want to start following the player immediately after finding them.
// Since getClosestPlayer finishes instantly, shouldTransition() should always return true.
new StateTransition({
parent: getClosestPlayer,
child: followPlayer,
onTransition: (quiet) => quiet || bot.chat(`Hi ${targets.entity.username}!`),
shouldTransition: () => bot.entity.position.distanceTo(targets.entity.position) <= 50,
// shouldTransition: () => getClosestPlayer.distanceToTarget() < 100 || console.info("player too far!") && false,
}),
// If the distance to the player is less than two blocks, switch from the followPlayer
// state to the lookAtPlayer state.
new StateTransition({
parent: followPlayer,
child: lookAtPlayer,
// onTransition: () => console.log(targets),
shouldTransition: () => followPlayer.distanceToTarget() < 2,
}),
// If the distance to the player is more than two blocks, switch from the lookAtPlayer
// state to the followPlayer state.
new StateTransition({
parent: lookAtPlayer,
child: followPlayer,
shouldTransition: () => lookAtPlayer.distanceToTarget() >= 5,
}),
new StateTransition({
parent: lookAtPlayer,
child: stay,
onTransition: () => bot.chat("ok, staying"),
// shouldTransition: () => true,
}),
new StateTransition({
parent: stay,
child: getClosestPlayer,
// shouldTransition: () => Math.random() > 0.01,
// shouldTransition: () => Math.random() > 0.1 && getClosestPlayer.distanceToTarget() < 2,
}),
];
// Now we just wrap our transition list in a nested state machine layer. We want the bot
// to start on the getClosestPlayer state, so we'll specify that here.
const rootLayer = new NestedStateMachine(transitions, getClosestPlayer, stay);
// We can start our state machine simply by creating a new instance.
cfg.stateMachines.follow = new BotStateMachine(bot, rootLayer);
const webserver = new StateMachineWebserver(bot, cfg.stateMachines.follow);
webserver.startServer();
// mineflayerViewer(bot, { port: 3000 })
// const path = [bot.entity.position.clone()]
// bot.on('move', () => {
// if (path[path.length - 1].distanceTo(bot.entity.position) > 1) {
// path.push(bot.entity.position.clone())
// bot.viewer.drawLine('path', path)
// }
// })
}
const load = (config) => {
cfg = config
bot = cfg.bot
// cfg.inventory = {
// auto: true,
// quiet: false
// }
// bot.on('chat', inventory)
bot.loadPlugin(pathfinder)
// statemachineInit()
}
const unload = () => {
// bot.off('chat', inventory)
}
module.exports = { load, unload }

56
package.json Normal file
View File

@ -0,0 +1,56 @@
{
"name": "angram-bot",
"version": "0.1.0",
"description": "A high-level, general purpose and modular minecraft bot using hot re-loadable (without restarting the bot!) plugins. Batteries included, launch to run!",
"main": "index.js",
"scripts": {
"test": "jest --verbose",
"pretest": "pnpm run lint && require-self",
"prepare": "pnpm install require-self && require-self",
"lint": "standard",
"fix": "standard --fix",
"dev": "node ./lib/index.js localhost",
"prod": "node ./lib/index.js games.protospace.ca"
},
"repository": {
"type": "git",
"url": "git+https://github.com/PrismarineJS/prismarine-template.git"
},
"keywords": [
"prismarine",
"template",
"minecraft",
"mineflayer",
"ai"
],
"author": "Jay",
"license": "MIT",
"bugs": {
"url": "https://github.com/PrismarineJS/prismarine-template/issues"
},
"homepage": "https://github.com/PrismarineJS/prismarine-template#readme",
"devDependencies": {
"jest": "^26.6.3",
"require-self": "^0.2.3"
},
"dependencies": {
"minecraft-data": "^2.70.2",
"mineflayer": "^2.34.0",
"mineflayer-armor-manager": "^1.3",
"mineflayer-pathfinder": "^1.1",
"mineflayer-pvp": "^1",
"prismarine-block": "^1",
"prismarine-chat": "^1",
"prismarine-entity": "^1.1.0",
"prismarine-gameplay": "github:TheDudeFromCI/prismarine-gameplay#crafting",
"prismarine-item": "^1.5.0",
"prismarine-nbt": "^1.3",
"prismarine-recipe": "^1",
"typescript": "^4",
"vec3": "^0.1",
"dotenv-packed": "^1.2"
},
"files": [
"lib/**/*"
]
}