351 lines
13 KiB
JavaScript
351 lines
13 KiB
JavaScript
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
|