Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS
GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/lib/plugins/villager.js
9427 views
1
const assert = require('assert')
2
const { once } = require('../promise_utils')
3
4
module.exports = inject
5
6
function inject (bot, { version }) {
7
const { entitiesByName } = bot.registry
8
const Item = require('prismarine-item')(bot.registry)
9
10
let selectTrade
11
if (bot.supportFeature('useMCTrSel')) {
12
bot._client.registerChannel('MC|TrSel', 'i32')
13
selectTrade = (choice) => {
14
bot._client.writeChannel('MC|TrSel', choice)
15
}
16
} else {
17
selectTrade = (choice) => {
18
bot._client.write('select_trade', { slot: choice })
19
}
20
}
21
22
const tradeListSchema = [
23
'container',
24
[
25
{ type: 'i32', name: 'windowId' },
26
{
27
name: 'trades',
28
type: [
29
'array',
30
{
31
countType: 'i8',
32
type: [
33
'container',
34
[
35
{ type: 'slot', name: 'inputItem1' },
36
{ type: 'slot', name: 'outputItem' },
37
{ type: 'bool', name: 'hasItem2' },
38
{
39
name: 'inputItem2',
40
type: [
41
'switch',
42
{
43
compareTo: 'hasItem2',
44
fields: {
45
true: 'slot',
46
false: 'void'
47
}
48
}
49
]
50
},
51
{ type: 'bool', name: 'tradeDisabled' },
52
{ type: 'i32', name: 'nbTradeUses' },
53
{ type: 'i32', name: 'maximumNbTradeUses' }
54
]
55
]
56
}]
57
}
58
]
59
]
60
61
let tradeListPacket
62
if (bot.supportFeature('useMCTrList')) {
63
tradeListPacket = 'MC|TrList'
64
bot._client.registerChannel('MC|TrList', tradeListSchema)
65
} else if (bot.supportFeature('usetraderlist')) {
66
tradeListPacket = 'minecraft:trader_list'
67
bot._client.registerChannel('minecraft:trader_list', tradeListSchema)
68
} else {
69
tradeListPacket = 'trade_list'
70
}
71
72
async function openVillager (villagerEntity) {
73
const villagerType = entitiesByName.villager ? entitiesByName.villager.id : entitiesByName.Villager.id
74
assert.strictEqual(villagerEntity.entityType, villagerType)
75
let ready = false
76
77
const villagerPromise = bot.openEntity(villagerEntity)
78
bot._client.on(tradeListPacket, gotTrades)
79
const villager = await villagerPromise
80
if (villager.type !== 'minecraft:villager' && villager.type !== 'minecraft:merchant') {
81
throw new Error('Expected minecraft:villager or minecraft:mechant type, but got ' + villager.type)
82
}
83
84
villager.trades = null
85
villager.selectedTrade = null
86
87
villager.once('close', () => {
88
bot._client.removeListener(tradeListPacket, gotTrades)
89
})
90
91
villager.trade = async (index, count) => {
92
await bot.trade(villager, index, count)
93
}
94
95
if (!ready) await once(villager, 'ready')
96
return villager
97
98
async function gotTrades (packet) {
99
const villager = await villagerPromise
100
if (packet.windowId !== villager.id) return
101
assert.ok(packet.trades)
102
villager.trades = packet.trades.map(trade => {
103
trade.inputs = [trade.inputItem1 = Item.fromNotch(trade.inputItem1 || { blockId: -1 })]
104
if (trade.inputItem2?.itemCount != null) {
105
trade.inputs.push(trade.inputItem2 = Item.fromNotch(trade.inputItem2 || { blockId: -1 }))
106
}
107
108
trade.hasItem2 = !!(trade.inputItem2 && trade.inputItem2.type && trade.inputItem2.count)
109
trade.outputs = [trade.outputItem = Item.fromNotch(trade.outputItem || { blockId: -1 })]
110
111
if (trade.demand !== undefined && trade.specialPrice !== undefined) { // the price is affected by demand and reputation
112
const demandDiff = Math.max(0, Math.floor(trade.inputItem1.count * trade.demand * trade.priceMultiplier))
113
trade.realPrice = Math.min(Math.max((trade.inputItem1.count + trade.specialPrice + demandDiff), 1), trade.inputItem1.stackSize)
114
} else {
115
trade.realPrice = trade.inputItem1.count
116
}
117
return trade
118
})
119
if (!ready) {
120
ready = true
121
villager.emit('ready')
122
}
123
}
124
}
125
126
async function trade (villager, index, count) {
127
const choice = parseInt(index, 10) // allow string argument
128
assert.notStrictEqual(villager.trades, null)
129
assert.notStrictEqual(villager.trades[choice], null)
130
const Trade = villager.trades[choice]
131
villager.selectedTrade = Trade
132
count = count || Trade.maximumNbTradeUses - Trade.nbTradeUses
133
assert.ok(Trade.maximumNbTradeUses - Trade.nbTradeUses > 0, 'trade blocked')
134
assert.ok(Trade.maximumNbTradeUses - Trade.nbTradeUses >= count)
135
136
const itemCount1 = villager.count(Trade.inputItem1.type, Trade.inputItem1.metadata)
137
const hasEnoughItem1 = itemCount1 >= Trade.realPrice * count
138
let hasEnoughItem2 = true
139
let itemCount2 = 0
140
if (Trade.hasItem2) {
141
itemCount2 = villager.count(Trade.inputItem2.type, Trade.inputItem2.metadata)
142
hasEnoughItem2 = itemCount2 >= Trade.inputItem2.count * count
143
}
144
if (!hasEnoughItem1) {
145
throw new Error('Not enough item 1 to trade')
146
}
147
if (!hasEnoughItem2) {
148
throw new Error('Not enough item 2 to trade')
149
}
150
151
selectTrade(choice)
152
if (bot.supportFeature('selectingTradeMovesItems')) { // 1.14+ the server moves items around by itself after selecting a trade
153
const proms = []
154
proms.push(once(villager, 'updateSlot:0'))
155
if (Trade.hasItem2) proms.push(once(villager, 'updateSlot:1'))
156
if (bot.supportFeature('setSlotAsTransaction')) {
157
proms.push(once(villager, 'updateSlot:2'))
158
}
159
await Promise.all(proms)
160
}
161
162
for (let i = 0; i < count; i++) {
163
await putRequirements(villager, Trade)
164
// ToDo: See if this does anything kappa
165
Trade.nbTradeUses++
166
if (Trade.maximumNbTradeUses - Trade.nbTradeUses === 0) {
167
Trade.tradeDisabled = true
168
}
169
if (!bot.supportFeature('setSlotAsTransaction')) {
170
villager.updateSlot(2, Object.assign({}, Trade.outputItem))
171
172
const [slot1, slot2] = villager.slots
173
if (slot1) {
174
assert.strictEqual(slot1.type, Trade.inputItem1.type)
175
const updatedCount1 = slot1.count - Trade.realPrice
176
const updatedSlot1 = updatedCount1 <= 0
177
? null
178
: { ...slot1, count: updatedCount1 }
179
villager.updateSlot(0, updatedSlot1)
180
}
181
182
if (slot2) {
183
assert.strictEqual(slot2.type, Trade.inputItem2.type)
184
const updatedCount2 = slot2.count - Trade.inputItem2.count
185
const updatedSlot2 = updatedCount2 <= 0
186
? null
187
: { ...slot2, count: updatedCount2 }
188
villager.updateSlot(1, updatedSlot2)
189
}
190
}
191
192
await bot.putAway(2)
193
}
194
195
for (const i of [0, 1]) {
196
if (villager.slots[i]) {
197
await bot.putAway(i) // 1.14+ whole stacks of items will automatically be placed , so there might be some left over
198
}
199
}
200
}
201
202
async function putRequirements (window, Trade) {
203
const [slot1, slot2] = window.slots
204
const { type: type1, metadata: metadata1 } = Trade.inputItem1
205
206
const input1 = slot1
207
? Math.max(0, Trade.realPrice - slot1.count)
208
: Trade.realPrice
209
if (input1) {
210
await deposit(window, type1, metadata1, input1, 0)
211
}
212
if (Trade.hasItem2) {
213
const { count: tradeCount2, type: type2, metadata: metadata2 } = Trade.inputItem2
214
215
const input2 = slot2
216
? Math.max(0, tradeCount2 - slot2.count)
217
: tradeCount2
218
if (input2) {
219
await deposit(window, type2, metadata2, input2, 1)
220
}
221
}
222
}
223
224
async function deposit (window, itemType, metadata, count, slot) {
225
const options = {
226
window,
227
itemType,
228
metadata,
229
count,
230
sourceStart: window.inventoryStart,
231
sourceEnd: window.inventoryEnd,
232
destStart: slot,
233
destEnd: slot + 1
234
}
235
await bot.transfer(options)
236
}
237
238
bot.openVillager = openVillager
239
bot.trade = trade
240
}
241
242