Skip to content

Commit

Permalink
Add maxDurability field, fix handling item sent by server with new …
Browse files Browse the repository at this point in the history
…`sentByServer` arg (#106)

* Add `maxDurability` field, fix handling item sent by server with new `sentByServer` arg

* fix type typo

* anvil: only set new item durability if it changed
  • Loading branch information
extremeheat authored Jul 30, 2023
1 parent 6ce147e commit d36111d
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 35 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ console.log(Item.fromNotch(notchItem))

## API

### Item(type, count[, metadata, nbt, stackId])
### Item(type, count[, metadata, nbt, stackId, sentByServer])

* sentByServer - whether this item was sent by the server to the client, so default
initialization will not be done on the item. For example, tools will not have the
default item NBT written to them.

#### Item.toNotch(item[, serverAuthoritative])

Expand Down Expand Up @@ -116,6 +120,9 @@ See https://minecraft.gamepedia.com/Anvil_mechanics#Anvil_Uses

If the current item is a type of Spawn Egg, the protocol name of the entity that will be spawned. For example, a zombie spawn egg on 1.8 will return `Zombie`.

#### item.maxDurability

Max durability for the item, if it supports durability

## History

Expand Down
3 changes: 2 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Tags, TagType } from 'prismarine-nbt'
export type ItemLike = Item | null

export class Item {
constructor(type: number, count: number, metadata?: number, nbt?: object, stackId?: number);
constructor(type: number, count: number, metadata?: number, nbt?: object, stackId?: number, sentByServer?: boolean);
type: number;
slot: number;
count: number;
Expand All @@ -15,6 +15,7 @@ export class Item {
name: string;
displayName: string;
stackSize: number;
maxDurability: number;
durabilityUsed: number;
get enchants(): { name: string; lvl: number }[];
set enchants(enchantments: { name: string; lvl: number }[]);
Expand Down
36 changes: 20 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ 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) {
constructor (type, count, metadata, nbt, stackId, sentByServer) {
if (type == null) return

if (metadata instanceof Object) {
sentByServer = stackId
stackId = nbt
nbt = metadata
metadata = 0
Expand All @@ -19,7 +20,8 @@ function loader (registryOrVersion) {

// Probably add a new feature to mcdata, e.g itemsCanHaveStackId
if (registry.type === 'bedrock') {
this.stackId = stackId ?? Item.nextStackId()
if (stackId == null && !sentByServer) stackId = Item.nextStackId()
this.stackId = stackId
} else {
this.stackId = null
}
Expand All @@ -29,14 +31,18 @@ function loader (registryOrVersion) {
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
}

// The 'itemEnum.maxDurability' checks to see if this item can lose durability
if (itemEnum.maxDurability && !this.durabilityUsed) this.durabilityUsed = 0
// 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'
Expand Down Expand Up @@ -126,24 +132,24 @@ function loader (registryOrVersion) {
if (registry.type === 'pc') {
if (registry.supportFeature('itemSerializationWillOnlyUsePresent')) {
if (networkItem.present === false) return null
return new Item(networkItem.itemId, networkItem.itemCount, networkItem.nbtData)
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)
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)
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)
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)
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
Expand Down Expand Up @@ -298,14 +304,12 @@ function loader (registryOrVersion) {
}

get durabilityUsed () {
if (Object.keys(this).length === 0) return null
const where = registry.supportFeature('whereDurabilityIsSerialized')
if (where === 'Damage') {
return this?.nbt?.value?.Damage?.value ?? 0
} else if (where === 'metadata') {
return this.metadata ?? 0
}
throw new Error("Don't know how to get item durability for this mc version")
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) {
Expand Down
5 changes: 3 additions & 2 deletions lib/anvil.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ function loader (registry, Item) {

let finalItem = null
if (itemOne) {
finalItem = new Item(itemOne.type, itemOne.count, 0, JSON.parse(JSON.stringify(itemOne.nbt)))
finalItem = new Item(itemOne.type, itemOne.count, 0, JSON.parse(JSON.stringify(itemOne.nbt)), null, true)
const resultDurability = itemOne.durabilityUsed - data.fixedDurability
const repairCost = Math.max(itemOne.repairCost, (itemTwo?.repairCost ?? 0)) * 2 + 1
if (data?.finalEnchs.length > 0) finalItem.enchants = data.finalEnchs
if (rename) finalItem.customName = renamedName
finalItem.repairCost = repairCost
if (itemOne.name !== 'enchanted_book') finalItem.durabilityUsed = itemOne.durabilityUsed - data.fixedDurability
if (resultDurability && itemOne.name !== 'enchanted_book') finalItem.durabilityUsed = resultDurability
}
return { xpCost: cost, item: finalItem, usedMats: data.usedMats }
}
Expand Down
30 changes: 15 additions & 15 deletions test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('test based on examples', () => {
const ironShovelItem = new Item(256, 1)

it('constructor makes item correctly', () => {
const val = { type: 256, count: 1, metadata: 0, nbt: null, name: 'iron_shovel', displayName: 'Iron Shovel', stackSize: 1, stackId: null }
const val = { type: 256, count: 1, metadata: 0, nbt: null, name: 'iron_shovel', displayName: 'Iron Shovel', stackSize: 1, stackId: null, maxDurability: 250 }
expect(JSON.parse(JSON.stringify(ironShovelItem))).toStrictEqual(val)
})

Expand All @@ -19,7 +19,7 @@ describe('test based on examples', () => {
it('use .fromNotch', () => {
const toNotch = Item.toNotch(ironShovelItem)
const fromNotch = Item.fromNotch(toNotch)
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: null, stackSize: 1, type: 256, stackId: null }
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: null, stackSize: 1, type: 256, stackId: null, maxDurability: 250 }
expect(JSON.parse(JSON.stringify(fromNotch))).toStrictEqual(expectedObj)
})
})
Expand All @@ -28,19 +28,19 @@ describe('test based on examples', () => {
const ironShovelItem = new Item(472, 1)

it('constructor makes item correctly', () => {
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: { name: '', type: 'compound', value: { Damage: { type: 'int', value: 0 } } }, stackSize: 1, type: 472, stackId: null }
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: null, stackSize: 1, type: 472, stackId: null, maxDurability: 250 }
expect(JSON.parse(JSON.stringify(ironShovelItem))).toStrictEqual(expectedObj)
})

it('use .toNotch', () => {
const expectedObj = { itemCount: 1, itemId: 472, present: true, nbtData: { name: '', type: 'compound', value: { Damage: { type: 'int', value: 0 } } } }
const expectedObj = { itemCount: 1, itemId: 472, present: true, nbtData: undefined }
expect(Item.toNotch(ironShovelItem)).toStrictEqual(expectedObj)
})

it('use .fromNotch', () => {
const toNotch = Item.toNotch(ironShovelItem)
const fromNotch = Item.fromNotch(toNotch)
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: { name: '', type: 'compound', value: { Damage: { type: 'int', value: 0 } } }, stackSize: 1, type: 472, stackId: null }
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: null, stackSize: 1, type: 472, stackId: null, maxDurability: 250 }
expect(JSON.parse(JSON.stringify(fromNotch))).toStrictEqual(expectedObj)
})
})
Expand All @@ -50,7 +50,7 @@ describe('test based on examples', () => {
const ironShovelItem = new Item(registry.itemsByName.iron_shovel.id, 1)

it('constructor makes item correctly', () => {
const val = { type: registry.itemsByName.iron_shovel.id, count: 1, metadata: 0, nbt: { name: '', type: 'compound', value: { Damage: { type: 'int', value: 0 } } }, name: 'iron_shovel', displayName: 'Iron Shovel', stackSize: 1, stackId: 0 }
const val = { type: registry.itemsByName.iron_shovel.id, count: 1, metadata: 0, nbt: { name: '', type: 'compound', value: { Damage: { type: 'int', value: 0 } } }, name: 'iron_shovel', displayName: 'Iron Shovel', stackSize: 1, stackId: 0, maxDurability: 250 }
expect(JSON.parse(JSON.stringify(ironShovelItem))).toStrictEqual(val)
})

Expand All @@ -61,7 +61,7 @@ describe('test based on examples', () => {
it('use .fromNotch', () => {
const toNotch = Item.toNotch(ironShovelItem)
const fromNotch = Item.fromNotch(toNotch)
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: { name: '', type: 'compound', value: { Damage: { type: 'int', value: 0 } } }, stackSize: 1, type: registry.itemsByName.iron_shovel.id, stackId: 0 }
const expectedObj = { count: 1, displayName: 'Iron Shovel', metadata: 0, name: 'iron_shovel', nbt: { name: '', type: 'compound', value: { Damage: { type: 'int', value: 0 } } }, stackSize: 1, type: registry.itemsByName.iron_shovel.id, stackId: 0, maxDurability: 250 }
expect(JSON.parse(JSON.stringify(fromNotch))).toStrictEqual(expectedObj)
})
})
Expand Down Expand Up @@ -350,30 +350,30 @@ describe('set item.enchants', () => {
const Item = require('prismarine-item')(registry)

it('unenchanted stone sword', () => {
const newItem = new Item(704, 1)
const newItem = new Item(704, 1, undefined, undefined, 0, true)
const item = Item.fromNotch({ network_id: 704, count: 1, metadata: 0, stack_id: 0, has_stack_id: true, extra: { has_nbt: false, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
expect(newItem).toStrictEqual(item)
})
it('unbreaking 1 iron pickaxe', () => {
const newItem = new Item(716, 1)
const newItem = new Item(716, 1, undefined, undefined, 1, true)
const item = Item.fromNotch({ network_id: 716, count: 1, metadata: 0, stack_id: 1, has_stack_id: true, extra: { has_nbt: true, nbt: { version: 1, nbt: { name: '', type: 'compound', value: { ench: { type: 'list', value: { type: 'compound', value: [{ id: { type: 'short', value: 17 }, lvl: { type: 'short', value: 1 } }] } }, RepairCost: { type: 'int', value: 3 } } } }, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
newItem.repairCost = 3
expect(newItem).toStrictEqual(item)
})
it('efficiency 5 diamond shovel', () => {
const newItem = new Item(720, 1)
const newItem = new Item(720, 1, undefined, undefined, 2, true)
const item = Item.fromNotch({ network_id: 720, count: 1, metadata: 0, stack_id: 2, has_stack_id: true, extra: { has_nbt: true, nbt: { version: 1, nbt: { name: '', type: 'compound', value: { ench: { type: 'list', value: { type: 'compound', value: [{ id: { type: 'short', value: 15 }, lvl: { type: 'short', value: 5 } }] } }, RepairCost: { type: 'int', value: 2 } } } }, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
newItem.repairCost = 2
expect(newItem).toStrictEqual(item)
})
it('protection 4, mending diamond leggings', () => {
const newItem = new Item(752, 1)
const newItem = new Item(752, 1, undefined, undefined, 3, true)
const item = Item.fromNotch({ network_id: 752, count: 1, metadata: 0, stack_id: 3, has_stack_id: true, extra: { has_nbt: true, nbt: { version: 1, nbt: { name: '', type: 'compound', value: { ench: { type: 'list', value: { type: 'compound', value: [{ id: { type: 'short', value: 0 }, lvl: { type: 'short', value: 4 } }, { id: { type: 'short', value: 26 }, lvl: { type: 'short', value: 1 } }] } }, RepairCost: { type: 'int', value: 3 } } } }, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
Expand All @@ -385,30 +385,30 @@ describe('set item.enchants', () => {
const Item = require('prismarine-item')('bedrock_1.19.1')

it('unenchanted iron hoe', () => {
const newItem = new Item(754, 1)
const newItem = new Item(754, 1, undefined, undefined, 0, true)
const item = Item.fromNotch({ network_id: 754, count: 1, metadata: 0, stack_id: 0, has_stack_id: true, extra: { has_nbt: false, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
expect(newItem).toStrictEqual(item)
})
it('silk touch stone axe', () => {
const newItem = new Item(743, 1)
const newItem = new Item(743, 1, undefined, undefined, 1, true)
const item = Item.fromNotch({ network_id: 743, count: 1, metadata: 0, stack_id: 1, has_stack_id: true, extra: { has_nbt: true, nbt: { version: 1, nbt: { name: '', type: 'compound', value: { ench: { type: 'list', value: { type: 'compound', value: [{ id: { type: 'short', value: 16 }, lvl: { type: 'short', value: 1 } }] } }, RepairCost: { type: 'int', value: 1 } } } }, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
newItem.repairCost = 1
expect(newItem).toStrictEqual(item)
})
it('lure 3 fishing rod', () => {
const newItem = new Item(836, 1)
const newItem = new Item(836, 1, undefined, undefined, 2, true)
const item = Item.fromNotch({ network_id: 836, count: 1, metadata: 0, stack_id: 2, has_stack_id: true, extra: { has_nbt: true, nbt: { version: 1, nbt: { name: '', type: 'compound', value: { ench: { type: 'list', value: { type: 'compound', value: [{ id: { type: 'short', value: 24 }, lvl: { type: 'short', value: 3 } }] } }, RepairCost: { type: 'int', value: 2 } } } }, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
newItem.repairCost = 2
expect(newItem).toStrictEqual(item)
})
it('fire prot 3, unbreaking 2, respiration 3 diamond helmet', () => {
const newItem = new Item(786, 1)
const newItem = new Item(786, 1, undefined, undefined, 3, true)
const item = Item.fromNotch({ network_id: 786, count: 1, metadata: 0, stack_id: 3, has_stack_id: true, extra: { has_nbt: true, nbt: { version: 1, nbt: { name: '', type: 'compound', value: { ench: { type: 'list', value: { type: 'compound', value: [{ id: { type: 'short', value: 1 }, lvl: { type: 'short', value: 3 } }, { id: { type: 'short', value: 17 }, lvl: { type: 'short', value: 2 } }, { id: { type: 'short', value: 6 }, lvl: { type: 'short', value: 3 } }] } }, RepairCost: { type: 'int', value: 3 } } } }, can_place_on: [], can_destroy: [] } })
const enchs = item.enchants
newItem.enchants = enchs
Expand Down

0 comments on commit d36111d

Please sign in to comment.