import type { MobEffectInstance, NbtTag } from 'deepslate' import { ItemStack, NbtCompound, NbtList, PotionContents } from 'deepslate' import { Identifier } from 'deepslate/core' import type { ResolvedItem } from '../services/ResolvedItem.js' import { makeDescriptionId, mergeTextComponentStyles } from '../Utils.js' import { TextComponent } from './TextComponent.jsx' interface Props { item: ResolvedItem, advanced?: boolean, resolver: (item: ItemStack) => ResolvedItem, } export function ItemTooltip({ item, advanced, resolver }: Props) { if (item.has('hide_tooltip')) { return <> } return <> {!advanced && !item.has('custom_name') && item.is('filled_map') && item.has('map_id') && ( tag.getAsNumber())], color: 'gray' }} /> )} {!item.has('hide_additional_tooltip') && <> {item.is('filled_map') && advanced && (item.get('map_id', tag => tag.isNumber()) ? tag.getAsNumber())], color: 'gray' }} /> : )} {(item.id.path.endsWith('_banner') || item.is('shield')) && item.get('banner_patterns', tag => tag.isList() ? tag : [])?.map(layer => )} {item.is('crossbow') && item.getChargedProjectile() && ( )} {item.is('disc_fragment_5') && ( )} {item.is('firework_rocket') && item.has('fireworks') && <> {((item.get('fireworks', tag => tag.isCompound() ? tag.getNumber('flight_duration') : 0) ?? 0) > 0) && ( tag.isCompound() ? tag.getNumber('flight_duration') : 0)], color: 'gray'}} /> )} {/* TODO: firework explosions */} } {item.is('firework_star') && item.has('firework_explosion') && ( tag.isCompound() ? tag.getString('shape') : '')}`, color: 'gray' }} /> // TODO: additional stuff )} {/* TODO: painting variants */} {item.is('goat_horn') && item.has('instrument') && ( tag.isCompound() ? tag.get('description')?.toSimplifiedJson() : { translate: makeDescriptionId('instrument', Identifier.parse(tag.getAsString()))} ), { color: 'gray' })} /> )} {(item.is('lingering_potion') || item.is('potion') || item.is('splash_potion') || item.is('tipped_arrow')) && ( tag) ?? NbtCompound.create())} factor={item.is('lingering_potion') ? 0.25 : item.is('tipped_arrow') ? 0.125 : 1} /> )} {/* TODO: mob buckets */} {/* TODO: smithing templates */} {item.is('written_book') && item.has('written_book_content') && <> tag.isCompound() ? tag.getString('author') : undefined) ?? ''], color: 'gray' }} /> tag.isCompound() ? tag.getNumber('generation') : undefined) ?? 0}`, color: 'gray' }} /> } {(item.is('beehive') || item.is('bee_nest')) && <> tag.isList() ? tag.length : 0) ?? 0, 3], color: 'gray' }} /> tag.isCompound() ? tag.getString('honey_level') : 0) ?? 0, 5], color: 'gray' }} /> } {item.is('decorated_pot') && item.has('pot_decorations') && <> {item.get('pot_decorations', tag => tag.isList() ? tag.map(e => ) : undefined)} } {item.id.path.endsWith('_shulker_box') && <> {item.has('container_loot') && ( )} {(item.get('container', tag => tag.isList() ? tag.getItems() : []) ?? []).slice(0, 5).map(e => { const subItem = resolver(ItemStack.fromNbt(e.isCompound() ? e.getCompound('item') : new NbtCompound())) return })} {(item.get('container', tag => tag.isList() ? tag.length : 0) ?? 0) > 5 && ( tag.isList() ? tag.length : 0) ?? 0) - 5], italic: true }} /> )} } {/* TODO: spawner and trial spawner */} } {item.showInTooltip('jukebox_playable') && <> tag.isCompound() ? ( tag.hasCompound('song') ? tag.getCompound('song').get('description')?.toSimplifiedJson() : { translate: makeDescriptionId('jukebox_song', Identifier.parse(tag.getString('song')))} ) : {}) ?? {}, { color: 'gray'})} /> } {item.showInTooltip('trim') && <> tag.isCompound() ? ( tag.hasCompound('pattern') ? tag.getCompound('pattern').get('description')?.toSimplifiedJson() : { translate: makeDescriptionId('trim_pattern', Identifier.parse(tag.getString('pattern'))), color: BUILTIN_TRIM_MATERIALS[tag.getString('material').replace(/^minecraft:/, '')] ?? 'gray' } ) : '')] }} /> tag.isCompound() ? ( tag.hasCompound('material') ? tag.getCompound('material').get('description')?.toSimplifiedJson() : { translate: makeDescriptionId('trim_material', Identifier.parse(tag.getString('material'))), color: BUILTIN_TRIM_MATERIALS[tag.getString('material').replace(/^minecraft:/, '')] ?? 'gray' } ) : '')] }}/> } {item.showInTooltip('stored_enchantments') && ( tag)} /> )} {item.showInTooltip('enchantments') && ( tag)} /> )} {item.showInTooltip('dyed_color') && (advanced ? tag.isCompound() ? tag.getNumber('rgb') : tag.getAsNumber())?.toString(16).padStart(6, '0')}`], color: 'gray' }} /> : )} {item.getLore().map((component) => )} {item.showInTooltip('attribute_modifiers') && ( tag)} /> )} {item.showInTooltip('unbreakable') && ( )} {item.has('ominous_bottle_amplifier') && ( tag.getAsNumber()) ?? 0, duration: 120000 }]}} /> )} {/* TODO: creative-only suspicious stew effects */} {/* TODO: can break and can place on */} {advanced && item.isDamageable() && ( )} {advanced && <> {item.getSize() > 0 && } } } const BUILTIN_TRIM_MATERIALS: Record = { amethyst: '#9A5CC6', copper: '#B4684D', diamond: '#6EECD2', emerald: '#11A036', gold: '#DEB12D', iron: '#ECECEC', lapis: '#416E97', netherite: '#625859', quartz: '#E3D4C4', redstone: '#971607', } const HARMFUL_EFFECTS = new Set([ 'minecraft:slowness', 'minecraft:mining_fatigue', 'minecraft:instant_damage', 'minecraft:nausea', 'minecraft:blindness', 'minecraft:hunger', 'minecraft:weakness', 'minecraft:poison', 'minecraft:wither', 'minecraft:levitation', 'minecraft:unluck', 'minecraft:darkness', 'minecraft:wind_charged', 'minecraft:weaving', 'minecraft:oozing', 'minecraft:infested', ]) function PotionContentsTooltip({ contents, factor }: { contents: PotionContents, factor?: number }) { const effects = PotionContents.getAllEffects(contents) return <> {effects.map(e => { const color = HARMFUL_EFFECTS.has(e.effect.toString()) ? 'red' : 'blue' let component: any = { translate: makeDescriptionId('effect', e.effect) } if (e.amplifier > 0) { component = { translate: 'potion.withAmplifier', with: [component, { translate: `potion.potency.${e.amplifier}` }] } } if (e.duration === -1 || e.duration > 20) { component = { translate: 'potion.withDuration', with: [component, formatDuration(e, factor ?? 1)] } } return })} {effects.length === 0 && } } function formatDuration(effect: MobEffectInstance, factor: number) { if (effect.duration === -1) { return { translate: 'effect.duration.infinite' } } const ticks = Math.floor(effect.duration * factor) let seconds = Math.floor(ticks / 20) let minutes = Math.floor(seconds / 60) seconds %= 60 const hours = Math.floor(minutes / 60) minutes %= 60 return `${hours > 0 ? `${hours}:` : ''}${minutes.toFixed().padStart(2, '0')}:${seconds.toFixed().padStart(2, '0')}` } function EnchantmentsTooltip({ data }: { data: NbtTag | undefined }) { if (!data || !data.isCompound()) { return <> } const levels = data.hasCompound('levels') ? data.getCompound('levels') : data return <> {[...levels.keys()].map((key) => { const level = levels.getNumber(key) if (level <= 0) return <> const id = Identifier.parse(key) return })} } const EQUIPMENT_GROUPS = [ 'any', 'mainhand', 'offhand', 'hand', 'feet', 'legs', 'chest', 'head', 'armor', 'body', ] const MODIFIER_OPERATIONS = [ 'add_value', 'add_multiplied_base', 'add_multiplied_total', ] const NEGATIVE_ATTRIBUTES = new Set([ 'minecraft:burning_time', 'minecraft:fall_damage_multiplier', ]) const NEUTRAL_ATTRIBUTES = new Set([ 'minecraft:gravity', 'minecraft:scale', ]) function AttributeModifiersTooltip({ data }: { data: NbtTag | undefined }) { const modifiers = data?.isList() ? data : data?.isCompound() ? data.getList('modifiers') : new NbtList() return <> {EQUIPMENT_GROUPS.map(group => { let first = true return modifiers.map((e) => { if (!e.isCompound()) return const slot = e.has('slot') ? e.getString('slot') : 'any' if (slot !== group) return const wasFirst = first first = false let amount = e.getNumber('amount') const type = Identifier.parse(e.getString('type')) const id = Identifier.parse(e.getString('id')) const operation = MODIFIER_OPERATIONS.indexOf(e.getString('operation')) let absolute = false if (id.equals(Identifier.create('base_attack_damage'))) { amount += 1 absolute = true } else if (id.equals(Identifier.create('base_attack_speed'))) { amount += 4 absolute = true } if (operation !== 0) { amount *= 100 } else if (type.equals(Identifier.create('knockback_resistance'))) { amount *= 10 } return <> {wasFirst && <> } {absolute ? ( ) : amount > 0 ? ( ) : amount < 0 ? ( ) : <>} }) })} }