minecraft-inventory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +566 -0
  2. package/package.json +37 -0
  3. package/src/InventoryGUI.tsx +108 -0
  4. package/src/cache/textureCache.ts +95 -0
  5. package/src/components/CursorItem/CursorItem.tsx +94 -0
  6. package/src/components/CursorItem/index.ts +1 -0
  7. package/src/components/HUD/HUD.tsx +11 -0
  8. package/src/components/HUD/index.ts +1 -0
  9. package/src/components/Hotbar/Hotbar.tsx +180 -0
  10. package/src/components/Hotbar/index.ts +1 -0
  11. package/src/components/InventoryOverlay/InventoryOverlay.tsx +196 -0
  12. package/src/components/InventoryOverlay/index.ts +2 -0
  13. package/src/components/InventoryWindow/EnchantmentOptions.tsx +109 -0
  14. package/src/components/InventoryWindow/InventoryBackground.tsx +110 -0
  15. package/src/components/InventoryWindow/InventoryWindow.tsx +120 -0
  16. package/src/components/InventoryWindow/ProgressBar.tsx +78 -0
  17. package/src/components/InventoryWindow/VillagerTradeList.tsx +136 -0
  18. package/src/components/InventoryWindow/index.ts +5 -0
  19. package/src/components/ItemCanvas/ItemCanvas.tsx +154 -0
  20. package/src/components/ItemCanvas/index.ts +1 -0
  21. package/src/components/JEI/JEI.module.css +37 -0
  22. package/src/components/JEI/JEI.tsx +303 -0
  23. package/src/components/JEI/index.ts +2 -0
  24. package/src/components/RecipeGuide/RecipeInventoryView.tsx +293 -0
  25. package/src/components/RecipeGuide/index.ts +1 -0
  26. package/src/components/Slot/Slot.module.css +111 -0
  27. package/src/components/Slot/Slot.tsx +363 -0
  28. package/src/components/Slot/index.ts +1 -0
  29. package/src/components/Text/MessageFormatted.css +5 -0
  30. package/src/components/Text/MessageFormatted.tsx +79 -0
  31. package/src/components/Text/MessageFormattedString.tsx +74 -0
  32. package/src/components/Text/chatUtils.ts +172 -0
  33. package/src/components/Tooltip/Tooltip.module.css +56 -0
  34. package/src/components/Tooltip/Tooltip.tsx +130 -0
  35. package/src/components/Tooltip/index.ts +1 -0
  36. package/src/connector/demo.ts +213 -0
  37. package/src/connector/index.ts +4 -0
  38. package/src/connector/mineflayer.ts +113 -0
  39. package/src/connector/types.ts +41 -0
  40. package/src/context/InventoryContext.tsx +157 -0
  41. package/src/context/ScaleContext.tsx +73 -0
  42. package/src/context/TextureContext.tsx +70 -0
  43. package/src/globals.d.ts +4 -0
  44. package/src/hooks/useKeyboardShortcuts.ts +41 -0
  45. package/src/hooks/useMobile.ts +28 -0
  46. package/src/index.tsx +65 -0
  47. package/src/mount.tsx +52 -0
  48. package/src/registry/index.ts +21 -0
  49. package/src/registry/inventories.ts +612 -0
  50. package/src/styles/tokens.css +47 -0
  51. package/src/types.ts +176 -0
@@ -0,0 +1,56 @@
1
+ .tooltip {
2
+ background: #100010;
3
+ border: 1px solid #100010;
4
+ border-radius: 5px;
5
+ display: flex;
6
+ flex-direction: column;
7
+ font-family: 'Minecraftia', 'Minecraft', monospace;
8
+ image-rendering: pixelated;
9
+ line-height: 1.2;
10
+ /* MC tooltip: 2-layer purple gradient inner glow */
11
+ box-shadow:
12
+ inset 0 1px 0 0 #5000a0,
13
+ inset 1px 0 0 0 #5000a0,
14
+ inset -1px 0 0 0 #5000a0,
15
+ inset 0 -1px 0 0 #28007f,
16
+ 0 0 0 1px #100010;
17
+ }
18
+
19
+ .name {
20
+ font-weight: bold;
21
+ white-space: nowrap;
22
+ margin-bottom: 2px;
23
+ }
24
+
25
+ .section {
26
+ display: flex;
27
+ flex-direction: column;
28
+ }
29
+
30
+ .enchantment {
31
+ color: #8080ff;
32
+ white-space: nowrap;
33
+ }
34
+
35
+ .lore {
36
+ display: flex;
37
+ flex-direction: column;
38
+ color: #6b29a4;
39
+ font-style: italic;
40
+ border-top: 1px solid #2d0069;
41
+ margin-top: 3px;
42
+ padding-top: 3px;
43
+ }
44
+
45
+ .durability {
46
+ color: #dddddd;
47
+ border-top: 1px solid #2d0069;
48
+ margin-top: 3px;
49
+ padding-top: 3px;
50
+ }
51
+
52
+ .typeId {
53
+ color: #444455;
54
+ font-size: 0.8em;
55
+ margin-top: 2px;
56
+ }
@@ -0,0 +1,130 @@
1
+ import React, { useRef, useState, useEffect, useLayoutEffect } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+ import type { ItemStack } from '../../types'
4
+ import { useScale } from '../../context/ScaleContext'
5
+ import { MessageFormattedString } from '../Text/MessageFormattedString'
6
+ import styles from './Tooltip.module.css'
7
+
8
+ interface TooltipProps {
9
+ item: ItemStack
10
+ mouseX: number
11
+ mouseY: number
12
+ visible: boolean
13
+ }
14
+
15
+ function getRarityColor(item: ItemStack): string {
16
+ if (item.enchantments && item.enchantments.length > 0) return '#8080ff'
17
+ return '#ffffff'
18
+ }
19
+
20
+ function formatEnchantment(e: { name: string; level: number }): string {
21
+ const roman = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X']
22
+ return e.level <= 10 ? `${e.name} ${roman[e.level]}` : `${e.name} ${e.level}`
23
+ }
24
+
25
+ export function Tooltip({ item, mouseX, mouseY, visible }: TooltipProps) {
26
+ const { scale } = useScale()
27
+ const ref = useRef<HTMLDivElement>(null)
28
+ const [pos, setPos] = useState({ x: -9999, y: -9999 })
29
+ const [ready, setReady] = useState(false)
30
+
31
+ // Recompute position every time mouse moves or item changes
32
+ useLayoutEffect(() => {
33
+ if (!visible || !ref.current) return
34
+ const el = ref.current
35
+ const tw = el.offsetWidth
36
+ const th = el.offsetHeight
37
+ const vw = window.innerWidth
38
+ const vh = window.innerHeight
39
+ const gapX = 12
40
+ const gapY = 2
41
+
42
+ let x = mouseX + gapX
43
+ let y = mouseY - th - gapY // above cursor by default
44
+
45
+ if (x + tw > vw - 4) x = mouseX - tw - gapX
46
+ if (y < 4) y = mouseY + gapY // flip below if no space above
47
+ if (y + th > vh - 4) y = vh - th - 4
48
+
49
+ setPos({ x, y })
50
+ setReady(true)
51
+ }, [mouseX, mouseY, visible, item, scale])
52
+
53
+ // Reset ready when tooltip becomes hidden
54
+ useEffect(() => {
55
+ if (!visible) setReady(false)
56
+ }, [visible])
57
+
58
+ if (!visible) return null
59
+
60
+ const nameColor = getRarityColor(item)
61
+ const hasDurability =
62
+ item.durability !== undefined &&
63
+ item.maxDurability !== undefined &&
64
+ item.durability < item.maxDurability
65
+
66
+ const fs = Math.round(8 * scale)
67
+ const pad = Math.round(4 * scale)
68
+ const gap2 = Math.round(1 * scale)
69
+
70
+ const tooltip = (
71
+ <div
72
+ ref={ref}
73
+ className={['mc-inv-tooltip', styles.tooltip].join(' ')}
74
+ style={{
75
+ position: 'fixed',
76
+ left: pos.x,
77
+ top: pos.y,
78
+ zIndex: 10000,
79
+ fontSize: fs,
80
+ padding: pad,
81
+ gap: gap2,
82
+ minWidth: Math.round(80 * scale),
83
+ maxWidth: Math.round(220 * scale),
84
+ visibility: ready ? 'visible' : 'hidden',
85
+ pointerEvents: 'none',
86
+ }}
87
+ >
88
+ <div className={styles.name} style={{ color: nameColor }}>
89
+ <MessageFormattedString
90
+ text={item.displayName ?? (item.name ? item.name.replace(/_/g, ' ') : `Item #${item.type}`)}
91
+ fallbackColor={nameColor}
92
+ />
93
+ </div>
94
+
95
+ {item.enchantments && item.enchantments.length > 0 && (
96
+ <div className={styles.section}>
97
+ {item.enchantments.map((e, i) => (
98
+ <div key={i} className={styles.enchantment}>
99
+ {formatEnchantment(e)}
100
+ </div>
101
+ ))}
102
+ </div>
103
+ )}
104
+
105
+ {item.lore && item.lore.length > 0 && (
106
+ <div className={styles.lore}>
107
+ {item.lore.map((line, i) => (
108
+ <div key={i}>
109
+ <MessageFormattedString text={line} />
110
+ </div>
111
+ ))}
112
+ </div>
113
+ )}
114
+
115
+ {hasDurability && (
116
+ <div className={styles.durability}>
117
+ Durability: {item.durability} / {item.maxDurability}
118
+ </div>
119
+ )}
120
+
121
+ <div className={styles.typeId}>
122
+ {item.name ?? `#${item.type}`}
123
+ {item.metadata !== undefined && item.metadata > 0 ? `:${item.metadata}` : ''}
124
+ </div>
125
+ </div>
126
+ )
127
+
128
+ // Portal to document.body so position:fixed works even inside transformed ancestors
129
+ return createPortal(tooltip, document.body)
130
+ }
@@ -0,0 +1 @@
1
+ export { Tooltip } from './Tooltip'
@@ -0,0 +1,213 @@
1
+ import type { InventoryAction, InventoryWindowState, PlayerState, SlotState, ItemStack } from '../types'
2
+ import type { InventoryConnector, ConnectorListener, ConnectorEvent } from './types'
3
+
4
+ export interface ActionLogEntry {
5
+ id: number
6
+ timestamp: number
7
+ action: InventoryAction
8
+ description: string
9
+ }
10
+
11
+ export interface DemoConnectorOptions {
12
+ windowType: string
13
+ windowTitle?: string
14
+ slots?: SlotState[]
15
+ playerInventory?: SlotState[]
16
+ onAction?: (entry: ActionLogEntry) => void
17
+ }
18
+
19
+ let actionCounter = 0
20
+
21
+ function describeAction(action: InventoryAction): string {
22
+ switch (action.type) {
23
+ case 'click':
24
+ return `${action.mode === 'shift' ? 'Shift-' : ''}${action.button === 'right' ? 'Right' : 'Left'} click slot ${action.slotIndex}${action.mode === 'number' ? ` (hotbar ${action.numberKey})` : ''}`
25
+ case 'drop':
26
+ return `Drop ${action.all ? 'all' : 'one'} from slot ${action.slotIndex}`
27
+ case 'drag':
28
+ return `Drag ${action.button} across slots: ${action.slots.join(', ')}`
29
+ case 'close':
30
+ return 'Close window'
31
+ case 'trade':
32
+ return `Trade at index ${action.tradeIndex}`
33
+ case 'rename':
34
+ return `Rename to "${action.text}"`
35
+ case 'enchant':
36
+ return `Select enchantment ${action.enchantIndex}`
37
+ case 'beacon':
38
+ return `Set beacon effects: ${action.primaryEffect} / ${action.secondaryEffect}`
39
+ case 'hotbar-swap':
40
+ return `Swap slot ${action.slotIndex} with hotbar ${action.hotbarSlot}`
41
+ default:
42
+ return JSON.stringify(action)
43
+ }
44
+ }
45
+
46
+ export function createDemoConnector(options: DemoConnectorOptions): InventoryConnector & {
47
+ actionLog: ActionLogEntry[]
48
+ updateSlots(slots: SlotState[]): void
49
+ setHeldItem(item: ItemStack | null): void
50
+ openWindow(type: string, title: string, slots: SlotState[]): void
51
+ closeWindowExternal(): void
52
+ } {
53
+ const listeners = new Set<ConnectorListener>()
54
+ const actionLog: ActionLogEntry[] = []
55
+
56
+ let windowState: InventoryWindowState | null = {
57
+ windowId: 1,
58
+ type: options.windowType,
59
+ title: options.windowTitle ?? options.windowType,
60
+ slots: options.slots ?? [],
61
+ heldItem: null,
62
+ }
63
+
64
+ let playerState: PlayerState = {
65
+ activeHotbarSlot: 0,
66
+ inventory: options.playerInventory ?? [],
67
+ }
68
+
69
+ function emit(event: ConnectorEvent) {
70
+ listeners.forEach((l) => l(event))
71
+ }
72
+
73
+ return {
74
+ actionLog,
75
+
76
+ getWindowState: () => windowState,
77
+ getPlayerState: () => playerState,
78
+
79
+ sendAction: (action: InventoryAction) => {
80
+ const entry: ActionLogEntry = {
81
+ id: ++actionCounter,
82
+ timestamp: Date.now(),
83
+ action,
84
+ description: describeAction(action),
85
+ }
86
+ actionLog.unshift(entry)
87
+ if (actionLog.length > 100) actionLog.splice(100)
88
+ options.onAction?.(entry)
89
+
90
+ // Demo: simulate simple click behavior
91
+ if (action.type === 'click' && windowState) {
92
+ const slots = [...windowState.slots]
93
+ const slotState = slots.find((s) => s.index === action.slotIndex)
94
+ const held = windowState.heldItem
95
+
96
+ if (action.button === 'left' && action.mode === 'normal') {
97
+ if (held && slotState) {
98
+ const idx = slots.indexOf(slotState)
99
+ const existing = slotState.item
100
+ if (!existing) {
101
+ slots[idx] = { ...slotState, item: held }
102
+ windowState = { ...windowState, slots, heldItem: null }
103
+ } else if (
104
+ existing.type === held.type &&
105
+ (existing.name ?? '') === (held.name ?? '') &&
106
+ (existing.metadata ?? 0) === (held.metadata ?? 0)
107
+ ) {
108
+ // Same item type — combine stacks
109
+ const maxStack = 64
110
+ const space = maxStack - existing.count
111
+ if (space > 0) {
112
+ const take = Math.min(held.count, space)
113
+ slots[idx] = { ...slotState, item: { ...existing, count: existing.count + take } }
114
+ const remain = held.count - take
115
+ windowState = { ...windowState, slots, heldItem: remain > 0 ? { ...held, count: remain } : null }
116
+ } else {
117
+ // Stack full — swap
118
+ slots[idx] = { ...slotState, item: held }
119
+ windowState = { ...windowState, slots, heldItem: existing }
120
+ }
121
+ } else {
122
+ // Different item types — swap
123
+ slots[idx] = { ...slotState, item: held }
124
+ windowState = { ...windowState, slots, heldItem: existing }
125
+ }
126
+ } else if (!held && slotState?.item) {
127
+ const idx = slots.indexOf(slotState)
128
+ slots[idx] = { ...slotState, item: null }
129
+ windowState = { ...windowState, slots, heldItem: slotState.item }
130
+ }
131
+ } else if (action.button === 'right' && action.mode === 'normal') {
132
+ if (!held && slotState?.item) {
133
+ const item = slotState.item
134
+ const half = Math.ceil(item.count / 2)
135
+ const remain = item.count - half
136
+ const idx = slots.indexOf(slotState)
137
+ slots[idx] = { ...slotState, item: remain > 0 ? { ...item, count: remain } : null }
138
+ windowState = { ...windowState, slots, heldItem: { ...item, count: half } }
139
+ } else if (held && slotState) {
140
+ const idx = slots.indexOf(slotState)
141
+ const existing = slotState.item
142
+ if (!existing || existing.type === held.type) {
143
+ slots[idx] = {
144
+ ...slotState,
145
+ item: { ...held, count: (existing?.count ?? 0) + 1 },
146
+ }
147
+ const newCount = held.count - 1
148
+ windowState = {
149
+ ...windowState,
150
+ slots,
151
+ heldItem: newCount > 0 ? { ...held, count: newCount } : null,
152
+ }
153
+ }
154
+ }
155
+ } else if (action.mode === 'shift' && slotState?.item) {
156
+ // Simulate shift-click: just log it
157
+ }
158
+
159
+ emit({ type: 'windowUpdate', state: windowState })
160
+ }
161
+
162
+ if (action.type === 'drop' && windowState) {
163
+ const slots = [...windowState.slots]
164
+ const slotState = slots.find((s) => s.index === action.slotIndex)
165
+ if (slotState?.item) {
166
+ const idx = slots.indexOf(slotState)
167
+ if (action.all) {
168
+ slots[idx] = { ...slotState, item: null }
169
+ } else {
170
+ const count = slotState.item.count - 1
171
+ slots[idx] = { ...slotState, item: count > 0 ? { ...slotState.item, count } : null }
172
+ }
173
+ windowState = { ...windowState, slots }
174
+ emit({ type: 'windowUpdate', state: windowState })
175
+ }
176
+ }
177
+ },
178
+
179
+ closeWindow: () => {
180
+ windowState = null
181
+ emit({ type: 'windowClose' })
182
+ },
183
+
184
+ subscribe: (listener: ConnectorListener) => {
185
+ listeners.add(listener)
186
+ return () => listeners.delete(listener)
187
+ },
188
+
189
+ updateSlots: (slots: SlotState[]) => {
190
+ if (windowState) {
191
+ windowState = { ...windowState, slots }
192
+ emit({ type: 'windowUpdate', state: windowState })
193
+ }
194
+ },
195
+
196
+ setHeldItem: (item: ItemStack | null) => {
197
+ if (windowState) {
198
+ windowState = { ...windowState, heldItem: item }
199
+ emit({ type: 'heldItemChange', item })
200
+ }
201
+ },
202
+
203
+ openWindow: (type: string, title: string, slots: SlotState[]) => {
204
+ windowState = { windowId: ++actionCounter, type, title, slots, heldItem: null }
205
+ emit({ type: 'windowOpen', state: windowState })
206
+ },
207
+
208
+ closeWindowExternal: () => {
209
+ windowState = null
210
+ emit({ type: 'windowClose' })
211
+ },
212
+ }
213
+ }
@@ -0,0 +1,4 @@
1
+ export type { InventoryConnector, ConnectorEvent, ConnectorListener, MineflayerBot } from './types'
2
+ export { createMineflayerConnector } from './mineflayer'
3
+ export { createDemoConnector } from './demo'
4
+ export type { DemoConnectorOptions, ActionLogEntry } from './demo'
@@ -0,0 +1,113 @@
1
+ import type { InventoryAction, InventoryWindowState, PlayerState, SlotState, ItemStack } from '../types'
2
+ import type { InventoryConnector, ConnectorListener, ConnectorEvent, MineflayerBot } from './types'
3
+
4
+ function botSlotToItemStack(slot: MineflayerBot['heldItem']): ItemStack | null {
5
+ if (!slot || slot.type === -1 || slot.type === 0) return null
6
+ return {
7
+ type: slot.type,
8
+ count: slot.count,
9
+ metadata: slot.metadata,
10
+ nbt: slot.nbt as Record<string, unknown> | undefined,
11
+ }
12
+ }
13
+
14
+ function botSlotsToSlotStates(slots: MineflayerBot['inventory']['slots']): SlotState[] {
15
+ return slots.map((slot, index) => ({
16
+ index,
17
+ item: botSlotToItemStack(slot),
18
+ }))
19
+ }
20
+
21
+ function modeFromAction(action: InventoryAction): [number, number] {
22
+ if (action.type !== 'click') return [0, 0]
23
+ if (action.mode === 'shift') return [action.button === 'left' ? 0 : 1, 1]
24
+ if (action.mode === 'number' && action.numberKey !== undefined) return [action.numberKey, 2]
25
+ if (action.mode === 'middle') return [2, 3]
26
+ if (action.mode === 'drop') return [action.button === 'left' ? 0 : 1, 4]
27
+ if (action.mode === 'drag') return [action.button === 'left' ? 0 : 4, 5]
28
+ if (action.mode === 'double') return [0, 6]
29
+ return [action.button === 'left' ? 0 : 1, 0]
30
+ }
31
+
32
+ export function createMineflayerConnector(bot: MineflayerBot): InventoryConnector {
33
+ const listeners = new Set<ConnectorListener>()
34
+
35
+ function emit(event: ConnectorEvent) {
36
+ listeners.forEach((l) => l(event))
37
+ }
38
+
39
+ function buildWindowState(): InventoryWindowState | null {
40
+ const win = bot.currentWindow
41
+ if (!win) return null
42
+ return {
43
+ windowId: win.id,
44
+ type: win.type ?? 'unknown',
45
+ title: win.title,
46
+ slots: botSlotsToSlotStates(win.slots),
47
+ heldItem: botSlotToItemStack(bot.heldItem),
48
+ }
49
+ }
50
+
51
+ function buildPlayerState(): PlayerState {
52
+ const inv = bot.inventory.slots
53
+ return {
54
+ activeHotbarSlot: bot.quickBarSlot,
55
+ inventory: botSlotsToSlotStates(inv),
56
+ }
57
+ }
58
+
59
+ const onWindowOpen = () => {
60
+ const state = buildWindowState()
61
+ if (state) emit({ type: 'windowOpen', state })
62
+ }
63
+
64
+ const onWindowClose = () => {
65
+ emit({ type: 'windowClose' })
66
+ }
67
+
68
+ const onSetSlot = () => {
69
+ const state = buildWindowState()
70
+ if (state) emit({ type: 'windowUpdate', state })
71
+ else {
72
+ emit({ type: 'playerUpdate', state: buildPlayerState() })
73
+ }
74
+ }
75
+
76
+ bot.on('windowOpen', onWindowOpen)
77
+ bot.on('windowClose', onWindowClose)
78
+ bot.on('setSlot', onSetSlot)
79
+
80
+ return {
81
+ getWindowState: buildWindowState,
82
+ getPlayerState: buildPlayerState,
83
+
84
+ sendAction: async (action: InventoryAction) => {
85
+ if (action.type === 'click') {
86
+ const [mouseButton, mode] = modeFromAction(action)
87
+ await bot.clickWindow(action.slotIndex, mouseButton, mode)
88
+ } else if (action.type === 'drop') {
89
+ await bot.clickWindow(action.slotIndex, action.all ? 1 : 0, 4)
90
+ } else if (action.type === 'close') {
91
+ const win = bot.currentWindow
92
+ if (win) bot.closeWindow(win)
93
+ } else if (action.type === 'hotbar-swap') {
94
+ await bot.clickWindow(action.slotIndex, action.hotbarSlot, 2)
95
+ }
96
+ },
97
+
98
+ closeWindow: () => {
99
+ const win = bot.currentWindow
100
+ if (win) bot.closeWindow(win)
101
+ },
102
+
103
+ subscribe: (listener: ConnectorListener) => {
104
+ listeners.add(listener)
105
+ return () => {
106
+ listeners.delete(listener)
107
+ bot.off('windowOpen', onWindowOpen)
108
+ bot.off('windowClose', onWindowClose)
109
+ bot.off('setSlot', onSetSlot)
110
+ }
111
+ },
112
+ }
113
+ }
@@ -0,0 +1,41 @@
1
+ import type { InventoryAction, InventoryWindowState, PlayerState, TradeOffer } from '../types'
2
+
3
+ export interface InventoryConnector {
4
+ getWindowState(): InventoryWindowState | null
5
+ getPlayerState(): PlayerState | null
6
+ sendAction(action: InventoryAction): void | Promise<void>
7
+ closeWindow(): void | Promise<void>
8
+ subscribe(listener: ConnectorListener): () => void
9
+ }
10
+
11
+ export type ConnectorEvent =
12
+ | { type: 'windowOpen'; state: InventoryWindowState }
13
+ | { type: 'windowClose' }
14
+ | { type: 'windowUpdate'; state: InventoryWindowState }
15
+ | { type: 'playerUpdate'; state: PlayerState }
16
+ | { type: 'heldItemChange'; item: InventoryWindowState['heldItem'] }
17
+
18
+ export type ConnectorListener = (event: ConnectorEvent) => void
19
+
20
+ export interface MineflayerBot {
21
+ inventory: {
22
+ slots: Array<{ type: number; count: number; metadata?: number; nbt?: unknown } | null>
23
+ }
24
+ currentWindow: {
25
+ id: number
26
+ type: string
27
+ title?: string
28
+ slots: Array<{ type: number; count: number; metadata?: number; nbt?: unknown } | null>
29
+ } | null
30
+ heldItem: { type: number; count: number; metadata?: number; nbt?: unknown } | null
31
+ health: number
32
+ food: number
33
+ foodSaturation: number
34
+ experience: { level: number; progress: number; points: number }
35
+ quickBarSlot: number
36
+ clickWindow(slot: number, mouseButton: number, mode: number): Promise<void>
37
+ closeWindow(window: unknown): void
38
+ on(event: string, listener: (...args: unknown[]) => void): void
39
+ off(event: string, listener: (...args: unknown[]) => void): void
40
+ removeListener(event: string, listener: (...args: unknown[]) => void): void
41
+ }