mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-30 17:49:34 +00:00
Render items with custom colors + update deepslate
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -23,7 +23,7 @@
|
|||||||
"brace": "^0.11.1",
|
"brace": "^0.11.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"comment-json": "^4.1.1",
|
"comment-json": "^4.1.1",
|
||||||
"deepslate": "^0.13.2",
|
"deepslate": "^0.15.5",
|
||||||
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
|
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
|
||||||
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
|
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
|
||||||
"highlight.js": "^11.5.1",
|
"highlight.js": "^11.5.1",
|
||||||
@@ -1955,9 +1955,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/deepslate": {
|
"node_modules/deepslate": {
|
||||||
"version": "0.13.2",
|
"version": "0.15.5",
|
||||||
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.15.5.tgz",
|
||||||
"integrity": "sha512-6pa9mgPu4A+RqYoN7AH79oKzzSNfvCJsrBKHE+AQjt20Uo33qJIRNG+2+sFHx84PAPJ3Z1CCnWWV+kBniD8E2g==",
|
"integrity": "sha512-t+ucG50ldN0HMiDfXk8Tpzpw2ld6BA6NxJClQV+sB429vn1YmTk6WZx8GXd1exgUf/XYNR+ax+cxv/9Zr6Qy3g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gl-matrix": "^3.3.0",
|
"gl-matrix": "^3.3.0",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
@@ -6698,9 +6698,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"deepslate": {
|
"deepslate": {
|
||||||
"version": "0.13.2",
|
"version": "0.15.5",
|
||||||
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/deepslate/-/deepslate-0.15.5.tgz",
|
||||||
"integrity": "sha512-6pa9mgPu4A+RqYoN7AH79oKzzSNfvCJsrBKHE+AQjt20Uo33qJIRNG+2+sFHx84PAPJ3Z1CCnWWV+kBniD8E2g==",
|
"integrity": "sha512-t+ucG50ldN0HMiDfXk8Tpzpw2ld6BA6NxJClQV+sB429vn1YmTk6WZx8GXd1exgUf/XYNR+ax+cxv/9Zr6Qy3g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"gl-matrix": "^3.3.0",
|
"gl-matrix": "^3.3.0",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"brace": "^0.11.1",
|
"brace": "^0.11.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"comment-json": "^4.1.1",
|
"comment-json": "^4.1.1",
|
||||||
"deepslate": "^0.13.2",
|
"deepslate": "^0.15.5",
|
||||||
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
|
"deepslate-1.18": "npm:deepslate@^0.9.0-beta.9",
|
||||||
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
|
"deepslate-1.18.2": "npm:deepslate@^0.9.0-beta.13",
|
||||||
"highlight.js": "^11.5.1",
|
"highlight.js": "^11.5.1",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
|
import type { ItemStack } from 'deepslate'
|
||||||
|
import { Identifier } from 'deepslate-1.18.2'
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||||
import { useVersion } from '../contexts/Version.jsx'
|
import { useVersion } from '../contexts/Version.jsx'
|
||||||
import { useAsync } from '../hooks/useAsync.js'
|
import { useAsync } from '../hooks/useAsync.js'
|
||||||
import type { Item } from '../previews/LootTable.js'
|
import { itemHasGlint, MaxDamageItems } from '../previews/LootTable.js'
|
||||||
import { MaxDamageItems } from '../previews/LootTable.js'
|
|
||||||
import { getAssetUrl } from '../services/DataFetcher.js'
|
|
||||||
import { renderItem } from '../services/Resources.js'
|
import { renderItem } from '../services/Resources.js'
|
||||||
import { getCollections } from '../services/Schemas.js'
|
import { getCollections } from '../services/Schemas.js'
|
||||||
import { ItemTooltip } from './ItemTooltip.jsx'
|
import { ItemTooltip } from './ItemTooltip.jsx'
|
||||||
import { Octicon } from './Octicon.jsx'
|
import { Octicon } from './Octicon.jsx'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: Item,
|
item: ItemStack,
|
||||||
slotDecoration?: boolean,
|
slotDecoration?: boolean,
|
||||||
advancedTooltip?: boolean,
|
advancedTooltip?: boolean,
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ export function ItemDisplay({ item, slotDecoration, advancedTooltip }: Props) {
|
|||||||
return () => el.current?.removeEventListener('mousemove', onMove)
|
return () => el.current?.removeEventListener('mousemove', onMove)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const maxDamage = MaxDamageItems.get(item.id)
|
const maxDamage = MaxDamageItems.get(item.id.toString())
|
||||||
|
|
||||||
return <div class="item-display" ref={el}>
|
return <div class="item-display" ref={el}>
|
||||||
<ItemItself item={item} />
|
<ItemItself item={item} />
|
||||||
@@ -43,23 +43,22 @@ export function ItemDisplay({ item, slotDecoration, advancedTooltip }: Props) {
|
|||||||
</svg>
|
</svg>
|
||||||
</>}
|
</>}
|
||||||
{slotDecoration && <>
|
{slotDecoration && <>
|
||||||
{(maxDamage && (item.tag?.Damage ?? 0) > 0) && <svg class="item-durability" width="100%" height="100%" viewBox="0 0 18 18">
|
{(maxDamage && item.tag.getNumber('Damage') > 0) && <svg class="item-durability" width="100%" height="100%" viewBox="0 0 18 18">
|
||||||
<rect x="3" y="14" width="13" height="2" fill="#000" />
|
<rect x="3" y="14" width="13" height="2" fill="#000" />
|
||||||
<rect x="3" y="14" width={`${(maxDamage - item.tag.Damage) / maxDamage * 13}`} height="1" fill={`hsl(${(maxDamage - item.tag.Damage) / maxDamage * 120}deg, 100%, 50%)`} />
|
<rect x="3" y="14" width={`${(maxDamage - item.tag.getNumber('Damage')) / maxDamage * 13}`} height="1" fill={`hsl(${(maxDamage - item.tag.getNumber('Damage')) / maxDamage * 120}deg, 100%, 50%)`} />
|
||||||
</svg>}
|
</svg>}
|
||||||
<div class="item-slot-overlay"></div>
|
<div class="item-slot-overlay"></div>
|
||||||
</>}
|
</>}
|
||||||
<ItemTooltip {...item} advanced={advancedTooltip} offset={tooltipOffset} swap={tooltipSwap} />
|
<ItemTooltip item={item} advanced={advancedTooltip} offset={tooltipOffset} swap={tooltipSwap} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemItself({ item }: Props) {
|
function ItemItself({ item }: Props) {
|
||||||
const { version } = useVersion()
|
const { version } = useVersion()
|
||||||
const [errored, setErrored] = useState(false)
|
|
||||||
|
|
||||||
const isEnchanted = (item.tag?.Enchantments?.length ?? 0) > 0 || (item.tag?.StoredEnchantments?.length ?? 0) > 0
|
const hasGlint = itemHasGlint(item)
|
||||||
|
|
||||||
if (errored || (item.id.includes(':') && !item.id.startsWith('minecraft:'))) {
|
if (item.id.namespace !== Identifier.DEFAULT_NAMESPACE) {
|
||||||
return Octicon.package
|
return Octicon.package
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,31 +68,22 @@ function ItemItself({ item }: Props) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const texturePath = `item/${item.id.replace(/^minecraft:/, '')}`
|
const modelPath = `item/${item.id.path}`
|
||||||
if (collections.get('texture').includes('minecraft:' + texturePath)) {
|
|
||||||
const src = getAssetUrl(version, 'textures', texturePath)
|
|
||||||
return <>
|
|
||||||
<img src={src} alt="" onError={() => setErrored(true)} draggable={false} />
|
|
||||||
{isEnchanted && <div class="item-glint" style={{'--mask-image': `url("${src}")`}}></div>}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelPath = `item/${item.id.replace(/^minecraft:/, '')}`
|
|
||||||
if (collections.get('model').includes('minecraft:' + modelPath)) {
|
if (collections.get('model').includes('minecraft:' + modelPath)) {
|
||||||
return <RenderedItem item={item} isEnchanted={isEnchanted} />
|
return <RenderedItem item={item} hasGlint={hasGlint} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return Octicon.package
|
return Octicon.package
|
||||||
}
|
}
|
||||||
|
|
||||||
function RenderedItem({ item, isEnchanted }: Props & { isEnchanted: boolean }) {
|
function RenderedItem({ item, hasGlint }: Props & { hasGlint: boolean }) {
|
||||||
const { version } = useVersion()
|
const { version } = useVersion()
|
||||||
const { value: src } = useAsync(() => renderItem(version, item.id), [version, item])
|
const { value: src } = useAsync(() => renderItem(version, item), [version, item])
|
||||||
|
|
||||||
if (src) {
|
if (src) {
|
||||||
return <>
|
return <>
|
||||||
<img src={src} alt={item.id} class="model" draggable={false} />
|
<img src={src} alt={item.id.toString()} class="model" draggable={false} />
|
||||||
{isEnchanted && <div class="item-glint" style={{'--mask-image': `url("${src}")`}}></div>}
|
{hasGlint && <div class="item-glint" style={{'--mask-image': `url("${src}")`}}></div>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ItemStack } from 'deepslate'
|
||||||
|
import { Identifier, NbtList, NbtType } from 'deepslate'
|
||||||
import { useVersion } from '../contexts/Version.jsx'
|
import { useVersion } from '../contexts/Version.jsx'
|
||||||
import { useAsync } from '../hooks/useAsync.js'
|
import { useAsync } from '../hooks/useAsync.js'
|
||||||
import { getEnchantmentData, MaxDamageItems } from '../previews/LootTable.js'
|
import { getEnchantmentData, MaxDamageItems } from '../previews/LootTable.js'
|
||||||
@@ -5,31 +7,32 @@ import { getTranslation } from '../services/Resources.js'
|
|||||||
import { TextComponent } from './TextComponent.jsx'
|
import { TextComponent } from './TextComponent.jsx'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string,
|
item: ItemStack,
|
||||||
tag?: any,
|
|
||||||
advanced?: boolean,
|
advanced?: boolean,
|
||||||
offset?: [number, number],
|
offset?: [number, number],
|
||||||
swap?: boolean,
|
swap?: boolean,
|
||||||
}
|
}
|
||||||
export function ItemTooltip({ id, tag, advanced, offset = [0, 0], swap }: Props) {
|
export function ItemTooltip({ item, advanced, offset = [0, 0], swap }: Props) {
|
||||||
const { version } = useVersion()
|
const { version } = useVersion()
|
||||||
const { value: translatedName } = useAsync(() => {
|
const { value: translatedName } = useAsync(() => {
|
||||||
const key = id.split(':').join('.')
|
const key = `${item.id.namespace}.${item.id.path}`
|
||||||
return getTranslation(version, `item.${key}`) ?? getTranslation(version, `block.${key}`)
|
return getTranslation(version, `item.${key}`) ?? getTranslation(version, `block.${key}`)
|
||||||
}, [version, id])
|
}, [version, item.id])
|
||||||
const displayName = tag?.display?.Name
|
const displayName = item.tag.getCompound('display').getString('Name')
|
||||||
const name = displayName ? JSON.parse(displayName) : (translatedName ?? fakeTranslation(id))
|
const name = displayName ? JSON.parse(displayName) : (translatedName ?? fakeTranslation(item.id.path))
|
||||||
|
|
||||||
const maxDamage = MaxDamageItems.get(id)
|
const maxDamage = MaxDamageItems.get(item.id.toString())
|
||||||
const enchantments = (id === 'minecraft:enchanted_book' ? tag?.StoredEnchantments : tag?.Enchantments) ?? []
|
const enchantments = (item.id.equals(Identifier.create('enchanted_book')) ? item.tag.getList('StoredEnchantments', NbtType.Compound) : item.tag.getList('Enchantments', NbtType.Compound)) ?? NbtList.create()
|
||||||
|
|
||||||
return <div class="item-tooltip" style={offset && {
|
return <div class="item-tooltip" style={offset && {
|
||||||
left: (swap ? undefined : `${offset[0]}px`),
|
left: (swap ? undefined : `${offset[0]}px`),
|
||||||
right: (swap ? `${offset[0]}px` : undefined),
|
right: (swap ? `${offset[0]}px` : undefined),
|
||||||
top: `${offset[1]}px`,
|
top: `${offset[1]}px`,
|
||||||
}}>
|
}}>
|
||||||
<TextComponent component={name} base={{ color: 'white' }} />
|
<TextComponent component={name} base={{ color: 'white', italic: displayName.length > 0 }} />
|
||||||
{enchantments.map(({ id, lvl }: { id: string, lvl: number }) => {
|
{enchantments.map(enchantment => {
|
||||||
|
const id = enchantment.getString('id')
|
||||||
|
const lvl = enchantment.getNumber('lvl')
|
||||||
const ench = getEnchantmentData(id)
|
const ench = getEnchantmentData(id)
|
||||||
const component: any[] = [{ translate: `enchantment.${id.replace(':', '.')}`, color: ench?.curse ? 'red' : 'gray' }]
|
const component: any[] = [{ translate: `enchantment.${id.replace(':', '.')}`, color: ench?.curse ? 'red' : 'gray' }]
|
||||||
if (lvl !== 1 || ench?.maxLevel !== 1) {
|
if (lvl !== 1 || ench?.maxLevel !== 1) {
|
||||||
@@ -37,24 +40,23 @@ export function ItemTooltip({ id, tag, advanced, offset = [0, 0], swap }: Props)
|
|||||||
}
|
}
|
||||||
return <TextComponent component={component} />
|
return <TextComponent component={component} />
|
||||||
})}
|
})}
|
||||||
{tag?.display && <>
|
{item.tag.hasCompound('display') && <>
|
||||||
{tag?.display?.color && (advanced
|
{item.tag.getCompound('display').hasNumber('color') && (advanced
|
||||||
? <TextComponent component={{ translate: 'item.color', with: [`#${tag.display.color.toString(16).padStart(6, '0')}`], color: 'gray' }} />
|
? <TextComponent component={{ translate: 'item.color', with: [`#${item.tag.getCompound('display').getNumber('color').toString(16).padStart(6, '0')}`], color: 'gray' }} />
|
||||||
: <TextComponent component={{ translate: 'item.dyed', color: 'gray' }} />)}
|
: <TextComponent component={{ translate: 'item.dyed', color: 'gray' }} />)}
|
||||||
{(tag?.display?.Lore ?? []).map((line: any) => <TextComponent component={JSON.parse(line)} base={{ color: 'dark_purple', italic: true }} />)}
|
{(item.tag.getCompound('display').getList('Lore', NbtType.String)).map((line) => <TextComponent component={JSON.parse(line.getAsString())} base={{ color: 'dark_purple', italic: true }} />)}
|
||||||
</>}
|
</>}
|
||||||
{tag?.Unbreakable === true && <TextComponent component={{ translate: 'item.unbreakable', color: 'blue' }} />}
|
{item.tag.getBoolean('Unbreakable') && <TextComponent component={{ translate: 'item.unbreakable', color: 'blue' }} />}
|
||||||
{(advanced && (tag?.Damage ?? 0) > 0 && maxDamage) && <TextComponent component={{ translate: 'item.durability', with: [`${maxDamage - tag.Damage}`, `${maxDamage}`] }} />}
|
{(advanced && item.tag.getNumber('Damage') > 0 && maxDamage) && <TextComponent component={{ translate: 'item.durability', with: [`${maxDamage - item.tag.getNumber('Damage')}`, `${maxDamage}`] }} />}
|
||||||
{advanced && <>
|
{advanced && <>
|
||||||
<TextComponent component={{ text: id, color: 'dark_gray'}} />
|
<TextComponent component={{ text: item.id.toString(), color: 'dark_gray'}} />
|
||||||
{tag && <TextComponent component={{ translate: 'item.nbt_tags', with: [Object.keys(tag).length], color: 'dark_gray' }} />}
|
{item.tag.size > 0 && <TextComponent component={{ translate: 'item.nbt_tags', with: [item.tag.size], color: 'dark_gray' }} />}
|
||||||
</>}
|
</>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function fakeTranslation(str: string) {
|
function fakeTranslation(str: string) {
|
||||||
const colon = str.indexOf(':')
|
return str
|
||||||
return str.slice(colon + 1)
|
|
||||||
.replace(/[_\/]/g, ' ')
|
.replace(/[_\/]/g, ' ')
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import type { Random } from 'deepslate'
|
import type { Random } from 'deepslate'
|
||||||
import { LegacyRandom } from 'deepslate'
|
import { Identifier, ItemStack, LegacyRandom, NbtCompound, NbtInt, NbtList, NbtShort, NbtString, NbtTag, NbtType } from 'deepslate'
|
||||||
import type { VersionId } from '../services/Schemas.js'
|
import type { VersionId } from '../services/Schemas.js'
|
||||||
import { clamp, deepClone, getWeightedRandom, isObject } from '../Utils.js'
|
import { clamp, deepClone, getWeightedRandom, isObject } from '../Utils.js'
|
||||||
|
|
||||||
export interface Item {
|
|
||||||
id: string,
|
|
||||||
count: number,
|
|
||||||
tag?: any,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SlottedItem {
|
export interface SlottedItem {
|
||||||
slot: number,
|
slot: number,
|
||||||
item: Item,
|
item: ItemStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ItemConsumer = (item: Item) => void
|
type ItemConsumer = (item: ItemStack) => void
|
||||||
|
|
||||||
const StackMixers = {
|
const StackMixers = {
|
||||||
container: fillContainer,
|
container: fillContainer,
|
||||||
@@ -44,7 +38,7 @@ interface LootContext extends LootOptions {
|
|||||||
|
|
||||||
export function generateLootTable(lootTable: any, options: LootOptions) {
|
export function generateLootTable(lootTable: any, options: LootOptions) {
|
||||||
const ctx = createLootContext(options)
|
const ctx = createLootContext(options)
|
||||||
const result: Item[] = []
|
const result: ItemStack[] = []
|
||||||
generateTable(lootTable, item => result.push(item), ctx)
|
generateTable(lootTable, item => result.push(item), ctx)
|
||||||
const mixer = StackMixers[options.stackMixer]
|
const mixer = StackMixers[options.stackMixer]
|
||||||
return mixer(result, ctx)
|
return mixer(result, ctx)
|
||||||
@@ -52,11 +46,11 @@ export function generateLootTable(lootTable: any, options: LootOptions) {
|
|||||||
|
|
||||||
const SLOT_COUNT = 27
|
const SLOT_COUNT = 27
|
||||||
|
|
||||||
function fillContainer(items: Item[], ctx: LootContext): SlottedItem[] {
|
function fillContainer(items: ItemStack[], ctx: LootContext): SlottedItem[] {
|
||||||
const slots = shuffle([...Array(SLOT_COUNT)].map((_, i) => i), ctx)
|
const slots = shuffle([...Array(SLOT_COUNT)].map((_, i) => i), ctx)
|
||||||
|
|
||||||
const queue = items.filter(i => i.id !== 'minecraft:air' && i.count > 1)
|
const queue = items.filter(i => !i.id.equals(Identifier.create('air')) && i.count > 1)
|
||||||
items = items.filter(i => i.id !== 'minecraft:air' && i.count === 1)
|
items = items.filter(i => !i.id.equals(Identifier.create('air')) && i.count === 1)
|
||||||
|
|
||||||
while (SLOT_COUNT - items.length - queue.length > 0 && queue.length > 0) {
|
while (SLOT_COUNT - items.length - queue.length > 0 && queue.length > 0) {
|
||||||
const [itemA] = queue.splice(ctx.random.nextInt(queue.length), 1)
|
const [itemA] = queue.splice(ctx.random.nextInt(queue.length), 1)
|
||||||
@@ -81,21 +75,21 @@ function fillContainer(items: Item[], ctx: LootContext): SlottedItem[] {
|
|||||||
if (slot === undefined) {
|
if (slot === undefined) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (item.id !== 'minecraft:air' && item.count > 0) {
|
if (!item.id.equals(Identifier.create('air')) && item.count > 0) {
|
||||||
results.push({ slot, item })
|
results.push({ slot, item })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignSlots(items: Item[]): SlottedItem[] {
|
function assignSlots(items: ItemStack[]): SlottedItem[] {
|
||||||
const results: SlottedItem[] = []
|
const results: SlottedItem[] = []
|
||||||
let slot = 0
|
let slot = 0
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (slot >= 27) {
|
if (slot >= 27) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (item.id !== 'minecraft:air' && item.count > 0) {
|
if (!item.id.equals(Identifier.create('air')) && item.count > 0) {
|
||||||
results.push({ slot, item })
|
results.push({ slot, item })
|
||||||
slot += 1
|
slot += 1
|
||||||
}
|
}
|
||||||
@@ -103,7 +97,7 @@ function assignSlots(items: Item[]): SlottedItem[] {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitItem(item: Item, count: number): Item {
|
function splitItem(item: ItemStack, count: number): ItemStack {
|
||||||
const splitCount = Math.min(count, item.count)
|
const splitCount = Math.min(count, item.count)
|
||||||
const other = deepClone(item)
|
const other = deepClone(item)
|
||||||
other.count = splitCount
|
other.count = splitCount
|
||||||
@@ -234,11 +228,11 @@ function createItem(entry: any, consumer: ItemConsumer, ctx: LootContext) {
|
|||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'item':
|
case 'item':
|
||||||
entryConsumer({ id: entry.name, count: 1 })
|
entryConsumer(new ItemStack(Identifier.parse(entry.name), 1))
|
||||||
break
|
break
|
||||||
case 'tag':
|
case 'tag':
|
||||||
ctx.getItemTag(entry.name).forEach(tagEntry => {
|
ctx.getItemTag(entry.name).forEach(tagEntry => {
|
||||||
entryConsumer({ id: tagEntry, count: 1 })
|
entryConsumer(new ItemStack(Identifier.parse(tagEntry), 1))
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'loot_table':
|
case 'loot_table':
|
||||||
@@ -254,7 +248,7 @@ function computeWeight(entry: any, luck: number) {
|
|||||||
return Math.max(Math.floor((entry.weight ?? 1) + (entry.quality ?? 0) * luck), 0)
|
return Math.max(Math.floor((entry.weight ?? 1) + (entry.quality ?? 0) * luck), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LootFunction = (item: Item, ctx: LootContext) => void
|
type LootFunction = (item: ItemStack, ctx: LootContext) => void
|
||||||
|
|
||||||
function decorateFunctions(functions: any[], consumer: ItemConsumer, ctx: LootContext): ItemConsumer {
|
function decorateFunctions(functions: any[], consumer: ItemConsumer, ctx: LootContext): ItemConsumer {
|
||||||
const compositeFunction = composeFunctions(functions)
|
const compositeFunction = composeFunctions(functions)
|
||||||
@@ -277,12 +271,12 @@ function composeFunctions(functions: any[]): LootFunction {
|
|||||||
|
|
||||||
const LootFunctions: Record<string, (params: any) => LootFunction> = {
|
const LootFunctions: Record<string, (params: any) => LootFunction> = {
|
||||||
enchant_randomly: ({ enchantments }) => (item, ctx) => {
|
enchant_randomly: ({ enchantments }) => (item, ctx) => {
|
||||||
const isBook = item.id === 'minecraft:book'
|
const isBook = item.id.equals(Identifier.create('book'))
|
||||||
if (enchantments === undefined || enchantments.length === 0) {
|
if (enchantments === undefined || enchantments.length === 0) {
|
||||||
enchantments = [...Enchantments.keys()]
|
enchantments = [...Enchantments.keys()]
|
||||||
.filter(e => {
|
.filter(e => {
|
||||||
const data = getEnchantmentData(e)
|
const data = getEnchantmentData(e)
|
||||||
return data.discoverable && (isBook || data.canEnchant(item.id))
|
return data.discoverable && (isBook || data.canEnchant(item.id.toString()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (enchantments.length > 0) {
|
if (enchantments.length > 0) {
|
||||||
@@ -290,27 +284,37 @@ const LootFunctions: Record<string, (params: any) => LootFunction> = {
|
|||||||
const data = getEnchantmentData(id)
|
const data = getEnchantmentData(id)
|
||||||
const lvl = ctx.random.nextInt(data.maxLevel - data.minLevel + 1) + data.minLevel
|
const lvl = ctx.random.nextInt(data.maxLevel - data.minLevel + 1) + data.minLevel
|
||||||
if (isBook) {
|
if (isBook) {
|
||||||
item.tag = {}
|
item.tag = new NbtCompound()
|
||||||
item.count = 1
|
item.count = 1
|
||||||
}
|
}
|
||||||
enchantItem(item, { id, lvl })
|
enchantItem(item, { id, lvl })
|
||||||
if (isBook) {
|
if (isBook) {
|
||||||
item.id = 'minecraft:enchanted_book'
|
item.id = Identifier.create('enchanted_book')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enchant_with_levels: ({ levels, treasure }) => (item, ctx) => {
|
enchant_with_levels: ({ levels, treasure }) => (item, ctx) => {
|
||||||
const enchants = selectEnchantments(ctx.random, item, computeInt(levels, ctx), treasure)
|
const enchants = selectEnchantments(ctx.random, item, computeInt(levels, ctx), treasure)
|
||||||
const isBook = item.id === 'minecraft:book'
|
const isBook = item.id.equals(Identifier.create('book'))
|
||||||
if (isBook) {
|
if (isBook) {
|
||||||
item.count = 1
|
item.count = 1
|
||||||
item.tag = {}
|
item.tag = new NbtCompound()
|
||||||
}
|
}
|
||||||
for (const enchant of enchants) {
|
for (const enchant of enchants) {
|
||||||
enchantItem(item, enchant)
|
enchantItem(item, enchant)
|
||||||
}
|
}
|
||||||
if (isBook) {
|
if (isBook) {
|
||||||
item.id = 'minecraft:enchanted_book'
|
item.id = Identifier.create('enchanted_book')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exploration_map: ({ decoration }) => (item) => {
|
||||||
|
if (!item.id.equals(Identifier.create('map'))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.id = Identifier.create('filled_map')
|
||||||
|
const color = decoration === 'mansion' ? 5393476 : decoration === 'monument' ? 3830373 : -1
|
||||||
|
if (color >= 0) {
|
||||||
|
getOrCreateTag(item, 'display').set('MapColor', new NbtInt(color))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
limit_count: ({ limit }) => (item, ctx) => {
|
limit_count: ({ limit }) => (item, ctx) => {
|
||||||
@@ -322,12 +326,12 @@ const LootFunctions: Record<string, (params: any) => LootFunction> = {
|
|||||||
item.count = clamp(oldCount + computeInt(count, ctx), 0, 64)
|
item.count = clamp(oldCount + computeInt(count, ctx), 0, 64)
|
||||||
},
|
},
|
||||||
set_damage: ({ damage, add }) => (item, ctx) => {
|
set_damage: ({ damage, add }) => (item, ctx) => {
|
||||||
const maxDamage = MaxDamageItems.get(item.id)
|
const maxDamage = MaxDamageItems.get(item.id.toString())
|
||||||
if (maxDamage) {
|
if (maxDamage) {
|
||||||
const oldDamage = add ? 1 - (item.tag?.Damage ?? 0) / maxDamage : 0
|
const oldDamage = add ? 1 - item.tag.getNumber('Damage') / maxDamage : 0
|
||||||
const newDamage = 1 - clamp(computeFloat(damage, ctx) + oldDamage, 0, 1)
|
const newDamage = 1 - clamp(computeFloat(damage, ctx) + oldDamage, 0, 1)
|
||||||
const finalDamage = Math.floor(newDamage * maxDamage)
|
const finalDamage = Math.floor(newDamage * maxDamage)
|
||||||
item.tag = { ...item.tag, Damage: finalDamage }
|
item.tag.set('Damage', new NbtInt(finalDamage))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set_enchantments: ({ enchantments, add }) => (item, ctx) => {
|
set_enchantments: ({ enchantments, add }) => (item, ctx) => {
|
||||||
@@ -337,13 +341,23 @@ const LootFunctions: Record<string, (params: any) => LootFunction> = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
set_lore: ({ lore, replace }) => (item) => {
|
set_lore: ({ lore, replace }) => (item) => {
|
||||||
const lines = lore.map((line: any) => JSON.stringify(line))
|
const lines: string[] = lore.flatMap((line: any) => line !== undefined ? [JSON.stringify(line)] : [])
|
||||||
const newLore = replace ? lines : [...(item.tag?.display?.Lore ?? []), ...lines]
|
const newLore = replace ? lines : [...item.tag.getCompound('display').getList('Lore', NbtType.String).map(s => s.getAsString()), ...lines]
|
||||||
item.tag = { ...item.tag, display: { ...item.tag?.display, Lore: newLore } }
|
getOrCreateTag(item, 'display').set('Lore', new NbtList(newLore.map(l => new NbtString(l))))
|
||||||
},
|
},
|
||||||
set_name: ({ name }) => (item) => {
|
set_name: ({ name }) => (item) => {
|
||||||
const newName = JSON.stringify(name)
|
if (name !== undefined) {
|
||||||
item.tag = { ...item.tag, display: { ...item.tag?.display, Name: newName } }
|
const newName = JSON.stringify(name)
|
||||||
|
getOrCreateTag(item, 'display').set('Name', new NbtString(newName))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set_nbt: ({ tag }) => (item) => {
|
||||||
|
try {
|
||||||
|
const newTag = NbtTag.fromString(tag)
|
||||||
|
if (newTag.isCompound()) {
|
||||||
|
item.tag = newTag
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,30 +531,28 @@ function testDamageSourcePredicate(_predicate: any, _ctx: LootContext) {
|
|||||||
return false // TODO
|
return false // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
function enchantItem(item: Item, enchant: Enchant, additive?: boolean) {
|
function enchantItem(item: ItemStack, enchant: Enchant, additive?: boolean) {
|
||||||
if (!item.tag) {
|
const listKey = item.id.equals(Identifier.create('book')) ? 'StoredEnchantments' : 'Enchantments'
|
||||||
item.tag = {}
|
if (!item.tag.hasList(listKey, NbtType.Compound)) {
|
||||||
|
item.tag.set(listKey, new NbtList())
|
||||||
}
|
}
|
||||||
const listKey = (item.id === 'minecraft:book') ? 'StoredEnchantments' : 'Enchantments'
|
const enchantments = item.tag.getList(listKey, NbtType.Compound).getItems()
|
||||||
if (!item.tag[listKey] || !Array.isArray(item.tag[listKey])) {
|
|
||||||
item.tag[listKey] = []
|
|
||||||
}
|
|
||||||
const enchantments = item.tag[listKey] as any[]
|
|
||||||
let index = enchantments.findIndex((e: any) => e.id === enchant.id)
|
let index = enchantments.findIndex((e: any) => e.id === enchant.id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
const oldEnch = enchantments[index]
|
const oldEnch = enchantments[index]
|
||||||
oldEnch.lvl = Math.max(additive ? oldEnch.lvl + enchant.lvl : enchant.lvl, 0)
|
oldEnch.set('lvl', new NbtShort(Math.max(additive ? oldEnch.getNumber('lvl') + enchant.lvl : enchant.lvl, 0)))
|
||||||
} else {
|
} else {
|
||||||
enchantments.push(enchant)
|
enchantments.push(new NbtCompound().set('id', new NbtString(enchant.id)).set('lvl', new NbtShort(enchant.lvl)))
|
||||||
index = enchantments.length - 1
|
index = enchantments.length - 1
|
||||||
}
|
}
|
||||||
if (enchantments[index].lvl === 0) {
|
if (enchantments[index].getNumber('lvl') === 0) {
|
||||||
enchantments.splice(index, 1)
|
enchantments.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
item.tag.set(listKey, new NbtList(enchantments))
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectEnchantments(random: Random, item: Item, levels: number, treasure: boolean): Enchant[] {
|
function selectEnchantments(random: Random, item: ItemStack, levels: number, treasure: boolean): Enchant[] {
|
||||||
const enchantmentValue = EnchantmentItems.get(item.id) ?? 0
|
const enchantmentValue = EnchantmentItems.get(item.id.toString()) ?? 0
|
||||||
if (enchantmentValue <= 0) {
|
if (enchantmentValue <= 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -573,13 +585,13 @@ function getEnchantWeight(ench: Enchant) {
|
|||||||
return EnchantmentsRarityWeights.get(getEnchantmentData(ench.id)?.rarity ?? 'common') ?? 0
|
return EnchantmentsRarityWeights.get(getEnchantmentData(ench.id)?.rarity ?? 'common') ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableEnchantments(item: Item, levels: number, treasure: boolean): Enchant[] {
|
function getAvailableEnchantments(item: ItemStack, levels: number, treasure: boolean): Enchant[] {
|
||||||
const result = []
|
const result = []
|
||||||
const isBook = item.id === 'minecraft:book'
|
const isBook = item.id.equals(Identifier.create('book'))
|
||||||
|
|
||||||
for (const id of Enchantments.keys()) {
|
for (const id of Enchantments.keys()) {
|
||||||
const ench = getEnchantmentData(id)!
|
const ench = getEnchantmentData(id)!
|
||||||
if ((!ench.treasure || treasure) && ench.discoverable && (ench.canEnchant(item.id) || isBook)) {
|
if ((!ench.treasure || treasure) && ench.discoverable && (ench.canEnchant(item.id.toString()) || isBook)) {
|
||||||
for (let lvl = ench.maxLevel; lvl > ench.minLevel - 1; lvl -= 1) {
|
for (let lvl = ench.maxLevel; lvl > ench.minLevel - 1; lvl -= 1) {
|
||||||
if (levels >= ench.minCost(lvl) && levels <= ench.maxCost(lvl)) {
|
if (levels >= ench.minCost(lvl) && levels <= ench.maxCost(lvl)) {
|
||||||
result.push({ id, lvl })
|
result.push({ id, lvl })
|
||||||
@@ -1018,3 +1030,38 @@ const EnchantmentsCategories = new Map(Object.entries<string[]>({
|
|||||||
crossbow: ['minecraft:crossbow'],
|
crossbow: ['minecraft:crossbow'],
|
||||||
vanishable: [...BREAKABLE, 'minecraft:compass'],
|
vanishable: [...BREAKABLE, 'minecraft:compass'],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const AlwaysHasGlint = new Set([
|
||||||
|
'minecraft:debug_stick',
|
||||||
|
'minecraft:enchanted_golden_apple',
|
||||||
|
'minecraft:enchanted_book',
|
||||||
|
'minecraft:end_crystal',
|
||||||
|
'minecraft:experience_bottle',
|
||||||
|
'minecraft:written_book',
|
||||||
|
])
|
||||||
|
|
||||||
|
export function itemHasGlint(item: ItemStack) {
|
||||||
|
if (AlwaysHasGlint.has(item.id.toString())) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (item.id.equals(Identifier.create('compass')) && (item.tag.has('LodestoneDimension') || item.tag.has('LodestonePos'))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ((item.id.equals(Identifier.create('potion')) || item.id.equals(Identifier.create('splash_potion')) || item.id.equals(Identifier.create('lingering_potion'))) && (item.tag.has('Potion') || item.tag.has('CustomPotionEffects'))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (item.tag.getList('Enchantments').length > 0 || item.tag.getList('StoredEnchantments').length > 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrCreateTag(item: ItemStack, key: string) {
|
||||||
|
if (item.tag.hasCompound(key)) {
|
||||||
|
return item.tag.getCompound(key)
|
||||||
|
} else {
|
||||||
|
const tag = new NbtCompound()
|
||||||
|
item.tag.set(key, tag)
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { BooleanHookParams, EnumOption, Hook, INode, NodeChildren, NumberHookParams, StringHookParams, ValidationOption } from '@mcschema/core'
|
import type { BooleanHookParams, EnumOption, Hook, INode, NodeChildren, NumberHookParams, StringHookParams, ValidationOption } from '@mcschema/core'
|
||||||
import { DataModel, ListNode, MapNode, ModelPath, ObjectNode, Path, relativePath, StringNode } from '@mcschema/core'
|
import { DataModel, ListNode, MapNode, ModelPath, ObjectNode, Path, relativePath, StringNode } from '@mcschema/core'
|
||||||
|
import { Identifier, ItemStack } from 'deepslate'
|
||||||
import type { ComponentChildren, JSX } from 'preact'
|
import type { ComponentChildren, JSX } from 'preact'
|
||||||
import { memo } from 'preact/compat'
|
import { memo } from 'preact/compat'
|
||||||
import { useState } from 'preact/hooks'
|
import { useState } from 'preact/hooks'
|
||||||
@@ -139,7 +140,7 @@ const renderHtml: RenderHook = {
|
|||||||
let label: undefined | string | JSX.Element
|
let label: undefined | string | JSX.Element
|
||||||
if (['loot_pool.entries.entry', 'loot_entry.alternatives.children.entry', 'loot_entry.group.children.entry', 'loot_entry.sequence.children.entry', 'function.set_contents.entries.entry'].includes(cPath.getContext().join('.'))) {
|
if (['loot_pool.entries.entry', 'loot_entry.alternatives.children.entry', 'loot_entry.group.children.entry', 'loot_entry.sequence.children.entry', 'function.set_contents.entries.entry'].includes(cPath.getContext().join('.'))) {
|
||||||
if (isObject(cValue) && typeof cValue.type === 'string' && cValue.type.replace(/^minecraft:/, '') === 'item' && typeof cValue.name === 'string') {
|
if (isObject(cValue) && typeof cValue.type === 'string' && cValue.type.replace(/^minecraft:/, '') === 'item' && typeof cValue.name === 'string') {
|
||||||
label = <ItemDisplay item={{ id: cValue.name, count: 1 }} />
|
label = <ItemDisplay item={new ItemStack(Identifier.parse(cValue.name), 1)} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { BlockModelProvider, TextureAtlasProvider, UV } from 'deepslate/render'
|
import type { BlockModelProvider, ItemStack, TextureAtlasProvider, UV } from 'deepslate/render'
|
||||||
import { BlockModel, Identifier, ItemRenderer, TextureAtlas, upperPowerOfTwo } from 'deepslate/render'
|
import { BlockModel, Identifier, ItemRenderer, TextureAtlas, upperPowerOfTwo } from 'deepslate/render'
|
||||||
import { message } from '../Utils.js'
|
import { message } from '../Utils.js'
|
||||||
import { fetchLanguage, fetchResources } from './DataFetcher.js'
|
import { fetchLanguage, fetchResources } from './DataFetcher.js'
|
||||||
@@ -26,8 +26,8 @@ export async function getResources(version: VersionId) {
|
|||||||
const RENDER_SIZE = 128
|
const RENDER_SIZE = 128
|
||||||
const ItemRenderCache = new Map<string, Promise<string>>()
|
const ItemRenderCache = new Map<string, Promise<string>>()
|
||||||
|
|
||||||
export async function renderItem(version: VersionId, item: string) {
|
export async function renderItem(version: VersionId, item: ItemStack) {
|
||||||
const cache_key = `${version} ${item}`
|
const cache_key = `${version} ${item.toString()}`
|
||||||
const cached = ItemRenderCache.get(cache_key)
|
const cached = ItemRenderCache.get(cache_key)
|
||||||
if (cached !== undefined) {
|
if (cached !== undefined) {
|
||||||
return cached
|
return cached
|
||||||
@@ -42,7 +42,8 @@ export async function renderItem(version: VersionId, item: string) {
|
|||||||
if (!gl) {
|
if (!gl) {
|
||||||
throw new Error('Cannot get WebGL2 context')
|
throw new Error('Cannot get WebGL2 context')
|
||||||
}
|
}
|
||||||
const renderer = new ItemRenderer(gl, Identifier.parse(item), resources)
|
const renderer = new ItemRenderer(gl, item, resources)
|
||||||
|
console.log('Rendering', item.toString())
|
||||||
renderer.drawItem()
|
renderer.drawItem()
|
||||||
return canvas.toDataURL()
|
return canvas.toDataURL()
|
||||||
})()
|
})()
|
||||||
|
|||||||
Reference in New Issue
Block a user