Skip to content

Commit 3fe6dcd

Browse files
committed
Fix #813 item displays in recipe viewer in 1.20.4
1 parent 28f6f51 commit 3fe6dcd

File tree

4 files changed

+297
-152
lines changed

4 files changed

+297
-152
lines changed

src/app/components/generator/McdocRenderer.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ import type { ListType, LiteralType, McdocType, NumericType, PrimitiveArrayType,
88
import { handleAttributes } from '@spyglassmc/mcdoc/lib/runtime/attribute/index.js'
99
import type { SimplifiedEnum, SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion, SimplifiedStructType, SimplifiedStructTypePairField } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js'
1010
import { getValues } from '@spyglassmc/mcdoc/lib/runtime/completer/index.js'
11-
import { Identifier, ItemStack } from 'deepslate'
11+
import { Identifier as Identifier1204, ItemStack as ItemStack1204 } from 'deepslate-1.20.4/core'
12+
import { Identifier, ItemStack } from 'deepslate/core'
1213
import DOMPurify from 'dompurify'
1314
import { marked } from 'marked'
1415
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
1516
import config from '../../Config.js'
1617
import { useLocale } from '../../contexts/Locale.jsx'
18+
import { useVersion } from '../../contexts/Version.jsx'
1719
import { useFocus } from '../../hooks/useFocus.js'
20+
import { checkVersion } from '../../services/Versions.js'
1821
import { generateColor, hexId, intToHexRgb, randomInt, randomSeed } from '../../Utils.js'
1922
import { Btn } from '../Btn.jsx'
2023
import { ItemDisplay } from '../ItemDisplay.jsx'
24+
import { ItemDisplay1204 } from '../ItemDisplay1204.jsx'
2125
import { Octicon } from '../Octicon.jsx'
2226
import { formatIdentifier, getCategory, getChange, getDefault, getItemType, isDefaultCollapsedType, isFixedList, isInlineTuple, isListOrArray, isNumericType, isSelectRegistry, quickEqualTypes, simplifyType } from './McdocHelpers.js'
2327

@@ -138,6 +142,8 @@ const SPECIAL_UNSET = '__unset__'
138142

139143
function StringHead({ type, optional, excludeStrings, node, ctx }: Props<StringType>) {
140144
const { locale } = useLocale()
145+
const { version } = useVersion()
146+
const use1204 = !checkVersion(version, '1.20.5')
141147

142148
const nodeValue = (JsonStringNode.is(node) ? node.value : undefined)?.replaceAll('\n', '\\n')
143149
const [value, setValue] = useState(nodeValue)
@@ -202,7 +208,9 @@ function StringHead({ type, optional, excludeStrings, node, ctx }: Props<StringT
202208

203209
return <>
204210
{((idRegistry === 'item' || idRegistry === 'block') && idTags !== 'implicit' && value && !value.startsWith('#')) && <label>
205-
<ItemDisplay item={new ItemStack(Identifier.parse(value), 1)} />
211+
{use1204
212+
? <ItemDisplay1204 item={new ItemStack1204(Identifier1204.parse(value), 1)} />
213+
: <ItemDisplay item={new ItemStack(Identifier.parse(value), 1)} />}
206214
</label>}
207215
{isSelect ? <>
208216
<select value={value === undefined ? SPECIAL_UNSET : value} onInput={(e) => onChangeValue((e.target as HTMLInputElement).value)}>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Identifier, ItemStack } from 'deepslate/core'
2+
import type { VersionId } from '../../services/Versions.js'
3+
import { checkVersion } from '../../services/Versions.js'
4+
import { jsonToNbt } from '../../Utils.js'
5+
6+
export function placeItems(version: VersionId, recipe: any, animation: number, itemTags: Map<string, any>): Map<string, ItemStack> {
7+
const items = new Map<string, ItemStack>()
8+
const type: string = recipe.type?.replace(/^minecraft:/, '')
9+
if (!type || type.startsWith('crafting_special') || type === 'crafting_decorated_pot') {
10+
return items
11+
}
12+
13+
if (type === 'crafting_shapeless') {
14+
const ingredients: any[] = Array.isArray(recipe.ingredients) ? recipe.ingredients : []
15+
ingredients.forEach((ingredient, i) => {
16+
const choices = allIngredientChoices(version, ingredient, itemTags)
17+
if (i >= 0 && i < 9 && choices.length > 0) {
18+
const choice = choices[(3 * i + animation) % choices.length]
19+
items.set(`crafting.${i}`, choice)
20+
}
21+
})
22+
} else if (type === 'crafting_shaped') {
23+
const keys = new Map<string, ItemStack>()
24+
for (const [key, ingredient] of Object.entries(recipe.key ?? {})) {
25+
const choices = allIngredientChoices(version, ingredient, itemTags)
26+
if (choices.length > 0) {
27+
const choice = choices[animation % choices.length]
28+
keys.set(key, choice)
29+
}
30+
}
31+
const pattern = Array.isArray(recipe.pattern) ? recipe.pattern : []
32+
for (let row = 0; row < Math.min(3, pattern.length); row += 1) {
33+
for (let col = 0; col < Math.min(3, pattern[row].length); col += 1) {
34+
const key = pattern[row].split('')[col]
35+
const choice = key === ' ' ? undefined : keys.get(key)
36+
if (choice) {
37+
items.set(`crafting.${row * 3 + col}`, choice)
38+
}
39+
}
40+
}
41+
} else if (type === 'crafting_transmute') {
42+
const inputs = allIngredientChoices(version, recipe.input, itemTags)
43+
if (inputs.length > 0) {
44+
const choice = inputs[animation % inputs.length]
45+
items.set('crafting.0', choice)
46+
}
47+
const materials = allIngredientChoices(version, recipe.material, itemTags)
48+
if (materials.length > 0) {
49+
const choice = materials[animation % materials.length]
50+
items.set('crafting.1', choice)
51+
}
52+
} else if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
53+
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
54+
if (choices.length > 0) {
55+
const choice = choices[animation % choices.length]
56+
items.set('smelting.ingredient', choice)
57+
}
58+
} else if (type === 'stonecutting') {
59+
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
60+
if (choices.length > 0) {
61+
const choice = choices[animation % choices.length]
62+
items.set('stonecutting.ingredient', choice)
63+
}
64+
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
65+
for (const ingredient of ['template', 'base', 'addition'] as const) {
66+
const choices = allIngredientChoices(version, recipe[ingredient], itemTags)
67+
if (choices.length > 0) {
68+
const choice = choices[animation % choices.length]
69+
items.set(`smithing.${ingredient}`, choice)
70+
}
71+
}
72+
}
73+
74+
let resultSlot = 'crafting.result'
75+
if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
76+
resultSlot = 'smelting.result'
77+
} else if (type === 'stonecutting') {
78+
resultSlot = 'stonecutting.result'
79+
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
80+
resultSlot = 'smithing.result'
81+
}
82+
const result = recipe.result
83+
if (type === 'smithing_trim') {
84+
const base = items.get('smithing.base')
85+
if (base) {
86+
items.set(resultSlot, base)
87+
}
88+
} else if (typeof result === 'string') {
89+
items.set(resultSlot, new ItemStack(Identifier.parse(result), 1))
90+
} else if (typeof result === 'object' && result !== null) {
91+
const id = typeof result.id === 'string' ? result.id
92+
: typeof result.item === 'string' ? result.item
93+
: 'minecraft:air'
94+
if (id !== 'minecraft:air') {
95+
const count = typeof result.count === 'number' ? result.count : 1
96+
const components = new Map(Object.entries(result.components ?? {})
97+
.map(([k, v]) => [k, jsonToNbt(v)]))
98+
items.set(resultSlot, new ItemStack(Identifier.parse(id), count, components))
99+
}
100+
}
101+
102+
return items
103+
}
104+
105+
function allIngredientChoices(version: VersionId, ingredient: any, itemTags: Map<string, any>): ItemStack[] {
106+
if (Array.isArray(ingredient)) {
107+
return ingredient.flatMap(i => allIngredientChoices(version, i, itemTags))
108+
}
109+
110+
if (checkVersion(version, '1.21.2')) {
111+
if (ingredient !== null) {
112+
if (typeof ingredient === 'string') {
113+
if (ingredient.startsWith('#')) {
114+
return parseTag(version, ingredient.slice(1), itemTags)
115+
}
116+
return [new ItemStack(Identifier.parse(ingredient), 1)]
117+
}
118+
}
119+
120+
return [new ItemStack(Identifier.create('stone'), 1)]
121+
} else {
122+
if (typeof ingredient === 'object' && ingredient !== null) {
123+
if (typeof ingredient.item === 'string') {
124+
return [new ItemStack(Identifier.parse(ingredient.item), 1)]
125+
} else if (typeof ingredient.tag === 'string') {
126+
return parseTag(version, ingredient.tag, itemTags)
127+
}
128+
}
129+
}
130+
131+
return []
132+
}
133+
134+
function parseTag(version: VersionId, tagId: any, itemTags: Map<string, any>): ItemStack[] {
135+
const tag: any = itemTags.get(tagId.replace(/^minecraft:/, ''))
136+
if (typeof tag === 'object' && tag !== null && Array.isArray(tag.values)) {
137+
return tag.values.flatMap((value: any) => {
138+
if (typeof value !== 'string') return []
139+
if (value.startsWith('#')) return parseTag(version, value.slice(1), itemTags)
140+
return [new ItemStack(Identifier.parse(value), 1)]
141+
})
142+
}
143+
return []
144+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { Identifier, ItemStack } from 'deepslate-1.20.4/core'
2+
import type { VersionId } from '../../services/Versions.js'
3+
4+
export function placeItems(version: VersionId, recipe: any, animation: number, itemTags: Map<string, any>): Map<string, ItemStack> {
5+
const items = new Map<string, ItemStack>()
6+
const type: string = recipe.type?.replace(/^minecraft:/, '')
7+
if (!type || type.startsWith('crafting_special') || type === 'crafting_decorated_pot') {
8+
return items
9+
}
10+
11+
if (type === 'crafting_shapeless') {
12+
const ingredients: any[] = Array.isArray(recipe.ingredients) ? recipe.ingredients : []
13+
ingredients.forEach((ingredient, i) => {
14+
const choices = allIngredientChoices(version, ingredient, itemTags)
15+
if (i >= 0 && i < 9 && choices.length > 0) {
16+
const choice = choices[(3 * i + animation) % choices.length]
17+
items.set(`crafting.${i}`, choice)
18+
}
19+
})
20+
} else if (type === 'crafting_shaped') {
21+
const keys = new Map<string, ItemStack>()
22+
for (const [key, ingredient] of Object.entries(recipe.key ?? {})) {
23+
const choices = allIngredientChoices(version, ingredient, itemTags)
24+
if (choices.length > 0) {
25+
const choice = choices[animation % choices.length]
26+
keys.set(key, choice)
27+
}
28+
}
29+
const pattern = Array.isArray(recipe.pattern) ? recipe.pattern : []
30+
for (let row = 0; row < Math.min(3, pattern.length); row += 1) {
31+
for (let col = 0; col < Math.min(3, pattern[row].length); col += 1) {
32+
const key = pattern[row].split('')[col]
33+
const choice = key === ' ' ? undefined : keys.get(key)
34+
if (choice) {
35+
items.set(`crafting.${row * 3 + col}`, choice)
36+
}
37+
}
38+
}
39+
} else if (type === 'crafting_transmute') {
40+
const inputs = allIngredientChoices(version, recipe.input, itemTags)
41+
if (inputs.length > 0) {
42+
const choice = inputs[animation % inputs.length]
43+
items.set('crafting.0', choice)
44+
}
45+
const materials = allIngredientChoices(version, recipe.material, itemTags)
46+
if (materials.length > 0) {
47+
const choice = materials[animation % materials.length]
48+
items.set('crafting.1', choice)
49+
}
50+
} else if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
51+
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
52+
if (choices.length > 0) {
53+
const choice = choices[animation % choices.length]
54+
items.set('smelting.ingredient', choice)
55+
}
56+
} else if (type === 'stonecutting') {
57+
const choices = allIngredientChoices(version, recipe.ingredient, itemTags)
58+
if (choices.length > 0) {
59+
const choice = choices[animation % choices.length]
60+
items.set('stonecutting.ingredient', choice)
61+
}
62+
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
63+
for (const ingredient of ['template', 'base', 'addition'] as const) {
64+
const choices = allIngredientChoices(version, recipe[ingredient], itemTags)
65+
if (choices.length > 0) {
66+
const choice = choices[animation % choices.length]
67+
items.set(`smithing.${ingredient}`, choice)
68+
}
69+
}
70+
}
71+
72+
let resultSlot = 'crafting.result'
73+
if (type === 'smelting' || type === 'smoking' || type === 'blasting' || type === 'campfire_cooking') {
74+
resultSlot = 'smelting.result'
75+
} else if (type === 'stonecutting') {
76+
resultSlot = 'stonecutting.result'
77+
} else if (type === 'smithing_transform' || type === 'smithing_trim') {
78+
resultSlot = 'smithing.result'
79+
}
80+
const result = recipe.result
81+
if (type === 'smithing_trim') {
82+
const base = items.get('smithing.base')
83+
if (base) {
84+
items.set(resultSlot, base)
85+
}
86+
} else if (typeof result === 'string') {
87+
items.set(resultSlot, new ItemStack(Identifier.parse(result), 1))
88+
} else if (typeof result === 'object' && result !== null) {
89+
const id = typeof result.id === 'string' ? result.id
90+
: typeof result.item === 'string' ? result.item
91+
: 'minecraft:air'
92+
if (id !== 'minecraft:air') {
93+
const count = typeof result.count === 'number' ? result.count : 1
94+
items.set(resultSlot, new ItemStack(Identifier.parse(id), count))
95+
}
96+
}
97+
98+
return items
99+
}
100+
101+
function allIngredientChoices(version: VersionId, ingredient: any, itemTags: Map<string, any>): ItemStack[] {
102+
if (Array.isArray(ingredient)) {
103+
return ingredient.flatMap(i => allIngredientChoices(version, i, itemTags))
104+
}
105+
106+
if (typeof ingredient === 'object' && ingredient !== null) {
107+
if (typeof ingredient.item === 'string') {
108+
return [new ItemStack(Identifier.parse(ingredient.item), 1)]
109+
} else if (typeof ingredient.tag === 'string') {
110+
return parseTag(version, ingredient.tag, itemTags)
111+
}
112+
}
113+
114+
return []
115+
}
116+
117+
function parseTag(version: VersionId, tagId: any, itemTags: Map<string, any>): ItemStack[] {
118+
const tag: any = itemTags.get(tagId.replace(/^minecraft:/, ''))
119+
if (typeof tag === 'object' && tag !== null && Array.isArray(tag.values)) {
120+
return tag.values.flatMap((value: any) => {
121+
if (typeof value !== 'string') return []
122+
if (value.startsWith('#')) return parseTag(version, value.slice(1), itemTags)
123+
return [new ItemStack(Identifier.parse(value), 1)]
124+
})
125+
}
126+
return []
127+
}

0 commit comments

Comments
 (0)