Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add brewing stand methods #3313

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@
- [villager "ready"](#villager-ready)
- [villager.trades](#villagertrades)
- [villager.trade(tradeIndex, [times])](#villagertradetradeindex-times)
- [mineflayer.BrewingStand](#mineflayerbrewingstand)
- [brewingStand "update"](#brewingstand-update)
- [brewingStand "brewingStopped"](#brewingstand-brewingstopped)
- [brewingStand.takeIngredient()](#brewingstandtakeingredient)
- [brewingStand.takeFuel()](#brewingstandtakefuel)
- [brewingStand.takePotion(slot)](#brewingstandtakepotionslot)
- [brewingStand.takePotions()](#brewingstandtakepotions)
- [brewingStand.putIngredient(itemType, metadata, count)](#brewingstandputingredientitemtype-metadata-count)
- [brewingStand.putFuel(itemType, metadata, count)](#brewingstandputfuelitemtype-metadata-count)
- [brewingStand.putPotion(slot, itemType, metadata, count)](#brewingstandputpotionslot-itemtype-metadata-count)
- [brewingStand.ingredientItem()](#brewingstandingredientitem)
- [brewingStand.fuelItem()](#brewingstandfuelitem)
- [brewingStand.potions()](#brewingstandpotions)
- [brewingStand.fuel](#brewingstandfuel)
- [brewingStand.progress](#brewingstandprogress)
- [brewingStand.progressSeconds](#brewingstandprogressseconds)
- [mineflayer.ScoreBoard](#mineflayerscoreboard)
- [ScoreBoard.name](#scoreboardname)
- [ScoreBoard.title](#scoreboardtitle)
Expand Down Expand Up @@ -662,6 +678,73 @@ Looks like:
#### villager.trade(tradeIndex, [times])
Is the same as [bot.trade(villagerInstance, tradeIndex, [times])](#bottradevillagerinstance-tradeindex-times)


### mineflayer.BrewingStand

Extends windows.Window for brewing stand.
See `bot.openBrewingStand(brewingStandBlock)`.

#### brewingStand "update"

Fires when `brewingStand.fuel` or `brewingStand.progress` update.

#### brewingStand "brewingStopped"

Fires when the active brewing stand finishes brewing or gets stopped due to removal of ingredient.

#### brewingStand.takeIngredient()

This function returns a `Promise`, with `item` as its argument upon completion.

#### brewingStand.takeFuel()

This function returns a `Promise`, with `item` as its argument upon completion. Only works post-1.9.

#### brewingStand.takePotion(slot)

This function returns a `Promise`, with `item` as its argument upon completion. `slot` can be 0, 1, or 2.

#### brewingStand.takePotions()

This function takes all potions in the brewing stand. No return value.

#### brewingStand.putIngredient(itemType, metadata, count)

This function returns a `Promise`, with `void` as its argument upon completion.

#### brewingStand.putFuel(itemType, metadata, count)

This function returns a `Promise`, with `void` as its argument upon completion. Only works post-1.9.

#### brewingStand.putPotion(slot, itemType, metadata, count)

This function returns a `Promise`, with `void` as its argument upon completion. `slot` is the destination slot of the potion. Can be 0, 1, or 2.

#### brewingStand.ingredientItem()

Returns `Item` instance which is the brewing ingredient.

#### brewingStand.fuelItem()

Returns `Item` instance which is the fuel (blaze powder). Only works post-1.9.

#### brewingStand.potions()

Returns array of `Item` instances with `null` as empty slots.

#### brewingStand.fuel

How much fuel is left in the brewing stand. Integer between 0 and 20.

#### brewingStand.progress

Ticks until potion finishes brewing. Integer between 0 and 400.

#### brewingStand.progressSeconds

Seconds until potion finishes brewing. Decimal between 0 and 20.


### mineflayer.ScoreBoard

#### ScoreBoard.name
Expand Down
83 changes: 83 additions & 0 deletions examples/brewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* simple example for using the built-in brewing stand methods
* give the bot 3 water bottles, nether wart, a ghast tear, and blaze powder for fuel
* place a brewing stand near the bot, then type 'open' in chat to have the bot open the brewing stand
* type 'brew' in chat, then the bot will brew a potion of regeneration
* type 'info' at any time, and the bot will log info about the brewing stand to the console
*
* made by Jovan04 2/15/2023
*/

const mineflayer = require('mineflayer')
const { once } = require('events')
let stand

if (process.argv.length < 6 || process.argv.length > 8) {
console.log('Usage : node brewer.js <host> <port> [<username>] [<auth>]')
process.exit(1)
}

const bot = mineflayer.createBot({
host: process.argv[2],
port: parseInt(process.argv[3]),
username: process.argv[4] || 'brewer',
auth: process.argv[5] || 'offline'
})

bot.once('spawn', () => {
console.log(`bot joined the game with username ${bot.username}`)
})

bot.on('chat', async (username, message) => {
if (username === bot.username) return

if (message === 'open') {
const standBlock = bot.findBlock({
maxDistance: 6,
matching: bot.registry.blocksByName.brewing_stand.id
})

if (!standBlock) {
console.log("I couldn't find a brewing stand block near me!")
return
}

stand = await bot.openBrewingStand(standBlock)
}

if (message === 'info') {
if (!stand) {
console.log("I don't have a brewing stand open!")
return
}

console.log(`fuel: ${stand.fuel}; progress: ${stand.progress}; seconds: ${stand.progressSeconds}.`)
if (bot.registry.isNewerOrEqualTo('1.9')) { // before 1.9, blaze powder wasn't needed for brewing
console.log('fuelItem:')
console.log(stand.fuelItem())
}
console.log('ingredient:')
console.log(stand.ingredientItem())
console.log('potions:')
console.log(stand.potions())
}

if (message === 'brew') {
if (!stand) {
console.log("I don't have a brewing stand open!")
return
}

await stand.putPotion(0, bot.registry.itemsByName.potion.id, null, 1)
await stand.putPotion(1, bot.registry.itemsByName.potion.id, null, 1)
await stand.putPotion(2, bot.registry.itemsByName.potion.id, null, 1)
if (bot.registry.isNewerOrEqualTo('1.9')) { // before 1.9, blaze powder wasn't needed for brewing
await stand.putFuel(bot.registry.itemsByName.blaze_powder.id, null, 1)
}
await stand.putIngredient(bot.registry.itemsByName.nether_wart.id, null, 1)
await once(stand, 'brewingStopped')
await stand.putIngredient(bot.registry.itemsByName.ghast_tear.id, null, 1)
await once(stand, 'brewingStopped')
await stand.takePotions()
}
})
32 changes: 32 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ export interface Bot extends TypedEmitter<BotEvents> {
slot: number,
pages: string[]
) => Promise<void>

openBrewingStand: (brewingStand: Block) => Promise<BrewingStand>

openContainer: (chest: Block | Entity, direction?: Vec3, cursorPos?: Vec3) => Promise<Chest | Dispenser>

Expand Down Expand Up @@ -658,6 +660,36 @@ interface ConditionalStorageEvents extends StorageEvents {
ready: () => void
}

export class BrewingStand extends Window<StorageManager> {
constructor ();

fuel: any

progress: number

progressSeconds: number

takeIngredient: () => Promise<any>

takeFuel: () => Promise<any>

takePotion: (slot: number) => Promise<any>

takePotions: () => Promise<void>

putIngredient: (itemType: any, metadata: any, count: any) => Promise<void>

putFuel: (itemType: any, metadata: any, count: any) => Promise<void>

putPotion: (slot: any, itemType: any, metadata: any, count: any) => Promise<void>

ingredientItem: () => any

fuelItem: () => any

potions: () => any
}

export class Chest extends Window<StorageEvents> {
constructor ();

Expand Down
3 changes: 2 additions & 1 deletion lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ const plugins = {
anvil: require('./plugins/anvil'),
place_entity: require('./plugins/place_entity'),
generic_place: require('./plugins/generic_place'),
particle: require('./plugins/particle')
particle: require('./plugins/particle'),
brewing_stand: require('./plugins/brewing_stand')
}

const minecraftData = require('minecraft-data')
Expand Down
137 changes: 137 additions & 0 deletions lib/plugins/brewing_stand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const assert = require('assert')
const { onceWithCleanup } = require('../promise_utils')

module.exports = inject

function inject (bot) {
const allowedWindowTypes = ['minecraft:brewing_stand']

function matchWindowType (window) {
for (const type of allowedWindowTypes) {
if (window.type.startsWith(type)) return true
}
return false
}

async function openBrewingStand (brewingStandBlock) {
const brewingStandPromise = bot.openBlock(brewingStandBlock)
let fuelPacket

if (bot.registry.isNewerOrEqualTo('1.9')) { // this packet doesn't come if the brewing stand has no fuel on versions 1.14-1.16
try {
[fuelPacket] = await onceWithCleanup(bot._client, 'craft_progress_bar', { timeout: 2500, checkCondition: (packet) => (packet.property === 1) })
} catch (err) {
if (!bot.registry.isNewerOrEqualTo('1.14') || bot.registry.isNewerOrEqualTo('1.17')) {
throw err
} else {
fuelPacket = { value: 0 }
}
}
}
const brewingStand = await brewingStandPromise

if (!matchWindowType(brewingStand)) {
throw new Error('This is not a brewing-stand-like window')
}

brewingStand.fuel = fuelPacket?.value || null
brewingStand.progress = 0
brewingStand.progressSeconds = 0
brewingStand.takeIngredient = takeIngredient
brewingStand.takeFuel = takeFuel
brewingStand.takePotion = takePotion
brewingStand.takePotions = takePotions
brewingStand.putIngredient = putIngredient
brewingStand.putFuel = putFuel
brewingStand.putPotion = putPotion
brewingStand.ingredientItem = function () { return this.slots[3] } // returns ingredient in the top slot
brewingStand.fuelItem = function () { return this.slots[4] } // returns item in the fuel slot
brewingStand.potions = function () { return [this.slots[0], this.slots[1], this.slots[2]] } // returns array containing potions currently in the stand from left to right

bot._client.on('craft_progress_bar', onUpdateWindowProperty)
brewingStand.once('close', () => {
bot._client.removeListener('craft_progress_bar', onUpdateWindowProperty)
})

return brewingStand

function onUpdateWindowProperty (packet) {
if (packet.windowId !== brewingStand.id) return

switch (packet.property) {
case 0: // Current progress
brewingStand.progress = packet.value
brewingStand.progressSeconds = ticksToSeconds(packet.value)
if (packet.value === 0) {
brewingStand.emit('brewingStopped') // currently fires whenever the brewing stand finishes brewing or when it's stopped by removing an ingredient. how can we make an event that fires only when the brewing is finished? triggered by the sound, perhaps?
}
break
case 1: // Current fuel
brewingStand.fuel = packet.value
break
}

brewingStand.emit('update')
}

async function takeSomething (item) {
assert.ok(item)
await bot.putAway(item.slot)
return item
}

async function takeIngredient () {
return takeSomething(brewingStand.ingredientItem())
}

async function takeFuel () {
return takeSomething(brewingStand.fuelItem())
}

async function takePotion (slot) {
return takeSomething(brewingStand.potions()[slot])
}

async function takePotions () {
for (const i of [0, 1, 2]) {
if (brewingStand.potions()[i]) await takeSomething(brewingStand.potions()[i])
}
}

async function putSomething (destSlot, itemType, metadata, count) {
const options = {
window: brewingStand,
itemType,
metadata,
count,
sourceStart: brewingStand.inventoryStart,
sourceEnd: brewingStand.inventoryEnd,
destStart: destSlot,
destEnd: destSlot + 1
}
await bot.transfer(options)
}

async function putIngredient (itemType, metadata, count) {
await putSomething(3, itemType, metadata, count)
}

async function putFuel (itemType, metadata, count) {
await putSomething(4, itemType, metadata, count)
}

async function putPotion (slot, itemType, metadata, count) {
if (![0, 1, 2].includes(slot)) {
throw new Error(`Invalid slot ${slot}. Slot must be in [0, 1, 2].`)
}
// do we want to add a check here for whether or not the item we're trying to put in is a potion? furnace.putFuel doesn't have that check, but it might be useful
await putSomething(slot, itemType, metadata, count)
}
}

function ticksToSeconds (ticks) {
return ticks * 0.05
}

bot.openBrewingStand = openBrewingStand
}
Loading
Loading