minecraft-inventory 0.1.3 → 0.1.5
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.
- package/package.json +2 -2
- package/src/assets/entities/horse.png +0 -0
- package/src/assets/entities/llama.png +0 -0
- package/src/assets/entities/player.png +0 -0
- package/src/components/InventoryOverlay/InventoryOverlay.tsx +56 -9
- package/src/components/InventoryWindow/AnvilInput.tsx +102 -0
- package/src/components/InventoryWindow/EntityDisplay.tsx +46 -0
- package/src/components/InventoryWindow/InventoryBackground.tsx +72 -31
- package/src/components/InventoryWindow/InventoryWindow.tsx +14 -0
- package/src/components/ItemCanvas/ItemCanvas.tsx +10 -7
- package/src/components/JEI/JEI.tsx +7 -1
- package/src/components/Notes/Notes.tsx +76 -45
- package/src/components/Slot/Slot.tsx +114 -11
- package/src/connector/demo.ts +65 -0
- package/src/connector/mineflayer.ts +83 -25
- package/src/context/InventoryContext.tsx +160 -9
- package/src/context/TextureContext.tsx +9 -2
- package/src/generated/localTextures.ts +24 -15
- package/src/hooks/useKeyboardShortcuts.ts +61 -6
- package/src/index.tsx +5 -1
- package/src/registry/index.ts +30 -1
- package/src/registry/inventories.ts +99 -6
- package/src/styles/tokens.css +6 -0
- package/src/types.ts +30 -0
- package/src/utils/isItemEqual.ts +41 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import React, { createContext, useContext, useEffect, useRef, useState, useCallback } from 'react'
|
|
1
|
+
import React, { createContext, useContext, useEffect, useRef, useState, useCallback, useMemo } from 'react'
|
|
2
2
|
import type { InventoryWindowState, PlayerState, ItemStack, SlotState } from '../types'
|
|
3
3
|
import type { InventoryConnector } from '../connector/types'
|
|
4
|
+
import { isItemEqual, getMaxStackSize } from '../utils/isItemEqual'
|
|
5
|
+
|
|
6
|
+
export interface DragPreviewEntry {
|
|
7
|
+
count: number
|
|
8
|
+
}
|
|
4
9
|
|
|
5
10
|
export interface InventoryContextValue {
|
|
6
11
|
windowState: InventoryWindowState | null
|
|
@@ -12,6 +17,8 @@ export interface InventoryContextValue {
|
|
|
12
17
|
isDragging: boolean
|
|
13
18
|
dragSlots: number[]
|
|
14
19
|
dragButton: 'left' | 'right' | null
|
|
20
|
+
/** Client-side preview of item counts for each slot in the current drag */
|
|
21
|
+
dragPreview: Map<number, DragPreviewEntry>
|
|
15
22
|
startDrag: (slotIndex: number, button: 'left' | 'right') => void
|
|
16
23
|
addDragSlot: (slotIndex: number) => void
|
|
17
24
|
endDrag: () => void
|
|
@@ -24,6 +31,17 @@ export interface InventoryContextValue {
|
|
|
24
31
|
/** Pixel offset from the cursor item's top-left to the grab point, preserving pick-up position */
|
|
25
32
|
grabOffset: { x: number; y: number }
|
|
26
33
|
setGrabOffset: (offset: { x: number; y: number }) => void
|
|
34
|
+
/** Whether drag/spread operations are disabled */
|
|
35
|
+
noDragSpread: boolean
|
|
36
|
+
/** Whether P-key slot numbering mode is active */
|
|
37
|
+
pKeyActive: boolean
|
|
38
|
+
setPKeyActive: (v: boolean) => void
|
|
39
|
+
/** Currently focused slot index (via P-key number entry) */
|
|
40
|
+
focusedSlot: number | null
|
|
41
|
+
setFocusedSlot: (slot: number | null) => void
|
|
42
|
+
/** Pending first digit for P-key slot number entry */
|
|
43
|
+
pKeyDigit: string
|
|
44
|
+
setPKeyDigit: (d: string) => void
|
|
27
45
|
}
|
|
28
46
|
|
|
29
47
|
const InventoryContext = createContext<InventoryContextValue | null>(null)
|
|
@@ -37,9 +55,10 @@ export function useInventoryContext(): InventoryContextValue {
|
|
|
37
55
|
interface InventoryProviderProps {
|
|
38
56
|
connector: InventoryConnector | null
|
|
39
57
|
children: React.ReactNode
|
|
58
|
+
noDragSpread?: boolean
|
|
40
59
|
}
|
|
41
60
|
|
|
42
|
-
export function InventoryProvider({ connector, children }: InventoryProviderProps) {
|
|
61
|
+
export function InventoryProvider({ connector, children, noDragSpread = false }: InventoryProviderProps) {
|
|
43
62
|
const [windowState, setWindowState] = useState<InventoryWindowState | null>(
|
|
44
63
|
() => connector?.getWindowState() ?? null,
|
|
45
64
|
)
|
|
@@ -53,12 +72,24 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
|
|
|
53
72
|
const [isDragging, setIsDragging] = useState(false)
|
|
54
73
|
const [dragSlots, setDragSlots] = useState<number[]>([])
|
|
55
74
|
const [dragButton, setDragButton] = useState<'left' | 'right' | null>(null)
|
|
75
|
+
const [dragPreview, setDragPreview] = useState<Map<number, DragPreviewEntry>>(new Map())
|
|
56
76
|
const [activeNumberKey, setActiveNumberKey] = useState<number | null>(null)
|
|
57
77
|
const [grabOffset, setGrabOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
|
|
78
|
+
const [pKeyActive, setPKeyActive] = useState(false)
|
|
79
|
+
const [focusedSlot, setFocusedSlot] = useState<number | null>(null)
|
|
80
|
+
const [pKeyDigit, setPKeyDigit] = useState('')
|
|
58
81
|
|
|
59
82
|
const connectorRef = useRef(connector)
|
|
60
83
|
connectorRef.current = connector
|
|
61
84
|
|
|
85
|
+
// Refs so endDrag can read current values without being in its dep array
|
|
86
|
+
const heldItemRef = useRef(heldItem)
|
|
87
|
+
heldItemRef.current = heldItem
|
|
88
|
+
const windowStateRef = useRef(windowState)
|
|
89
|
+
windowStateRef.current = windowState
|
|
90
|
+
const dragButtonRef = useRef(dragButton)
|
|
91
|
+
dragButtonRef.current = dragButton
|
|
92
|
+
|
|
62
93
|
useEffect(() => {
|
|
63
94
|
if (!connector) return
|
|
64
95
|
setWindowState(connector.getWindowState())
|
|
@@ -92,35 +123,134 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
|
|
|
92
123
|
[],
|
|
93
124
|
)
|
|
94
125
|
|
|
126
|
+
const computeDragPreview = useCallback((slots: number[], button: 'left' | 'right', held: ItemStack | null, ws: InventoryWindowState | null) => {
|
|
127
|
+
const preview = new Map<number, DragPreviewEntry>()
|
|
128
|
+
if (!held || slots.length === 0) return preview
|
|
129
|
+
|
|
130
|
+
const maxStack = getMaxStackSize(held)
|
|
131
|
+
|
|
132
|
+
if (button === 'left') {
|
|
133
|
+
// Only spread into compatible slots (empty or same item type)
|
|
134
|
+
const compatibleSlots = slots.filter((idx) => {
|
|
135
|
+
const existingItem = ws?.slots.find((s) => s.index === idx)?.item
|
|
136
|
+
return !existingItem || isItemEqual(existingItem, held)
|
|
137
|
+
})
|
|
138
|
+
if (compatibleSlots.length === 0) return preview
|
|
139
|
+
const perSlot = Math.floor(held.count / compatibleSlots.length)
|
|
140
|
+
// Vanilla behavior: if perSlot=0 (more slots than items), nothing is distributed
|
|
141
|
+
if (perSlot === 0) return preview
|
|
142
|
+
for (const idx of compatibleSlots) {
|
|
143
|
+
const existingCount = ws?.slots.find((s) => s.index === idx)?.item?.count ?? 0
|
|
144
|
+
const total = Math.min(existingCount + perSlot, maxStack)
|
|
145
|
+
preview.set(idx, { count: total })
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
for (const idx of slots) {
|
|
149
|
+
const existing = ws?.slots.find((s) => s.index === idx)
|
|
150
|
+
const existingItem = existing?.item
|
|
151
|
+
if (existingItem && !isItemEqual(existingItem, held)) continue
|
|
152
|
+
const existingCount = existingItem ? existingItem.count : 0
|
|
153
|
+
const total = Math.min(existingCount + 1, maxStack)
|
|
154
|
+
preview.set(idx, { count: total })
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return preview
|
|
158
|
+
}, [])
|
|
159
|
+
|
|
95
160
|
const startDrag = useCallback((slotIndex: number, button: 'left' | 'right') => {
|
|
161
|
+
if (noDragSpread) return
|
|
96
162
|
setIsDragging(true)
|
|
97
163
|
setDragButton(button)
|
|
98
164
|
setDragSlots([slotIndex])
|
|
99
|
-
|
|
100
|
-
}, [])
|
|
165
|
+
setDragPreview(new Map())
|
|
166
|
+
}, [noDragSpread])
|
|
101
167
|
|
|
102
168
|
const addDragSlot = useCallback((slotIndex: number) => {
|
|
103
169
|
setDragSlots((prev) => {
|
|
104
170
|
if (prev.includes(slotIndex)) return prev
|
|
105
|
-
|
|
171
|
+
const next = [...prev, slotIndex]
|
|
172
|
+
setDragPreview(computeDragPreview(next, dragButton!, heldItem, windowState))
|
|
173
|
+
return next
|
|
106
174
|
})
|
|
107
|
-
}, [])
|
|
175
|
+
}, [computeDragPreview, dragButton, heldItem, windowState])
|
|
108
176
|
|
|
109
177
|
const endDrag = useCallback(() => {
|
|
110
178
|
setDragSlots((slots) => {
|
|
111
|
-
|
|
112
|
-
|
|
179
|
+
const button = dragButtonRef.current
|
|
180
|
+
const held = heldItemRef.current
|
|
181
|
+
const ws = windowStateRef.current
|
|
182
|
+
|
|
183
|
+
// Only send drag action if multiple slots were involved (single slot = normal click)
|
|
184
|
+
if (slots.length > 1 && button && held) {
|
|
185
|
+
connectorRef.current?.sendAction({ type: 'drag', slots, button })
|
|
186
|
+
|
|
187
|
+
// Optimistic client-side update: apply item distribution immediately
|
|
188
|
+
// so slots visually update before server responds.
|
|
189
|
+
const maxStack = getMaxStackSize(held)
|
|
190
|
+
const newSlots = ws ? [...ws.slots] : []
|
|
191
|
+
|
|
192
|
+
if (button === 'left') {
|
|
193
|
+
const compatibleSlots = slots.filter((idx) => {
|
|
194
|
+
const existing = newSlots.find((s) => s.index === idx)?.item
|
|
195
|
+
return !existing || isItemEqual(existing, held)
|
|
196
|
+
})
|
|
197
|
+
if (compatibleSlots.length > 0) {
|
|
198
|
+
const perSlot = Math.floor(held.count / compatibleSlots.length)
|
|
199
|
+
// Vanilla behavior: if perSlot=0 (more slots than items), nothing is distributed
|
|
200
|
+
if (perSlot > 0) {
|
|
201
|
+
let totalPlaced = 0
|
|
202
|
+
for (const idx of compatibleSlots) {
|
|
203
|
+
const existingIdx = newSlots.findIndex((s) => s.index === idx)
|
|
204
|
+
const existingCount = existingIdx >= 0 ? (newSlots[existingIdx].item?.count ?? 0) : 0
|
|
205
|
+
const add = Math.min(perSlot, maxStack - existingCount)
|
|
206
|
+
totalPlaced += add
|
|
207
|
+
const newCount = existingCount + add
|
|
208
|
+
if (existingIdx >= 0) {
|
|
209
|
+
newSlots[existingIdx] = { index: idx, item: { ...held, count: newCount } }
|
|
210
|
+
} else {
|
|
211
|
+
newSlots.push({ index: idx, item: { ...held, count: newCount } })
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (ws) setWindowState({ ...ws, slots: newSlots })
|
|
215
|
+
const remaining = held.count - totalPlaced
|
|
216
|
+
if (remaining > 0) setHeldItemState({ ...held, count: remaining })
|
|
217
|
+
else setHeldItemState(null)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
// Right-click drag: place 1 per slot
|
|
222
|
+
let remaining = held.count
|
|
223
|
+
for (const idx of slots) {
|
|
224
|
+
if (remaining <= 0) break
|
|
225
|
+
const existingIdx = newSlots.findIndex((s) => s.index === idx)
|
|
226
|
+
const existingItem = existingIdx >= 0 ? newSlots[existingIdx].item : null
|
|
227
|
+
if (existingItem && !isItemEqual(existingItem, held)) continue
|
|
228
|
+
const existingCount = existingItem?.count ?? 0
|
|
229
|
+
const newCount = Math.min(existingCount + 1, maxStack)
|
|
230
|
+
if (existingIdx >= 0) {
|
|
231
|
+
newSlots[existingIdx] = { index: idx, item: { ...held, count: newCount } }
|
|
232
|
+
} else {
|
|
233
|
+
newSlots.push({ index: idx, item: { ...held, count: newCount } })
|
|
234
|
+
}
|
|
235
|
+
remaining--
|
|
236
|
+
}
|
|
237
|
+
if (ws) setWindowState({ ...ws, slots: newSlots })
|
|
238
|
+
if (remaining <= 0) setHeldItemState(null)
|
|
239
|
+
else setHeldItemState({ ...held, count: remaining })
|
|
240
|
+
}
|
|
113
241
|
}
|
|
114
242
|
return []
|
|
115
243
|
})
|
|
116
244
|
setIsDragging(false)
|
|
117
245
|
setDragButton(null)
|
|
118
|
-
|
|
246
|
+
setDragPreview(new Map())
|
|
247
|
+
}, [])
|
|
119
248
|
|
|
120
249
|
const cancelDrag = useCallback(() => {
|
|
121
250
|
setIsDragging(false)
|
|
122
251
|
setDragSlots([])
|
|
123
252
|
setDragButton(null)
|
|
253
|
+
setDragPreview(new Map())
|
|
124
254
|
}, [])
|
|
125
255
|
|
|
126
256
|
const getSlot = useCallback(
|
|
@@ -130,6 +260,19 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
|
|
|
130
260
|
[windowState],
|
|
131
261
|
)
|
|
132
262
|
|
|
263
|
+
// Expose state to window for easier debugging from browser DevTools.
|
|
264
|
+
// Access via: window.__mcInv
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
;(window as unknown as Record<string, unknown>).__mcInv = {
|
|
267
|
+
get windowState() { return windowStateRef.current },
|
|
268
|
+
get heldItem() { return heldItemRef.current },
|
|
269
|
+
get dragSlots() { return dragSlots },
|
|
270
|
+
get isDragging() { return isDragging },
|
|
271
|
+
get focusedSlot() { return focusedSlot },
|
|
272
|
+
get pKeyActive() { return pKeyActive },
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
133
276
|
const value: InventoryContextValue = {
|
|
134
277
|
windowState,
|
|
135
278
|
playerState,
|
|
@@ -140,6 +283,7 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
|
|
|
140
283
|
isDragging,
|
|
141
284
|
dragSlots,
|
|
142
285
|
dragButton,
|
|
286
|
+
dragPreview,
|
|
143
287
|
startDrag,
|
|
144
288
|
addDragSlot,
|
|
145
289
|
endDrag,
|
|
@@ -151,6 +295,13 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
|
|
|
151
295
|
getSlot,
|
|
152
296
|
grabOffset,
|
|
153
297
|
setGrabOffset,
|
|
298
|
+
noDragSpread,
|
|
299
|
+
pKeyActive,
|
|
300
|
+
setPKeyActive,
|
|
301
|
+
focusedSlot,
|
|
302
|
+
setFocusedSlot,
|
|
303
|
+
pKeyDigit,
|
|
304
|
+
setPKeyDigit,
|
|
154
305
|
}
|
|
155
306
|
|
|
156
307
|
return <InventoryContext.Provider value={value}>{children}</InventoryContext.Provider>
|
|
@@ -9,7 +9,13 @@ export const MC_GUI_BASE = MC_ASSETS_BASE
|
|
|
9
9
|
|
|
10
10
|
export interface TextureConfig {
|
|
11
11
|
baseUrl: string
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Resolve item texture URL.
|
|
14
|
+
* When `item.textureKey` is set it is used as the path relative to the items texture root
|
|
15
|
+
* (e.g. `"item/dye_black"` → `<base>/item/dye_black.png`).
|
|
16
|
+
* Otherwise falls back to `name` then `type`.
|
|
17
|
+
*/
|
|
18
|
+
getItemTextureUrl(item: { type: number; name?: string; textureKey?: string }): string
|
|
13
19
|
getBlockTextureUrl(item: { type: number; name?: string }): string
|
|
14
20
|
/** Supports full mc-assets paths (e.g. "1.21.11/textures/gui/container/anvil.png") */
|
|
15
21
|
getGuiTextureUrl(path: string, version?: string): string
|
|
@@ -23,8 +29,9 @@ function buildDefault(base: string): TextureConfig {
|
|
|
23
29
|
return {
|
|
24
30
|
baseUrl: base,
|
|
25
31
|
|
|
26
|
-
getItemTextureUrl({ type, name }) {
|
|
32
|
+
getItemTextureUrl({ type, name, textureKey }) {
|
|
27
33
|
const root = isRemote ? MC_ITEMS_BASE : base
|
|
34
|
+
if (textureKey) return `${root}/${textureKey}.png`
|
|
28
35
|
if (name) return `${root}/item/${name}.png`
|
|
29
36
|
return `${root}/item/${type}.png`
|
|
30
37
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-texture-imports.mjs — do not edit manually
|
|
2
|
-
// Re-run `
|
|
2
|
+
// Re-run `pnpm gen:textures` after changing inventories.
|
|
3
3
|
|
|
4
4
|
import _gui_container_inventory from 'mc-assets/dist/other-textures/latest/gui/container/inventory.png'
|
|
5
5
|
import _gui_container_shulker_box from 'mc-assets/dist/other-textures/latest/gui/container/shulker_box.png'
|
|
@@ -13,6 +13,7 @@ import _gui_container_anvil from 'mc-assets/dist/other-textures/latest/gui/conta
|
|
|
13
13
|
import _gui_container_grindstone from 'mc-assets/dist/other-textures/latest/gui/container/grindstone.png'
|
|
14
14
|
import _gui_container_enchanting_table from 'mc-assets/dist/other-textures/latest/gui/container/enchanting_table.png'
|
|
15
15
|
import _gui_container_smithing from 'mc-assets/dist/other-textures/latest/gui/container/smithing.png'
|
|
16
|
+
import _gui_container_smithing_2 from 'mc-assets/dist/other-textures/1.19.4/gui/container/smithing.png'
|
|
16
17
|
import _gui_container_hopper from 'mc-assets/dist/other-textures/latest/gui/container/hopper.png'
|
|
17
18
|
import _gui_container_dispenser from 'mc-assets/dist/other-textures/latest/gui/container/dispenser.png'
|
|
18
19
|
import _gui_container_beacon from 'mc-assets/dist/other-textures/latest/gui/container/beacon.png'
|
|
@@ -24,15 +25,21 @@ import _gui_container_stonecutter from 'mc-assets/dist/other-textures/latest/gui
|
|
|
24
25
|
import _gui_container_crafter from 'mc-assets/dist/other-textures/latest/gui/container/crafter.png'
|
|
25
26
|
import _gui_container_creative_inventory_tab_items from 'mc-assets/dist/other-textures/latest/gui/container/creative_inventory/tab_items.png'
|
|
26
27
|
import _gui_widgets from 'mc-assets/dist/other-textures/1.15/gui/widgets.png'
|
|
28
|
+
import _gui_sprites_container_anvil_text_field from 'mc-assets/dist/other-textures/latest/gui/sprites/container/anvil/text_field.png'
|
|
29
|
+
import _gui_sprites_container_anvil_text_field_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/anvil/text_field_disabled.png'
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Maps each inventory type name to its texture path (version prefix stripped).
|
|
30
|
-
* Useful for building custom texture resolvers or inspecting the full texture list.
|
|
31
33
|
*/
|
|
32
34
|
export const allContainerPaths: Record<string, string> = {
|
|
33
35
|
player: 'gui/container/inventory.png',
|
|
34
36
|
chest: 'gui/container/shulker_box.png',
|
|
35
|
-
generic_9x1: 'gui/container/
|
|
37
|
+
generic_9x1: 'gui/container/generic_54.png',
|
|
38
|
+
generic_9x2: 'gui/container/generic_54.png',
|
|
39
|
+
generic_9x3: 'gui/container/generic_54.png',
|
|
40
|
+
generic_9x4: 'gui/container/generic_54.png',
|
|
41
|
+
generic_9x5: 'gui/container/generic_54.png',
|
|
42
|
+
generic_9x6: 'gui/container/generic_54.png',
|
|
36
43
|
large_chest: 'gui/container/generic_54.png',
|
|
37
44
|
crafting_table: 'gui/container/crafting_table.png',
|
|
38
45
|
furnace: 'gui/container/furnace.png',
|
|
@@ -62,7 +69,7 @@ export const allContainerPaths: Record<string, string> = {
|
|
|
62
69
|
hotbar: 'gui/widgets.png',
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
// Internal:
|
|
72
|
+
// Internal: versioned texture key → bundled asset URL (or undefined → remote fallback)
|
|
66
73
|
const _map: Record<string, string | undefined> = {
|
|
67
74
|
'1.21.11/textures/gui/container/inventory.png': _gui_container_inventory,
|
|
68
75
|
'1.21.11/textures/gui/container/shulker_box.png': _gui_container_shulker_box,
|
|
@@ -76,7 +83,7 @@ const _map: Record<string, string | undefined> = {
|
|
|
76
83
|
'1.21.11/textures/gui/container/grindstone.png': _gui_container_grindstone,
|
|
77
84
|
'1.21.11/textures/gui/container/enchanting_table.png': _gui_container_enchanting_table,
|
|
78
85
|
'1.21.11/textures/gui/container/smithing.png': _gui_container_smithing,
|
|
79
|
-
'1.16.4/textures/gui/container/smithing.png':
|
|
86
|
+
'1.16.4/textures/gui/container/smithing.png': _gui_container_smithing_2,
|
|
80
87
|
'1.21.11/textures/gui/container/hopper.png': _gui_container_hopper,
|
|
81
88
|
'1.21.11/textures/gui/container/dispenser.png': _gui_container_dispenser,
|
|
82
89
|
'1.21.11/textures/gui/container/beacon.png': _gui_container_beacon,
|
|
@@ -88,25 +95,27 @@ const _map: Record<string, string | undefined> = {
|
|
|
88
95
|
'1.21.11/textures/gui/container/crafter.png': _gui_container_crafter,
|
|
89
96
|
'1.21.11/textures/gui/container/creative_inventory/tab_items.png': _gui_container_creative_inventory_tab_items,
|
|
90
97
|
'1.15/textures/gui/widgets.png': _gui_widgets,
|
|
98
|
+
'1.21.11/textures/gui/sprites/container/anvil/text_field.png': _gui_sprites_container_anvil_text_field,
|
|
99
|
+
'1.21.11/textures/gui/sprites/container/anvil/text_field_disabled.png': _gui_sprites_container_anvil_text_field_disabled,
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
/**
|
|
94
103
|
* Partial TextureConfig that resolves inventory GUI textures from locally bundled
|
|
95
104
|
* mc-assets assets instead of remote GitHub URLs.
|
|
96
105
|
*
|
|
97
|
-
* Pass to `<TextureProvider config={localTexturesConfig}>` to use offline/bundled assets
|
|
106
|
+
* Pass to `<TextureProvider config={localTexturesConfig}>` to use offline/bundled assets.
|
|
98
107
|
*
|
|
99
|
-
*
|
|
100
|
-
* import { localTexturesConfig } from './generated/localTextures'
|
|
101
|
-
* <TextureProvider config={localTexturesConfig}>
|
|
102
|
-
* <InventoryOverlay type="chest" ... />
|
|
103
|
-
* </TextureProvider>
|
|
104
|
-
* ```
|
|
108
|
+
* Unknown paths (not bundled) fall back to the mc-assets remote URL automatically.
|
|
105
109
|
*/
|
|
110
|
+
const _MC_ASSETS_REMOTE = "https://raw.githubusercontent.com/zardoy/mc-assets/refs/heads/gh-pages"
|
|
106
111
|
export const localTexturesConfig = {
|
|
107
112
|
getGuiTextureUrl(path: string): string {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
const local = _map[path] as string | undefined
|
|
114
|
+
if (local) return local
|
|
115
|
+
// Fall back to remote mc-assets URL for paths not in the bundle
|
|
116
|
+
if (path.endsWith('.png') && path.includes('/textures/')) {
|
|
117
|
+
return `${_MC_ASSETS_REMOTE}/${path}`
|
|
118
|
+
}
|
|
119
|
+
return path
|
|
111
120
|
},
|
|
112
121
|
}
|
|
@@ -1,15 +1,70 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
1
|
+
import { useEffect, useCallback, useRef } from 'react'
|
|
2
2
|
import { useInventoryContext } from '../context/InventoryContext'
|
|
3
3
|
|
|
4
4
|
export function useKeyboardShortcuts(enabled = true) {
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
setActiveNumberKey,
|
|
7
|
+
hoveredSlot,
|
|
8
|
+
sendAction,
|
|
9
|
+
pKeyActive,
|
|
10
|
+
setPKeyActive,
|
|
11
|
+
focusedSlot,
|
|
12
|
+
setFocusedSlot,
|
|
13
|
+
pKeyDigit,
|
|
14
|
+
setPKeyDigit,
|
|
15
|
+
} = useInventoryContext()
|
|
16
|
+
|
|
17
|
+
const pKeyDigitRef = useRef(pKeyDigit)
|
|
18
|
+
pKeyDigitRef.current = pKeyDigit
|
|
6
19
|
|
|
7
20
|
useEffect(() => {
|
|
8
21
|
if (!enabled) return
|
|
9
22
|
|
|
10
23
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
11
|
-
//
|
|
12
|
-
if (e.
|
|
24
|
+
// Ignore when typing in inputs
|
|
25
|
+
if ((e.target as HTMLElement)?.tagName === 'INPUT' || (e.target as HTMLElement)?.tagName === 'TEXTAREA') return
|
|
26
|
+
|
|
27
|
+
// P toggles slot numbering mode
|
|
28
|
+
if (e.code === 'KeyP' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
|
29
|
+
if (pKeyActive) {
|
|
30
|
+
setPKeyActive(false)
|
|
31
|
+
setFocusedSlot(null)
|
|
32
|
+
setPKeyDigit('')
|
|
33
|
+
} else {
|
|
34
|
+
setPKeyActive(true)
|
|
35
|
+
setPKeyDigit('')
|
|
36
|
+
}
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Escape exits P-key mode
|
|
41
|
+
if (e.code === 'Escape' && pKeyActive) {
|
|
42
|
+
setPKeyActive(false)
|
|
43
|
+
setFocusedSlot(null)
|
|
44
|
+
setPKeyDigit('')
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Number entry during P-key mode
|
|
49
|
+
if (pKeyActive && e.code >= 'Digit0' && e.code <= 'Digit9') {
|
|
50
|
+
const digit = e.code.replace('Digit', '')
|
|
51
|
+
const current = pKeyDigitRef.current
|
|
52
|
+
if (current.length === 0) {
|
|
53
|
+
setPKeyDigit(digit)
|
|
54
|
+
} else {
|
|
55
|
+
const slotNum = parseInt(current + digit, 10)
|
|
56
|
+
setPKeyDigit('')
|
|
57
|
+
if (slotNum <= 99) {
|
|
58
|
+
setFocusedSlot(slotNum)
|
|
59
|
+
// Exit P mode but keep focused slot
|
|
60
|
+
setPKeyActive(false)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Normal hotbar number keys (only when P-key not active)
|
|
67
|
+
if (!pKeyActive && e.code >= 'Digit1' && e.code <= 'Digit9') {
|
|
13
68
|
setActiveNumberKey(parseInt(e.code.replace('Digit', ''), 10) - 1)
|
|
14
69
|
}
|
|
15
70
|
|
|
@@ -26,7 +81,7 @@ export function useKeyboardShortcuts(enabled = true) {
|
|
|
26
81
|
}
|
|
27
82
|
|
|
28
83
|
const handleKeyUp = (e: KeyboardEvent) => {
|
|
29
|
-
if (e.code >= 'Digit1' && e.code <= 'Digit9') {
|
|
84
|
+
if (!pKeyActive && e.code >= 'Digit1' && e.code <= 'Digit9') {
|
|
30
85
|
setActiveNumberKey(null)
|
|
31
86
|
}
|
|
32
87
|
}
|
|
@@ -37,5 +92,5 @@ export function useKeyboardShortcuts(enabled = true) {
|
|
|
37
92
|
window.removeEventListener('keydown', handleKeyDown)
|
|
38
93
|
window.removeEventListener('keyup', handleKeyUp)
|
|
39
94
|
}
|
|
40
|
-
}, [enabled, hoveredSlot, sendAction, setActiveNumberKey])
|
|
95
|
+
}, [enabled, hoveredSlot, sendAction, setActiveNumberKey, pKeyActive, setPKeyActive, focusedSlot, setFocusedSlot, setPKeyDigit])
|
|
41
96
|
}
|
package/src/index.tsx
CHANGED
|
@@ -34,6 +34,9 @@ export type { MountedInventory } from './mount'
|
|
|
34
34
|
export { useMobile } from './hooks/useMobile'
|
|
35
35
|
export { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
|
|
36
36
|
|
|
37
|
+
// Utilities
|
|
38
|
+
export { isItemEqual, getMaxStackSize } from './utils/isItemEqual'
|
|
39
|
+
|
|
37
40
|
// Connector
|
|
38
41
|
export { createMineflayerConnector } from './connector/mineflayer'
|
|
39
42
|
export { createDemoConnector } from './connector/demo'
|
|
@@ -46,7 +49,7 @@ export type {
|
|
|
46
49
|
export type { ActionLogEntry, DemoConnectorOptions } from './connector/demo'
|
|
47
50
|
|
|
48
51
|
// Registry
|
|
49
|
-
export { registerInventoryType, getInventoryType, getAllInventoryTypes } from './registry'
|
|
52
|
+
export { registerInventoryType, registerTypeAlias, getInventoryType, getAllInventoryTypes } from './registry'
|
|
50
53
|
export type { InventoryTypeDefinition, WindowType } from './registry'
|
|
51
54
|
|
|
52
55
|
// Types
|
|
@@ -63,4 +66,5 @@ export type {
|
|
|
63
66
|
ClickMode,
|
|
64
67
|
RecipeGuide,
|
|
65
68
|
RecipeNavFrame,
|
|
69
|
+
EntityDisplayArea,
|
|
66
70
|
} from './types'
|
package/src/registry/index.ts
CHANGED
|
@@ -5,12 +5,41 @@ const registry = new Map<string, InventoryTypeDefinition>(
|
|
|
5
5
|
Object.entries(inventoryDefinitions),
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Maps alternative names/aliases to canonical inventory type names.
|
|
10
|
+
* Both the alias and the canonical name resolve to the same definition.
|
|
11
|
+
*
|
|
12
|
+
* Add entries here to support shorthand or legacy type identifiers.
|
|
13
|
+
*/
|
|
14
|
+
const typeAliases: Record<string, string> = {
|
|
15
|
+
// Shorthand aliases
|
|
16
|
+
crafting3x3: 'crafting_table',
|
|
17
|
+
crafting: 'crafting_table',
|
|
18
|
+
chest: 'chest', // already canonical, listed for clarity
|
|
19
|
+
// Protocol-level minecraft: prefix stripping is handled in getInventoryType
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
export function registerInventoryType(def: InventoryTypeDefinition): void {
|
|
9
23
|
registry.set(def.name, def)
|
|
10
24
|
}
|
|
11
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Resolve an inventory type name or alias to its definition.
|
|
28
|
+
* Handles:
|
|
29
|
+
* - Exact matches (e.g. "crafting_table")
|
|
30
|
+
* - Aliases defined in `typeAliases` (e.g. "crafting3x3" → "crafting_table")
|
|
31
|
+
* - "minecraft:" namespace prefix (e.g. "minecraft:generic_9x3" → "generic_9x3")
|
|
32
|
+
*/
|
|
12
33
|
export function getInventoryType(name: string): InventoryTypeDefinition | undefined {
|
|
13
|
-
|
|
34
|
+
// Strip "minecraft:" namespace prefix if present
|
|
35
|
+
const stripped = name.startsWith('minecraft:') ? name.slice('minecraft:'.length) : name
|
|
36
|
+
// Resolve alias if defined
|
|
37
|
+
const canonical = typeAliases[stripped] ?? stripped
|
|
38
|
+
return registry.get(canonical)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function registerTypeAlias(alias: string, canonical: string): void {
|
|
42
|
+
typeAliases[alias] = canonical
|
|
14
43
|
}
|
|
15
44
|
|
|
16
45
|
export function getAllInventoryTypes(): InventoryTypeDefinition[] {
|