minecraft-inventory 0.1.21 → 0.1.23

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-inventory",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "release": {
@@ -310,7 +310,7 @@ export function InventoryOverlay({
310
310
  lineHeight: 1,
311
311
  }}
312
312
  >
313
- INV 0.1.21
313
+ INV 0.1.23
314
314
  </a>
315
315
  )}
316
316
 
@@ -12,9 +12,8 @@ export function AnvilCost({ properties, backgroundWidth }: AnvilCostProps) {
12
12
  const { windowState } = useInventoryContext()
13
13
 
14
14
  const cost = properties.repairCost ?? 0
15
- if (cost <= 0) return null
16
-
17
15
  const hasResult = windowState?.slots.some((s) => s.index === 2 && s.item !== null) ?? false
16
+ if (cost <= 0) return null
18
17
  if (!hasResult) return null
19
18
 
20
19
  const tooExpensive = cost >= 40
@@ -1,5 +1,6 @@
1
- import React from 'react'
1
+ import React, { useState } from 'react'
2
2
  import { useInventoryContext } from '../../context/InventoryContext'
3
+ import { useTextures } from '../../context/TextureContext'
3
4
  import { useScale } from '../../context/ScaleContext'
4
5
 
5
6
  const ENCHANTMENT_NAMES: Record<number, string> = {
@@ -59,6 +60,8 @@ function toRoman(num: number): string {
59
60
  return result
60
61
  }
61
62
 
63
+ const SPRITE_BASE = '1.21.11/textures/gui/sprites/container/enchanting_table'
64
+
62
65
  interface EnchantmentOptionsProps {
63
66
  properties: Record<string, number>
64
67
  x: number
@@ -72,8 +75,27 @@ const OPTION_KEYS = [
72
75
  ]
73
76
 
74
77
  export function EnchantmentOptions({ properties, x, y }: EnchantmentOptionsProps) {
75
- const { sendAction } = useInventoryContext()
78
+ const { sendAction, resolveEnchantmentName } = useInventoryContext()
79
+ const textures = useTextures()
76
80
  const { scale } = useScale()
81
+ const [hoveredSlot, setHoveredSlot] = useState<number | null>(null)
82
+
83
+ const getName = (id: number): string => {
84
+ if (id < 0) return '???'
85
+ return resolveEnchantmentName?.(id) ?? ENCHANTMENT_NAMES[id] ?? `Enchant #${id}`
86
+ }
87
+
88
+ const getSlotSprite = (disabled: boolean, hovered: boolean) => {
89
+ if (disabled) return textures.getGuiTextureUrl(`${SPRITE_BASE}/enchantment_slot_disabled.png`)
90
+ if (hovered) return textures.getGuiTextureUrl(`${SPRITE_BASE}/enchantment_slot_highlighted.png`)
91
+ return textures.getGuiTextureUrl(`${SPRITE_BASE}/enchantment_slot.png`)
92
+ }
93
+
94
+ const getLevelSprite = (slot: number, disabled: boolean) => {
95
+ const n = slot + 1
96
+ if (disabled) return textures.getGuiTextureUrl(`${SPRITE_BASE}/level_${n}_disabled.png`)
97
+ return textures.getGuiTextureUrl(`${SPRITE_BASE}/level_${n}.png`)
98
+ }
77
99
 
78
100
  return (
79
101
  <div
@@ -90,36 +112,100 @@ export function EnchantmentOptions({ properties, x, y }: EnchantmentOptionsProps
90
112
  const level = properties[levelKey] ?? 0
91
113
  const enchId = properties[idKey] ?? -1
92
114
  const levelClue = properties[clueKey] ?? 0
93
- const name = ENCHANTMENT_NAMES[enchId] ?? (enchId >= 0 ? `Enchant #${enchId}` : '???')
94
115
  const disabled = level <= 0
116
+ const hovered = hoveredSlot === i && !disabled
117
+ const name = getName(enchId)
95
118
  const levelSuffix = levelClue > 0 ? ` ${toRoman(levelClue)}` : ''
119
+ // Vanilla colors
120
+ const nameColor = disabled ? '#342F25' : hovered ? '#FFFF80' : '#685E4A'
121
+ const costColor = disabled ? '#407F10' : '#80FF20'
96
122
 
97
123
  return (
98
124
  <div
99
125
  key={i}
100
126
  onClick={() => !disabled && sendAction({ type: 'enchant', enchantIndex: slot })}
127
+ onMouseEnter={() => setHoveredSlot(i)}
128
+ onMouseLeave={() => setHoveredSlot(null)}
101
129
  style={{
130
+ position: 'relative',
102
131
  width: 108 * scale,
103
132
  height: 19 * scale,
104
- display: 'flex',
105
- alignItems: 'center',
106
- padding: `0 ${4 * scale}px`,
107
- gap: 4 * scale,
108
- background: disabled ? 'rgba(0,0,0,0.3)' : 'rgba(0,60,0,0.5)',
109
- cursor: disabled ? 'not-allowed' : 'pointer',
110
- border: `${scale}px solid ${disabled ? '#333333' : '#446644'}`,
111
- boxSizing: 'border-box',
112
- fontSize: 6 * scale,
113
- fontFamily: "'Minecraft', monospace",
114
- color: disabled ? '#555555' : '#80c060',
133
+ cursor: disabled ? 'default' : 'pointer',
134
+ overflow: 'hidden',
115
135
  }}
116
136
  >
117
- <span style={{ color: disabled ? '#555' : '#558855', fontWeight: 'bold' }}>
118
- {level}
119
- </span>
120
- <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
121
- {disabled ? '' : `${name}${levelSuffix}`}
122
- </span>
137
+ {/* Background sprite */}
138
+ <img
139
+ src={getSlotSprite(disabled, hovered)}
140
+ alt=""
141
+ aria-hidden
142
+ draggable={false}
143
+ style={{
144
+ position: 'absolute',
145
+ left: 0,
146
+ top: 0,
147
+ width: 108 * scale,
148
+ height: 19 * scale,
149
+ imageRendering: 'pixelated',
150
+ }}
151
+ />
152
+ {/* Level icon (16×16 at +1,+1) */}
153
+ {!disabled && (
154
+ <img
155
+ src={getLevelSprite(i, disabled)}
156
+ alt=""
157
+ aria-hidden
158
+ draggable={false}
159
+ style={{
160
+ position: 'absolute',
161
+ left: 1 * scale,
162
+ top: 1 * scale,
163
+ width: 16 * scale,
164
+ height: 16 * scale,
165
+ imageRendering: 'pixelated',
166
+ }}
167
+ />
168
+ )}
169
+ {/* Enchantment name text (at +20, +2) */}
170
+ {!disabled && (
171
+ <span
172
+ style={{
173
+ position: 'absolute',
174
+ left: 20 * scale,
175
+ top: 2 * scale,
176
+ fontSize: 8 * scale,
177
+ fontFamily: "'Minecraft', monospace",
178
+ color: nameColor,
179
+ whiteSpace: 'nowrap',
180
+ overflow: 'hidden',
181
+ textOverflow: 'clip',
182
+ maxWidth: 70 * scale,
183
+ lineHeight: `${16 * scale}px`,
184
+ textShadow: 'none',
185
+ }}
186
+ >
187
+ {`${name}${levelSuffix}`}
188
+ </span>
189
+ )}
190
+ {/* Cost number (right-aligned, vertically centered) */}
191
+ {!disabled && (
192
+ <span
193
+ style={{
194
+ position: 'absolute',
195
+ right: 2 * scale,
196
+ top: 0,
197
+ height: 19 * scale,
198
+ display: 'flex',
199
+ alignItems: 'center',
200
+ fontSize: 8 * scale,
201
+ fontFamily: "'Minecraft', monospace",
202
+ color: costColor,
203
+ textShadow: 'none',
204
+ }}
205
+ >
206
+ {level}
207
+ </span>
208
+ )}
123
209
  </div>
124
210
  )
125
211
  })}
@@ -52,6 +52,26 @@ export interface MineflayerConnectorOptions {
52
52
  * Return a human-readable string for display.
53
53
  */
54
54
  formatTitle?: (rawTitle: any) => string
55
+
56
+ /**
57
+ * Optional client-side anvil cost calculator. Called when items in an anvil
58
+ * window change to compute repairCost instantly (before the server's
59
+ * craft_progress_bar packet arrives). Receives the two input slot items.
60
+ * Return the XP cost, or null/0 to clear. Server packets will override this.
61
+ *
62
+ * @example Using prismarine-item:
63
+ * ```ts
64
+ * const Item = require('prismarine-item')(bot.registry)
65
+ * createMineflayerConnector(bot, {
66
+ * computeAnvilCost: (item1, item2) => {
67
+ * if (!item1) return null
68
+ * const { xpCost } = Item.anvil(item1, item2, bot.game.gameMode === 'creative')
69
+ * return xpCost
70
+ * },
71
+ * })
72
+ * ```
73
+ */
74
+ computeAnvilCost?: (itemOne: RawSlot | null, itemTwo: RawSlot | null) => number | null
55
75
  }
56
76
 
57
77
  function makeSlotConverter(itemMapper?: MineflayerConnectorOptions['itemMapper']) {
@@ -138,6 +158,7 @@ export function createMineflayerConnector(bot: MineflayerBot, options?: Mineflay
138
158
  const convert = makeSlotConverter(options?.itemMapper)
139
159
  const hotbarOnly = options?.hotbarOnly ?? false
140
160
  const formatTitle = options?.formatTitle
161
+ const computeAnvilCost = options?.computeAnvilCost
141
162
 
142
163
  // Track window properties (furnace progress, enchant levels, etc.)
143
164
  const windowProperties: Record<string, number> = {}
@@ -297,6 +318,27 @@ export function createMineflayerConnector(bot: MineflayerBot, options?: Mineflay
297
318
  }
298
319
  }
299
320
 
321
+ /** Compute anvil cost client-side if callback is provided and window is anvil. */
322
+ function tryComputeAnvilCost() {
323
+ if (!computeAnvilCost || !currentWindowType) return
324
+ const resolved = getInventoryType(currentWindowType)
325
+ if (resolved?.name !== 'anvil') return
326
+ const win = bot.currentWindow
327
+ if (!win) return
328
+ const item1 = (win.slots[0] as RawSlot | null) ?? null
329
+ const item2 = (win.slots[1] as RawSlot | null) ?? null
330
+ try {
331
+ const cost = item1 ? computeAnvilCost(item1, item2) : null
332
+ // Only SET client cost, never delete server-provided values.
333
+ // The server's craft_progress_bar will send 0 to clear when appropriate.
334
+ if (cost != null && cost > 0) {
335
+ windowProperties.repairCost = cost
336
+ }
337
+ } catch (err) {
338
+ // Client-side computation failed — rely on server cost
339
+ }
340
+ }
341
+
300
342
  const onWindowOpen = () => {
301
343
  const state = buildWindowState()
302
344
  if (state) emit({ type: 'windowOpen', state })
@@ -307,6 +349,7 @@ export function createMineflayerConnector(bot: MineflayerBot, options?: Mineflay
307
349
  }
308
350
 
309
351
  const onSetSlot = () => {
352
+ tryComputeAnvilCost()
310
353
  const state = buildWindowState()
311
354
  if (state) emit({ type: 'windowUpdate', state })
312
355
  emit({ type: 'playerUpdate', state: buildPlayerState() })
@@ -496,11 +539,17 @@ export function createMineflayerConnector(bot: MineflayerBot, options?: Mineflay
496
539
  return
497
540
  }
498
541
 
499
- if (action.type === 'rename' && win && isAnvilWindow(win)) {
500
- const w = win as { slots?: unknown[]; findInventoryItem?: (id: number) => unknown; rename: (item: unknown, name: string) => Promise<void> }
501
- const inputSlot = w.slots?.[0] as { type?: number; metadata?: number; count?: number; nbt?: unknown } | null
502
- const item = inputSlot?.type ? (w.findInventoryItem?.(inputSlot.type) ?? inputSlot) : null
503
- if (item) await w.rename(item, action.text)
542
+ if (action.type === 'rename' && win && isAnvilWindow(win) && ext._client) {
543
+ // Send name_item packet directly don't use mineflayer's win.rename()
544
+ // because it tries to transfer items from player inventory into anvil,
545
+ // which fails when the user already placed items via the UI.
546
+ if (ext.supportFeature?.('useMCItemName')) {
547
+ if (!ext._client.registerChannel) return
548
+ ext._client.registerChannel('MC|ItemName', 'string')
549
+ ext._client.writeChannel?.('MC|ItemName', action.text)
550
+ } else {
551
+ ext._client.write('name_item', { name: action.text })
552
+ }
504
553
  return
505
554
  }
506
555
 
@@ -49,6 +49,8 @@ export interface InventoryContextValue {
49
49
  dragEndedRef: React.MutableRefObject<boolean>
50
50
  /** When true, empty slot labels (Head, Chest, Legs, etc.) are not rendered */
51
51
  noPlaceholders: boolean
52
+ /** Optional resolver for enchantment ID → display name (version-aware) */
53
+ resolveEnchantmentName?: (id: number) => string | undefined
52
54
  }
53
55
 
54
56
  const InventoryContext = createContext<InventoryContextValue | null>(null)
@@ -64,9 +66,10 @@ interface InventoryProviderProps {
64
66
  children: React.ReactNode
65
67
  noDragSpread?: boolean
66
68
  noPlaceholders?: boolean
69
+ resolveEnchantmentName?: (id: number) => string | undefined
67
70
  }
68
71
 
69
- export function InventoryProvider({ connector, children, noDragSpread = false, noPlaceholders = false }: InventoryProviderProps) {
72
+ export function InventoryProvider({ connector, children, noDragSpread = false, noPlaceholders = false, resolveEnchantmentName }: InventoryProviderProps) {
70
73
  const [windowState, setWindowState] = useState<InventoryWindowState | null>(
71
74
  () => connector?.getWindowState() ?? null,
72
75
  )
@@ -318,6 +321,7 @@ export function InventoryProvider({ connector, children, noDragSpread = false, n
318
321
  pKeyDigit,
319
322
  setPKeyDigit,
320
323
  dragEndedRef,
324
+ resolveEnchantmentName,
321
325
  }
322
326
 
323
327
  valueRef.current = value
@@ -35,6 +35,15 @@ import _gui_sprites_container_smoker_lit_progress from 'mc-assets/dist/other-tex
35
35
  import _gui_sprites_container_smoker_burn_progress from 'mc-assets/dist/other-textures/latest/gui/sprites/container/smoker/burn_progress.png'
36
36
  import _gui_sprites_container_brewing_stand_brew_progress from 'mc-assets/dist/other-textures/latest/gui/sprites/container/brewing_stand/brew_progress.png'
37
37
  import _gui_sprites_container_brewing_stand_fuel_length from 'mc-assets/dist/other-textures/latest/gui/sprites/container/brewing_stand/fuel_length.png'
38
+ import _gui_sprites_container_enchanting_table_enchantment_slot from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/enchantment_slot.png'
39
+ import _gui_sprites_container_enchanting_table_enchantment_slot_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/enchantment_slot_disabled.png'
40
+ import _gui_sprites_container_enchanting_table_enchantment_slot_highlighted from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png'
41
+ import _gui_sprites_container_enchanting_table_level_1 from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_1.png'
42
+ import _gui_sprites_container_enchanting_table_level_2 from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_2.png'
43
+ import _gui_sprites_container_enchanting_table_level_3 from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_3.png'
44
+ import _gui_sprites_container_enchanting_table_level_1_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_1_disabled.png'
45
+ import _gui_sprites_container_enchanting_table_level_2_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_2_disabled.png'
46
+ import _gui_sprites_container_enchanting_table_level_3_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_3_disabled.png'
38
47
 
39
48
  /**
40
49
  * Versioned texture path → bundled asset URL (or undefined for remote fallback).
@@ -75,6 +84,15 @@ export const bundledTextureMap: Record<string, string | undefined> = {
75
84
  '1.21.11/textures/gui/sprites/container/smoker/burn_progress.png': _gui_sprites_container_smoker_burn_progress,
76
85
  '1.21.11/textures/gui/sprites/container/brewing_stand/brew_progress.png': _gui_sprites_container_brewing_stand_brew_progress,
77
86
  '1.21.11/textures/gui/sprites/container/brewing_stand/fuel_length.png': _gui_sprites_container_brewing_stand_fuel_length,
87
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot.png': _gui_sprites_container_enchanting_table_enchantment_slot,
88
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_disabled.png': _gui_sprites_container_enchanting_table_enchantment_slot_disabled,
89
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png': _gui_sprites_container_enchanting_table_enchantment_slot_highlighted,
90
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1.png': _gui_sprites_container_enchanting_table_level_1,
91
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2.png': _gui_sprites_container_enchanting_table_level_2,
92
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3.png': _gui_sprites_container_enchanting_table_level_3,
93
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1_disabled.png': _gui_sprites_container_enchanting_table_level_1_disabled,
94
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2_disabled.png': _gui_sprites_container_enchanting_table_level_2_disabled,
95
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3_disabled.png': _gui_sprites_container_enchanting_table_level_3_disabled,
78
96
  }
79
97
 
80
98
 
@@ -116,6 +134,15 @@ export const allTexturePaths: readonly string[] = [
116
134
  'gui/sprites/container/smoker/burn_progress.png',
117
135
  'gui/sprites/container/brewing_stand/brew_progress.png',
118
136
  'gui/sprites/container/brewing_stand/fuel_length.png',
137
+ 'gui/sprites/container/enchanting_table/enchantment_slot.png',
138
+ 'gui/sprites/container/enchanting_table/enchantment_slot_disabled.png',
139
+ 'gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png',
140
+ 'gui/sprites/container/enchanting_table/level_1.png',
141
+ 'gui/sprites/container/enchanting_table/level_2.png',
142
+ 'gui/sprites/container/enchanting_table/level_3.png',
143
+ 'gui/sprites/container/enchanting_table/level_1_disabled.png',
144
+ 'gui/sprites/container/enchanting_table/level_2_disabled.png',
145
+ 'gui/sprites/container/enchanting_table/level_3_disabled.png',
119
146
  ]
120
147
 
121
148
  /**
@@ -749,4 +749,14 @@ export const registryExtraTextures: string[] = [
749
749
  // Brewing stand progress bar sprites
750
750
  '1.21.11/textures/gui/sprites/container/brewing_stand/brew_progress.png',
751
751
  '1.21.11/textures/gui/sprites/container/brewing_stand/fuel_length.png',
752
+ // Enchanting table slot backgrounds & level icons
753
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot.png',
754
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_disabled.png',
755
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png',
756
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1.png',
757
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2.png',
758
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3.png',
759
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1_disabled.png',
760
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2_disabled.png',
761
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3_disabled.png',
752
762
  ]