A high-level, general purpose and modular minecraft bot using hot re-loadable (without restarting the bot!) plugins. Batteries included, launch to run!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

342 lines
12 KiB

let bot
let mcData
let quiet
// import v from 'vec3'
const v = require('vec3')
let cfg = {
info: {
quiet: quiet,
recentCommand: null,
},
// to satisfy typescript
quiet: null,
bot: bot
}
var RelativePosEnum
(function (RelativePosEnum) {
RelativePosEnum[RelativePosEnum["feet"] = 0] = "feet"
RelativePosEnum[RelativePosEnum["standing"] = 1] = "standing"
RelativePosEnum[RelativePosEnum["head"] = 2] = "head"
RelativePosEnum[RelativePosEnum["looking"] = 3] = "looking"
RelativePosEnum[RelativePosEnum["infront"] = 4] = "infront"
RelativePosEnum[RelativePosEnum["behind"] = 5] = "behind"
})(RelativePosEnum || (RelativePosEnum = {}))
function relPosToBlock(entity, relPos) {
let pos
if (!(relPos in RelativePosEnum)) return console.warn("info: not a relative position:", relPos)
relPos = typeof relPos === "number" ? RelativePosEnum[relPos] : relPos
switch (relPos) {
case "feet":
pos = entity.position;
break
case "standing":
pos = entity.position.offset(0, -1, 0)
break
// todo use CARDINAL directions api from pathfinder
// case "behind":
// entity.yaw
// pos = entity.position
// break
// case "front":
// case "infront":
// pos = entity.position
// break
case "head":
pos = entity.position.offset(0, 1.85, 0)
break
case "looking":
if (entity === bot.entity) {
return bot.blockAtCursor()
// return bot.blockInSight(128, 128)
}
default:
quiet || bot.chat(`info: pos '${relPos}' not implemented`)
break
}
if (pos) {
// nearest block
return bot.blockAt(pos)
}
}
function isVec3(vec) {
return vec?.length === 3 || vec.x && vec.y && vec.z
}
function block(entity = bot.entity, pos = entity?.position?.offset(0, -1, 0)) {
console.assert(pos || entity)
const block = isVec3(pos) ? bot.blockAt(v(pos))
: typeof pos === "string" ? relPosToBlock(entity, RelativePosEnum[pos]) : pos
console.log(block, block?.getProperties && block.getProperties())
if (!block) {
quiet || bot.chat("empty block")
return block
}
bot.lookAt(block?.position)
let info = [block.type, block.name]
if (block.metadata) info.push(Object.entries(block.getProperties()))
quiet || bot.chat(info.join(": "))
}
function item(
entity = bot.entity,
slot = entity.heldItem
) {
const item = typeof slot === "number" ?
bot.inventory.slots[slot + bot.QUICK_BAR_START] :
slot
console.log("info item:", item)
if (!item) {
quiet || bot.chat("no item")
return item
}
let info = [item.type, item.name, item.count]
if (item.metadata) info.push("meta: " + item.metadata.length)
if (item.nbt) {
info.push(compound_value(item.nbt))
}
quiet || bot.chat(info.join("; "))
function compound_value(obj) {
if (typeof obj.value == "object") {
return compound_value(obj.value)
} else if (obj.value) {
return obj.value
} else if (typeof obj == "object") {
const keys = Object.keys(obj)
return keys.map(key => {
return `${key}: ${compound_value(obj[key])}`
});
} else {
return obj
}
}
return item
}
var VillagerProfession
(function (VillagerProfession) {
VillagerProfession[VillagerProfession["Unemployed"] = 0] = "Unemployed"
VillagerProfession[VillagerProfession["Armourer"] = 1] = "Armourer"
VillagerProfession[VillagerProfession["Butcher"] = 2] = "Butcher"
VillagerProfession[VillagerProfession["Cartographer"] = 3] = "Cartographer"
VillagerProfession[VillagerProfession["Cleric"] = 4] = "Cleric"
VillagerProfession[VillagerProfession["Farmer"] = 5] = "Farmer"
VillagerProfession[VillagerProfession["Fisherman"] = 6] = "Fisherman"
VillagerProfession[VillagerProfession["Fletcher"] = 7] = "Fletcher"
VillagerProfession[VillagerProfession["Leatherworker"] = 8] = "Leatherworker"
VillagerProfession[VillagerProfession["Librarian"] = 9] = "Librarian"
VillagerProfession[VillagerProfession["Mason"] = 10] = "Mason"
VillagerProfession[VillagerProfession["Nitwit"] = 11] = "Nitwit"
VillagerProfession[VillagerProfession["Shepherd"] = 12] = "Shepherd"
VillagerProfession[VillagerProfession["Toolsmith"] = 13] = "Toolsmith"
VillagerProfession[VillagerProfession["Weaponsmith"] = 14] = "Weaponsmith"
})(VillagerProfession || (VillagerProfession = {}))
function entity(name = bot.nearestEntity()) {
const entity = typeof name === "string" ? (name = name.toLowerCase()) && bot.nearestEntity((entity) => {
const enames = [entity.username?.toLowerCase(), entity.name, entity.displayName?.toLowerCase()]
return enames.includes(name)
}) : name
console.log("info entity:", entity)
if (!entity) {
quiet || bot.chat("no entity")
return entity
}
entity?.position && bot.lookAt(entity.position)
let info = [(entity.entityType && entity.entityType + ":" || "") + (entity.type === "object" ? entity.kind : entity.type)]
info.push(entity.username || entity.name)
// TODO various info depending on the type of entity; player, villager, etc
// TODO refactor and combine with compound_value()
switch (entity.type) {
case "object":
switch (entity.kind) {
case "Drops":
const item = entity.metadata[7];
entity.metadata;
if (item.present) {
info.push("item: " + item.itemId + ": "
+ mcData.items[item.itemId].name
+ `x${item.itemCount}`);
}
else {
console.warn("info entity: metadata expected item, got:", entity.metadata[7]);
}
break;
default:
}
break;
case "mob":
switch (name) {
case 'villager':
const { villagerProfession, ...otherProps } = entity.metadata[17]
info.push(VillagerProfession[villagerProfession])
info.push(Object.entries(otherProps).toString())
break
default:
break
}
entity.metadata[8] && info.push("health:" + entity.metadata[8])
entity.metadata[15] && info.push("baby")
if (entity.heldItem) {
info.push("holding")
item(entity)
}
case "global":
case "orb":
case "player":
break;
case "other":
info.push("kind:" + entity.kind)
default:
}
if (entity.metadata) info.push("len: " + entity.metadata.length)
quiet || bot.chat(info.join("; "))
return entity
}
function blockOrItemFromId(num, quiet = cfg.info.quiet) {
const block = mcData?.blocks[num]
const item = mcData?.items[num]
// const entity = mcData?.entities[num]
if (block || item) {
quiet || bot.chat(
(block && `block: ${block.name}, ` || "")
+ (item && `item: ${item.name}, ` || "")
// + (entity && `entity: ${entity.name}, ` || "")
)
} else {
quiet || bot.chat("info: nonexistent block or item")
}
return { block, item }
}
function command(message_parts, player) {
if (message_parts.length > 0) {
cfg.info.recentCommand = message_parts
}
if (player === null)
player = void 0
switch (message_parts.length) {
case 0:
if (cfg.info.recentCommand) {
command(cfg.info.recentCommand, player)
} else {
// TODO dispatch on instance of entity, block, etc..
// TODO have the logic inside the function or with a utility function
block()
}
break;
case 1:
switch (message_parts[0]) {
case "quiet":
cfg.info.quiet = quiet = !quiet;
quiet || bot.chat(`info: ${quiet ? "" : "not "}being quiet`);
break;
case "i":
case "item":
item()
break
case "e":
case "entity":
entity()
break
case "me":
block(player)
break
case "b":
case "block":
block()
break
default:
const num = parseInt(message_parts[0])
if (isFinite(num)) {
blockOrItemFromId(num)
} else {
quiet || bot.chat("info usage: `!info [me|i|e|b|<num>|quiet]`")
}
break;
}
break;
case 2:
switch (message_parts[0]) {
case "i":
case "item":
switch (message_parts[1]) {
case "me":
item(player)
break
default:
const slot = parseInt(message_parts[1])
slot && item(undefined, slot)
break
}
break
case "b":
case "block":
switch (message_parts[1]) {
case "me":
block(player)
break
default:
const aPlayer = bot.players[message_parts[1]]
if (message_parts[1] in RelativePosEnum) {
block(undefined, message_parts[1])
} else if (aPlayer) {
if (aPlayer.entity) {
block(aPlayer.entity)
} else {
quiet || bot.chat(`info: player ${aPlayer.username} too far!`)
}
} else {
// or entity(message_parts[1]).position
console.log(bot.players[message_parts[1]])
quiet || bot.chat("info: not yet implemented")
}
}
break
case "me":
if (message_parts[1] in RelativePosEnum) {
block(player, message_parts[1])
}
break
case "e":
case "entity":
default:
entity(message_parts[1])
break;
}
break
case 4:
switch (message_parts[0]) {
case "b":
case "block":
default:
block(undefined, message_parts.slice(1))
break;
}
break;
default:
cfg.quiet || bot.chat("info: unknown command")
break;
}
}
const load = (config) => {
config.info = cfg.info
cfg = config
bot = cfg.bot
mcData = bot.mcData || (bot.mcData = require('minecraft-data')(bot.version))
}
const unload = () => {}
module.exports = { load, unload, command, block, item, entity }