256 lines
8.8 KiB
JavaScript
256 lines
8.8 KiB
JavaScript
const assert = require('assert')
|
|
const { once } = require('events')
|
|
|
|
module.exports = inject
|
|
|
|
function inject (bot, { version }) {
|
|
const { entitiesByName } = bot.registry
|
|
const Item = require('prismarine-item')(bot.registry)
|
|
|
|
let selectTrade
|
|
if (bot.supportFeature('useMCTrSel')) {
|
|
bot._client.registerChannel('MC|TrSel', 'i32')
|
|
selectTrade = (choice) => {
|
|
bot._client.writeChannel('MC|TrSel', choice)
|
|
}
|
|
} else {
|
|
selectTrade = (choice) => {
|
|
bot._client.write('select_trade', { slot: choice })
|
|
}
|
|
}
|
|
|
|
const tradeListSchema = [
|
|
'container',
|
|
[
|
|
{ type: 'i32', name: 'windowId' },
|
|
{
|
|
name: 'trades',
|
|
type: [
|
|
'array',
|
|
{
|
|
countType: 'i8',
|
|
type: [
|
|
'container',
|
|
[
|
|
{ type: 'slot', name: 'inputItem1' },
|
|
{ type: 'slot', name: 'outputItem' },
|
|
{ type: 'bool', name: 'hasItem2' },
|
|
{
|
|
name: 'inputItem2',
|
|
type: [
|
|
'switch',
|
|
{
|
|
compareTo: 'hasItem2',
|
|
fields: {
|
|
true: 'slot',
|
|
false: 'void'
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{ type: 'bool', name: 'tradeDisabled' },
|
|
{ type: 'i32', name: 'nbTradeUses' },
|
|
{ type: 'i32', name: 'maximumNbTradeUses' }
|
|
]
|
|
]
|
|
}]
|
|
}
|
|
]
|
|
]
|
|
|
|
let tradeListPacket
|
|
if (bot.supportFeature('useMCTrList')) {
|
|
tradeListPacket = 'MC|TrList'
|
|
bot._client.registerChannel('MC|TrList', tradeListSchema)
|
|
} else if (bot.supportFeature('usetraderlist')) {
|
|
tradeListPacket = 'minecraft:trader_list'
|
|
bot._client.registerChannel('minecraft:trader_list', tradeListSchema)
|
|
} else {
|
|
tradeListPacket = 'trade_list'
|
|
}
|
|
|
|
async function openVillager (villagerEntity) {
|
|
const villagerType = entitiesByName.villager ? entitiesByName.villager.id : entitiesByName.Villager.id
|
|
assert.strictEqual(villagerEntity.entityType, villagerType)
|
|
let ready = false
|
|
|
|
const villagerPromise = bot.openEntity(villagerEntity)
|
|
bot._client.on(tradeListPacket, gotTrades)
|
|
const villager = await villagerPromise
|
|
if (villager.type !== 'minecraft:villager' && villager.type !== 'minecraft:merchant') {
|
|
throw new Error('This is not a villager')
|
|
}
|
|
|
|
villager.trades = null
|
|
villager.selectedTrade = null
|
|
|
|
villager.once('close', () => {
|
|
bot._client.removeListener(tradeListPacket, gotTrades)
|
|
})
|
|
|
|
villager.trade = async (index, count) => {
|
|
await bot.trade(villager, index, count)
|
|
}
|
|
|
|
if (!ready) await once(villager, 'ready')
|
|
return villager
|
|
|
|
async function gotTrades (packet) {
|
|
const villager = await villagerPromise
|
|
if (packet.windowId !== villager.id) return
|
|
assert.ok(packet.trades)
|
|
villager.trades = packet.trades.map(trade => {
|
|
trade.inputs = [trade.inputItem1 = Item.fromNotch(trade.inputItem1 || { blockId: -1 })]
|
|
if (trade.inputItem2?.itemCount != null) {
|
|
trade.inputs.push(trade.inputItem2 = Item.fromNotch(trade.inputItem2 || { blockId: -1 }))
|
|
}
|
|
|
|
trade.hasItem2 = !!(trade.inputItem2 && trade.inputItem2.type && trade.inputItem2.count)
|
|
trade.outputs = [trade.outputItem = Item.fromNotch(trade.outputItem || { blockId: -1 })]
|
|
|
|
if (trade.demand !== undefined && trade.specialPrice !== undefined) { // the price is affected by demand and reputation
|
|
const demandDiff = Math.max(0, Math.floor(trade.inputItem1.count * trade.demand * trade.priceMultiplier))
|
|
trade.realPrice = Math.min(Math.max((trade.inputItem1.count + trade.specialPrice + demandDiff), 1), trade.inputItem1.stackSize)
|
|
} else {
|
|
trade.realPrice = trade.inputItem1.count
|
|
}
|
|
return trade
|
|
})
|
|
if (!ready) {
|
|
ready = true
|
|
villager.emit('ready')
|
|
}
|
|
}
|
|
}
|
|
|
|
async function trade (villager, index, count) {
|
|
const choice = parseInt(index, 10) // allow string argument
|
|
assert.notStrictEqual(villager.trades, null)
|
|
assert.notStrictEqual(villager.trades[choice], null)
|
|
const Trade = villager.trades[choice]
|
|
villager.selectedTrade = Trade
|
|
count = count || Trade.maximumNbTradeUses - Trade.nbTradeUses
|
|
assert.ok(Trade.maximumNbTradeUses - Trade.nbTradeUses > 0, 'trade blocked')
|
|
assert.ok(Trade.maximumNbTradeUses - Trade.nbTradeUses >= count)
|
|
|
|
const itemCount1 = villager.count(Trade.inputItem1.type, Trade.inputItem1.metadata)
|
|
const hasEnoughItem1 = itemCount1 >= Trade.realPrice * count
|
|
let hasEnoughItem2 = true
|
|
let itemCount2 = 0
|
|
if (Trade.hasItem2) {
|
|
itemCount2 = villager.count(Trade.inputItem2.type, Trade.inputItem2.metadata)
|
|
hasEnoughItem2 = itemCount2 >= Trade.inputItem2.count * count
|
|
}
|
|
if (!hasEnoughItem1 || !hasEnoughItem2) {
|
|
throw new Error('Not enough items to trade')
|
|
}
|
|
|
|
selectTrade(choice)
|
|
if (bot.supportFeature('selectingTradeMovesItems')) { // 1.14+ the server moves items around by itself after selecting a trade
|
|
const proms = []
|
|
proms.push(once(villager, 'updateSlot:0'))
|
|
if (Trade.hasItem2) proms.push(once(villager, 'updateSlot:1'))
|
|
if (bot.supportFeature('setSlotAsTransaction')) {
|
|
proms.push(once(villager, 'updateSlot:2'))
|
|
await new Promise((resolve, reject) => {
|
|
let countOfItemOneLeftToTake = itemCount1 > 64 ? 64 : itemCount1
|
|
let countOfItemTwoLeftToTake = 0
|
|
if (Trade.hasItem2) {
|
|
countOfItemTwoLeftToTake = itemCount2 > 64 ? 64 : itemCount2
|
|
}
|
|
const listener = (slot, oldItem, newItem) => {
|
|
if (!(slot >= villager.inventoryStart && slot <= villager.inventoryEnd)) return
|
|
if (newItem === null) {
|
|
if (oldItem.type === Trade.inputItem1.type) countOfItemOneLeftToTake -= oldItem.count
|
|
else if (Trade.hasItem2 && oldItem.type === Trade.inputItem2.type) countOfItemTwoLeftToTake -= oldItem.count
|
|
}
|
|
if (countOfItemOneLeftToTake === 0 && countOfItemTwoLeftToTake === 0) {
|
|
villager.off('updateSlot', listener)
|
|
resolve()
|
|
}
|
|
}
|
|
villager.on('updateSlot', listener)
|
|
})
|
|
}
|
|
await Promise.all(proms)
|
|
}
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
await putRequirements(villager, Trade, count)
|
|
// ToDo: See if this does anything kappa
|
|
Trade.nbTradeUses++
|
|
if (Trade.maximumNbTradeUses - Trade.nbTradeUses === 0) {
|
|
Trade.tradeDisabled = true
|
|
}
|
|
if (!bot.supportFeature('setSlotAsTransaction')) {
|
|
villager.updateSlot(2, Object.assign({}, Trade.outputItem))
|
|
|
|
const [slot1, slot2] = villager.slots
|
|
if (slot1) {
|
|
assert.strictEqual(slot1.type, Trade.inputItem1.type)
|
|
const updatedCount1 = slot1.count - Trade.realPrice
|
|
const updatedSlot1 = updatedCount1 <= 0
|
|
? null
|
|
: { ...slot1, count: updatedCount1 }
|
|
villager.updateSlot(0, updatedSlot1)
|
|
}
|
|
|
|
if (slot2) {
|
|
assert.strictEqual(slot2.type, Trade.inputItem2.type)
|
|
const updatedCount2 = slot2.count - Trade.inputItem2.count
|
|
const updatedSlot2 = updatedCount2 <= 0
|
|
? null
|
|
: { ...slot2, count: updatedCount2 }
|
|
villager.updateSlot(1, updatedSlot2)
|
|
}
|
|
}
|
|
|
|
await bot.putAway(2)
|
|
}
|
|
|
|
for (const i of [0, 1]) {
|
|
if (villager.slots[i]) {
|
|
await bot.putAway(i) // 1.14+ whole stacks of items will automatically be placed , so there might be some left over
|
|
}
|
|
}
|
|
}
|
|
|
|
async function putRequirements (window, Trade, count) {
|
|
const [slot1, slot2] = window.slots
|
|
const { stackSize: stackSize1, type: type1, metadata: metadata1 } = Trade.inputItem1
|
|
const tradeCount1 = Trade.realPrice
|
|
const neededCount1 = Math.min(stackSize1, tradeCount1 * count)
|
|
|
|
const input1 = !slot1
|
|
? neededCount1
|
|
: (slot1.count < tradeCount1 ? neededCount1 - slot1.count : 0)
|
|
await deposit(window, type1, metadata1, input1, 0)
|
|
if (Trade.hasItem2) {
|
|
const { count: tradeCount2, stackSize: stackSize2, type: type2, metadata: metadata2 } = Trade.inputItem2
|
|
const needCount2 = Math.min(stackSize2, tradeCount2 * count)
|
|
|
|
const input2 = !slot2
|
|
? needCount2
|
|
: (slot2.count < tradeCount2 ? needCount2 - slot2.count : 0)
|
|
await deposit(window, type2, metadata2, input2, 1)
|
|
}
|
|
}
|
|
|
|
async function deposit (window, itemType, metadata, count, slot) {
|
|
const options = {
|
|
window,
|
|
itemType,
|
|
metadata,
|
|
count,
|
|
sourceStart: window.inventoryStart,
|
|
sourceEnd: window.inventoryEnd,
|
|
destStart: slot,
|
|
destEnd: slot + 1
|
|
}
|
|
await bot.transfer(options)
|
|
}
|
|
|
|
bot.openVillager = openVillager
|
|
bot.trade = trade
|
|
}
|