minecraft-inventory 0.1.4 → 0.1.6
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/README.md +16 -3
- package/package.json +1 -1
- package/src/bundledTexturesConfig.ts +126 -0
- package/src/cache/blockRenderer.ts +103 -0
- package/src/cache/textureCache.ts +10 -0
- package/src/components/InventoryOverlay/InventoryOverlay.tsx +1 -1
- package/src/components/InventoryWindow/InventoryBackground.tsx +69 -82
- package/src/components/ItemCanvas/ItemCanvas.tsx +103 -17
- package/src/components/JEI/JEI.module.css +2 -0
- package/src/components/JEI/JEI.tsx +2 -3
- package/src/components/Notes/Notes.tsx +76 -45
- package/src/components/Slot/Slot.tsx +6 -0
- package/src/components/Text/MessageFormattedString.tsx +1 -0
- package/src/components/Tooltip/Tooltip.tsx +1 -2
- package/src/connector/demo.ts +65 -0
- package/src/connector/mineflayer.ts +18 -3
- package/src/context/InventoryContext.tsx +34 -17
- package/src/generated/localTextures.ts +66 -51
- package/src/index.tsx +19 -1
- package/src/registry/index.ts +30 -1
- package/src/registry/inventories.ts +19 -16
- package/src/types.ts +38 -0
|
@@ -96,6 +96,7 @@ export function JEI({
|
|
|
96
96
|
|
|
97
97
|
const ro = new ResizeObserver((entries) => {
|
|
98
98
|
for (const entry of entries) {
|
|
99
|
+
// console.log('got size', entry.target.className, entry.contentRect.width, entry.contentRect.height)
|
|
99
100
|
if (entry.target === (root as unknown as Element)) {
|
|
100
101
|
sizes.rootW = entry.contentRect.width
|
|
101
102
|
} else if (entry.target === (grid as unknown as Element)) {
|
|
@@ -240,8 +241,6 @@ export function JEI({
|
|
|
240
241
|
className="mc-inv-jei-header"
|
|
241
242
|
style={{
|
|
242
243
|
padding: `${padding}px`,
|
|
243
|
-
background: '#c6c6c6',
|
|
244
|
-
// border: `${scale}px solid #555555`,
|
|
245
244
|
flexShrink: 0,
|
|
246
245
|
}}
|
|
247
246
|
>
|
|
@@ -284,7 +283,7 @@ export function JEI({
|
|
|
284
283
|
>
|
|
285
284
|
◀
|
|
286
285
|
</button>
|
|
287
|
-
<span className="mc-inv-jei-page-counter" style={{ flex: 1, textAlign: 'center', color: '#
|
|
286
|
+
<span className="mc-inv-jei-page-counter" style={{ flex: 1, textAlign: 'center', color: '#ffffff' }}>
|
|
288
287
|
{page + 1} / {Math.max(1, totalPages)}
|
|
289
288
|
</span>
|
|
290
289
|
<button
|
|
@@ -45,6 +45,7 @@ export function Notes({
|
|
|
45
45
|
const [notes, setNotes] = useState<Note[]>([])
|
|
46
46
|
const [draft, setDraft] = useState('')
|
|
47
47
|
const [loading, setLoading] = useState(true)
|
|
48
|
+
const [showInput, setShowInput] = useState(false)
|
|
48
49
|
|
|
49
50
|
// Load notes on mount
|
|
50
51
|
useEffect(() => {
|
|
@@ -104,6 +105,14 @@ export function Notes({
|
|
|
104
105
|
setDraft('')
|
|
105
106
|
}, [draft, notes, saveNotes])
|
|
106
107
|
|
|
108
|
+
const handleAddButtonClick = useCallback(() => {
|
|
109
|
+
if (!showInput) {
|
|
110
|
+
setShowInput(true)
|
|
111
|
+
} else {
|
|
112
|
+
addNote()
|
|
113
|
+
}
|
|
114
|
+
}, [showInput, addNote])
|
|
115
|
+
|
|
107
116
|
const removeNote = useCallback((id: number) => {
|
|
108
117
|
const next = notes.filter((n) => n.id !== id)
|
|
109
118
|
saveNotes(next)
|
|
@@ -142,7 +151,7 @@ export function Notes({
|
|
|
142
151
|
</div>
|
|
143
152
|
) : notes.length === 0 ? (
|
|
144
153
|
<div style={{ color: '#888', fontSize: Math.round(6 * scale), textAlign: 'center', padding: `${4 * scale}px` }}>
|
|
145
|
-
No tasks yet
|
|
154
|
+
{/* No tasks yet */}
|
|
146
155
|
</div>
|
|
147
156
|
) : (
|
|
148
157
|
notes.map((note) => (
|
|
@@ -187,50 +196,72 @@ export function Notes({
|
|
|
187
196
|
boxSizing: 'border-box',
|
|
188
197
|
// borderTop: `${scale}px solid #555555`,
|
|
189
198
|
}}>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
199
|
+
{showInput && (
|
|
200
|
+
<textarea
|
|
201
|
+
value={draft}
|
|
202
|
+
rows={2}
|
|
203
|
+
autoFocus
|
|
204
|
+
onChange={(e) => {
|
|
205
|
+
setDraft(e.target.value.replace(/[\r\n]/g, ''))
|
|
206
|
+
}}
|
|
207
|
+
onKeyDown={(e) => {
|
|
208
|
+
if (e.code === 'Enter') { e.preventDefault(); addNote() }
|
|
209
|
+
if (e.code === 'Escape') { setShowInput(false); setDraft('') }
|
|
210
|
+
}}
|
|
211
|
+
placeholder="Add task…"
|
|
212
|
+
style={{
|
|
213
|
+
flex: 1,
|
|
214
|
+
minWidth: 0,
|
|
215
|
+
resize: 'none',
|
|
216
|
+
background: '#8b8b8b',
|
|
217
|
+
border: `${scale}px solid #373737`,
|
|
218
|
+
color: '#fff',
|
|
219
|
+
fontSize: Math.round(6 * scale),
|
|
220
|
+
padding: `${scale}px`,
|
|
221
|
+
fontFamily: 'inherit',
|
|
222
|
+
outline: 'none',
|
|
223
|
+
lineHeight: 1.4,
|
|
224
|
+
boxSizing: 'border-box',
|
|
225
|
+
overflowY: 'hidden',
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
)}
|
|
229
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: scale, flexShrink: 0 }}>
|
|
230
|
+
{showInput && (
|
|
231
|
+
<button
|
|
232
|
+
onClick={() => { setShowInput(false); setDraft('') }}
|
|
233
|
+
title="Hide"
|
|
234
|
+
style={{
|
|
235
|
+
background: '#5a3a3a',
|
|
236
|
+
color: '#ccc',
|
|
237
|
+
border: `${scale}px solid #666`,
|
|
238
|
+
cursor: 'pointer',
|
|
239
|
+
fontSize: Math.round(6 * scale),
|
|
240
|
+
padding: `${scale}px ${2 * scale}px`,
|
|
241
|
+
fontFamily: 'inherit',
|
|
242
|
+
lineHeight: 1,
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
×
|
|
246
|
+
</button>
|
|
247
|
+
)}
|
|
248
|
+
<button
|
|
249
|
+
onClick={handleAddButtonClick}
|
|
250
|
+
disabled={showInput && !draft.trim()}
|
|
251
|
+
style={{
|
|
252
|
+
background: '#404040',
|
|
253
|
+
color: '#fff',
|
|
254
|
+
border: `${scale}px solid #666`,
|
|
255
|
+
cursor: showInput && !draft.trim() ? 'not-allowed' : 'pointer',
|
|
256
|
+
fontSize: Math.round(6 * scale),
|
|
257
|
+
padding: `${scale}px ${2 * scale}px`,
|
|
258
|
+
fontFamily: 'inherit',
|
|
259
|
+
opacity: showInput && !draft.trim() ? 0.5 : 1,
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
+
|
|
263
|
+
</button>
|
|
264
|
+
</div>
|
|
234
265
|
</div>
|
|
235
266
|
</div>
|
|
236
267
|
)
|
|
@@ -244,6 +244,9 @@ export function Slot({
|
|
|
244
244
|
const touch = e.changedTouches[0]
|
|
245
245
|
if (Math.abs(touch.clientX - start.x) > 10 || Math.abs(touch.clientY - start.y) > 10) return
|
|
246
246
|
e.stopPropagation()
|
|
247
|
+
// Prevent the browser from firing a synthetic click after touchEnd.
|
|
248
|
+
// Without this, the click bubbles to the inventory window div which clears focusedSlot.
|
|
249
|
+
e.preventDefault()
|
|
247
250
|
|
|
248
251
|
if (pKeyActive) setPKeyActive(false)
|
|
249
252
|
|
|
@@ -319,6 +322,9 @@ export function Slot({
|
|
|
319
322
|
.filter(Boolean)
|
|
320
323
|
.join(' ')}
|
|
321
324
|
tabIndex={index >= 0 ? 0 : undefined}
|
|
325
|
+
data-slot={index}
|
|
326
|
+
data-debug={item?.debugKey ?? undefined}
|
|
327
|
+
data-texture={item?.textureKey ?? undefined}
|
|
322
328
|
style={{
|
|
323
329
|
width: renderSize,
|
|
324
330
|
height: renderSize,
|
|
@@ -16,6 +16,7 @@ const CODE_COLORS: Record<string, string> = {
|
|
|
16
16
|
function parseSectionCodes(text: string): MessageFormatPart[] {
|
|
17
17
|
const parts: MessageFormatPart[] = []
|
|
18
18
|
const regex = /§([0-9a-fk-orA-FK-OR])|([^§]+)/g
|
|
19
|
+
regex.lastIndex = 0
|
|
19
20
|
let color: string | undefined
|
|
20
21
|
let bold = false, italic = false, underlined = false
|
|
21
22
|
let strikethrough = false, obfuscated = false
|
|
@@ -85,8 +85,7 @@ export function Tooltip({ item, visible }: TooltipProps) {
|
|
|
85
85
|
fontSize: fs,
|
|
86
86
|
padding: pad,
|
|
87
87
|
gap: gap2,
|
|
88
|
-
|
|
89
|
-
maxWidth: Math.round(220 * scale),
|
|
88
|
+
width: 'max-content',
|
|
90
89
|
// Start invisible; applyPosition sets visibility after measuring dimensions
|
|
91
90
|
visibility: 'hidden',
|
|
92
91
|
pointerEvents: 'none',
|
package/src/connector/demo.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { InventoryAction, InventoryWindowState, PlayerState, SlotState, ItemStack } from '../types'
|
|
2
2
|
import type { InventoryConnector, ConnectorListener, ConnectorEvent } from './types'
|
|
3
|
+
import { isItemEqual, getMaxStackSize } from '../utils/isItemEqual'
|
|
3
4
|
|
|
4
5
|
export interface ActionLogEntry {
|
|
5
6
|
id: number
|
|
@@ -154,11 +155,75 @@ export function createDemoConnector(options: DemoConnectorOptions): InventoryCon
|
|
|
154
155
|
}
|
|
155
156
|
} else if (action.mode === 'shift' && slotState?.item) {
|
|
156
157
|
// Simulate shift-click: just log it
|
|
158
|
+
} else if (action.mode === 'double') {
|
|
159
|
+
// Double-click collect: pick up all matching items from other slots up to maxStack
|
|
160
|
+
const held = windowState.heldItem
|
|
161
|
+
if (held) {
|
|
162
|
+
const maxStack = getMaxStackSize(held)
|
|
163
|
+
let remaining = maxStack - held.count
|
|
164
|
+
for (let i = 0; i < slots.length && remaining > 0; i++) {
|
|
165
|
+
const s = slots[i]
|
|
166
|
+
if (!s.item || !isItemEqual(s.item, held)) continue
|
|
167
|
+
const take = Math.min(s.item.count, remaining)
|
|
168
|
+
slots[i] = { ...s, item: s.item.count - take > 0 ? { ...s.item, count: s.item.count - take } : null }
|
|
169
|
+
remaining -= take
|
|
170
|
+
}
|
|
171
|
+
windowState = { ...windowState, slots, heldItem: { ...held, count: maxStack - remaining } }
|
|
172
|
+
}
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
emit({ type: 'windowUpdate', state: windowState })
|
|
160
176
|
}
|
|
161
177
|
|
|
178
|
+
// Demo: simulate drag/spread behavior
|
|
179
|
+
if (action.type === 'drag' && windowState && windowState.heldItem) {
|
|
180
|
+
const held = windowState.heldItem
|
|
181
|
+
const maxStack = getMaxStackSize(held)
|
|
182
|
+
const slots = [...windowState.slots]
|
|
183
|
+
|
|
184
|
+
if (action.button === 'left') {
|
|
185
|
+
// Vanilla left-drag: distribute perSlot items evenly, remainder stays in cursor
|
|
186
|
+
const compatibleSlots = action.slots.filter((idx) => {
|
|
187
|
+
const existing = slots.find((s) => s.index === idx)?.item
|
|
188
|
+
return !existing || isItemEqual(existing, held)
|
|
189
|
+
})
|
|
190
|
+
if (compatibleSlots.length > 0) {
|
|
191
|
+
const perSlot = Math.floor(held.count / compatibleSlots.length)
|
|
192
|
+
// If perSlot=0 (more slots than items), nothing is distributed
|
|
193
|
+
if (perSlot > 0) {
|
|
194
|
+
let totalPlaced = 0
|
|
195
|
+
for (const idx of compatibleSlots) {
|
|
196
|
+
const si = slots.findIndex((s) => s.index === idx)
|
|
197
|
+
const existingCount = si >= 0 ? (slots[si].item?.count ?? 0) : 0
|
|
198
|
+
const add = Math.min(perSlot, maxStack - existingCount)
|
|
199
|
+
totalPlaced += add
|
|
200
|
+
const newCount = existingCount + add
|
|
201
|
+
if (si >= 0) slots[si] = { ...slots[si], item: { ...held, count: newCount } }
|
|
202
|
+
else slots.push({ index: idx, item: { ...held, count: newCount } })
|
|
203
|
+
}
|
|
204
|
+
const remaining = held.count - totalPlaced
|
|
205
|
+
windowState = { ...windowState, slots, heldItem: remaining > 0 ? { ...held, count: remaining } : null }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
// Place 1 item per slot (right-click drag)
|
|
210
|
+
let remaining = held.count
|
|
211
|
+
for (const idx of action.slots) {
|
|
212
|
+
if (remaining <= 0) break
|
|
213
|
+
const si = slots.findIndex((s) => s.index === idx)
|
|
214
|
+
const existing = si >= 0 ? slots[si].item : null
|
|
215
|
+
if (existing && !isItemEqual(existing, held)) continue
|
|
216
|
+
const existingCount = existing?.count ?? 0
|
|
217
|
+
const newCount = Math.min(existingCount + 1, maxStack)
|
|
218
|
+
if (si >= 0) slots[si] = { ...slots[si], item: { ...held, count: newCount } }
|
|
219
|
+
else slots.push({ index: idx, item: { ...held, count: newCount } })
|
|
220
|
+
remaining--
|
|
221
|
+
}
|
|
222
|
+
windowState = { ...windowState, slots, heldItem: remaining > 0 ? { ...held, count: remaining } : null }
|
|
223
|
+
}
|
|
224
|
+
emit({ type: 'windowUpdate', state: windowState })
|
|
225
|
+
}
|
|
226
|
+
|
|
162
227
|
if (action.type === 'drop' && windowState) {
|
|
163
228
|
const slots = [...windowState.slots]
|
|
164
229
|
const slotState = slots.find((s) => s.index === action.slotIndex)
|
|
@@ -10,19 +10,31 @@ export interface MineflayerConnectorOptions {
|
|
|
10
10
|
/**
|
|
11
11
|
* Custom item mapper called for every slot conversion from raw mineflayer data to
|
|
12
12
|
* {@link ItemStack}. Receives the raw slot data and the default-mapped stack.
|
|
13
|
-
* Return a modified stack to override fields (e.g. `name`, `textureKey`, `displayName
|
|
14
|
-
* or return the second argument unchanged to use the default mapping.
|
|
13
|
+
* Return a modified stack to override fields (e.g. `name`, `textureKey`, `displayName`,
|
|
14
|
+
* `texture`, `blockTexture`), or return the second argument unchanged to use the default mapping.
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
17
|
* ```ts
|
|
18
18
|
* createMineflayerConnector(bot, {
|
|
19
19
|
* itemMapper: (raw, mapped) => ({
|
|
20
20
|
* ...mapped,
|
|
21
|
-
* // Override texture for specific numeric type IDs:
|
|
22
21
|
* textureKey: raw.type === 438 ? 'item/potion_water' : mapped.textureKey,
|
|
23
22
|
* }),
|
|
24
23
|
* })
|
|
25
24
|
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example Block texture with isometric face slices
|
|
27
|
+
* ```ts
|
|
28
|
+
* itemMapper: (raw, mapped) => ({
|
|
29
|
+
* ...mapped,
|
|
30
|
+
* blockTexture: {
|
|
31
|
+
* source: blockAtlasUrl,
|
|
32
|
+
* top: { slice: [0, 0, 16, 16] },
|
|
33
|
+
* left: { slice: [16, 0, 16, 16] },
|
|
34
|
+
* right: { slice: [32, 0, 16, 16] },
|
|
35
|
+
* },
|
|
36
|
+
* })
|
|
37
|
+
* ```
|
|
26
38
|
*/
|
|
27
39
|
itemMapper?: (raw: RawSlot, mapped: ItemStack) => ItemStack
|
|
28
40
|
}
|
|
@@ -35,6 +47,9 @@ function makeSlotConverter(itemMapper?: MineflayerConnectorOptions['itemMapper']
|
|
|
35
47
|
count: slot.count,
|
|
36
48
|
metadata: slot.metadata,
|
|
37
49
|
nbt: slot.nbt as Record<string, unknown> | undefined,
|
|
50
|
+
// Default debug key: "<type>:<metadata>" — visible as data-debug on slot elements.
|
|
51
|
+
// Override via itemMapper if needed.
|
|
52
|
+
debugKey: slot.metadata ? `${slot.type}:${slot.metadata}` : String(slot.type),
|
|
38
53
|
}
|
|
39
54
|
return itemMapper ? itemMapper(slot, mapped) : mapped
|
|
40
55
|
}
|
|
@@ -137,12 +137,11 @@ export function InventoryProvider({ connector, children, noDragSpread = false }:
|
|
|
137
137
|
})
|
|
138
138
|
if (compatibleSlots.length === 0) return preview
|
|
139
139
|
const perSlot = Math.floor(held.count / compatibleSlots.length)
|
|
140
|
-
|
|
140
|
+
// Vanilla behavior: if perSlot=0 (more slots than items), nothing is distributed
|
|
141
|
+
if (perSlot === 0) return preview
|
|
141
142
|
for (const idx of compatibleSlots) {
|
|
142
143
|
const existingCount = ws?.slots.find((s) => s.index === idx)?.item?.count ?? 0
|
|
143
|
-
const
|
|
144
|
-
if (remainder > 0) remainder--
|
|
145
|
-
const total = Math.min(existingCount + add, maxStack)
|
|
144
|
+
const total = Math.min(existingCount + perSlot, maxStack)
|
|
146
145
|
preview.set(idx, { count: total })
|
|
147
146
|
}
|
|
148
147
|
} else {
|
|
@@ -197,21 +196,26 @@ export function InventoryProvider({ connector, children, noDragSpread = false }:
|
|
|
197
196
|
})
|
|
198
197
|
if (compatibleSlots.length > 0) {
|
|
199
198
|
const perSlot = Math.floor(held.count / compatibleSlots.length)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
}
|
|
211
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)
|
|
212
218
|
}
|
|
213
|
-
if (ws) setWindowState({ ...ws, slots: newSlots })
|
|
214
|
-
setHeldItemState(null)
|
|
215
219
|
}
|
|
216
220
|
} else {
|
|
217
221
|
// Right-click drag: place 1 per slot
|
|
@@ -256,6 +260,19 @@ export function InventoryProvider({ connector, children, noDragSpread = false }:
|
|
|
256
260
|
[windowState],
|
|
257
261
|
)
|
|
258
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
|
+
|
|
259
276
|
const value: InventoryContextValue = {
|
|
260
277
|
windowState,
|
|
261
278
|
playerState,
|
|
@@ -28,6 +28,72 @@ import _gui_widgets from 'mc-assets/dist/other-textures/1.15/gui/widgets.png'
|
|
|
28
28
|
import _gui_sprites_container_anvil_text_field from 'mc-assets/dist/other-textures/latest/gui/sprites/container/anvil/text_field.png'
|
|
29
29
|
import _gui_sprites_container_anvil_text_field_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/anvil/text_field_disabled.png'
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Versioned texture path → bundled asset URL (or undefined for remote fallback).
|
|
33
|
+
* Keys are full mc-assets paths e.g. "1.21.11/textures/gui/container/inventory.png"
|
|
34
|
+
*/
|
|
35
|
+
export const bundledTextureMap: Record<string, string | undefined> = {
|
|
36
|
+
'1.21.11/textures/gui/container/inventory.png': _gui_container_inventory,
|
|
37
|
+
'1.21.11/textures/gui/container/shulker_box.png': _gui_container_shulker_box,
|
|
38
|
+
'1.21.11/textures/gui/container/generic_54.png': _gui_container_generic_54,
|
|
39
|
+
'1.21.11/textures/gui/container/crafting_table.png': _gui_container_crafting_table,
|
|
40
|
+
'1.21.11/textures/gui/container/furnace.png': _gui_container_furnace,
|
|
41
|
+
'1.21.11/textures/gui/container/blast_furnace.png': _gui_container_blast_furnace,
|
|
42
|
+
'1.21.11/textures/gui/container/smoker.png': _gui_container_smoker,
|
|
43
|
+
'1.21.11/textures/gui/container/brewing_stand.png': _gui_container_brewing_stand,
|
|
44
|
+
'1.21.11/textures/gui/container/anvil.png': _gui_container_anvil,
|
|
45
|
+
'1.21.11/textures/gui/container/grindstone.png': _gui_container_grindstone,
|
|
46
|
+
'1.21.11/textures/gui/container/enchanting_table.png': _gui_container_enchanting_table,
|
|
47
|
+
'1.21.11/textures/gui/container/smithing.png': _gui_container_smithing,
|
|
48
|
+
'1.16.4/textures/gui/container/smithing.png': _gui_container_smithing_2,
|
|
49
|
+
'1.21.11/textures/gui/container/hopper.png': _gui_container_hopper,
|
|
50
|
+
'1.21.11/textures/gui/container/dispenser.png': _gui_container_dispenser,
|
|
51
|
+
'1.21.11/textures/gui/container/beacon.png': _gui_container_beacon,
|
|
52
|
+
'1.21.11/textures/gui/container/horse.png': _gui_container_horse,
|
|
53
|
+
'1.14/textures/gui/container/villager2.png': _gui_container_villager2,
|
|
54
|
+
'1.21.11/textures/gui/container/cartography_table.png': _gui_container_cartography_table,
|
|
55
|
+
'1.21.11/textures/gui/container/loom.png': _gui_container_loom,
|
|
56
|
+
'1.21.11/textures/gui/container/stonecutter.png': _gui_container_stonecutter,
|
|
57
|
+
'1.21.11/textures/gui/container/crafter.png': _gui_container_crafter,
|
|
58
|
+
'1.21.11/textures/gui/container/creative_inventory/tab_items.png': _gui_container_creative_inventory_tab_items,
|
|
59
|
+
'1.15/textures/gui/widgets.png': _gui_widgets,
|
|
60
|
+
'1.21.11/textures/gui/sprites/container/anvil/text_field.png': _gui_sprites_container_anvil_text_field,
|
|
61
|
+
'1.21.11/textures/gui/sprites/container/anvil/text_field_disabled.png': _gui_sprites_container_anvil_text_field_disabled,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* All texture paths without version prefix (e.g. "gui/container/inventory.png").
|
|
67
|
+
* Same set as bundledTextureMap keys with version stripped.
|
|
68
|
+
*/
|
|
69
|
+
export const allTexturePaths: readonly string[] = [
|
|
70
|
+
'gui/container/inventory.png',
|
|
71
|
+
'gui/container/shulker_box.png',
|
|
72
|
+
'gui/container/generic_54.png',
|
|
73
|
+
'gui/container/crafting_table.png',
|
|
74
|
+
'gui/container/furnace.png',
|
|
75
|
+
'gui/container/blast_furnace.png',
|
|
76
|
+
'gui/container/smoker.png',
|
|
77
|
+
'gui/container/brewing_stand.png',
|
|
78
|
+
'gui/container/anvil.png',
|
|
79
|
+
'gui/container/grindstone.png',
|
|
80
|
+
'gui/container/enchanting_table.png',
|
|
81
|
+
'gui/container/smithing.png',
|
|
82
|
+
'gui/container/hopper.png',
|
|
83
|
+
'gui/container/dispenser.png',
|
|
84
|
+
'gui/container/beacon.png',
|
|
85
|
+
'gui/container/horse.png',
|
|
86
|
+
'gui/container/villager2.png',
|
|
87
|
+
'gui/container/cartography_table.png',
|
|
88
|
+
'gui/container/loom.png',
|
|
89
|
+
'gui/container/stonecutter.png',
|
|
90
|
+
'gui/container/crafter.png',
|
|
91
|
+
'gui/container/creative_inventory/tab_items.png',
|
|
92
|
+
'gui/widgets.png',
|
|
93
|
+
'gui/sprites/container/anvil/text_field.png',
|
|
94
|
+
'gui/sprites/container/anvil/text_field_disabled.png',
|
|
95
|
+
]
|
|
96
|
+
|
|
31
97
|
/**
|
|
32
98
|
* Maps each inventory type name to its texture path (version prefix stripped).
|
|
33
99
|
*/
|
|
@@ -68,54 +134,3 @@ export const allContainerPaths: Record<string, string> = {
|
|
|
68
134
|
creative: 'gui/container/creative_inventory/tab_items.png',
|
|
69
135
|
hotbar: 'gui/widgets.png',
|
|
70
136
|
}
|
|
71
|
-
|
|
72
|
-
// Internal: versioned texture key → bundled asset URL (or undefined → remote fallback)
|
|
73
|
-
const _map: Record<string, string | undefined> = {
|
|
74
|
-
'1.21.11/textures/gui/container/inventory.png': _gui_container_inventory,
|
|
75
|
-
'1.21.11/textures/gui/container/shulker_box.png': _gui_container_shulker_box,
|
|
76
|
-
'1.21.11/textures/gui/container/generic_54.png': _gui_container_generic_54,
|
|
77
|
-
'1.21.11/textures/gui/container/crafting_table.png': _gui_container_crafting_table,
|
|
78
|
-
'1.21.11/textures/gui/container/furnace.png': _gui_container_furnace,
|
|
79
|
-
'1.21.11/textures/gui/container/blast_furnace.png': _gui_container_blast_furnace,
|
|
80
|
-
'1.21.11/textures/gui/container/smoker.png': _gui_container_smoker,
|
|
81
|
-
'1.21.11/textures/gui/container/brewing_stand.png': _gui_container_brewing_stand,
|
|
82
|
-
'1.21.11/textures/gui/container/anvil.png': _gui_container_anvil,
|
|
83
|
-
'1.21.11/textures/gui/container/grindstone.png': _gui_container_grindstone,
|
|
84
|
-
'1.21.11/textures/gui/container/enchanting_table.png': _gui_container_enchanting_table,
|
|
85
|
-
'1.21.11/textures/gui/container/smithing.png': _gui_container_smithing,
|
|
86
|
-
'1.16.4/textures/gui/container/smithing.png': _gui_container_smithing_2,
|
|
87
|
-
'1.21.11/textures/gui/container/hopper.png': _gui_container_hopper,
|
|
88
|
-
'1.21.11/textures/gui/container/dispenser.png': _gui_container_dispenser,
|
|
89
|
-
'1.21.11/textures/gui/container/beacon.png': _gui_container_beacon,
|
|
90
|
-
'1.21.11/textures/gui/container/horse.png': _gui_container_horse,
|
|
91
|
-
'1.14/textures/gui/container/villager2.png': _gui_container_villager2,
|
|
92
|
-
'1.21.11/textures/gui/container/cartography_table.png': _gui_container_cartography_table,
|
|
93
|
-
'1.21.11/textures/gui/container/loom.png': _gui_container_loom,
|
|
94
|
-
'1.21.11/textures/gui/container/stonecutter.png': _gui_container_stonecutter,
|
|
95
|
-
'1.21.11/textures/gui/container/crafter.png': _gui_container_crafter,
|
|
96
|
-
'1.21.11/textures/gui/container/creative_inventory/tab_items.png': _gui_container_creative_inventory_tab_items,
|
|
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,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Partial TextureConfig that resolves inventory GUI textures from locally bundled
|
|
104
|
-
* mc-assets assets instead of remote GitHub URLs.
|
|
105
|
-
*
|
|
106
|
-
* Pass to `<TextureProvider config={localTexturesConfig}>` to use offline/bundled assets.
|
|
107
|
-
*
|
|
108
|
-
* Unknown paths (not bundled) fall back to the mc-assets remote URL automatically.
|
|
109
|
-
*/
|
|
110
|
-
const _MC_ASSETS_REMOTE = "https://raw.githubusercontent.com/zardoy/mc-assets/refs/heads/gh-pages"
|
|
111
|
-
export const localTexturesConfig = {
|
|
112
|
-
getGuiTextureUrl(path: string): string {
|
|
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
|
|
120
|
-
},
|
|
121
|
-
}
|
package/src/index.tsx
CHANGED
|
@@ -49,7 +49,7 @@ export type {
|
|
|
49
49
|
export type { ActionLogEntry, DemoConnectorOptions } from './connector/demo'
|
|
50
50
|
|
|
51
51
|
// Registry
|
|
52
|
-
export { registerInventoryType, getInventoryType, getAllInventoryTypes } from './registry'
|
|
52
|
+
export { registerInventoryType, registerTypeAlias, getInventoryType, getAllInventoryTypes } from './registry'
|
|
53
53
|
export type { InventoryTypeDefinition, WindowType } from './registry'
|
|
54
54
|
|
|
55
55
|
// Types
|
|
@@ -67,4 +67,22 @@ export type {
|
|
|
67
67
|
RecipeGuide,
|
|
68
68
|
RecipeNavFrame,
|
|
69
69
|
EntityDisplayArea,
|
|
70
|
+
TextureSlice,
|
|
71
|
+
BlockFaceSlice,
|
|
72
|
+
BlockTextureRender,
|
|
70
73
|
} from './types'
|
|
74
|
+
|
|
75
|
+
// Bundled textures config
|
|
76
|
+
export {
|
|
77
|
+
createBundledTexturesConfig,
|
|
78
|
+
localBundledTexturesConfig,
|
|
79
|
+
allTexturePaths,
|
|
80
|
+
allContainerPaths,
|
|
81
|
+
} from './bundledTexturesConfig'
|
|
82
|
+
export type {
|
|
83
|
+
BundledTexturesConfig,
|
|
84
|
+
BundledTexturesConfigOptions,
|
|
85
|
+
} from './bundledTexturesConfig'
|
|
86
|
+
|
|
87
|
+
// Texture cache (for resetRenderedSlots / manual invalidation)
|
|
88
|
+
export { clearTextureCache } from './cache/textureCache'
|
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[] {
|