minecraft-inventory 0.1.44 → 0.1.46
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 +1 -1
- package/src/components/InventoryOverlay/InventoryOverlay.tsx +1 -1
- package/src/components/InventoryWindow/InventoryWindow.tsx +2 -2
- package/src/components/Slot/Slot.module.css +2 -5
- package/src/components/Slot/Slot.tsx +14 -3
- package/src/components/Tooltip/ItemTooltipBody.tsx +69 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -54
- package/src/connector/mineflayer.ts +57 -4
package/package.json
CHANGED
|
@@ -47,8 +47,9 @@ export function InventoryWindow({
|
|
|
47
47
|
const { scale, slotSize, contentSize } = useScale()
|
|
48
48
|
// borderPx removed from slot positioning: registry coords already point to the item area
|
|
49
49
|
const borderPx = 0
|
|
50
|
+
const isHotbar = type === 'hotbar'
|
|
50
51
|
|
|
51
|
-
useKeyboardShortcuts(enableKeyboardShortcuts)
|
|
52
|
+
useKeyboardShortcuts(enableKeyboardShortcuts && !isHotbar)
|
|
52
53
|
|
|
53
54
|
const effectiveSlots = useMemo(() => {
|
|
54
55
|
if (slotsProp) return slotsProp
|
|
@@ -73,7 +74,6 @@ export function InventoryWindow({
|
|
|
73
74
|
const effectiveTitle = (type === 'player' || isBeacon) ? undefined : (title ?? windowState?.title ?? def.title)
|
|
74
75
|
const isVillager = type === 'villager'
|
|
75
76
|
const isEnchanting = def?.name === 'enchanting_table'
|
|
76
|
-
const isHotbar = type === 'hotbar'
|
|
77
77
|
const isAnvil = def?.name === 'anvil'
|
|
78
78
|
|
|
79
79
|
const resolveItem = (slotIndex: number) => {
|
|
@@ -4,6 +4,8 @@ import { useInventoryContext } from '../../context/InventoryContext'
|
|
|
4
4
|
import { useScale } from '../../context/ScaleContext'
|
|
5
5
|
import { ItemCanvas } from '../ItemCanvas'
|
|
6
6
|
import { Tooltip } from '../Tooltip'
|
|
7
|
+
import { ItemTooltipBody } from '../Tooltip/ItemTooltipBody'
|
|
8
|
+
import tooltipStyles from '../Tooltip/Tooltip.module.css'
|
|
7
9
|
import { useMobile } from '../../hooks/useMobile'
|
|
8
10
|
import styles from './Slot.module.css'
|
|
9
11
|
|
|
@@ -606,7 +608,7 @@ function MobileSlotMenu({ item, x, y, onPickAll, onPickHalf, onDropOne, onDropAl
|
|
|
606
608
|
if (top + mh > vh - 4) top = vh - mh - 4
|
|
607
609
|
if (top < 4) top = 4
|
|
608
610
|
setPos({ left, top })
|
|
609
|
-
}, [x, y])
|
|
611
|
+
}, [x, y, item])
|
|
610
612
|
|
|
611
613
|
// Wrapper to handle both touch and click, preventing event bubbling to the slot
|
|
612
614
|
const touchBtn = (handler: () => void) => ({
|
|
@@ -631,8 +633,17 @@ function MobileSlotMenu({ item, x, y, onPickAll, onPickHalf, onDropOne, onDropAl
|
|
|
631
633
|
minWidth: 100 * scale,
|
|
632
634
|
}}
|
|
633
635
|
>
|
|
634
|
-
<div
|
|
635
|
-
{
|
|
636
|
+
<div
|
|
637
|
+
className={[tooltipStyles.tooltip, styles.mobileMenuInfo].join(' ')}
|
|
638
|
+
style={{
|
|
639
|
+
fontSize: Math.round(8 * scale),
|
|
640
|
+
padding: Math.round(3 * scale),
|
|
641
|
+
gap: Math.round(0.5 * scale),
|
|
642
|
+
width: 'max-content',
|
|
643
|
+
maxWidth: 'min(90vw, 280px)',
|
|
644
|
+
}}
|
|
645
|
+
>
|
|
646
|
+
<ItemTooltipBody item={item} />
|
|
636
647
|
</div>
|
|
637
648
|
<button className={styles.mobileBtn} {...touchBtn(onPickAll)}>Select All ({item.count})</button>
|
|
638
649
|
<button className={styles.mobileBtn} {...touchBtn(onPickHalf)}>Pick Half ({Math.ceil(item.count / 2)})</button>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { ItemStack } from '../../types'
|
|
3
|
+
import { MessageFormattedString } from '../Text/MessageFormattedString'
|
|
4
|
+
import styles from './Tooltip.module.css'
|
|
5
|
+
|
|
6
|
+
function getRarityColor(item: ItemStack): string {
|
|
7
|
+
if (item.enchantments && item.enchantments.length > 0) return '#8080ff'
|
|
8
|
+
return '#ffffff'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatEnchantment(e: { name: string; level: number }): string {
|
|
12
|
+
const roman = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X']
|
|
13
|
+
return e.level <= 10 ? `${e.name} ${roman[e.level]}` : `${e.name} ${e.level}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getItemTooltipDisplayName(item: ItemStack): string {
|
|
17
|
+
return item.displayName ?? (item.name ? item.name.replace(/_/g, ' ') : `Item #${item.type}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Item name, enchantments, lore, durability, and type id — shared by desktop tooltip and mobile menu. */
|
|
21
|
+
export function ItemTooltipBody({ item }: { item: ItemStack }) {
|
|
22
|
+
const nameColor = getRarityColor(item)
|
|
23
|
+
const hasDurability =
|
|
24
|
+
item.durability !== undefined &&
|
|
25
|
+
item.maxDurability !== undefined &&
|
|
26
|
+
item.durability < item.maxDurability
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
<div className={styles.name} style={{ color: nameColor }}>
|
|
31
|
+
<MessageFormattedString
|
|
32
|
+
text={getItemTooltipDisplayName(item)}
|
|
33
|
+
fallbackColor={nameColor}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{item.enchantments && item.enchantments.length > 0 && (
|
|
38
|
+
<div className={styles.section}>
|
|
39
|
+
{item.enchantments.map((e, i) => (
|
|
40
|
+
<div key={i} className={styles.enchantment}>
|
|
41
|
+
{formatEnchantment(e)}
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
{item.lore && item.lore.length > 0 && (
|
|
48
|
+
<div className={styles.lore}>
|
|
49
|
+
{item.lore.map((line, i) => (
|
|
50
|
+
<div key={i}>
|
|
51
|
+
<MessageFormattedString text={line} />
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
{hasDurability && (
|
|
58
|
+
<div className={styles.durability}>
|
|
59
|
+
Durability: {item.durability} / {item.maxDurability}
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
<div className={styles.typeId}>
|
|
64
|
+
{item.name ?? `#${item.type}`}
|
|
65
|
+
{item.metadata !== undefined && item.metadata > 0 ? `:${item.metadata}` : ''}
|
|
66
|
+
</div>
|
|
67
|
+
</>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -2,8 +2,8 @@ import React, { useRef, useEffect } from 'react'
|
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
3
|
import type { ItemStack } from '../../types'
|
|
4
4
|
import { useScale } from '../../context/ScaleContext'
|
|
5
|
-
import { MessageFormattedString } from '../Text/MessageFormattedString'
|
|
6
5
|
import { globalMouse } from '../../utils/globalMouse'
|
|
6
|
+
import { ItemTooltipBody } from './ItemTooltipBody'
|
|
7
7
|
import styles from './Tooltip.module.css'
|
|
8
8
|
|
|
9
9
|
interface TooltipProps {
|
|
@@ -11,16 +11,6 @@ interface TooltipProps {
|
|
|
11
11
|
visible: boolean
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function getRarityColor(item: ItemStack): string {
|
|
15
|
-
if (item.enchantments && item.enchantments.length > 0) return '#8080ff'
|
|
16
|
-
return '#ffffff'
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function formatEnchantment(e: { name: string; level: number }): string {
|
|
20
|
-
const roman = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X']
|
|
21
|
-
return e.level <= 10 ? `${e.name} ${roman[e.level]}` : `${e.name} ${e.level}`
|
|
22
|
-
}
|
|
23
|
-
|
|
24
14
|
export function Tooltip({ item, visible }: TooltipProps) {
|
|
25
15
|
const { scale } = useScale()
|
|
26
16
|
const ref = useRef<HTMLDivElement>(null)
|
|
@@ -63,12 +53,6 @@ export function Tooltip({ item, visible }: TooltipProps) {
|
|
|
63
53
|
|
|
64
54
|
if (!visible) return null
|
|
65
55
|
|
|
66
|
-
const nameColor = getRarityColor(item)
|
|
67
|
-
const hasDurability =
|
|
68
|
-
item.durability !== undefined &&
|
|
69
|
-
item.maxDurability !== undefined &&
|
|
70
|
-
item.durability < item.maxDurability
|
|
71
|
-
|
|
72
56
|
const fs = Math.round(8 * scale)
|
|
73
57
|
const pad = Math.round(3 * scale)
|
|
74
58
|
const gap2 = Math.round(0.5 * scale)
|
|
@@ -91,43 +75,7 @@ export function Tooltip({ item, visible }: TooltipProps) {
|
|
|
91
75
|
pointerEvents: 'none',
|
|
92
76
|
}}
|
|
93
77
|
>
|
|
94
|
-
<
|
|
95
|
-
<MessageFormattedString
|
|
96
|
-
text={item.displayName ?? (item.name ? item.name.replace(/_/g, ' ') : `Item #${item.type}`)}
|
|
97
|
-
fallbackColor={nameColor}
|
|
98
|
-
/>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{item.enchantments && item.enchantments.length > 0 && (
|
|
102
|
-
<div className={styles.section}>
|
|
103
|
-
{item.enchantments.map((e, i) => (
|
|
104
|
-
<div key={i} className={styles.enchantment}>
|
|
105
|
-
{formatEnchantment(e)}
|
|
106
|
-
</div>
|
|
107
|
-
))}
|
|
108
|
-
</div>
|
|
109
|
-
)}
|
|
110
|
-
|
|
111
|
-
{item.lore && item.lore.length > 0 && (
|
|
112
|
-
<div className={styles.lore}>
|
|
113
|
-
{item.lore.map((line, i) => (
|
|
114
|
-
<div key={i}>
|
|
115
|
-
<MessageFormattedString text={line} />
|
|
116
|
-
</div>
|
|
117
|
-
))}
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
|
-
|
|
121
|
-
{hasDurability && (
|
|
122
|
-
<div className={styles.durability}>
|
|
123
|
-
Durability: {item.durability} / {item.maxDurability}
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
|
|
127
|
-
<div className={styles.typeId}>
|
|
128
|
-
{item.name ?? `#${item.type}`}
|
|
129
|
-
{item.metadata !== undefined && item.metadata > 0 ? `:${item.metadata}` : ''}
|
|
130
|
-
</div>
|
|
78
|
+
<ItemTooltipBody item={item} />
|
|
131
79
|
</div>
|
|
132
80
|
)
|
|
133
81
|
|
|
@@ -147,6 +147,14 @@ function isEnchantmentTableWindow(win: unknown): win is { enchant: (choice: numb
|
|
|
147
147
|
return win != null && typeof (win as Record<string, unknown>).enchant === 'function'
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/** Registry property keys for the three enchant rows (craft_progress_bar data slots 0–2). */
|
|
151
|
+
const ENCHANT_OPTION_LEVEL_KEYS = ['topEnchantLevel', 'middleEnchantLevel', 'bottomEnchantLevel'] as const
|
|
152
|
+
|
|
153
|
+
function isEnchantingTableWindowType(type: string | undefined | null): boolean {
|
|
154
|
+
if (!type) return false
|
|
155
|
+
return getInventoryType(type)?.name === 'enchanting_table'
|
|
156
|
+
}
|
|
157
|
+
|
|
150
158
|
function isAnvilWindow(
|
|
151
159
|
win: unknown
|
|
152
160
|
): win is {
|
|
@@ -641,10 +649,55 @@ export function createMineflayerConnector(bot: MineflayerBot, options?: Mineflay
|
|
|
641
649
|
return
|
|
642
650
|
}
|
|
643
651
|
|
|
644
|
-
if (action.type === 'enchant'
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
652
|
+
if (action.type === 'enchant') {
|
|
653
|
+
if (!win) {
|
|
654
|
+
logActionEvent('connector.action.skipped', action, { reason: 'no_open_window' })
|
|
655
|
+
return
|
|
656
|
+
}
|
|
657
|
+
if (!isEnchantingTableWindowType(win.type)) {
|
|
658
|
+
logActionEvent('connector.action.skipped', action, {
|
|
659
|
+
reason: 'not_enchanting_table',
|
|
660
|
+
windowType: win.type,
|
|
661
|
+
})
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
const choice = action.enchantIndex
|
|
665
|
+
if (!Number.isInteger(choice) || choice < 0 || choice > 2) {
|
|
666
|
+
logActionEvent('connector.action.skipped', action, { reason: 'invalid_enchant_index' })
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
const optionLevel = windowProperties[ENCHANT_OPTION_LEVEL_KEYS[choice]] ?? -1
|
|
670
|
+
if (optionLevel <= 0) {
|
|
671
|
+
logActionEvent('connector.action.skipped', action, {
|
|
672
|
+
reason: 'enchant_option_unavailable',
|
|
673
|
+
level: optionLevel,
|
|
674
|
+
})
|
|
675
|
+
return
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Direct enchant_item packet — same as mineflayer/lib/plugins/enchantment_table.js.
|
|
679
|
+
// Does not require bot.openEnchantmentTable() (win.enchant is optional).
|
|
680
|
+
if (ext._client) {
|
|
681
|
+
const packet = {
|
|
682
|
+
windowId: win.id,
|
|
683
|
+
enchantment: choice,
|
|
684
|
+
}
|
|
685
|
+
logPacketWrite('enchant_item', packet)
|
|
686
|
+
ext._client.write('enchant_item', packet)
|
|
687
|
+
scheduleSlotUpdate()
|
|
688
|
+
logActionEvent('connector.action.success', action)
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (isEnchantmentTableWindow(win)) {
|
|
693
|
+
logHelperIntent(action, 'window.enchant', { enchantIndex: choice })
|
|
694
|
+
await win.enchant(choice)
|
|
695
|
+
onSetSlot()
|
|
696
|
+
logActionEvent('connector.action.success', action)
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
logActionEvent('connector.action.skipped', action, { reason: 'missing_client' })
|
|
648
701
|
return
|
|
649
702
|
}
|
|
650
703
|
|