const nbt = require('prismarine-nbt') function loader (registryOrVersion) { const registry = typeof registryOrVersion === 'string' ? require('prismarine-registry')(registryOrVersion) : registryOrVersion class Item { constructor (type, count, metadata, nbt, stackId, sentByServer) { if (type == null) return if (metadata instanceof Object) { sentByServer = stackId stackId = nbt nbt = metadata metadata = 0 } this.type = type this.count = count this.metadata = metadata == null ? 0 : metadata this.nbt = nbt || null // Probably add a new feature to mcdata, e.g itemsCanHaveStackId if (registry.type === 'bedrock') { if (stackId == null && !sentByServer) stackId = Item.nextStackId() this.stackId = stackId } else { this.stackId = null } const itemEnum = registry.items[type] if (itemEnum) { this.name = itemEnum.name this.displayName = itemEnum.displayName this.stackSize = itemEnum.stackSize this.maxDurability = itemEnum.maxDurability if ('variations' in itemEnum) { const variation = itemEnum.variations.find((item) => item.metadata === metadata) if (variation) this.displayName = variation.displayName } // Can't initialize fields if the item was sent by the server if (!sentByServer) { // The 'itemEnum.maxDurability' checks to see if this item can lose durability if (registry.supportFeature('explicitMaxDurability') && this.maxDurability && !this.durabilityUsed) this.durabilityUsed = 0 } } else { this.name = 'unknown' this.displayName = 'unknown' this.stackSize = 1 } } static equal (item1, item2, matchStackSize = true, matchNbt = true) { if (item1 == null && item2 == null) { return true } else if (item1 == null) { return false } else if (item2 == null) { return false } else { return ( item1.type === item2.type && item1.metadata === item2.metadata && (matchStackSize ? item1.count === item2.count : true) && (matchNbt ? JSON.stringify(item1.nbt) === JSON.stringify(item2.nbt) : true) ) } } // TODO: Move stack ID handling to prismarine-registry, as calling the loader again resets it here static currentStackId = 0 static nextStackId () { return Item.currentStackId++ } static toNotch (item, serverAuthoritative = true) { const hasNBT = item && item.nbt && Object.keys(item.nbt.value).length > 0 if (registry.type === 'pc') { if (registry.supportFeature('itemSerializationAllowsPresent')) { if (item == null) return { present: false } return { present: true, itemId: item.type, itemCount: item.count, nbtData: hasNBT ? item.nbt : undefined } } else if (registry.supportFeature('itemSerializationUsesBlockId')) { if (item == null) return { blockId: -1 } return { blockId: item.type, itemCount: item.count, itemDamage: item.metadata, nbtData: hasNBT ? item.nbt : undefined } } } else if (registry.type === 'bedrock') { if (item == null || item.type === 0) return { network_id: 0 } if (registry.supportFeature('itemSerializeUsesAuxValue')) { return { network_id: item.id, auxiliary_value: (item.metadata << 8) | (item.count & 0xff), can_place_on: item.blocksCanPlaceOn, can_destroy: item.blocksCanDestroy, blocking_tick: 0, has_nbt: hasNBT, nbt: hasNBT ? { version: 1, nbt: item.nbt } : undefined } } else { return { network_id: item.type, count: item.count, metadata: item.metadata, has_stack_id: serverAuthoritative, stack_id: serverAuthoritative ? item.stackId : undefined, block_runtime_id: 0, extra: { can_place_on: item.blocksCanPlaceOn, can_destroy: item.blocksCanDestroy, blocking_tick: 0, has_nbt: hasNBT, nbt: hasNBT ? { version: 1, nbt: item.nbt } : undefined } } } } throw new Error("Don't know how to serialize for this mc version ") } static fromNotch (networkItem, stackId) { if (registry.type === 'pc') { if (registry.supportFeature('itemSerializationWillOnlyUsePresent')) { if (networkItem.present === false) return null return new Item(networkItem.itemId, networkItem.itemCount, networkItem.nbtData, null, true) } else if (registry.supportFeature('itemSerializationAllowsPresent')) { if (networkItem.itemId === -1 || networkItem.present === false) return null return new Item(networkItem.itemId, networkItem.itemCount, networkItem.nbtData, null, true) } else if (registry.supportFeature('itemSerializationUsesBlockId')) { if (networkItem.blockId === -1) return null return new Item(networkItem.blockId, networkItem.itemCount, networkItem.itemDamage, networkItem.nbtData, null, true) } } else if (registry.type === 'bedrock') { if (networkItem.network_id === 0) return null if (registry.supportFeature('itemSerializeUsesAuxValue')) { const item = new Item(networkItem.network_id, networkItem.auxiliary_value & 0xff, networkItem.auxiliary_value >> 8, networkItem.nbt?.nbt, stackId, true) if (networkItem.can_place_on.length > 0) item.blocksCanPlaceOn = networkItem.can_place_on if (networkItem.can_destroy.length > 0) item.blocksCanDestroy = networkItem.can_destroy return item } else { const item = new Item(networkItem.network_id, networkItem.count, networkItem.metadata, networkItem.extra.nbt?.nbt, networkItem.stack_id, true) if (networkItem.extra.can_place_on.length > 0) item.blocksCanPlaceOn = networkItem.extra.can_place_on if (networkItem.extra.can_destroy.length > 0) item.blocksCanDestroy = networkItem.extra.can_destroy return item } } throw new Error("Don't know how to deserialize for this mc version ") } get customName () { return this?.nbt?.value?.display?.value?.Name?.value ?? null } set customName (newName) { if (!this.nbt) this.nbt = nbt.comp({}) if (!this.nbt.value.display) this.nbt.value.display = { type: 'compound', value: {} } this.nbt.value.display.value.Name = nbt.string(newName) } get customLore () { if (!this.nbt?.value?.display) return null return nbt.simplify(this.nbt).display.Lore ?? null } set customLore (newLore) { if (!this.nbt) this.nbt = nbt.comp({}) if (!this.nbt.value.display) this.nbt.value.display = { type: 'compound', value: {} } this.nbt.value.display.value.Lore = registry.supportFeature('itemLoreIsAString') ? nbt.string(newLore) : nbt.list(nbt.string(newLore)) } // gets the cost based on previous anvil uses get repairCost () { return this?.nbt?.value?.RepairCost?.value ?? 0 } set repairCost (newRepairCost) { if (!this?.nbt) this.nbt = nbt.comp({}) this.nbt.value.RepairCost = nbt.int(newRepairCost) } get enchants () { if (Object.keys(this).length === 0) return [] const enchantNbtKey = registry.supportFeature('nbtNameForEnchant') const typeOfEnchantLevelValue = registry.supportFeature('typeOfValueForEnchantLevel') const useStoredEnchantments = registry.supportFeature('booksUseStoredEnchantments') && this.name === 'enchanted_book' if (typeOfEnchantLevelValue === 'short' && enchantNbtKey === 'ench') { let itemEnch = [] if (useStoredEnchantments && this?.nbt?.value?.StoredEnchantments) { itemEnch = nbt.simplify(this.nbt).StoredEnchantments } else if (this?.nbt?.value?.ench) { itemEnch = nbt.simplify(this.nbt).ench } else { itemEnch = [] } return itemEnch.map((ench) => ({ lvl: ench.lvl, name: registry.enchantments[ench.id]?.name || null })) } else if (typeOfEnchantLevelValue === 'string' && enchantNbtKey === 'Enchantments') { let itemEnch = [] if (useStoredEnchantments && this?.nbt?.value?.StoredEnchantments) { itemEnch = nbt.simplify(this.nbt).StoredEnchantments } else if (this?.nbt?.value?.Enchantments) { itemEnch = nbt.simplify(this.nbt).Enchantments } else { itemEnch = [] } return itemEnch.map((ench) => ({ lvl: ench.lvl, name: typeof ench.id === 'string' ? ench.id.replace('minecraft:', '') : null })) } throw new Error("Don't know how to get the enchants from an item on this mc version") } set enchants (normalizedEnchArray) { const enchListName = registry.supportFeature('nbtNameForEnchant') const type = registry.supportFeature('typeOfValueForEnchantLevel') if (!type) throw new Error("Don't know the serialized type for enchant level") const useStoredEnchants = this.name === 'enchanted_book' && registry.supportFeature('booksUseStoredEnchantments') const enchs = normalizedEnchArray.map(({ name, lvl }) => { const value = type === 'short' ? registry.enchantmentsByName[name].id : `minecraft:${registry.enchantmentsByName[name].name}` return { id: { type, value }, lvl: nbt.short(lvl) } }) if (enchs.length !== 0) { if (!this.nbt) this.nbt = nbt.comp({}) this.nbt.value[useStoredEnchants ? 'StoredEnchantments' : enchListName] = nbt.list(nbt.comp(enchs)) } else if (this.enchants.length !== 0) { delete this.nbt?.[useStoredEnchants ? 'StoredEnchantments' : enchListName] } } get blocksCanPlaceOn () { const blockNames = this?.nbt?.value?.CanPlaceOn?.value?.value ?? [] return blockNames.map(name => [name]) } set blocksCanPlaceOn (newBlocks) { if (newBlocks.length === 0) { if (this.blocksCanPlaceOn.length !== 0) delete this.nbt.value.CanPlaceOn return } if (!this.nbt) this.nbt = nbt.comp({}) const blockNames = [] for (const block of newBlocks) { let [ns, name] = block.split(':') if (!name) { name = ns ns = 'minecraft' } blockNames.push(`${ns}:${name}`) } this.nbt.value.CanPlaceOn = nbt.list(nbt.string(blockNames)) } get blocksCanDestroy () { const blockNames = this?.nbt?.value?.CanDestroy?.value?.value ?? [] return blockNames.map(name => [name]) } set blocksCanDestroy (newBlocks) { if (newBlocks.length === 0) { if (this.blocksCanDestroy.length !== 0) delete this.nbt.value.CanDestroy return } if (!this.nbt) this.nbt = nbt.comp({}) const blockNames = [] for (const block of newBlocks) { let [ns, name] = block.split(':') if (!name) { name = ns ns = 'minecraft' } blockNames.push(`${ns}:${name}`) } this.nbt.value.CanDestroy = nbt.list(nbt.string(blockNames)) } get durabilityUsed () { const where = registry.supportFeature('whereDurabilityIsSerialized') let ret if (where === 'Damage') ret = this.nbt?.value?.Damage?.value else if (where === 'metadata') ret = this.metadata else throw new Error('unknown durability location') return ret ?? (this.maxDurability ? 0 : null) } set durabilityUsed (value) { const where = registry.supportFeature('whereDurabilityIsSerialized') if (where === 'Damage') { if (!this?.nbt) this.nbt = nbt.comp({}) this.nbt.value.Damage = nbt.int(value) } else if (where === 'metadata') { this.metadata = value } else { throw new Error("Don't know how to set item durability for this mc version") } } get spawnEggMobName () { if (registry.supportFeature('spawnEggsHaveSpawnedEntityInName')) { return this.name.replace('_spawn_egg', '') } if (registry.supportFeature('spawnEggsUseInternalIdInNbt')) { return registry.entitiesArray.find((o) => o.internalId === this.metadata).name } if (registry.supportFeature('spawnEggsUseEntityTagInNbt')) { const data = nbt.simplify(this.nbt) const entityName = data.EntityTag.id return entityName.replace('minecraft:', '') } throw new Error("Don't know how to get spawn egg mob name for this mc version") } } Item.anvil = require('./lib/anvil.js')(registry, Item) return Item } module.exports = loader