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.
- package/README.md +566 -0
- package/package.json +37 -0
- package/src/InventoryGUI.tsx +108 -0
- package/src/cache/textureCache.ts +95 -0
- package/src/components/CursorItem/CursorItem.tsx +94 -0
- package/src/components/CursorItem/index.ts +1 -0
- package/src/components/HUD/HUD.tsx +11 -0
- package/src/components/HUD/index.ts +1 -0
- package/src/components/Hotbar/Hotbar.tsx +180 -0
- package/src/components/Hotbar/index.ts +1 -0
- package/src/components/InventoryOverlay/InventoryOverlay.tsx +196 -0
- package/src/components/InventoryOverlay/index.ts +2 -0
- package/src/components/InventoryWindow/EnchantmentOptions.tsx +109 -0
- package/src/components/InventoryWindow/InventoryBackground.tsx +110 -0
- package/src/components/InventoryWindow/InventoryWindow.tsx +120 -0
- package/src/components/InventoryWindow/ProgressBar.tsx +78 -0
- package/src/components/InventoryWindow/VillagerTradeList.tsx +136 -0
- package/src/components/InventoryWindow/index.ts +5 -0
- package/src/components/ItemCanvas/ItemCanvas.tsx +154 -0
- package/src/components/ItemCanvas/index.ts +1 -0
- package/src/components/JEI/JEI.module.css +37 -0
- package/src/components/JEI/JEI.tsx +303 -0
- package/src/components/JEI/index.ts +2 -0
- package/src/components/RecipeGuide/RecipeInventoryView.tsx +293 -0
- package/src/components/RecipeGuide/index.ts +1 -0
- package/src/components/Slot/Slot.module.css +111 -0
- package/src/components/Slot/Slot.tsx +363 -0
- package/src/components/Slot/index.ts +1 -0
- package/src/components/Text/MessageFormatted.css +5 -0
- package/src/components/Text/MessageFormatted.tsx +79 -0
- package/src/components/Text/MessageFormattedString.tsx +74 -0
- package/src/components/Text/chatUtils.ts +172 -0
- package/src/components/Tooltip/Tooltip.module.css +56 -0
- package/src/components/Tooltip/Tooltip.tsx +130 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/connector/demo.ts +213 -0
- package/src/connector/index.ts +4 -0
- package/src/connector/mineflayer.ts +113 -0
- package/src/connector/types.ts +41 -0
- package/src/context/InventoryContext.tsx +157 -0
- package/src/context/ScaleContext.tsx +73 -0
- package/src/context/TextureContext.tsx +70 -0
- package/src/globals.d.ts +4 -0
- package/src/hooks/useKeyboardShortcuts.ts +41 -0
- package/src/hooks/useMobile.ts +28 -0
- package/src/index.tsx +65 -0
- package/src/mount.tsx +52 -0
- package/src/registry/index.ts +21 -0
- package/src/registry/inventories.ts +612 -0
- package/src/styles/tokens.css +47 -0
- package/src/types.ts +176 -0
package/README.md
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
# minecraft-inventory
|
|
2
|
+
|
|
3
|
+
A flexible, scalable React library for rendering Minecraft inventory GUIs. Designed for integration into real Minecraft clients (e.g., mineflayer bots, web clients) without using CSS `transform: scale` — all sizing is driven by CSS custom properties.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- All standard inventory types (chest, furnace, crafting table, villager trades, enchanting table, brewing stand, anvil, smithing table, horse, beacon, and more)
|
|
8
|
+
- CSS variable–based scaling — no layout jank, no `transform: scale`
|
|
9
|
+
- `<img>`-rendered item textures with automatic `items/` → `blocks/` fallback (via PrismarineJS asset mirror by default)
|
|
10
|
+
- Tooltips that follow the cursor, matching the original Minecraft style
|
|
11
|
+
- Full keyboard support: number keys (1-9) to swap hotbar, Q to drop, double-click to collect, scroll wheel to pick/place
|
|
12
|
+
- Mobile support: tap to open a context menu (take all / half / custom amount / drop)
|
|
13
|
+
- Optional JEI (item browser) panel on the left or right
|
|
14
|
+
- Bot connector layer — plugs into a mineflayer bot or any custom server connection
|
|
15
|
+
- Demo connector with action logging for local development
|
|
16
|
+
- Extendable registry — add new inventory types in one place
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Quick Start (React)
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import React from 'react'
|
|
24
|
+
import { createRoot } from 'react-dom/client'
|
|
25
|
+
import { InventoryGUI, createDemoConnector } from 'minecraft-inventory'
|
|
26
|
+
|
|
27
|
+
const connector = createDemoConnector({
|
|
28
|
+
windowType: 'chest',
|
|
29
|
+
windowTitle: 'My Chest',
|
|
30
|
+
slots: [
|
|
31
|
+
{ index: 0, item: { type: 265, name: 'iron_ingot', count: 32, displayName: 'Iron Ingot' } },
|
|
32
|
+
// ...more slots
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
function App() {
|
|
37
|
+
return (
|
|
38
|
+
<InventoryGUI
|
|
39
|
+
type="chest"
|
|
40
|
+
connector={connector}
|
|
41
|
+
scale={2}
|
|
42
|
+
showJEI
|
|
43
|
+
jeiItems={[
|
|
44
|
+
{ type: 1, name: 'stone', displayName: 'Stone', count: 1 },
|
|
45
|
+
{ type: 4, name: 'cobblestone', displayName: 'Cobblestone', count: 1 },
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Mount into the DOM
|
|
52
|
+
createRoot(document.getElementById('root')!).render(<App />)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start (No React — programmatic)
|
|
56
|
+
|
|
57
|
+
If you don't use React, use the `mountInventory` helper:
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<div id="inventory"></div>
|
|
61
|
+
<script type="module">
|
|
62
|
+
import { mountInventory, createDemoConnector } from 'minecraft-inventory'
|
|
63
|
+
|
|
64
|
+
const connector = createDemoConnector({
|
|
65
|
+
windowType: 'chest',
|
|
66
|
+
windowTitle: 'My Chest',
|
|
67
|
+
slots: [
|
|
68
|
+
{ index: 0, item: { type: 265, name: 'iron_ingot', count: 32, displayName: 'Iron Ingot' } },
|
|
69
|
+
],
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const inv = mountInventory(document.getElementById('inventory'), {
|
|
73
|
+
type: 'chest',
|
|
74
|
+
connector,
|
|
75
|
+
scale: 2,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Update props later
|
|
79
|
+
inv.update({ scale: 3 })
|
|
80
|
+
|
|
81
|
+
// Destroy when done
|
|
82
|
+
inv.destroy()
|
|
83
|
+
</script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install minecraft-inventory
|
|
92
|
+
# or
|
|
93
|
+
pnpm add minecraft-inventory
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Core API
|
|
99
|
+
|
|
100
|
+
### `<InventoryGUI>` — All-in-one component
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<InventoryGUI
|
|
104
|
+
type="chest" // Inventory type name (see registry)
|
|
105
|
+
title="My Chest" // Override window title
|
|
106
|
+
slots={[...]} // Optional: provide slots directly
|
|
107
|
+
properties={{ litTime: 100 }} // Optional: progress bar data
|
|
108
|
+
connector={connector} // Optional: bot connector
|
|
109
|
+
scale={2} // Scale multiplier (default: 2)
|
|
110
|
+
showJEI={false} // Show item browser panel
|
|
111
|
+
jeiItems={[...]} // Items to show in JEI
|
|
112
|
+
jeiPosition="right" // 'left' | 'right'
|
|
113
|
+
textureBaseUrl="/assets/mc" // Override texture base URL
|
|
114
|
+
enableKeyboardShortcuts={true}
|
|
115
|
+
onClose={() => {}}
|
|
116
|
+
/>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `<InventoryOverlay>` — Full-screen overlay with backdrop
|
|
120
|
+
|
|
121
|
+
Renders the inventory centered on screen with a semi-transparent backdrop. Clicking outside drops the held item or closes the window.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { InventoryOverlay, InventoryProvider, ScaleProvider, TextureProvider } from 'minecraft-inventory'
|
|
125
|
+
|
|
126
|
+
<TextureProvider>
|
|
127
|
+
<ScaleProvider scale={2}>
|
|
128
|
+
<InventoryProvider connector={connector}>
|
|
129
|
+
<InventoryOverlay
|
|
130
|
+
type="chest"
|
|
131
|
+
showBackdrop // default: true (50% black)
|
|
132
|
+
backdropColor="rgba(0,0,0,0.5)"
|
|
133
|
+
showHotbar // default: true
|
|
134
|
+
showJEI
|
|
135
|
+
jeiItems={items}
|
|
136
|
+
jeiPosition="right"
|
|
137
|
+
onClose={() => console.log('closed')}
|
|
138
|
+
>
|
|
139
|
+
{/* Extra children (e.g. notes panel) are rendered inside the centered anchor */}
|
|
140
|
+
</InventoryOverlay>
|
|
141
|
+
</InventoryProvider>
|
|
142
|
+
</ScaleProvider>
|
|
143
|
+
</TextureProvider>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Manual composition
|
|
147
|
+
|
|
148
|
+
For full control, use the provider components and compose manually:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import {
|
|
152
|
+
TextureProvider, ScaleProvider, InventoryProvider,
|
|
153
|
+
InventoryWindow, Hotbar, HUD, CursorItem, JEI,
|
|
154
|
+
} from 'minecraft-inventory'
|
|
155
|
+
|
|
156
|
+
function MyInventory({ connector }) {
|
|
157
|
+
return (
|
|
158
|
+
<TextureProvider baseUrl="https://example.com/assets">
|
|
159
|
+
<ScaleProvider scale={2}>
|
|
160
|
+
<InventoryProvider connector={connector}>
|
|
161
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
162
|
+
<InventoryWindow type="furnace" />
|
|
163
|
+
<JEI items={allItems} position="right" />
|
|
164
|
+
</div>
|
|
165
|
+
<Hotbar />
|
|
166
|
+
<HUD />
|
|
167
|
+
<CursorItem />
|
|
168
|
+
</InventoryProvider>
|
|
169
|
+
</ScaleProvider>
|
|
170
|
+
</TextureProvider>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Scaling
|
|
178
|
+
|
|
179
|
+
Scale is driven entirely by CSS custom properties — no `transform: scale`. Changing the scale prop recalculates all size tokens:
|
|
180
|
+
|
|
181
|
+
| Variable | Default (scale=2) |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `--mc-scale` | `2` |
|
|
184
|
+
| `--mc-slot-size` | `36px` (18 × scale) |
|
|
185
|
+
| `--mc-font-size` | `14px` (7 × scale) |
|
|
186
|
+
| `--mc-pixel` | `2px` |
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
// Changing scale re-renders the whole GUI at new dimensions
|
|
190
|
+
<InventoryGUI type="chest" scale={3} />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Textures
|
|
196
|
+
|
|
197
|
+
By default, item textures are fetched from the PrismarineJS Minecraft assets mirror on GitHub:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
https://raw.githubusercontent.com/PrismarineJS/minecraft-assets/master/data/1.21.4/item/{name}.png
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Provide `name` on each `ItemStack` to use the correct asset:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
{ type: 276, name: 'diamond_sword', count: 1, displayName: 'Diamond Sword' }
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
To use local textures (e.g., from a resource pack server):
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<InventoryGUI
|
|
213
|
+
type="chest"
|
|
214
|
+
textureBaseUrl="/assets/minecraft"
|
|
215
|
+
// → /assets/minecraft/item/diamond_sword.png
|
|
216
|
+
/>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
For full control, pass a custom texture config overriding any or all URL resolvers:
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { TextureProvider } from 'minecraft-inventory'
|
|
223
|
+
|
|
224
|
+
// Option A — simple base URL (all textures served from your CDN/server)
|
|
225
|
+
<TextureProvider baseUrl="https://yourserver.com/mc-assets/1.21.4">
|
|
226
|
+
{/* item textures: /mc-assets/1.21.4/items/{name}.png */}
|
|
227
|
+
{/* block textures: /mc-assets/1.21.4/blocks/{name}.png */}
|
|
228
|
+
{/* GUI textures: /mc-assets/1.21.4/textures/{path}.png */}
|
|
229
|
+
<InventoryWindow type="chest" />
|
|
230
|
+
</TextureProvider>
|
|
231
|
+
|
|
232
|
+
// Option B — replace individual resolvers (config is merged with defaults)
|
|
233
|
+
<TextureProvider config={{
|
|
234
|
+
getItemTextureUrl: ({ type, name }) =>
|
|
235
|
+
`https://cdn.example.com/items/${name ?? type}.png`,
|
|
236
|
+
|
|
237
|
+
getBlockTextureUrl: ({ type, name }) =>
|
|
238
|
+
`https://cdn.example.com/blocks/${name ?? type}.png`,
|
|
239
|
+
|
|
240
|
+
// version param lets you vary GUI textures per container (1.16.4, 1.21.4, etc.)
|
|
241
|
+
getGuiTextureUrl: (path, version = '1.16.4') =>
|
|
242
|
+
`https://cdn.example.com/gui/${version}/${path}.png`,
|
|
243
|
+
}}>
|
|
244
|
+
<InventoryWindow type="furnace" />
|
|
245
|
+
</TextureProvider>
|
|
246
|
+
|
|
247
|
+
// Option C — completely static local assets (no CDN)
|
|
248
|
+
<TextureProvider config={{
|
|
249
|
+
baseUrl: '/assets',
|
|
250
|
+
getItemTextureUrl: ({ name, type }) => `/assets/items/${name ?? type}.png`,
|
|
251
|
+
getBlockTextureUrl: ({ name, type }) => `/assets/blocks/${name ?? type}.png`,
|
|
252
|
+
getGuiTextureUrl: (path) => `/assets/gui/${path}.png`,
|
|
253
|
+
}}>
|
|
254
|
+
<InventoryWindow type="chest" />
|
|
255
|
+
</TextureProvider>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
> **Per-container texture version:** Each inventory definition can carry a `guiTextureVersion` string (e.g. `'1.21.4'`) that is passed to `getGuiTextureUrl` as the second argument. Older containers default to `'1.16.4'`; newer ones (crafter, new smithing table) use `'1.21.4'`. Specify it when [registering custom containers](#adding-new-inventory-types).
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Connector
|
|
263
|
+
|
|
264
|
+
The connector is the bridge between the GUI and a Minecraft server or bot.
|
|
265
|
+
|
|
266
|
+
### Mineflayer connector
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { createMineflayerConnector } from 'minecraft-inventory'
|
|
270
|
+
import mineflayer from 'mineflayer'
|
|
271
|
+
|
|
272
|
+
const bot = mineflayer.createBot({ ... })
|
|
273
|
+
|
|
274
|
+
bot.on('windowOpen', (window) => {
|
|
275
|
+
const connector = createMineflayerConnector(bot)
|
|
276
|
+
|
|
277
|
+
// Mount <InventoryGUI connector={connector} type={window.type} />
|
|
278
|
+
})
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The connector translates GUI actions (slot clicks, drag, drop, etc.) into `bot.clickWindow()` calls and subscribes to mineflayer inventory events to keep the GUI in sync.
|
|
282
|
+
|
|
283
|
+
### Demo connector (for local testing)
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import { createDemoConnector } from 'minecraft-inventory'
|
|
287
|
+
|
|
288
|
+
const connector = createDemoConnector({
|
|
289
|
+
windowType: 'crafting_table',
|
|
290
|
+
windowTitle: 'Crafting',
|
|
291
|
+
slots: [...],
|
|
292
|
+
onAction: (entry) => {
|
|
293
|
+
console.log(entry.description, entry.action)
|
|
294
|
+
},
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
// Update slots programmatically:
|
|
298
|
+
connector.updateSlots(newSlots)
|
|
299
|
+
connector.setHeldItem({ type: 264, name: 'diamond', count: 1 })
|
|
300
|
+
connector.openWindow('furnace', 'Furnace', furnaceSlots)
|
|
301
|
+
connector.closeWindowExternal()
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Custom connector
|
|
305
|
+
|
|
306
|
+
Implement the `InventoryConnector` interface for any other backend:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import type { InventoryConnector } from 'minecraft-inventory'
|
|
310
|
+
|
|
311
|
+
const myConnector: InventoryConnector = {
|
|
312
|
+
getWindowState: () => ({ windowId: 1, type: 'chest', slots: [...], heldItem: null }),
|
|
313
|
+
getPlayerState: () => null,
|
|
314
|
+
sendAction: async (action) => {
|
|
315
|
+
// send action to server
|
|
316
|
+
console.log('action', action)
|
|
317
|
+
},
|
|
318
|
+
closeWindow: () => {},
|
|
319
|
+
subscribe: (listener) => {
|
|
320
|
+
// call listener({ type: 'windowUpdate', state }) on changes
|
|
321
|
+
return () => {} // cleanup
|
|
322
|
+
},
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Keyboard Shortcuts (Desktop)
|
|
329
|
+
|
|
330
|
+
| Key | Action |
|
|
331
|
+
|---|---|
|
|
332
|
+
| Left click | Pick up all / place all |
|
|
333
|
+
| Right click | Pick up half / place one |
|
|
334
|
+
| Shift + Left click | Transfer to/from container |
|
|
335
|
+
| Double click | Collect all of same item type |
|
|
336
|
+
| 1–9 (while hovering) | Swap slot with hotbar slot N |
|
|
337
|
+
| Q (while hovering) | Drop one item |
|
|
338
|
+
| Ctrl+Q | Drop entire stack |
|
|
339
|
+
| Scroll up | Pick up one more (right-click equivalent) |
|
|
340
|
+
| Scroll down | Put one back |
|
|
341
|
+
| Esc | Close window (`onClose` callback) |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Mobile Support
|
|
346
|
+
|
|
347
|
+
On touch devices, tapping a slot with no held item opens a context menu instead of immediately picking up the item. The menu appears to the side in landscape or below in portrait.
|
|
348
|
+
|
|
349
|
+
Context menu options:
|
|
350
|
+
- **Take All** — picks up the entire stack
|
|
351
|
+
- **Take Half** — picks up half
|
|
352
|
+
- **Take Amount…** — `window.prompt` to enter a custom quantity
|
|
353
|
+
- **Drop** — drops the stack from the slot
|
|
354
|
+
- **Cancel**
|
|
355
|
+
|
|
356
|
+
When you have a held item, tapping a slot places/transfers it (same as left-click on desktop).
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## JEI — Item Browser
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
import { JEI } from 'minecraft-inventory'
|
|
364
|
+
|
|
365
|
+
const items = [
|
|
366
|
+
{ type: 264, name: 'diamond', displayName: 'Diamond' },
|
|
367
|
+
{ type: 265, name: 'iron_ingot', displayName: 'Iron Ingot' },
|
|
368
|
+
// ...
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
<JEI
|
|
372
|
+
items={items}
|
|
373
|
+
position="right" // 'left' | 'right'
|
|
374
|
+
onItemClick={(item) => {}} // left-click handler
|
|
375
|
+
onItemMiddleClick={(item) => {}} // middle-click handler
|
|
376
|
+
/>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Use the search bar to filter by display name or item name. Scroll wheel or arrow buttons to paginate.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Adding New Inventory Types
|
|
384
|
+
|
|
385
|
+
All inventory types live in a single registry file:
|
|
386
|
+
|
|
387
|
+
**`src/registry/inventories.ts`**
|
|
388
|
+
|
|
389
|
+
Add a new entry to the `inventoryDefinitions` object:
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
export const inventoryDefinitions: Record<string, InventoryTypeDefinition> = {
|
|
393
|
+
// ... existing types ...
|
|
394
|
+
|
|
395
|
+
my_custom_chest: {
|
|
396
|
+
name: 'my_custom_chest',
|
|
397
|
+
title: 'Custom Chest',
|
|
398
|
+
backgroundTexture: 'gui/container/my_custom_chest', // path under textures/
|
|
399
|
+
backgroundWidth: 176,
|
|
400
|
+
backgroundHeight: 166,
|
|
401
|
+
includesPlayerInventory: true,
|
|
402
|
+
includesHotbar: true,
|
|
403
|
+
slots: [
|
|
404
|
+
// Custom container slots (indices 0-N)
|
|
405
|
+
...gridSlots(0, 9, 3, 8, 18, 'container'),
|
|
406
|
+
// Player inventory (adjust indices to match server protocol)
|
|
407
|
+
...playerInv(84, 27, 54),
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Slot index conventions:**
|
|
414
|
+
- Container slots always start at `0`
|
|
415
|
+
- Player inventory and hotbar indices vary by window type (match the server protocol)
|
|
416
|
+
- Use `playerInv(yPos, invStartIndex, hotbarStartIndex)` for the standard 3×9 + hotbar layout
|
|
417
|
+
|
|
418
|
+
**Progress bars** (for furnaces, brewing stands, etc.):
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
progressBars: [
|
|
422
|
+
{
|
|
423
|
+
id: 'cook_arrow',
|
|
424
|
+
x: 79, y: 34, width: 24, height: 16,
|
|
425
|
+
direction: 'right', // 'right' | 'up' | 'down' | 'left'
|
|
426
|
+
textureX: 176, textureY: 14, // source coords in background texture
|
|
427
|
+
getValue: (props) => props.cookingProgress ?? 0,
|
|
428
|
+
getMax: (props) => props.totalCookTime || 200,
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Properties** (data slots from server):
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
properties: {
|
|
437
|
+
cookingProgress: { dataSlot: 2, description: 'Cook progress (0-200)' },
|
|
438
|
+
totalCookTime: { dataSlot: 3, description: 'Total cook time' },
|
|
439
|
+
},
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Registering at runtime** (optional, for plugins/mods):
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
import { registerInventoryType } from 'minecraft-inventory'
|
|
446
|
+
|
|
447
|
+
registerInventoryType({
|
|
448
|
+
name: 'my_mod_inventory',
|
|
449
|
+
title: 'Mod Inventory',
|
|
450
|
+
backgroundTexture: 'gui/container/my_mod',
|
|
451
|
+
backgroundWidth: 176,
|
|
452
|
+
backgroundHeight: 166,
|
|
453
|
+
slots: [...],
|
|
454
|
+
})
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Then use it anywhere:
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
<InventoryGUI type="my_mod_inventory" connector={connector} />
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## ItemStack Type
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
interface ItemStack {
|
|
469
|
+
type: number // Numeric item ID
|
|
470
|
+
name?: string // Snake_case name, e.g. 'diamond_sword' (used for texture URL)
|
|
471
|
+
count: number
|
|
472
|
+
metadata?: number
|
|
473
|
+
nbt?: Record<string, unknown>
|
|
474
|
+
displayName?: string
|
|
475
|
+
enchantments?: Array<{ name: string; level: number }>
|
|
476
|
+
lore?: string[]
|
|
477
|
+
durability?: number // Current durability value
|
|
478
|
+
maxDurability?: number // Max durability (renders bar when < max)
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Project Structure
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
src/
|
|
488
|
+
index.tsx # Library entry point / exports
|
|
489
|
+
types.ts # Core TypeScript types
|
|
490
|
+
registry/
|
|
491
|
+
index.ts # registerInventoryType / getInventoryType
|
|
492
|
+
inventories.ts # All built-in inventory type definitions ← ADD NEW TYPES HERE
|
|
493
|
+
components/
|
|
494
|
+
InventoryWindow/ # Main window renderer
|
|
495
|
+
Slot/ # Individual slot (click/drag/keyboard/mobile)
|
|
496
|
+
ItemCanvas/ # Canvas-based item texture rendering
|
|
497
|
+
Tooltip/ # Cursor-following item tooltip
|
|
498
|
+
JEI/ # Item browser panel
|
|
499
|
+
Hotbar/ # Hotbar with active slot indicator
|
|
500
|
+
HUD/ # XP bar
|
|
501
|
+
CursorItem/ # Floating item following mouse cursor
|
|
502
|
+
context/
|
|
503
|
+
InventoryContext.tsx # Shared state (held item, hover, drag)
|
|
504
|
+
ScaleContext.tsx # CSS variable scale provider
|
|
505
|
+
TextureContext.tsx # Texture URL resolver
|
|
506
|
+
connector/
|
|
507
|
+
types.ts # InventoryConnector interface
|
|
508
|
+
mineflayer.ts # Mineflayer bot adapter
|
|
509
|
+
demo.ts # Demo connector with action log
|
|
510
|
+
hooks/
|
|
511
|
+
useMobile.ts
|
|
512
|
+
useKeyboardShortcuts.ts
|
|
513
|
+
styles/
|
|
514
|
+
tokens.css # CSS custom property definitions
|
|
515
|
+
playground/
|
|
516
|
+
src/
|
|
517
|
+
App.tsx # Interactive playground / demo
|
|
518
|
+
mockItems.ts # Sample items for testing
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## Contributing
|
|
524
|
+
|
|
525
|
+
### Running the playground
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
pnpm install
|
|
529
|
+
pnpm dev
|
|
530
|
+
# Opens at http://localhost:3200
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Adding a new inventory type
|
|
534
|
+
|
|
535
|
+
1. Open `src/registry/inventories.ts`
|
|
536
|
+
2. Add a new key to `inventoryDefinitions` (see template above)
|
|
537
|
+
3. Test it in the playground by adding it to `INVENTORY_TYPES` in `playground/src/App.tsx`
|
|
538
|
+
4. Submit a PR with the new type and sample mock slots in `playground/src/mockItems.ts`
|
|
539
|
+
|
|
540
|
+
### Slot index reference
|
|
541
|
+
|
|
542
|
+
Each Minecraft window type has a fixed slot layout defined by the server protocol. The indices must match `prismarine-windows` / mineflayer slot numbering. Cross-reference with:
|
|
543
|
+
- [`prismarine-windows`](https://github.com/PrismarineJS/prismarine-windows) for JS slot maps
|
|
544
|
+
- Minecraft source: `net/minecraft/world/inventory/` for the canonical layout
|
|
545
|
+
|
|
546
|
+
### Connector protocol
|
|
547
|
+
|
|
548
|
+
When implementing a custom connector, actions have these shapes:
|
|
549
|
+
|
|
550
|
+
| Action type | Fields |
|
|
551
|
+
|---|---|
|
|
552
|
+
| `click` | `slotIndex`, `button` ('left'/'right'/'middle'), `mode` ('normal'/'shift'/'double'/'number'/'drop'), `numberKey?` |
|
|
553
|
+
| `drop` | `slotIndex`, `all` (boolean) |
|
|
554
|
+
| `drag` | `slots` (number[]), `button` |
|
|
555
|
+
| `trade` | `tradeIndex` |
|
|
556
|
+
| `rename` | `text` |
|
|
557
|
+
| `enchant` | `enchantIndex` (0/1/2) |
|
|
558
|
+
| `beacon` | `primaryEffect`, `secondaryEffect` |
|
|
559
|
+
| `hotbar-swap` | `slotIndex`, `hotbarSlot` (0-8) |
|
|
560
|
+
| `close` | — |
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## License
|
|
565
|
+
|
|
566
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "minecraft-inventory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"release": {
|
|
7
|
+
"initialVersion": {
|
|
8
|
+
"version": "0.1.0"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@rsbuild/core": "^1.7.3",
|
|
16
|
+
"@rsbuild/plugin-react": "^1.4.6",
|
|
17
|
+
"@types/node": "^25.4.0",
|
|
18
|
+
"@types/react": "^19.2.14",
|
|
19
|
+
"@types/react-dom": "^19.2.3",
|
|
20
|
+
"minecraft-data": "^3.105.0",
|
|
21
|
+
"typescript": "^5.9.3"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@xmcl/text-component": "^2.1.3",
|
|
25
|
+
"react": "^19.2.4",
|
|
26
|
+
"react-dom": "^19.2.4"
|
|
27
|
+
},
|
|
28
|
+
"repository": "https://github.com/zardoy/minecraft-inventory",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "rsbuild dev",
|
|
34
|
+
"build": "rsbuild build",
|
|
35
|
+
"preview": "rsbuild preview"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
import type { InventoryConnector } from './connector/types'
|
|
3
|
+
import type { SlotState } from './types'
|
|
4
|
+
import { InventoryProvider } from './context/InventoryContext'
|
|
5
|
+
import { ScaleProvider } from './context/ScaleContext'
|
|
6
|
+
import { TextureProvider } from './context/TextureContext'
|
|
7
|
+
import { InventoryWindow } from './components/InventoryWindow'
|
|
8
|
+
import { CursorItem } from './components/CursorItem'
|
|
9
|
+
import { JEI } from './components/JEI'
|
|
10
|
+
import type { JEIItem } from './components/JEI'
|
|
11
|
+
import type { TextureConfig } from './context/TextureContext'
|
|
12
|
+
import './styles/tokens.css'
|
|
13
|
+
|
|
14
|
+
export interface InventoryGUIProps {
|
|
15
|
+
type: string
|
|
16
|
+
title?: string
|
|
17
|
+
slots?: SlotState[]
|
|
18
|
+
properties?: Record<string, number>
|
|
19
|
+
connector?: InventoryConnector | null
|
|
20
|
+
scale?: number
|
|
21
|
+
showJEI?: boolean
|
|
22
|
+
jeiItems?: JEIItem[]
|
|
23
|
+
jeiPosition?: 'left' | 'right'
|
|
24
|
+
textureBaseUrl?: string
|
|
25
|
+
textureConfig?: Partial<TextureConfig>
|
|
26
|
+
enableKeyboardShortcuts?: boolean
|
|
27
|
+
onClose?: () => void
|
|
28
|
+
className?: string
|
|
29
|
+
style?: React.CSSProperties
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function InventoryGUI({
|
|
33
|
+
type,
|
|
34
|
+
title,
|
|
35
|
+
slots,
|
|
36
|
+
properties,
|
|
37
|
+
connector = null,
|
|
38
|
+
scale = 2,
|
|
39
|
+
showJEI = false,
|
|
40
|
+
jeiItems = [],
|
|
41
|
+
jeiPosition = 'right',
|
|
42
|
+
textureBaseUrl,
|
|
43
|
+
textureConfig,
|
|
44
|
+
enableKeyboardShortcuts = true,
|
|
45
|
+
onClose,
|
|
46
|
+
className,
|
|
47
|
+
style,
|
|
48
|
+
}: InventoryGUIProps) {
|
|
49
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
50
|
+
|
|
51
|
+
const handleClose = useCallback(
|
|
52
|
+
(e: KeyboardEvent) => {
|
|
53
|
+
if (e.code === 'Escape') onClose?.()
|
|
54
|
+
},
|
|
55
|
+
[onClose],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!onClose) return
|
|
60
|
+
window.addEventListener('keydown', handleClose)
|
|
61
|
+
return () => window.removeEventListener('keydown', handleClose)
|
|
62
|
+
}, [handleClose, onClose])
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<TextureProvider baseUrl={textureBaseUrl} config={textureConfig}>
|
|
66
|
+
<ScaleProvider scale={scale}>
|
|
67
|
+
<InventoryProvider connector={connector}>
|
|
68
|
+
<div
|
|
69
|
+
ref={containerRef}
|
|
70
|
+
className={className}
|
|
71
|
+
style={{
|
|
72
|
+
display: 'flex',
|
|
73
|
+
alignItems: 'flex-start',
|
|
74
|
+
gap: 4 * scale,
|
|
75
|
+
...style,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{showJEI && jeiPosition === 'left' && (
|
|
79
|
+
<JEI
|
|
80
|
+
items={jeiItems}
|
|
81
|
+
position="left"
|
|
82
|
+
style={{ alignSelf: 'stretch' }}
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
<InventoryWindow
|
|
87
|
+
type={type}
|
|
88
|
+
title={title}
|
|
89
|
+
slots={slots}
|
|
90
|
+
properties={properties}
|
|
91
|
+
enableKeyboardShortcuts={enableKeyboardShortcuts}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
{showJEI && jeiPosition === 'right' && (
|
|
95
|
+
<JEI
|
|
96
|
+
items={jeiItems}
|
|
97
|
+
position="right"
|
|
98
|
+
style={{ alignSelf: 'stretch' }}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<CursorItem />
|
|
104
|
+
</InventoryProvider>
|
|
105
|
+
</ScaleProvider>
|
|
106
|
+
</TextureProvider>
|
|
107
|
+
)
|
|
108
|
+
}
|