mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
Fix #813 item displays in recipe viewer in 1.20.4
This commit is contained in:
@@ -8,16 +8,20 @@ import type { ListType, LiteralType, McdocType, NumericType, PrimitiveArrayType,
|
|||||||
import { handleAttributes } from '@spyglassmc/mcdoc/lib/runtime/attribute/index.js'
|
import { handleAttributes } from '@spyglassmc/mcdoc/lib/runtime/attribute/index.js'
|
||||||
import type { SimplifiedEnum, SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion, SimplifiedStructType, SimplifiedStructTypePairField } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js'
|
import type { SimplifiedEnum, SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion, SimplifiedStructType, SimplifiedStructTypePairField } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js'
|
||||||
import { getValues } from '@spyglassmc/mcdoc/lib/runtime/completer/index.js'
|
import { getValues } from '@spyglassmc/mcdoc/lib/runtime/completer/index.js'
|
||||||
import { Identifier, ItemStack } from 'deepslate'
|
import { Identifier as Identifier1204, ItemStack as ItemStack1204 } from 'deepslate-1.20.4/core'
|
||||||
|
import { Identifier, ItemStack } from 'deepslate/core'
|
||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||||
import config from '../../Config.js'
|
import config from '../../Config.js'
|
||||||
import { useLocale } from '../../contexts/Locale.jsx'
|
import { useLocale } from '../../contexts/Locale.jsx'
|
||||||
|
import { useVersion } from '../../contexts/Version.jsx'
|
||||||
import { useFocus } from '../../hooks/useFocus.js'
|
import { useFocus } from '../../hooks/useFocus.js'
|
||||||
|
import { checkVersion } from '../../services/Versions.js'
|
||||||
import { generateColor, hexId, intToHexRgb, randomInt, randomSeed } from '../../Utils.js'
|
import { generateColor, hexId, intToHexRgb, randomInt, randomSeed } from '../../Utils.js'
|
||||||
import { Btn } from '../Btn.jsx'
|
import { Btn } from '../Btn.jsx'
|
||||||
import { ItemDisplay } from '../ItemDisplay.jsx'
|
import { ItemDisplay } from '../ItemDisplay.jsx'
|
||||||
|
import { ItemDisplay1204 } from '../ItemDisplay1204.jsx'
|
||||||
import { Octicon } from '../Octicon.jsx'
|
import { Octicon } from '../Octicon.jsx'
|
||||||
import { formatIdentifier, getCategory, getChange, getDefault, getItemType, isDefaultCollapsedType, isFixedList, isInlineTuple, isListOrArray, isNumericType, isSelectRegistry, quickEqualTypes, simplifyType } from './McdocHelpers.js'
|
import { formatIdentifier, getCategory, getChange, getDefault, getItemType, isDefaultCollapsedType, isFixedList, isInlineTuple, isListOrArray, isNumericType, isSelectRegistry, quickEqualTypes, simplifyType } from './McdocHelpers.js'
|
||||||
|
|
||||||
@@ -138,6 +142,8 @@ const SPECIAL_UNSET = '__unset__'
|
|||||||
|
|
||||||
function StringHead({ type, optional, excludeStrings, node, ctx }: Props<StringType>) {
|
function StringHead({ type, optional, excludeStrings, node, ctx }: Props<StringType>) {
|
||||||
const { locale } = useLocale()
|
const { locale } = useLocale()
|
||||||
|
const { version } = useVersion()
|
||||||
|
const use1204 = !checkVersion(version, '1.20.5')
|
||||||
|
|
||||||
const nodeValue = (JsonStringNode.is(node) ? node.value : undefined)?.replaceAll('\n', '\\n')
|
const nodeValue = (JsonStringNode.is(node) ? node.value : undefined)?.replaceAll('\n', '\\n')
|
||||||
const [value, setValue] = useState(nodeValue)
|
const [value, setValue] = useState(nodeValue)
|
||||||
@@ -202,7 +208,9 @@ function StringHead({ type, optional, excludeStrings, node, ctx }: Props<StringT
|
|||||||
|
|
||||||
return <>
|
return <>
|
||||||
{((idRegistry === 'item' || idRegistry === 'block') && idTags !== 'implicit' && value && !value.startsWith('#')) && <label>
|
{((idRegistry === 'item' || idRegistry === 'block') && idTags !== 'implicit' && value && !value.startsWith('#')) && <label>
|
||||||
<ItemDisplay item={new ItemStack(Identifier.parse(value), 1)} />
|
{use1204
|
||||||
|
? <ItemDisplay1204 item={new ItemStack1204(Identifier1204.parse(value), 1)} />
|
||||||
|
: <ItemDisplay item={new ItemStack(Identifier.parse(value), 1)} />}
|
||||||
</label>}
|
</label>}
|
||||||
{isSelect ? <>
|
{isSelect ? <>
|
||||||
<select value={value === undefined ? SPECIAL_UNSET : value} onInput={(e) => onChangeValue((e.target as HTMLInputElement).value)}>
|
<select value={value === undefined ? SPECIAL_UNSET : value} onInput={(e) => onChangeValue((e.target as HTMLInputElement).value)}>
|
||||||
|
|||||||
144
src/app/components/previews/Recipe.ts
Normal file
144
src/app/components/previews/Recipe.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { Identifier, ItemStack } from 'deepslate/core'
|
||||||
|
import type { VersionId } from '../../services/Versions.js'
|
||||||
|
import { checkVersion } from '../../services/Versions.js'
|
||||||
|
import { jsonToNbt } from '../../Utils.js'
|
||||||
|
|
||||||
|
export function placeItems(version: VersionId, recipe: any, animation: number, itemTags: Map<string, any>): Map<string, ItemStack> {
|
||||||
|
const items = new Map<string, ItemStack>()
|
||||||
|
const type: string = recipe.type?.replace(/^minecraft:/, '')
|
||||||
|
if (!type || type.startsWith('crafting_special') || type === 'crafting_decorated_pot') {
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'crafting_shapeless') {
|
||||||
|
const ingredients: any[] = Array.isArray(recipe.ingredients) ? recipe.ingredients : []
|
||||||
|
ingredients.forEach((ingredient, i) => {
|
||||||
|
const choices = allIngredientChoices(version, ingredient, itemTags)
|
||||||
|
if (i >= 0 && i < 9 && choices.length > 0) {
|
||||||
|
const choice = choices[(3 * i + animation) % choices.length]
|
||||||
|
items.set(`crafting.${i}`, choice)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (type === 'crafting_shaped') {
|
||||||
|
const keys = new Map<string, ItemStack>()
|
||||||
|
for (const [key, ingredient] of Object.entries(recipe.key ?? {})) {
|
||||||
|
const choices = allIngredientChoices(version, ingredient, itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
keys.set(key, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pattern = Array.isArray(recipe.pattern) ? recipe.pattern : []
|
||||||
|
for (let row = 0; row < Math.min(3, pattern.length); row += 1) {
|
||||||
|
for (let col = 0; col < Math.min(3, pattern[row].length); col += 1) {
|
||||||
|
const key = pattern[row].split('')[col]
|
||||||
|
const choice = key === ' ' ? undefined : keys.get(key)
|
||||||
|
if (choice) {
|
||||||
|
items.set(`crafting.${row * 3 + col}`, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === 'crafting_transmute') {
|
||||||
|
const inputs = allIngredientChoices(version, recipe.input, itemTags)
|
||||||
|
if (inputs.length > 0) {
|
||||||
|
const choice = inputs[animation % inputs.length]
|
||||||
|
items.set('crafting.0', choice)
|
||||||
|
}
|
||||||
|
const materials = allIngredientChoices(version, recipe.material, itemTags)
|
||||||
|
if (materials.length > 0) {
|
||||||
|
const choice = materials[animation % materials.length]
|
||||||
|
items.set('crafting.1', choice)
|
||||||
|
}
|
||||||
|
} else if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
|
||||||
|
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
items.set('smelting.ingredient', choice)
|
||||||
|
}
|
||||||
|
} else if (type === 'stonecutting') {
|
||||||
|
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
items.set('stonecutting.ingredient', choice)
|
||||||
|
}
|
||||||
|
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
|
||||||
|
for (const ingredient of ['template', 'base', 'addition'] as const) {
|
||||||
|
const choices = allIngredientChoices(version, recipe[ingredient], itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
items.set(`smithing.${ingredient}`, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultSlot = 'crafting.result'
|
||||||
|
if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
|
||||||
|
resultSlot = 'smelting.result'
|
||||||
|
} else if (type === 'stonecutting') {
|
||||||
|
resultSlot = 'stonecutting.result'
|
||||||
|
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
|
||||||
|
resultSlot = 'smithing.result'
|
||||||
|
}
|
||||||
|
const result = recipe.result
|
||||||
|
if (type === 'smithing_trim') {
|
||||||
|
const base = items.get('smithing.base')
|
||||||
|
if (base) {
|
||||||
|
items.set(resultSlot, base)
|
||||||
|
}
|
||||||
|
} else if (typeof result === 'string') {
|
||||||
|
items.set(resultSlot, new ItemStack(Identifier.parse(result), 1))
|
||||||
|
} else if (typeof result === 'object' && result !== null) {
|
||||||
|
const id = typeof result.id === 'string' ? result.id
|
||||||
|
: typeof result.item === 'string' ? result.item
|
||||||
|
: 'minecraft:air'
|
||||||
|
if (id !== 'minecraft:air') {
|
||||||
|
const count = typeof result.count === 'number' ? result.count : 1
|
||||||
|
const components = new Map(Object.entries(result.components ?? {})
|
||||||
|
.map(([k, v]) => [k, jsonToNbt(v)]))
|
||||||
|
items.set(resultSlot, new ItemStack(Identifier.parse(id), count, components))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
function allIngredientChoices(version: VersionId, ingredient: any, itemTags: Map<string, any>): ItemStack[] {
|
||||||
|
if (Array.isArray(ingredient)) {
|
||||||
|
return ingredient.flatMap(i => allIngredientChoices(version, i, itemTags))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkVersion(version, '1.21.2')) {
|
||||||
|
if (ingredient !== null) {
|
||||||
|
if (typeof ingredient === 'string') {
|
||||||
|
if (ingredient.startsWith('#')) {
|
||||||
|
return parseTag(version, ingredient.slice(1), itemTags)
|
||||||
|
}
|
||||||
|
return [new ItemStack(Identifier.parse(ingredient), 1)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [new ItemStack(Identifier.create('stone'), 1)]
|
||||||
|
} else {
|
||||||
|
if (typeof ingredient === 'object' && ingredient !== null) {
|
||||||
|
if (typeof ingredient.item === 'string') {
|
||||||
|
return [new ItemStack(Identifier.parse(ingredient.item), 1)]
|
||||||
|
} else if (typeof ingredient.tag === 'string') {
|
||||||
|
return parseTag(version, ingredient.tag, itemTags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTag(version: VersionId, tagId: any, itemTags: Map<string, any>): ItemStack[] {
|
||||||
|
const tag: any = itemTags.get(tagId.replace(/^minecraft:/, ''))
|
||||||
|
if (typeof tag === 'object' && tag !== null && Array.isArray(tag.values)) {
|
||||||
|
return tag.values.flatMap((value: any) => {
|
||||||
|
if (typeof value !== 'string') return []
|
||||||
|
if (value.startsWith('#')) return parseTag(version, value.slice(1), itemTags)
|
||||||
|
return [new ItemStack(Identifier.parse(value), 1)]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
127
src/app/components/previews/Recipe1204.ts
Normal file
127
src/app/components/previews/Recipe1204.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { Identifier, ItemStack } from 'deepslate-1.20.4/core'
|
||||||
|
import type { VersionId } from '../../services/Versions.js'
|
||||||
|
|
||||||
|
export function placeItems(version: VersionId, recipe: any, animation: number, itemTags: Map<string, any>): Map<string, ItemStack> {
|
||||||
|
const items = new Map<string, ItemStack>()
|
||||||
|
const type: string = recipe.type?.replace(/^minecraft:/, '')
|
||||||
|
if (!type || type.startsWith('crafting_special') || type === 'crafting_decorated_pot') {
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'crafting_shapeless') {
|
||||||
|
const ingredients: any[] = Array.isArray(recipe.ingredients) ? recipe.ingredients : []
|
||||||
|
ingredients.forEach((ingredient, i) => {
|
||||||
|
const choices = allIngredientChoices(version, ingredient, itemTags)
|
||||||
|
if (i >= 0 && i < 9 && choices.length > 0) {
|
||||||
|
const choice = choices[(3 * i + animation) % choices.length]
|
||||||
|
items.set(`crafting.${i}`, choice)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (type === 'crafting_shaped') {
|
||||||
|
const keys = new Map<string, ItemStack>()
|
||||||
|
for (const [key, ingredient] of Object.entries(recipe.key ?? {})) {
|
||||||
|
const choices = allIngredientChoices(version, ingredient, itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
keys.set(key, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pattern = Array.isArray(recipe.pattern) ? recipe.pattern : []
|
||||||
|
for (let row = 0; row < Math.min(3, pattern.length); row += 1) {
|
||||||
|
for (let col = 0; col < Math.min(3, pattern[row].length); col += 1) {
|
||||||
|
const key = pattern[row].split('')[col]
|
||||||
|
const choice = key === ' ' ? undefined : keys.get(key)
|
||||||
|
if (choice) {
|
||||||
|
items.set(`crafting.${row * 3 + col}`, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === 'crafting_transmute') {
|
||||||
|
const inputs = allIngredientChoices(version, recipe.input, itemTags)
|
||||||
|
if (inputs.length > 0) {
|
||||||
|
const choice = inputs[animation % inputs.length]
|
||||||
|
items.set('crafting.0', choice)
|
||||||
|
}
|
||||||
|
const materials = allIngredientChoices(version, recipe.material, itemTags)
|
||||||
|
if (materials.length > 0) {
|
||||||
|
const choice = materials[animation % materials.length]
|
||||||
|
items.set('crafting.1', choice)
|
||||||
|
}
|
||||||
|
} else if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
|
||||||
|
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
items.set('smelting.ingredient', choice)
|
||||||
|
}
|
||||||
|
} else if (type === 'stonecutting') {
|
||||||
|
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
items.set('stonecutting.ingredient', choice)
|
||||||
|
}
|
||||||
|
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
|
||||||
|
for (const ingredient of ['template', 'base', 'addition'] as const) {
|
||||||
|
const choices = allIngredientChoices(version, recipe[ingredient], itemTags)
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const choice = choices[animation % choices.length]
|
||||||
|
items.set(`smithing.${ingredient}`, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultSlot = 'crafting.result'
|
||||||
|
if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
|
||||||
|
resultSlot = 'smelting.result'
|
||||||
|
} else if (type === 'stonecutting') {
|
||||||
|
resultSlot = 'stonecutting.result'
|
||||||
|
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
|
||||||
|
resultSlot = 'smithing.result'
|
||||||
|
}
|
||||||
|
const result = recipe.result
|
||||||
|
if (type === 'smithing_trim') {
|
||||||
|
const base = items.get('smithing.base')
|
||||||
|
if (base) {
|
||||||
|
items.set(resultSlot, base)
|
||||||
|
}
|
||||||
|
} else if (typeof result === 'string') {
|
||||||
|
items.set(resultSlot, new ItemStack(Identifier.parse(result), 1))
|
||||||
|
} else if (typeof result === 'object' && result !== null) {
|
||||||
|
const id = typeof result.id === 'string' ? result.id
|
||||||
|
: typeof result.item === 'string' ? result.item
|
||||||
|
: 'minecraft:air'
|
||||||
|
if (id !== 'minecraft:air') {
|
||||||
|
const count = typeof result.count === 'number' ? result.count : 1
|
||||||
|
items.set(resultSlot, new ItemStack(Identifier.parse(id), count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
function allIngredientChoices(version: VersionId, ingredient: any, itemTags: Map<string, any>): ItemStack[] {
|
||||||
|
if (Array.isArray(ingredient)) {
|
||||||
|
return ingredient.flatMap(i => allIngredientChoices(version, i, itemTags))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ingredient === 'object' && ingredient !== null) {
|
||||||
|
if (typeof ingredient.item === 'string') {
|
||||||
|
return [new ItemStack(Identifier.parse(ingredient.item), 1)]
|
||||||
|
} else if (typeof ingredient.tag === 'string') {
|
||||||
|
return parseTag(version, ingredient.tag, itemTags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTag(version: VersionId, tagId: any, itemTags: Map<string, any>): ItemStack[] {
|
||||||
|
const tag: any = itemTags.get(tagId.replace(/^minecraft:/, ''))
|
||||||
|
if (typeof tag === 'object' && tag !== null && Array.isArray(tag.values)) {
|
||||||
|
return tag.values.flatMap((value: any) => {
|
||||||
|
if (typeof value !== 'string') return []
|
||||||
|
if (value.startsWith('#')) return parseTag(version, value.slice(1), itemTags)
|
||||||
|
return [new ItemStack(Identifier.parse(value), 1)]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
import { Identifier, ItemStack } from 'deepslate'
|
|
||||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks'
|
import { useEffect, useMemo, useRef, useState } from 'preact/hooks'
|
||||||
import { useLocale, useVersion } from '../../contexts/index.js'
|
import { useLocale, useVersion } from '../../contexts/index.js'
|
||||||
import { useAsync } from '../../hooks/useAsync.js'
|
import { useAsync } from '../../hooks/useAsync.js'
|
||||||
import type { VersionId } from '../../services/index.js'
|
|
||||||
import { checkVersion, fetchAllPresets } from '../../services/index.js'
|
import { checkVersion, fetchAllPresets } from '../../services/index.js'
|
||||||
import { jsonToNbt, safeJsonParse } from '../../Utils.js'
|
import { safeJsonParse } from '../../Utils.js'
|
||||||
import { Btn, BtnMenu } from '../index.js'
|
import { Btn, BtnMenu } from '../index.js'
|
||||||
import { ItemDisplay } from '../ItemDisplay.jsx'
|
import { ItemDisplay } from '../ItemDisplay.jsx'
|
||||||
|
import { ItemDisplay1204 } from '../ItemDisplay1204.jsx'
|
||||||
import type { PreviewProps } from './index.js'
|
import type { PreviewProps } from './index.js'
|
||||||
|
import { placeItems } from './Recipe.js'
|
||||||
|
import { placeItems as placeItems1204 } from './Recipe1204.js'
|
||||||
|
|
||||||
const ANIMATION_TIME = 1000
|
const ANIMATION_TIME = 1000
|
||||||
|
|
||||||
export const RecipePreview = ({ docAndNode }: PreviewProps) => {
|
export const RecipePreview = ({ docAndNode }: PreviewProps) => {
|
||||||
const { locale } = useLocale()
|
const { locale } = useLocale()
|
||||||
const { version } = useVersion()
|
const { version } = useVersion()
|
||||||
|
const use1204 = !checkVersion(version, '1.20.5')
|
||||||
const [advancedTooltips, setAdvancedTooltips] = useState(true)
|
const [advancedTooltips, setAdvancedTooltips] = useState(true)
|
||||||
const [animation, setAnimation] = useState(0)
|
const [animation, setAnimation] = useState(0)
|
||||||
const overlay = useRef<HTMLDivElement>(null)
|
const overlay = useRef<HTMLDivElement>(null)
|
||||||
@@ -31,9 +33,12 @@ export const RecipePreview = ({ docAndNode }: PreviewProps) => {
|
|||||||
|
|
||||||
const text = docAndNode.doc.getText()
|
const text = docAndNode.doc.getText()
|
||||||
const recipe = safeJsonParse(text) ?? {}
|
const recipe = safeJsonParse(text) ?? {}
|
||||||
const items = useMemo<Map<Slot, ItemStack>>(() => {
|
const items = useMemo(() => {
|
||||||
|
if (use1204) {
|
||||||
|
return placeItems1204(version, recipe, animation, itemTags ?? new Map())
|
||||||
|
}
|
||||||
return placeItems(version, recipe, animation, itemTags ?? new Map())
|
return placeItems(version, recipe, animation, itemTags ?? new Map())
|
||||||
}, [text, animation, itemTags])
|
}, [use1204, text, animation, itemTags])
|
||||||
|
|
||||||
const gui = useMemo(() => {
|
const gui = useMemo(() => {
|
||||||
const type = recipe?.type?.replace(/^minecraft:/, '')
|
const type = recipe?.type?.replace(/^minecraft:/, '')
|
||||||
@@ -53,7 +58,9 @@ export const RecipePreview = ({ docAndNode }: PreviewProps) => {
|
|||||||
<img src={gui} alt="Crafting GUI" class="pixelated" draggable={false} />
|
<img src={gui} alt="Crafting GUI" class="pixelated" draggable={false} />
|
||||||
{[...items.entries()].map(([slot, item]) =>
|
{[...items.entries()].map(([slot, item]) =>
|
||||||
<div key={slot} style={slotStyle(slot)}>
|
<div key={slot} style={slotStyle(slot)}>
|
||||||
<ItemDisplay item={item} slotDecoration={true} advancedTooltip={advancedTooltips} />
|
{use1204
|
||||||
|
? <ItemDisplay1204 item={item as any} slotDecoration={true} advancedTooltip={advancedTooltips} />
|
||||||
|
: <ItemDisplay item={item as any} slotDecoration={true} advancedTooltip={advancedTooltips} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -68,7 +75,7 @@ export const RecipePreview = ({ docAndNode }: PreviewProps) => {
|
|||||||
const GUI_WIDTH = 176
|
const GUI_WIDTH = 176
|
||||||
const GUI_HEIGHT = 81
|
const GUI_HEIGHT = 81
|
||||||
const SLOT_SIZE = 18
|
const SLOT_SIZE = 18
|
||||||
const SLOTS = {
|
const SLOTS: Record<string, [number, number]> = {
|
||||||
'crafting.0': [29, 16],
|
'crafting.0': [29, 16],
|
||||||
'crafting.1': [47, 16],
|
'crafting.1': [47, 16],
|
||||||
'crafting.2': [65, 16],
|
'crafting.2': [65, 16],
|
||||||
@@ -89,10 +96,9 @@ const SLOTS = {
|
|||||||
'smithing.addition': [43, 47],
|
'smithing.addition': [43, 47],
|
||||||
'smithing.result': [97, 47],
|
'smithing.result': [97, 47],
|
||||||
}
|
}
|
||||||
type Slot = keyof typeof SLOTS
|
|
||||||
|
|
||||||
function slotStyle(slot: Slot) {
|
function slotStyle(slot: string) {
|
||||||
const [x, y] = SLOTS[slot]
|
const [x, y] = SLOTS[slot] ?? [0, 0]
|
||||||
return {
|
return {
|
||||||
left: `${x*100/GUI_WIDTH}%`,
|
left: `${x*100/GUI_WIDTH}%`,
|
||||||
top: `${y*100/GUI_HEIGHT}%`,
|
top: `${y*100/GUI_HEIGHT}%`,
|
||||||
@@ -100,143 +106,3 @@ function slotStyle(slot: Slot) {
|
|||||||
height: `${SLOT_SIZE*100/GUI_HEIGHT}%`,
|
height: `${SLOT_SIZE*100/GUI_HEIGHT}%`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function placeItems(version: VersionId, recipe: any, animation: number, itemTags: Map<string, any>) {
|
|
||||||
const items = new Map<Slot, ItemStack>()
|
|
||||||
const type: string = recipe.type?.replace(/^minecraft:/, '')
|
|
||||||
if (!type || type.startsWith('crafting_special') || type === 'crafting_decorated_pot') {
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'crafting_shapeless') {
|
|
||||||
const ingredients: any[] = Array.isArray(recipe.ingredients) ? recipe.ingredients : []
|
|
||||||
ingredients.forEach((ingredient, i) => {
|
|
||||||
const choices = allIngredientChoices(version, ingredient, itemTags)
|
|
||||||
if (i >= 0 && i < 9 && choices.length > 0) {
|
|
||||||
const choice = choices[(3 * i + animation) % choices.length]
|
|
||||||
items.set(`crafting.${i}` as Slot, choice)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (type === 'crafting_shaped') {
|
|
||||||
const keys = new Map<string, ItemStack>()
|
|
||||||
for (const [key, ingredient] of Object.entries(recipe.key ?? {})) {
|
|
||||||
const choices = allIngredientChoices(version, ingredient, itemTags)
|
|
||||||
if (choices.length > 0) {
|
|
||||||
const choice = choices[animation % choices.length]
|
|
||||||
keys.set(key, choice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const pattern = Array.isArray(recipe.pattern) ? recipe.pattern : []
|
|
||||||
for (let row = 0; row < Math.min(3, pattern.length); row += 1) {
|
|
||||||
for (let col = 0; col < Math.min(3, pattern[row].length); col += 1) {
|
|
||||||
const key = pattern[row].split('')[col]
|
|
||||||
const choice = key === ' ' ? undefined : keys.get(key)
|
|
||||||
if (choice) {
|
|
||||||
items.set(`crafting.${row * 3 + col}` as Slot, choice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (type === 'crafting_transmute') {
|
|
||||||
const inputs = allIngredientChoices(version, recipe.input, itemTags)
|
|
||||||
if (inputs.length > 0) {
|
|
||||||
const choice = inputs[animation % inputs.length]
|
|
||||||
items.set('crafting.0', choice)
|
|
||||||
}
|
|
||||||
const materials = allIngredientChoices(version, recipe.material, itemTags)
|
|
||||||
if (materials.length > 0) {
|
|
||||||
const choice = materials[animation % materials.length]
|
|
||||||
items.set('crafting.1', choice)
|
|
||||||
}
|
|
||||||
} else if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
|
|
||||||
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
|
|
||||||
if (choices.length > 0) {
|
|
||||||
const choice = choices[animation % choices.length]
|
|
||||||
items.set('smelting.ingredient' as Slot, choice)
|
|
||||||
}
|
|
||||||
} else if (type === 'stonecutting') {
|
|
||||||
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
|
|
||||||
if (choices.length > 0) {
|
|
||||||
const choice = choices[animation % choices.length]
|
|
||||||
items.set('stonecutting.ingredient' as Slot, choice)
|
|
||||||
}
|
|
||||||
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
|
|
||||||
for (const ingredient of ['template', 'base', 'addition'] as const) {
|
|
||||||
const choices = allIngredientChoices(version, recipe[ingredient], itemTags)
|
|
||||||
if (choices.length > 0) {
|
|
||||||
const choice = choices[animation % choices.length]
|
|
||||||
items.set(`smithing.${ingredient}`, choice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let resultSlot: Slot = 'crafting.result'
|
|
||||||
if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
|
|
||||||
resultSlot = 'smelting.result'
|
|
||||||
} else if (type === 'stonecutting') {
|
|
||||||
resultSlot = 'stonecutting.result'
|
|
||||||
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
|
|
||||||
resultSlot = 'smithing.result'
|
|
||||||
}
|
|
||||||
const result = recipe.result
|
|
||||||
if (type === 'smithing_trim') {
|
|
||||||
const base = items.get('smithing.base')
|
|
||||||
if (base) {
|
|
||||||
items.set(resultSlot, base)
|
|
||||||
}
|
|
||||||
} else if (typeof result === 'string') {
|
|
||||||
items.set(resultSlot, new ItemStack(Identifier.parse(result), 1))
|
|
||||||
} else if (typeof result === 'object' && result !== null) {
|
|
||||||
const id = typeof result.id === 'string' ? result.id
|
|
||||||
: typeof result.item === 'string' ? result.item
|
|
||||||
: 'minecraft:air'
|
|
||||||
if (id !== 'minecraft:air') {
|
|
||||||
const count = typeof result.count === 'number' ? result.count : 1
|
|
||||||
const components = new Map(Object.entries(result.components ?? {})
|
|
||||||
.map(([k, v]) => [k, jsonToNbt(v)]))
|
|
||||||
items.set(resultSlot, new ItemStack(Identifier.parse(id), count, components))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
function allIngredientChoices(version: VersionId, ingredient: any, itemTags: Map<string, any>): ItemStack[] {
|
|
||||||
if (Array.isArray(ingredient)) {
|
|
||||||
return ingredient.flatMap(i => allIngredientChoices(version, i, itemTags))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkVersion(version, '1.21.2')) {
|
|
||||||
if (ingredient !== null) {
|
|
||||||
if (typeof ingredient === 'string') {
|
|
||||||
if (ingredient.startsWith('#')) {
|
|
||||||
return parseTag(version, ingredient.slice(1), itemTags)
|
|
||||||
}
|
|
||||||
return [new ItemStack(Identifier.parse(ingredient), 1)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [new ItemStack(Identifier.create('stone'), 1)]
|
|
||||||
} else {
|
|
||||||
if (typeof ingredient === 'object' && ingredient !== null) {
|
|
||||||
if (typeof ingredient.item === 'string') {
|
|
||||||
return [new ItemStack(Identifier.parse(ingredient.item), 1)]
|
|
||||||
} else if (typeof ingredient.tag === 'string') {
|
|
||||||
return parseTag(version, ingredient.tag, itemTags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTag(version: VersionId, tagId: any, itemTags: Map<string, any>): ItemStack[] {
|
|
||||||
const tag: any = itemTags.get(tagId.replace(/^minecraft:/, ''))
|
|
||||||
if (typeof tag === 'object' && tag !== null && Array.isArray(tag.values)) {
|
|
||||||
return tag.values.flatMap((value: any) => {
|
|
||||||
if (typeof value !== 'string') return []
|
|
||||||
if (value.startsWith('#')) return parseTag(version, value.slice(1), itemTags)
|
|
||||||
return [new ItemStack(Identifier.parse(value), 1)]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user