Compare commits

...

14 Commits

Author SHA1 Message Date
jay
bf45a53f08 refactor: remove gameplay
temporarily remove gameplay until it is fixed and more stable

miner plugin won't work now
2020-12-24 10:40:46 +05:00
jay
a7ccb08d43 refactor: remove sleeper's prismarine-gameplay dependence 2020-12-24 10:35:01 +05:00
jay
8e4eb7748f style: fix crlf to lf 2020-12-24 09:33:08 +05:00
jay
47a944fe2a fix: 🐛 compat: don't use ?. for compat with older node.js 2020-12-24 09:19:32 +05:00
jay
1a3c345017 chore: 🙈 ignore .env 2020-12-24 09:16:11 +05:00
jay
de0af4d2ac feat: 🚀 add and use dotenv-packed
Use dotenv for more convenient credential management
2020-12-24 08:52:50 +05:00
jay
73d5f43ad3 feat: actually enable mover and eater plugins 2020-12-23 13:28:34 +05:00
jay
b970231519 feat: add mover plugin
General purpose mover / goto plugin.
Replaces the old solver based `comehere` in miner.js.
Instead, directly based on mineflayer bot apis and pathfinder.
This makes it simpler and easier to debug.
While less general, pathfinder is sophisticated enough for most cases.

For anything that needs moving from point A to point B.
Such as:
- following
- go to a location

Not in scope: locations and places. Would be a separate plugin.
2020-12-22 15:38:45 +05:00
jay
787d08dd31 feat: add automatic eater plugin 2020-12-22 15:29:06 +05:00
jay
f8df1fa319 feat: update command plugin 2020-12-22 11:31:46 +05:00
jay
8eb6d790b0 feat: update sleeper 2020-12-22 11:31:06 +05:00
jay
5ead6b7267 feat: update miner 2020-12-22 11:30:16 +05:00
jay
149a01551c feat: update inventory 2020-12-22 11:29:31 +05:00
jay
b1f27f455d feat: update guard 2020-12-22 11:28:44 +05:00
11 changed files with 879 additions and 368 deletions

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@
# misc # misc
.DS_Store .DS_Store
.env
.env.local .env.local
.env.development.local .env.development.local
.env.test.local .env.test.local

View File

@@ -1,4 +1,4 @@
// TODO reload const env = require("dotenv-packed").parseEnv().parsed
const fs = require('fs'); const fs = require('fs');
let cfg = { let cfg = {
admin: "Applezaus", admin: "Applezaus",
@@ -12,14 +12,14 @@ const mineflayer = require("mineflayer");
const bot = const bot =
!isNaN(parseInt(process.argv[3])) && parseInt(process.argv[3]) > 1e2 ? !isNaN(parseInt(process.argv[3])) && parseInt(process.argv[3]) > 1e2 ?
mineflayer.createBot({ mineflayer.createBot({
host: process.argv[2] || process.env.MINECRAFT_HOST || 'localhost', // Change this to the ip you want. host: process.argv[2] || process.env.MINECRAFT_HOST || env.MINECRAFT_HOST || 'localhost', // Change this to the ip you want.
port: parseInt(process.argv[3]) || process.env.MINECRAFT_PORT // || 58471, port: parseInt(process.argv[3]) || process.env.MINECRAFT_PORT || env.MINECRAFT_PORT // || 58471,
}) })
: :
mineflayer.createBot({ mineflayer.createBot({
host: process.argv[2] || process.env.MINECRAFT_HOST || 'localhost', // Change this to the ip you want. host: process.argv[2] || process.env.MINECRAFT_HOST || env.MINECRAFT_HOST || 'localhost', // Change this to the ip you want.
username: process.argv[3] || process.env.MINECRAFT_USER, username: process.argv[3] || process.env.MINECRAFT_USER || env.MINECRAFT_USER,
password: process.argv[4] || process.env.MINECRAFT_PASS, password: process.argv[4] || process.env.MINECRAFT_PASS || env.MINECRAFT_PASS,
// port: process.argv[5] || process.env.MINECRAFT_PORT || 58471, // port: process.argv[5] || process.env.MINECRAFT_PORT || 58471,
}) })
@@ -29,7 +29,7 @@ let plugins = {}
function loadplugin(pluginname, pluginpath) { function loadplugin(pluginname, pluginpath) {
try { try {
plugins[pluginname] = require(pluginpath) plugins[pluginname] = require(pluginpath)
plugins[pluginname]?.load(cfg) plugins[pluginname].load(cfg)
} catch (error) { } catch (error) {
if (error.code == 'MODULE_NOT_FOUND') { if (error.code == 'MODULE_NOT_FOUND') {
console.warn('plugin not used:', pluginpath) console.warn('plugin not used:', pluginpath)
@@ -44,7 +44,7 @@ function unloadplugin(pluginname, pluginpath) {
const plugin = require.resolve(pluginpath) const plugin = require.resolve(pluginpath)
try { try {
if (plugin && require.cache[plugin]) { if (plugin && require.cache[plugin]) {
require.cache[plugin].exports?.unload() require.cache[plugin].exports.unload()
delete plugins[pluginname] delete plugins[pluginname]
delete require.cache[plugin] delete require.cache[plugin]
} }
@@ -94,12 +94,12 @@ bot.once("spawn", () => {
command: require('./plugins/command'), command: require('./plugins/command'),
sleeper: require('./plugins/sleeper'), sleeper: require('./plugins/sleeper'),
armor: require('./plugins/armor'), armor: require('./plugins/armor'),
// mover: require('./plugins/mover'), mover: require('./plugins/mover'),
guard: require('./plugins/guard'), guard: require('./plugins/guard'),
inventory: require('./plugins/inventory'), inventory: require('./plugins/inventory'),
// eater: require('./plugins/eater'), eater: require('./plugins/eater'),
finder: require('./plugins/finder'), finder: require('./plugins/finder'),
miner: require('./plugins/miner'), // miner: require('./plugins/miner.js'),
// statemachine: require('./plugins/statemachine'), // statemachine: require('./plugins/statemachine'),
} }

View File

@@ -6,7 +6,7 @@ let bot = {}
// bot.chatAddPattern(new RegExp(`^hi|hello ${bot.username}`, "i"), 'greet', "General greeting") // bot.chatAddPattern(new RegExp(`^hi|hello ${bot.username}`, "i"), 'greet', "General greeting")
function todo() { function todo() {
bot.chat("not implemented yet") cfg.quiet && console.warn("not implemented") || bot.chat("not implemented yet")
} }
function checkBlockExists(name) { function checkBlockExists(name) {
@@ -34,7 +34,12 @@ const events = {
if (username === cfg.admin) { if (username === cfg.admin) {
message = message.replace("\\", "@") message = message.replace("\\", "@")
console.info("whispered command", message) console.info("whispered command", message)
bot.chat("/" + message) if (/^!/.test(message)) {
command(username, message)
} else {
bot.chat(message)
// bot.chat("/" + message)
}
} else { } else {
bot.whisper(cfg.admin, `gossip ${username}: ${message}`) bot.whisper(cfg.admin, `gossip ${username}: ${message}`)
console.info(username, "whispered", message) console.info(username, "whispered", message)
@@ -47,6 +52,19 @@ const events = {
const events_registered = [] const events_registered = []
function command(username, message) { function command(username, message) {
function fuzzyRespond(responses, probability = 1, timeout = 1) {
if (Math.random() < probability) {
const response = responses[Math.floor(Math.random() * responses.length)]
return setTimeout(() => bot.chat(response), timeout * Math.random() * 1000)
}
}
function swingArm(swung = 3) {
if (swung > 0) {
setTimeout(swingArm, 500 * Math.random(), --swung)
bot.swingArm()
}
}
if (username === bot.username && !message.startsWith("!")) return if (username === bot.username && !message.startsWith("!")) return
@@ -59,240 +77,457 @@ function command(username, message) {
return 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)) { if (message.startsWith("!") || cfg.botAddress.test(message)) {
message = cfg.botAddress.test(message) ? cfg.botAddress.exec(message)[1] : message message = cfg.botAddress.test(message) ? cfg.botAddress.exec(message)[1] : message
console.log(message) console.log(message)
message = message.slice(1) message = message.slice(1) // remove `!`
message_parts = message.split(/\s+/) // TODO command dispatchEvent, for aliases
switch (message_parts[0]) { function subcommand(message) {
// case "follow": const message_parts = message.split(/\s+/)
// // if(username === cfg.admin)
// cfg.stateMachines.follow.transitions[4].trigger() switch (message_parts[0]) {
// // cfg.stateMachines.follow.transitions[1].trigger() case "stop":
// break; bot.pathfinder && bot.pathfinder.setGoal(null)
// case "stay": bot.stopDigging()
// // if(username === cfg.admin) bot.chat("ok")
// cfg.stateMachines.follow.transitions[3].trigger() break;
// break;
case "echo": case "mute":
// bot.chat(message_parts[1]) case "quiet":
// bot.chat(message.slice(1)) case "silent":
bot.chat(message) cfg.quiet = !cfg.quiet
break; cfg.quiet || bot.chat(`ok, ${cfg.quiet ? "" : "not "}being ${message_parts[0]}`)
case "autosleep": break;
case "sleep": // case "follow":
message_parts[1] = message_parts[0] == "autosleep" ? "auto" : message_parts[1] // // if(username === cfg.admin)
switch (message_parts[1]) { // cfg.stateMachines.follow.transitions[4].trigger()
case "auto": // // cfg.stateMachines.follow.transitions[1].trigger()
cfg.sleep.auto = !cfg.sleep.auto // break;
bot.chat(`ok, ${cfg.sleep.auto ? "" : "not "}auto sleeping `) // case "stay":
bot.chat("/afk") // // if(username === cfg.admin)
break; // cfg.stateMachines.follow.transitions[3].trigger()
case "silent": // break;
case "quiet": case "echo":
cfg.sleep.quiet = !cfg.sleep.quiet // bot.chat(message_parts[1])
bot.chat(`ok, ${cfg.sleep.quiet ? "" : "not "}sleeping quietly`) // bot.chat(message.slice(1))
break; cfg.quiet && bot.whisper(username, message) || bot.chat(message)
case "timeout": break;
if (!message_parts[2]) { case "autosleep":
bot.chat(`sleeping every ${Math.round(cfg.sleep.timeout / 60 / 1000)}mins`) case "sleep":
return message_parts[1] = message_parts[0] == "autosleep" ? "auto" : message_parts[1]
} switch (message_parts[1]) {
const timeout = parseFloat(message_parts[2], 10) case "auto":
if (timeout) { cfg.sleep.auto = !cfg.sleep.auto
cfg.sleep.timeout = timeout * 60 * 1000 bot.chat(`ok, ${cfg.sleep.auto ? "" : "not "}auto sleeping`)
cfg.sleep.timeoutFn = null bot.chat("/afk")
cfg.plugins.sleeper.sleep(true) //reset timer
bot.chat(`ok, next sleep attempt in ${timeout}mins`)
break; break;
} case "silent":
default: case "quiet":
bot.chat(`usage: !sleep [auto | quiet | timeout <mins>], or zzz for manual sleep`) cfg.sleep.quiet = !cfg.sleep.quiet
break; bot.chat(`ok, ${cfg.sleep.quiet ? "" : "not "}sleeping quietly`)
} break;
break; //todo; needed? case "timeout":
case "zzz": if (!message_parts[2]) {
cfg.plugins.sleeper.sleep() bot.chat(`sleeping every ${Math.round(cfg.sleep.timeout / 60 / 1000)}mins`)
case "afk": return
bot.chat("/afk") }
break; const timeout = parseFloat(message_parts[2], 10)
case "awaken": if (timeout) {
case "wake": cfg.sleep.timeout = timeout * 60 * 1000
case "wakeup": cfg.sleep.timeoutFn = null
cfg.plugins.sleeper.wake() cfg.plugins.sleeper.sleep(true) //reset timer
break; bot.chat(`ok, next sleep attempt in ${timeout}mins`)
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; break;
} }
default: default:
bot.chat("don't know wym") bot.chat(`usage: !sleep [auto | quiet | timeout <mins>], or zzz for manual sleep`)
} break;
// entity = new BehaviorGetClosestEntity(bot, bot.nearestEntity(), a => EntityFilters().MobsOnly(a)) }
// if (entity) { break; //todo; needed?
// bot.attack(entity, true) case "zzz":
// } else { cfg.plugins.sleeper.sleep()
// bot.chat('no nearby entities') case "afk":
bot.chat("/afk")
break;
case "awaken":
case "wake":
case "wakeup":
cfg.plugins.sleeper.wake()
break;
case "autoeat":
case "eat":
switch (message_parts[1]) {
case "auto":
cfg.eat.auto = !cfg.eat.auto
bot.chat(`ok, ${cfg.eat.auto ? "" : "not "}auto eating `)
break;
case "at":
case "set":
const amount = parseInt(message_parts[2], 10)
if (amount < 20 && amount > 0) {
cfg.eat.startAt = amount
cfg.eat.quiet || bot.chat(`ok, eating when hunger at ${amount}`)
}
break
case "silent":
case "quiet":
cfg.eat.quiet = !!!cfg.eat.quiet
break;
default:
cfg.plugins.eater.eat((err) => { if (err) { bot.chat(err.message) } })
// bot.chat(`usage: !sleep [auto | quiet | timeout <mins>], or zzz for manual sleep`)
break;
}
break;
case "follow":
subcommand("go follow me")
break;
case "come":
subcommand("go follow once")
break;
case "move":
case "go":
// TODO move most of the subcommands into mover.js?
const message_parts2 = message_parts.slice(2)
switch (message_parts[1]) {
case "init":
cfg.plugins.mover.initMoves()
break
case "near":
// message_parts2 = message_parts.slice(2)
switch (message_parts2.length) {
case 0:
cfg.plugins.mover.moveNear(bot.nearestEntity().position)
break
case 1:
switch (message_parts2[0]) {
case "me":
if (player) {
cfg.plugins.mover.moveNear(player.position)
} else {
cfg.quiet || bot.chat("can't see you")
}
break;
default:
const aPlayer = bot.players[message_parts[2]] ? bot.players[message_parts[2]].entity : null
if (aPlayer) {
cfg.plugins.mover.moveNear(aPlayer.position)
} else {
cfg.quiet || bot.chat(`can't see ${message_parts[2]}`)
}
break;
}
break
case 2:
todo()
// bot.lookAt({}) goalxz?
break
case 3:
//TODO more checks
cfg.plugins.mover.moveNear(message_parts2)
break
default:
break
}
break
case "follow":
// message_parts2 = message_parts.slice(2)
switch (message_parts2.length) {
case 0:
cfg.plugins.mover.follow(bot.nearestEntity())
break
case 1:
switch (message_parts2[0]) {
case "me":
case "once":
if (player) {
cfg.plugins.mover.follow(player, message_parts2[0] !== "once")
} else {
cfg.quiet || bot.chat("can't see you")
}
break;
default:
const aPlayer = bot.players[message_parts[2]] ? bot.players[message_parts[2]].entity : null
if (aPlayer) {
cfg.plugins.mover.follow(aPlayer)
} else {
cfg.quiet || bot.chat(`can't see ${message_parts[2]}`)
}
break;
}
break
// case 2:
// bot.lookAt({}) goalxz?
// break
// case 3:
//TODO more checks
// cfg.plugins.mover.moveNear(message_parts2)
// break
default:
todo()
break
}
break
case "stop":
cfg.plugins.mover.stop()
break
default:
return todo()
break;
}
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 1:
bot.lookAt(bot.nearestEntity().position)
break
case 2:
switch (message_parts[1]) {
case "me":
if (player) {
bot.lookAt((new v.Vec3(0, 1, 0)).add(player.position))
} else {
cfg.quiet || bot.chat("can't see you")
}
break;
case "this":
// TODO lookat the block the user is looking at
if (player) {
bot.lookAt((new v.Vec3(0, 1, 0)).add(player.position))
todo()
} else {
cfg.quiet || bot.chat("can't see you")
}
break;
break
default:
const aPlayer = bot.players[message_parts[2]] ? bot.players[message_parts[2]].entity : null
if (aPlayer) bot.lookAt((new v.Vec3(0, 1, 0)).add(aPlayer.position))
break;
}
break
case 3:
todo()
// bot.lookAt({})
break
case 4:
//TODO more checks
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 inventory.js
case "craft":
cfg.plugins.inventory.craftItem(message_parts[1])
break
// 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
// } // }
// // bot.attack(new BehaviorGetClosestEntity(bot, {}, EntityFilters().MobsOnly())) // break;
break; case "location":
case "find": // TODO put in /lib/location
switch (message_parts.length) { switch (message_parts[1]) {
case 2: case "add":
cfg.plugins.finder.findBlocks(message_parts[1]) case "record":
break
default: break;
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 default:
// case "give": break;
// // switch (message_parts[1]) { }
// // case "hand": case "where":
// // case "right": case "where?":
// // break; // TODO put in /lib/location
// // case "left": console.log(bot.entity.position)
// // break; if (cfg.mods.includes(username)) //anarchy
// // // case "slot": bot.chat(
// // default: bot.game.dimension.split(":", 2)[1].replace("_", " ")
// // let slot = parseInt(message_parts[1]) + " " + bot.entity.position.floored().toString()
// // if (!isNaN(slot)) { )
break;
// // } else { case "warp":
// if (message_parts[1] == "spawn")
// // } // if (cfg.mods.includes(username)) //anarchy
// // 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) bot.chat("/" + message)
break;
default:
if (cfg.mods.includes(username))
bot.chat("/" + message)
break;
}
}
subcommand(message)
} else {
// TODO, maybe extract to a new function `fuzzychat`?, so can address direct messages
switch (message) {
case "wassup":
case "what's cooking":
case "what's new":
case "what's up":
case "whats cooking":
case "whats new":
case "whats up":
case "sup":
case "suh":
fuzzyRespond([
"jus chilin", "nothin", "random stuff"
], 0.3, 3)
case "hi":
case "hey":
case "hello":
case "ola":
case "howdy":
case "heyo":
case "yo":
if (player) bot.lookAt((new v.Vec3(0, 1, 0)).add(player.position))
// TODO sneak
// function swingArm() {
// if (swung > 0) {
// setTimeout(swing, 500 * Math.random())
// bot.p()
// swung--
// }
// }
setTimeout(swingArm, 1000, 4) // or sneak greating
return
case "F":
return fuzzyRespond(["F"], 0.9, 1)
case "RIP":
return fuzzyRespond(["F", "oh no"], 0.3, 2)
case "69":
case "cool":
Math.random() < 0.5 && bot.chat("nice!")
return
case "awesome":
case "superb":
case "nice":
case "nice!":
return fuzzyRespond(["cool", "very nice!"], 0.3, 5)
case "good bot":
return fuzzyRespond(["thanks", "why, thank you!", ":)", "(:", "you're too kind!"], 1, 5)
case "bad bot":
case "not nice":
return fuzzyRespond([":(", "):", "sorry"], 0.1, 10)
default:
break; break;
} }
} }

113
lib/plugins/eater.js Normal file
View File

@@ -0,0 +1,113 @@
let cfg = {}
let bot = {}
let isEating = false
function callbackHandle(err) {
if (err) console.error(err)
}
function eat(callback) {
isEating = true
const foodNames = require('minecraft-data')(bot.version).foodsArray.map((item) => item.name)
let available_food = bot.inventory
.items()
.filter((item) => foodNames.includes(item.name))
if (available_food.length === 0 || !available_food) {
isEating = false
return callback(new Error('No food found.'))
}
if (cfg.eat.bannedFood.length > 0) {
available_food = available_food.filter(
(item) => !cfg.eat.bannedFood.includes(item.name)
)
}
let priority = cfg.eat.priority
let best_food = available_food.reduce((prev, current) => (prev[priority] > current[priority]) ? prev : current)
if (!best_food) {
isEating = false
return callback(new Error('No best food has been found.'))
}
bot.emit('eat_start')
bot.equip(best_food, 'hand', function (error) {
if (error) {
console.error(error)
isEating = false
bot.emit('eat_stop')
} else {
bot.consume(function (err) {
if (err) {
console.error(err)
isEating = false
bot.emit('eat_stop')
return callback(err)
} else {
isEating = false
bot.emit('eat_stop')
callback(null)
if (!bot.food === 20) eat(callbackHandle)
}
})
}
})
}
function checkFood() {
console.info("eater: "
// , " status: ", !isEating
, cfg.eat.auto && "auto"
, bot.food < cfg.eat.startAt && "hungry"
, "hunger:", bot.food
, "at:", cfg.eat.startAt)
if (
!isEating
&& cfg.eat.auto
&& bot.food < cfg.eat.startAt
) {
if (
(bot.pathfinder
&& !(bot.pathfinder.isMining() || bot.pathfinder.isBuilding())
// TODO implement better idle state
) || true // idle most likely
) {
eat(callbackHandle)
}
}
}
function resetEat(value) {
// to prevent the plugin from breaking if the bot gets killed while eating btw
isEating = !!value // false
}
const load = (config) => {
cfg = config
bot = cfg.bot
cfg.eat = {
priority: 'saturation', //'foodPoints', //
// startAt: 19, //anarchy
// startAt: 18,
startAt: 14,
bannedFood: [
"enchanted_golden_apple", "golden_apple", "pufferfish", "chorus_fruit"
],
auto: true
}
bot.on('health', checkFood)
bot.on('spawn', resetEat)
}
const unload = () => {
bot.off('health', checkFood)
bot.off('spawn', resetEat)
}
module.exports = { load, unload, eat, resetEat }

View File

@@ -6,10 +6,12 @@ let cfg = {}
let bot = {} let bot = {}
let guardPos let guardPos
const filterallMobs = e => e.type === 'mob' const filterAllMobs = e => e.type === 'mob'
// const filterPlayers = e => e.type === 'player' && e.username !== 'Applezaus' const filterPlayers = e => e.type === 'player'
const filterMods = e => e.type === 'player' && [cfg.mods].includes(e.username)
// const filterPassiveMobs = e => e.kind === 'Passive mobs' // const filterPassiveMobs = e => e.kind === 'Passive mobs'
const filterCreeper = e => e.mobType === 'Creeper' const filterCreeper = e => e.mobType === 'Creeper'
// const filterCreeperOrEndermen = e => ['Creeper', 'EndderMen'].includes(e.mobType) //TODO endmen hostile by default?
const filterHostileMobs = e => e.kind === 'Hostile mobs' const filterHostileMobs = e => e.kind === 'Hostile mobs'
// // not needed if detecting by kind // // not needed if detecting by kind
// && e.mobType !== 'Armor Stand' // Mojang classifies armor stands as mobs for some reason? // && e.mobType !== 'Armor Stand' // Mojang classifies armor stands as mobs for some reason?
@@ -60,33 +62,56 @@ function lookForMobs() {
const filter = e => e.position.distanceTo(bot.entity.position) < 16 && filterHostileMobs(e) const filter = e => e.position.distanceTo(bot.entity.position) < 16 && filterHostileMobs(e)
const entityEnemy = bot.nearestEntity(filter) const entityEnemy = bot.nearestEntity(filter)
if (entityEnemy) { if (entityEnemy && !filterCreeper(entityEnemy)) {
if(filterCreeper(entityEnemy)) // if(filterCreeper(entityEnemy))
// Start attacking // Start attacking
// bot.off('time', lookForMobs) // bot.off('time', lookForMobs)
// bot.on('physicTick', lookForMobs) // bot.on('physicTick', lookForMobs)
bot.pvp.movements.canDig = !!cfg.move && cfg.move.canDig
bot.pvp.attack(entityEnemy) bot.pvp.attack(entityEnemy)
} else if (entityEnemy) {
bot.lookAt(
// (new v.Vec3(0, 1, 0)).add(
entityEnemy.position
// )
)
cfg.quiet || bot.chat("AH! A creeper! They creep me out!")
} }
} }
function guardSelf(entity) { function guardSelf(entity) {
if (!cfg.guard.self || entity !== bot.entity || guardPos) return // Do nothing if (!cfg.guard.self || entity !== bot.entity || guardPos) return // Do nothing
// bot.chat("") // bot.chat("")
bot.chat(( const hurtMessages = [
() => { const a = ["ouch!", "oww!", "help", "", "", ""]; return a[Math.floor(Math.random() * a.length)] } "ouch!", "oww!", "help", "monster!"
)()) ]
const hurtMessagesPlayer = [
"leave me alone!", "don't hurt me!", "bully!", "go away!", "stop", "no stop", "stop it", "murderer!",
"what have i ever done to you? :(",
]
console.info(bot.nearestEntity(filterAllMobs))
console.info(bot.nearestEntity(filterallMobs)) // Only look for mobs within 5 blocks
// const filter = e => e.position.distanceTo(bot.entity.position) < 5 && filterHostileMobs(e) || filterPlayers(e) && !filterMods(e)//anarchy
// Only look for mobs within 10 blocks const filter = e => e.position.distanceTo(bot.entity.position) < 5 && filterHostileMobs(e)
const filter = e => e.position.distanceTo(bot.entity.position) < 10 && filterHostileMobs(e)
const entityEnemy = bot.nearestEntity(filter) const entityEnemy = bot.nearestEntity(filter)
cfg.quiet || 0.2 < Math.random() && entityEnemy && bot.chat(
// TODO use fuzzy message function
filterPlayers(entityEnemy) ?
hurtMessagesPlayer[Math.floor(Math.random() * hurtMessagesPlayer.length)]
:
hurtMessages[Math.floor(Math.random() * hurtMessages.length)]
)
if (entityEnemy && !filterCreeper(entityEnemy)) { if (entityEnemy && !filterCreeper(entityEnemy)) {
// Start attacking // Start attacking
// bot.off('time', lookForMobs) // bot.off('time', lookForMobs)
// bot.on('physicTick', lookForMobs) // bot.on('physicTick', lookForMobs)
bot.pvp.attack(entityEnemy) bot.pvp.attack(entityEnemy)
bot.pvp.movements.canDig = !!cfg.move && cfg.move.canDig
} }
} }

View File

@@ -10,22 +10,18 @@
*/ */
const mineflayer = require('mineflayer') 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 cfg = {}
let bot = {} let bot = {}
// let mcd // let mcd
const alt_destinations = {
offhand: "off-hand",
lefthand: "off-hand",
shield: "off-hand",
chest: "torso",
}
function inventory(username, message) { function inventory(username, message) {
if (username === bot.username) return if (username === bot.username) return
const command = message.split(' ') const command = message.split(' ')
@@ -72,8 +68,8 @@ function inventory(username, message) {
function sayItems(items = bot.inventory.items()) { function sayItems(items = bot.inventory.items()) {
const output = items.map(itemToString).join(', ') const output = items.map(itemToString).join(', ')
console.info("inventory:", output)
if (output) { if (output) {
console.info("inventory:", output)
!cfg.quiet && bot.chat(output) !cfg.quiet && bot.chat(output)
} else { } else {
!cfg.quiet && bot.chat('empty') !cfg.quiet && bot.chat('empty')
@@ -104,8 +100,20 @@ function tossItem(name, amount) {
function equipItem(name, destination, quiet = false) { function equipItem(name, destination, quiet = false) {
const item = itemByName(name) const item = itemByName(name)
if (item) { if (item) {
bot.equip(item, destination, checkIfEquipped) if (Object.keys(alt_destinations).includes(destination)) {
destination = alt_destinations[destination]
}
try {
bot.equip(item, destination, checkIfEquipped)
} catch (error) {
if (error.code == 'ERR_ASSERTION' && error.message.startsWith("invalid destination:")) {
bot.chat(error.message)
} else {
console.error(error)
}
}
} else { } else {
!quiet && bot.chat(`I have no ${name}`) !quiet && bot.chat(`I have no ${name}`)
} }
@@ -120,6 +128,9 @@ function equipItem(name, destination, quiet = false) {
} }
function unequipItem(destination) { function unequipItem(destination) {
if (Object.keys(alt_destinations).includes(destination)) {
destination = alt_destinations[destination]
}
bot.unequip(destination, (err) => { bot.unequip(destination, (err) => {
if (err) { if (err) {
bot.chat(`cannot unequip: ${err.message}`) bot.chat(`cannot unequip: ${err.message}`)
@@ -134,27 +145,54 @@ function useEquippedItem() {
bot.activateItem() bot.activateItem()
} }
function craftItem(name, amount) { function craftItem(name, amount = 1) {
amount = parseInt(amount, 10) amount = parseInt(amount, 10)
const item = require('minecraft-data')(bot.version).findItemOrBlockByName(name) const mcData = require('minecraft-data')(bot.version)
const item = mcData.findItemOrBlockByName(name)
const craftingTable = bot.findBlock({ const craftingTable = bot.findBlock({
matching: 58 matching: mcData.blocksByName["crafting_table"].id
}) })
const recipesNoTable = bot.recipesFor(item.id)
let recipes
if (recipesNoTable.length > 0) {
bot.chat("can make without crafting table!")
recipes = recipesNoTable
} else if (craftingTable) {
recipes = bot.recipesFor(item.id, null, null, craftingTable)
} else {
bot.chat("Couldn't find a crafting table. Maybe craft one?")
}
if (item) { if (item) {
const recipe = bot.recipesFor(item.id, null, 1, craftingTable)[0] let craftSuccess
if (recipe) { recipes && cfg.quiet || bot.chat(`${recipes.length} recipes`)
bot.chat(`I can make ${name}`) for (let recipe in recipes) {
bot.craft(recipe, amount, craftingTable, (err) => { if (craftSuccess) {
if (err) { break
bot.chat(`error making ${name}`) }
} else { setTimeout(craftIt, Math.random() * 5000, recipes[recipe], amount, craftingTable)
bot.chat(`did the recipe for ${name} ${amount} times`)
}
})
} else {
bot.chat(`I cannot make ${name}`)
} }
function craftIt(recipe, amount, craftingTable) {
cfg.quiet || bot.chat(`I can make ${name}`)
console.log("craft:", recipe)
console.time("craft recipe")
bot.craft(recipe, amount, craftingTable, craftError)
console.timeEnd("craft recipe")
}
function craftError(err) {
if (err) {
console.error(err)
cfg.quiet || bot.chat(`error making ${name}: ${err.message}`)
// continue
} else {
craftSuccess = true
bot.chat(`did the recipe for ${name} ${amount} times`)
// break
}
}
craftSuccess || cfg.quiet || bot.chat(`I couldn't make ${name}`)
} else { } else {
bot.chat(`unknown item: ${name}`) bot.chat(`unknown item: ${name}`)
} }
@@ -187,4 +225,4 @@ const unload = () => {
bot.off('chat', inventory) bot.off('chat', inventory)
} }
module.exports = { load, unload, equipItem } module.exports = { load, unload, equipItem, craftItem }

View File

@@ -1,5 +1,6 @@
const mineflayer = require('mineflayer') // const mineflayer = require('mineflayer')
const pathfinder = require('mineflayer-pathfinder').pathfinder // const { pathfinder } = require('mineflayer-pathfinder')
let pathfinder
const { const {
gameplay, gameplay,
MoveTo, MoveTo,
@@ -10,8 +11,7 @@ const {
Craft Craft
} = require('prismarine-gameplay') } = require('prismarine-gameplay')
// const { Gameplay } = require('prismarine-gameplay/lib/gameplay') // const { Gameplay } = require('prismarine-gameplay/lib/gameplay')
// const { Vec3 } = require('vec3') const { Vec3 } = require('vec3')
let mcData = require('minecraft-data')
let cfg = {} let cfg = {}
let bot = {} let bot = {}
@@ -34,8 +34,9 @@ let bot = {}
// bot.on('chat', (username, message) => mine(username, message)) // bot.on('chat', (username, message) => mine(username, message))
function checkBlockExists(name){ function checkBlockExists(name){
if (mcData.blocksByName[name] === undefined) { const item = require('minecraft-data')(bot.version).findItemOrBlockByName(name)
bot.chat(`${name} is not a block name`) if (item === undefined) {
bot.chat(`${name} is not a block or item name`)
return false return false
} else { } else {
return true return true
@@ -121,10 +122,11 @@ function miner(username, message) {
itemType: command[2], itemType: command[2],
count: parseInt(command[1]) count: parseInt(command[1])
}), logError) }), logError)
break break
case /^collect [a-zA-Z_]+$/.test(message): case /^collect [a-zA-Z_]+$/.test(message):
if(!checkBlockExists(command[2])) {return false} if (!checkBlockExists(command[1])) { return false }
bot.gameplay.solveFor( bot.gameplay.solveFor(
new ObtainItem({ new ObtainItem({
itemType: command[1] itemType: command[1]
@@ -141,7 +143,7 @@ function miner(username, message) {
}), logError) }), logError)
break break
case /^craft [0-9]+ [a-zA-Z_]+$/.test(message): case /^craftmine [0-9]+ [a-zA-Z_]+$/.test(message):
if(!checkBlockExists(command[2])) {return false} if(!checkBlockExists(command[2])) {return false}
bot.gameplay.solveFor( bot.gameplay.solveFor(
new Craft({ new Craft({
@@ -152,23 +154,36 @@ function miner(username, message) {
break break
case /^stop$/.test(message): case /^stop$/.test(message):
bot.chat("♪♪ can't stop me now!! ♪♪") cfg.quiet || bot.chat("♪♪ can't stop me now!! ♪♪")
bot.pathfinder.setGoal(null)
bot.stopDigging()
// player = bot.player.entity // player = bot.player.entity
if (player) { // if (player) {
bot.gameplay.solveFor( // bot.gameplay.solveFor(
new MoveTo({ // new MoveTo({
x: player.position.x, // x: player.position.x,
y: player.position.y, // y: player.position.y,
z: player.position.z // z: player.position.z
}), logError) // }), logError)
} // }
break break
} }
} }
function logError(err) { function logError(err) {
if (err) if (err) {
bot.chat(`Failed task: ${err.message}`) switch (err.message) {
case "No more solutions available!":
console.log("miner: failed")
cfg.quiet || bot.chat("miner: out of solutions")
break;
default:
console.log(err)
cfg.quiet || bot.chat("miner: unknown error, check logs")
break;
}
}
} }
@@ -176,10 +191,15 @@ const load = (config) => {
cfg = config cfg = config
bot = cfg.bot bot = cfg.bot
mcData = mcData(bot.version) pathfinder = bot.pathfinder || bot.loadPlugin(require('mineflayer-pathfinder').pathfinder)
bot.loadPlugin(pathfinder) // bot.loadPlugin(pathfinder)
cfg.plugins.mover.initMoves(bot)
bot.loadPlugin(gameplay) bot.loadPlugin(gameplay)
cfg.plugins.mover.initMoves(bot)
bot.on("chat", miner) bot.on("chat", miner)
// bot.pathfinder.canDig
} }
const unload = () => { const unload = () => {
@@ -187,4 +207,4 @@ const unload = () => {
bot.off("chat", miner) bot.off("chat", miner)
} }
module.exports = { load, unload } module.exports = { load, unload, miner }

95
lib/plugins/mover.js Normal file
View File

@@ -0,0 +1,95 @@
// import { EntityFilters } from "mineflayer-statemachine"
// import v from "vec3"
// import { Movements } from "mineflayer-pathfinder"
// const mineflayer = require('mineflayer')
const { Movements } = require('mineflayer-pathfinder')
// const { GoalBLah } = require('mineflayer-pathfinder').goals
const v = require('vec3')
let cfg = {}
let bot = {}
// let moving
let pathfinder
let movements = []
function initMoves(bot = bot, mcData = require('minecraft-data')(bot.version)) {
let defaultMove = new Movements(bot, mcData)
defaultMove.canDig = false
defaultMove.scafoldingBlocks.push(mcData.blocksByName.slime_block.id)
// defaultMove.blocksCantBreak.add(mcData.blocksByName.glass.id)
// defaultMove.blocksToAvoid.add(mcData.blocksByName.magma.id)
movements.defaultMove = defaultMove
bot.pathfinder.setMovements(defaultMove)
}
function moveNear(pos, distance = 3) {
const { GoalNear } = require('mineflayer-pathfinder').goals
cfg.quiet || bot.chat(`moving to ${pos}`)
pos = v(pos)
bot.pathfinder.setMovements(movements.defaultMove)
bot.pathfinder.setGoal(new GoalNear(pos.x, pos.y, pos.z, distance))
}
function follow(entity, dynamic = true) {
console.assert(entity)
const { GoalFollow } = require('mineflayer-pathfinder').goals
cfg.quiet && console.log(entity)
|| bot.chat(
`following ${entity.type
}: ${entity.username || entity.displayName
}${dynamic ? "" : " once"}`
)
entity = entity.entity ? entity.entity : entity
// console.log(entity)
bot.pathfinder.setMovements(movements.defaultMove)
bot.pathfinder.setGoal(new GoalFollow(entity, 3), dynamic)
}
function hit(blockOrEntity) {
bot.chat(`hitting ${entity.name || entity.type}`)
}
function stop() {
bot.pathfinder.setGoal(null)
bot.stopDigging()
}
const load = (config) => {
cfg = config
bot = cfg.bot
cfg.move = {
// auto: true,
canDig: false,
// list: ["hello", "wassup"],
quiet: !!cfg.quiet,
movements: []
}
pathfinder = bot.pathfinder || bot.loadPlugin(require('mineflayer-pathfinder').pathfinder)
// initMoves(bot, mcData)
setTimeout(initMoves, 500, bot)
// bot.loadPlugin(pathfinder)
// bot.on('time', hello)
}
const unload = () => {
// TODO stop pathfinding maybe?
}
module.exports = { load, unload, stop, initMoves, moveNear, follow }

View File

@@ -1,13 +1,6 @@
const pathfinder = require('mineflayer-pathfinder').pathfinder let pathfinder
const { Bot } = require('mineflayer') //TODO replace with simple pathfinder motions
const {
gameplay,
MoveTo,
MoveToInteract,
ObtainItem,
// Craft
} = require('prismarine-gameplay')
let cfg = {} let cfg = {}
let bot = {} let bot = {}
@@ -33,44 +26,36 @@ function sleep(quiet) {
} }
if (bed && bedstatus == "") { if (bed && bedstatus == "") {
bot.lookAt(bed.position) bot.lookAt(bed.position)
// const nearbed = bot.waitForChunksToLoad(() => {
bot.gameplay.solveFor( cfg.plugins.moveNear(bed.position)
new MoveTo((bed.position.range = 2) && bed.position), (err) => { bot.sleep(bed, (err) => {
// new MoveTo(bed.position), (err) => {
// new MoveToInteract(bed.position), (err) => {
if (err) { if (err) {
!quiet && bot.chat(`can't reach bed: ${err.message}`) !quiet && bot.chat(`can't sleep: ${err.message}`)
} else { } else {
bot.waitForChunksToLoad(() => { !quiet && bot.chat("zzz")
bot.sleep(bed, (err) => { console.log("sleeping? ", bot.isSleeping)
if (err) { // hack until this is fixed
!quiet && bot.chat(`can't sleep: ${err.message}`) // bot.isSleeping = bot.isSleeping ? bot.isSleeping : true
} else { bot.isSleeping = true
!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)) { } else if (inv && inv.equipItem("red_bed", "hand", true)) {
// doesn't work fortunately // doesn't work fortunately
// FIXME: DONT IMPLEMENT until it is detected as NOT NETHER // FIXME: DONT IMPLEMENT until it is detected as NOT NETHER
bot.placeBlock() // bot.placeBlock()
} else { } else {
bot.gameplay.solveFor( // TODO: use mover
new ObtainItem("bed"), (err) => { // bot.gameplay.solveFor(
if (err) { // new ObtainItem("bed"), (err) => {
!quiet && bot.chat(`need a${bedstatus} bed: may not see if just placed`) // if (err) {
} // !quiet && bot.chat(`need a${bedstatus} bed: may not see if just placed`)
} // }
) // }
// )
// bot.chat('/afk') // bot.chat('/afk')
} }
bot.pathfinder.movements
} }
function wake() { function wake() {
@@ -86,7 +71,7 @@ function wake() {
function autoSleep() { function autoSleep() {
if (!bot.time.isDay && !cfg.sleep.timeoutFn && cfg.sleep.auto && !bot.isSleeping) { if (!bot.time.isDay && !cfg.sleep.timeoutFn && cfg.sleep.auto && !bot.isSleeping) {
sleep() sleep()
cfg.sleep.timeoutFn = setTimeout(() => { cfg.sleep.timeoutFn = null }, cfg.sleep.timeout) // give 2 seconds for multiple events cfg.sleep.timeoutFn = setTimeout(() => { cfg.sleep.timeoutFn = null }, cfg.sleep.timeout)
console.log("sleeping?", bot.isSleeping, bot.time) console.log("sleeping?", bot.isSleeping, bot.time)
} }
} }
@@ -98,11 +83,11 @@ const load = (config) => {
auto: true, auto: true,
// timeout: 30 * 1000, // timeout: 30 * 1000,
timeout: 2 * 60 * 1000, timeout: 2 * 60 * 1000,
quiet: false quiet: !!cfg.quiet
} }
bot.loadPlugin(pathfinder) pathfinder = bot.pathfinder || require('mineflayer-pathfinder').pathfinder
bot.loadPlugin(gameplay) // bot.loadPlugin(pathfinder)
inv = cfg.plugins["inventory"] inv = cfg.plugins["inventory"]
bot.on("time", autoSleep) bot.on("time", autoSleep)

View File

@@ -42,7 +42,6 @@
"prismarine-block": "^1", "prismarine-block": "^1",
"prismarine-chat": "^1", "prismarine-chat": "^1",
"prismarine-entity": "^1.1.0", "prismarine-entity": "^1.1.0",
"prismarine-gameplay": "github:TheDudeFromCI/prismarine-gameplay#crafting",
"prismarine-item": "^1.5.0", "prismarine-item": "^1.5.0",
"prismarine-nbt": "^1.3", "prismarine-nbt": "^1.3",
"prismarine-recipe": "^1", "prismarine-recipe": "^1",