🔥 Nuke all mcschema related code

This commit is contained in:
Misode
2024-10-15 05:14:02 +02:00
parent b9a23d0f47
commit ccdcf9e7e3
39 changed files with 199 additions and 3220 deletions

263
package-lock.json generated
View File

@@ -10,22 +10,6 @@
"license": "MIT",
"dependencies": {
"@giscus/react": "^2.2.3",
"@mcschema/core": "^0.13.0",
"@mcschema/java-1.15": "^0.2.13",
"@mcschema/java-1.16": "^0.6.20",
"@mcschema/java-1.17": "^0.2.40",
"@mcschema/java-1.18": "^0.3.16",
"@mcschema/java-1.18.2": "^0.1.26",
"@mcschema/java-1.19": "^0.1.54",
"@mcschema/java-1.19.3": "^0.0.17",
"@mcschema/java-1.19.4": "^0.1.21",
"@mcschema/java-1.20": "^0.0.24",
"@mcschema/java-1.20.2": "^0.0.15",
"@mcschema/java-1.20.3": "^0.0.16",
"@mcschema/java-1.20.5": "^0.0.42",
"@mcschema/java-1.21": "^0.0.27",
"@mcschema/java-1.21.2": "^0.0.13",
"@mcschema/locales": "^0.1.104",
"@zip.js/zip.js": "^2.4.5",
"brace": "^0.11.1",
"buffer": "^6.0.3",
@@ -68,9 +52,6 @@
"vite-plugin-static-copy": "^0.12.0"
}
},
"@mcschema/java-1.18@0.3.0-beta": {
"extraneous": true
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -629,128 +610,6 @@
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.4.2.tgz",
"integrity": "sha512-VMOxsWh/QDwrxPsgkSQnuZ+8mfNy1OTjzzUdLBvvZtpahwPTHTeVZ51RZRqO4xfKVrR+btIPA8D01IL3xeG66w=="
},
"node_modules/@mcschema/core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.13.0.tgz",
"integrity": "sha512-nJRDvdEI2Z7Yw7eWKcbkuLFKtPIyeCc1d04E7EeUOpwxfHffgL5a0VBD2PX692z3igfTHGNXgPkzrleV91Q/ww=="
},
"node_modules/@mcschema/java-1.15": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.15/-/java-1.15-0.2.13.tgz",
"integrity": "sha512-f3NanEsd48svxZfqquSylXrHZDd5vjvi8D6r15blrCi2J2C42032Ir0dMPD38hLxu9ey0eIrgucDApn/GXO1uA==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.16": {
"version": "0.6.20",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.16/-/java-1.16-0.6.20.tgz",
"integrity": "sha512-pJihtCYPloVAk6Tq2JVvfk666rPLaNmXkHhD6/a1kk/AUaP2wk/3YuYdXyRqz7/lmxkBpJK/BvhAO4ZtsQfNtg==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.17": {
"version": "0.2.40",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.17/-/java-1.17-0.2.40.tgz",
"integrity": "sha512-aFlH9APa4JEMsz0y3BKjYGILXn6QQcQDOCHA+qgdXDjc0xsrHffO1l/TVVDi5GQCCqEgGA15guqfLVV+7Vtb2A==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.18": {
"version": "0.3.16",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.18/-/java-1.18-0.3.16.tgz",
"integrity": "sha512-crxXl0OW6BYlD+Cjaa8cJEsoehnBa6whrE6JBG37nZ1nQBl0e9vun4fbaZi0buzFjeJGZ0st7Lm0LsFJRe7FEw==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.18.2": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.18.2/-/java-1.18.2-0.1.26.tgz",
"integrity": "sha512-GN6gCyEyTTJTiv0vn4cTVMYVgG2BJpDXJh2KPrg5cT/7gCpHUrt4PJAyzEaYLkDZoW17p2gWuY12gIM/yfuiDA==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.19": {
"version": "0.1.54",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.19/-/java-1.19-0.1.54.tgz",
"integrity": "sha512-eh8kTBs3EAGlWzTbmGzoIRtePd2dUkttooGsmXmjadvwf/yT19Ew+P+U4Y6LLFLnNoWJxruCVhznS1KxzoT0wA==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.19.3": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.19.3/-/java-1.19.3-0.0.17.tgz",
"integrity": "sha512-8V3UpdmVPb2JZJ5A3Mi6J5Lf5dicmY9w11dq8VQm9jSEPwY7IXSnE4l9J3AmFkIgAhiaNTQ+/lbmliR7LNUAlQ==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.19.4": {
"version": "0.1.21",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.19.4/-/java-1.19.4-0.1.21.tgz",
"integrity": "sha512-QZhe7L6t7/Uf6vK0pXWSJYoGA0D+VAv10wrMrwG6jTHoCI1iPkFhet9JqUnOwOv0Ql+jwh7zDNQjTHJIkHwfMQ==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.20": {
"version": "0.0.24",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20/-/java-1.20-0.0.24.tgz",
"integrity": "sha512-Bcm4n7YTXQ/6mGmj7l9KcRXl8ta9nokNZDyrgEeexsrE9jzj1ef6TsNra2bI9kCka9l4s4XzZ1VDuM/Ag/Ud0g==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.20.2": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20.2/-/java-1.20.2-0.0.15.tgz",
"integrity": "sha512-crGOWodFnWT5XGjp2b8kZeXKsHpdQHDaES09NYf/VNWUIdGNYD7hXlIVvQEsqbZt1ABuisIKWvNYZJwH7Fhdew==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.20.3": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20.3/-/java-1.20.3-0.0.16.tgz",
"integrity": "sha512-XNX02G7RHB8u/ibwU0GSo+lsz/5rcduHnWKk/BJcGH6Q2NswLKJDiyt8Ow5KrcmtCBxprCLjg5pJaj/Ql6aKoQ==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.20.5": {
"version": "0.0.42",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20.5/-/java-1.20.5-0.0.42.tgz",
"integrity": "sha512-LgVeCvHQPMQUMPPiJ5tkm8RG7EKowvw6eHxu8RPakeX/Del5OYkQI252hkP2RC3AA46NHE+iKqtsk8A+lvCuow==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.21": {
"version": "0.0.27",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.21/-/java-1.21-0.0.27.tgz",
"integrity": "sha512-X2o8VJouEv5ZixLrpngyq0srwYB8Bp0lmIBKRJvJ+7/5RB6Mrf9I/Z4y7BkPjpKjw8TME1r5Tw/g7sjrYXvEUQ==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/java-1.21.2": {
"version": "0.0.13",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.21.2/-/java-1.21.2-0.0.13.tgz",
"integrity": "sha512-qGDOczhRd/KY+75oXxoKwZSuJS8k038mkiSqCbe+TFapXCZSv87TOnXCUD+LFK0v51ejwFM6mErv8ZJinuKq9g==",
"dependencies": {
"@mcschema/core": "^0.13.0"
}
},
"node_modules/@mcschema/locales": {
"version": "0.1.104",
"resolved": "https://registry.npmjs.org/@mcschema/locales/-/locales-0.1.104.tgz",
"integrity": "sha512-fZ9zzb4OvMnxRFtVYpPuFDugd2LGYMfGRsYzl+RG6wFZAUuB6pe+igNmK5o8VfX1ldO2W5FWkddgh/MjtemFOA=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -4775,128 +4634,6 @@
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.4.2.tgz",
"integrity": "sha512-VMOxsWh/QDwrxPsgkSQnuZ+8mfNy1OTjzzUdLBvvZtpahwPTHTeVZ51RZRqO4xfKVrR+btIPA8D01IL3xeG66w=="
},
"@mcschema/core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.13.0.tgz",
"integrity": "sha512-nJRDvdEI2Z7Yw7eWKcbkuLFKtPIyeCc1d04E7EeUOpwxfHffgL5a0VBD2PX692z3igfTHGNXgPkzrleV91Q/ww=="
},
"@mcschema/java-1.15": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.15/-/java-1.15-0.2.13.tgz",
"integrity": "sha512-f3NanEsd48svxZfqquSylXrHZDd5vjvi8D6r15blrCi2J2C42032Ir0dMPD38hLxu9ey0eIrgucDApn/GXO1uA==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.16": {
"version": "0.6.20",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.16/-/java-1.16-0.6.20.tgz",
"integrity": "sha512-pJihtCYPloVAk6Tq2JVvfk666rPLaNmXkHhD6/a1kk/AUaP2wk/3YuYdXyRqz7/lmxkBpJK/BvhAO4ZtsQfNtg==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.17": {
"version": "0.2.40",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.17/-/java-1.17-0.2.40.tgz",
"integrity": "sha512-aFlH9APa4JEMsz0y3BKjYGILXn6QQcQDOCHA+qgdXDjc0xsrHffO1l/TVVDi5GQCCqEgGA15guqfLVV+7Vtb2A==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.18": {
"version": "0.3.16",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.18/-/java-1.18-0.3.16.tgz",
"integrity": "sha512-crxXl0OW6BYlD+Cjaa8cJEsoehnBa6whrE6JBG37nZ1nQBl0e9vun4fbaZi0buzFjeJGZ0st7Lm0LsFJRe7FEw==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.18.2": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.18.2/-/java-1.18.2-0.1.26.tgz",
"integrity": "sha512-GN6gCyEyTTJTiv0vn4cTVMYVgG2BJpDXJh2KPrg5cT/7gCpHUrt4PJAyzEaYLkDZoW17p2gWuY12gIM/yfuiDA==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.19": {
"version": "0.1.54",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.19/-/java-1.19-0.1.54.tgz",
"integrity": "sha512-eh8kTBs3EAGlWzTbmGzoIRtePd2dUkttooGsmXmjadvwf/yT19Ew+P+U4Y6LLFLnNoWJxruCVhznS1KxzoT0wA==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.19.3": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.19.3/-/java-1.19.3-0.0.17.tgz",
"integrity": "sha512-8V3UpdmVPb2JZJ5A3Mi6J5Lf5dicmY9w11dq8VQm9jSEPwY7IXSnE4l9J3AmFkIgAhiaNTQ+/lbmliR7LNUAlQ==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.19.4": {
"version": "0.1.21",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.19.4/-/java-1.19.4-0.1.21.tgz",
"integrity": "sha512-QZhe7L6t7/Uf6vK0pXWSJYoGA0D+VAv10wrMrwG6jTHoCI1iPkFhet9JqUnOwOv0Ql+jwh7zDNQjTHJIkHwfMQ==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.20": {
"version": "0.0.24",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20/-/java-1.20-0.0.24.tgz",
"integrity": "sha512-Bcm4n7YTXQ/6mGmj7l9KcRXl8ta9nokNZDyrgEeexsrE9jzj1ef6TsNra2bI9kCka9l4s4XzZ1VDuM/Ag/Ud0g==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.20.2": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20.2/-/java-1.20.2-0.0.15.tgz",
"integrity": "sha512-crGOWodFnWT5XGjp2b8kZeXKsHpdQHDaES09NYf/VNWUIdGNYD7hXlIVvQEsqbZt1ABuisIKWvNYZJwH7Fhdew==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.20.3": {
"version": "0.0.16",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20.3/-/java-1.20.3-0.0.16.tgz",
"integrity": "sha512-XNX02G7RHB8u/ibwU0GSo+lsz/5rcduHnWKk/BJcGH6Q2NswLKJDiyt8Ow5KrcmtCBxprCLjg5pJaj/Ql6aKoQ==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.20.5": {
"version": "0.0.42",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.20.5/-/java-1.20.5-0.0.42.tgz",
"integrity": "sha512-LgVeCvHQPMQUMPPiJ5tkm8RG7EKowvw6eHxu8RPakeX/Del5OYkQI252hkP2RC3AA46NHE+iKqtsk8A+lvCuow==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.21": {
"version": "0.0.27",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.21/-/java-1.21-0.0.27.tgz",
"integrity": "sha512-X2o8VJouEv5ZixLrpngyq0srwYB8Bp0lmIBKRJvJ+7/5RB6Mrf9I/Z4y7BkPjpKjw8TME1r5Tw/g7sjrYXvEUQ==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/java-1.21.2": {
"version": "0.0.13",
"resolved": "https://registry.npmjs.org/@mcschema/java-1.21.2/-/java-1.21.2-0.0.13.tgz",
"integrity": "sha512-qGDOczhRd/KY+75oXxoKwZSuJS8k038mkiSqCbe+TFapXCZSv87TOnXCUD+LFK0v51ejwFM6mErv8ZJinuKq9g==",
"requires": {
"@mcschema/core": "^0.13.0"
}
},
"@mcschema/locales": {
"version": "0.1.104",
"resolved": "https://registry.npmjs.org/@mcschema/locales/-/locales-0.1.104.tgz",
"integrity": "sha512-fZ9zzb4OvMnxRFtVYpPuFDugd2LGYMfGRsYzl+RG6wFZAUuB6pe+igNmK5o8VfX1ldO2W5FWkddgh/MjtemFOA=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",

View File

@@ -16,22 +16,6 @@
"license": "MIT",
"dependencies": {
"@giscus/react": "^2.2.3",
"@mcschema/core": "^0.13.0",
"@mcschema/java-1.15": "^0.2.13",
"@mcschema/java-1.16": "^0.6.20",
"@mcschema/java-1.17": "^0.2.40",
"@mcschema/java-1.18": "^0.3.16",
"@mcschema/java-1.18.2": "^0.1.26",
"@mcschema/java-1.19": "^0.1.54",
"@mcschema/java-1.19.3": "^0.0.17",
"@mcschema/java-1.19.4": "^0.1.21",
"@mcschema/java-1.20": "^0.0.24",
"@mcschema/java-1.20.2": "^0.0.15",
"@mcschema/java-1.20.3": "^0.0.16",
"@mcschema/java-1.20.5": "^0.0.42",
"@mcschema/java-1.21": "^0.0.27",
"@mcschema/java-1.21.2": "^0.0.13",
"@mcschema/locales": "^0.1.104",
"@zip.js/zip.js": "^2.4.5",
"brace": "^0.11.1",
"buffer": "^6.0.3",

View File

@@ -1,5 +1,3 @@
import type { DataModel } from '@mcschema/core'
import { Path } from '@mcschema/core'
import * as zip from '@zip.js/zip.js'
import type { Identifier, NbtTag, Random } from 'deepslate'
import { Matrix3, Matrix4, NbtByte, NbtCompound, NbtDouble, NbtInt, NbtList, NbtString, Vector } from 'deepslate'
@@ -47,21 +45,6 @@ export function generateColor() {
return Math.floor(Math.random() * 16777215)
}
export function newSeed(model: DataModel) {
const seed = Math.floor(Math.random() * (4294967296)) - 2147483648
const dimensions = model.get(new Path(['dimensions']))
model.set(new Path(['seed']), seed, true)
if (isObject(dimensions)) {
Object.keys(dimensions).forEach(id => {
model.set(new Path(['dimensions', id, 'generator', 'seed']), seed, true)
model.set(new Path(['dimensions', id, 'generator', 'biome_source', 'seed']), seed, true)
})
}
model.set(new Path(['placement', 'salt']), Math.abs(seed), true)
model.set(new Path(['generator', 'seed']), seed, true)
model.set(new Path(['generator', 'biome_source', 'seed']), seed)
}
export function htmlEncode(str: string) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g, '&#x2F;')

View File

@@ -3,10 +3,9 @@ import { Identifier } from 'deepslate/core'
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks'
import { useVersion } from '../contexts/Version.jsx'
import { useAsync } from '../hooks/useAsync.js'
import { fetchItemComponents } from '../services/index.js'
import { fetchItemComponents, fetchRegistries } from '../services/index.js'
import { ResolvedItem } from '../services/ResolvedItem.js'
import { renderItem } from '../services/Resources.js'
import { getCollections } from '../services/Schemas.js'
import { jsonToNbt } from '../Utils.js'
import { ItemTooltip } from './ItemTooltip.jsx'
import { Octicon } from './Octicon.jsx'
@@ -83,14 +82,17 @@ function ItemItself({ item }: ResolvedProps) {
return Octicon.package
}
const { value: collections } = useAsync(() => getCollections(version), [])
const { value: allModels, loading: loadingModels } = useAsync(async () => {
const registries = await fetchRegistries(version)
return registries.get('model')
}, [version])
if (collections === undefined) {
if (loadingModels || allModels === undefined) {
return null
}
const modelPath = `item/${item.id.path}`
if (collections.get('model').includes('minecraft:' + modelPath)) {
if (allModels && allModels.includes('minecraft:' + modelPath)) {
return <RenderedItem item={item} />
}

View File

@@ -3,8 +3,8 @@ import { Identifier } from 'deepslate-1.20.4/core'
import { useEffect, useRef, useState } from 'preact/hooks'
import { useVersion } from '../contexts/Version.jsx'
import { useAsync } from '../hooks/useAsync.js'
import { fetchRegistries } from '../services/index.js'
import { renderItem } from '../services/Resources1204.js'
import { getCollections } from '../services/Schemas.js'
import { ItemTooltip1204 } from './ItemTooltip1204.jsx'
import { Octicon } from './Octicon.jsx'
import { itemHasGlint } from './previews/LootTable1204.js'
@@ -69,14 +69,17 @@ function ItemItself({ item }: Props) {
return Octicon.package
}
const { value: collections } = useAsync(() => getCollections(version), [])
const { value: allModels, loading: loadingModels } = useAsync(async () => {
const registries = await fetchRegistries(version)
return registries.get('model')
}, [version])
if (collections === undefined) {
if (loadingModels || allModels === undefined) {
return null
}
const modelPath = `item/${item.id.path}`
if (collections.get('model').includes('minecraft:' + modelPath)) {
if (allModels && allModels.includes('minecraft:' + modelPath)) {
return <RenderedItem item={item} hasGlint={hasGlint} />
}

View File

@@ -1,4 +1,3 @@
import type { NodeChildren } from '@mcschema/core'
import { NumberInput, RangeInput } from '../index.js'
import { CustomizedInput } from './CustomizedInput.jsx'
@@ -12,7 +11,6 @@ interface Props {
initial?: number,
error?: string,
onChange: (value: number) => void,
children?: NodeChildren,
}
export function CustomizedSlider(props: Props) {
const isInteger = (props.step ?? 1) >= 1

View File

@@ -1,13 +1,13 @@
import { DataModel } from '@mcschema/core'
import { useState } from 'preact/hooks'
import { Analytics } from '../../Analytics.js'
import { useLocale, useProject } from '../../contexts/index.js'
import type { FileModel } from '../../services/index.js'
import { Btn } from '../Btn.js'
import { TextInput } from '../forms/index.js'
import { Modal } from '../Modal.js'
interface Props {
model: DataModel,
model: FileModel,
id: string,
method: string,
onClose: () => void,
@@ -29,7 +29,7 @@ export function FileCreation({ model, id, method, onClose }: Props) {
return
}
Analytics.saveProjectFile(id, projects.length, project.files.length, method as any)
updateFile(id, undefined, { type: id, id: fileId, data: DataModel.unwrapLists(model.data) })
updateFile(id, undefined, { type: id, id: fileId, data: model.data })
onClose()
}

View File

@@ -1,69 +1,59 @@
import type { DataModel } from '@mcschema/core'
import { Path } from '@mcschema/core'
import { useState } from 'preact/hooks'
import { useModel } from '../../hooks/index.js'
import type { VersionId } from '../../services/index.js'
import { useVersion } from '../../contexts/Version.jsx'
import type { FileModel } from '../../services/index.js'
import { checkVersion } from '../../services/index.js'
import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview, RecipePreview, StructureSetPreview } from '../previews/index.js'
export const HasPreview = ['loot_table', 'recipe', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'model']
type PreviewPanelProps = {
model: DataModel | undefined,
version: VersionId,
model: FileModel | undefined,
id: string,
shown: boolean,
onError: (message: string) => unknown,
}
export function PreviewPanel({ model, version, id, shown }: PreviewPanelProps) {
const [, setCount] = useState(0)
useModel(model, () => {
setCount(count => count + 1)
})
export function PreviewPanel({ model, id, shown }: PreviewPanelProps) {
const { version } = useVersion()
if (!model) return <></>
const data = model.get(new Path([]))
if (!data) return <></>
if (id === 'loot_table') {
return <LootTablePreview {...{ model, version, shown, data }} />
return <LootTablePreview {...{ model, shown }} />
}
if (id === 'recipe') {
return <RecipePreview {...{ model, version, shown, data }} />
return <RecipePreview {...{ model, shown }} />
}
if (id === 'dimension' && model.get(new Path(['generator', 'type']))?.endsWith('noise')) {
return <BiomeSourcePreview {...{ model, version, shown, data }} />
if (id === 'dimension' && model.data.generator?.type?.endsWith('noise')) {
return <BiomeSourcePreview {...{ model, shown }} />
}
if (id === 'worldgen/density_function') {
return <DensityFunctionPreview {...{ model, version, shown, data }} />
return <DensityFunctionPreview {...{ model, shown }} />
}
if (id === 'worldgen/noise') {
return <NoisePreview {...{ model, version, shown, data }} />
return <NoisePreview {...{ model, shown }} />
}
if (id === 'worldgen/noise_settings' && checkVersion(version, '1.18')) {
return <NoiseSettingsPreview {...{ model, version, shown, data }} />
return <NoiseSettingsPreview {...{ model, shown }} />
}
if ((id === 'worldgen/placed_feature' || (id === 'worldgen/configured_feature' && checkVersion(version, '1.16', '1.17')))) {
return <DecoratorPreview {...{ model, version, shown, data }} />
return <DecoratorPreview {...{ model, shown }} />
}
if (id === 'worldgen/structure_set' && checkVersion(version, '1.19')) {
return <StructureSetPreview {...{ model, version, shown, data }} />
return <StructureSetPreview {...{ model, shown }} />
}
if (id === 'block_definition') {
return <BlockStatePreview {...{ model, version, shown, data }} />
return <BlockStatePreview {...{ model, shown }} />
}
if (id === 'model') {
return <ModelPreview {...{ model, version, shown, data }} />
return <ModelPreview {...{ model, shown }} />
}
return <></>

View File

@@ -1,10 +1,8 @@
import type { DataModel } from '@mcschema/core'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { Analytics } from '../../Analytics.js'
import config from '../../Config.js'
import { disectFilePath, DRAFT_PROJECT, getFilePath, useLocale, useProject, useVersion } from '../../contexts/index.js'
import { useFocus } from '../../hooks/useFocus.js'
import type { VersionId } from '../../services/index.js'
import { stringifySource } from '../../services/index.js'
import { Store } from '../../Store.js'
import { writeZip } from '../../Utils.js'
@@ -15,9 +13,6 @@ import type { TreeViewGroupRenderer, TreeViewLeafRenderer } from '../TreeView.js
import { TreeView } from '../TreeView.js'
interface Props {
model: DataModel | undefined,
version: VersionId,
id: string,
onError: (message: string) => unknown,
onRename: (file: { type: string, id: string }) => unknown,
onCreate: () => unknown,

View File

@@ -1,14 +1,12 @@
import { DataModel, Path } from '@mcschema/core'
import { route } from 'preact-router'
import { useCallback, useEffect, useErrorBoundary, useMemo, useRef, useState } from 'preact/hooks'
import { useCallback, useEffect, useErrorBoundary, useMemo, useState } from 'preact/hooks'
import { Analytics } from '../../Analytics.js'
import type { ConfigGenerator } from '../../Config.js'
import config from '../../Config.js'
import { DRAFT_PROJECT, useLocale, useProject, useVersion } from '../../contexts/index.js'
import { AsyncCancel, useActiveTimeout, useAsync, useModel, useSearchParam } from '../../hooks/index.js'
import { getOutput } from '../../schema/transformOutput.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet } from '../../services/index.js'
import { AsyncCancel, useActiveTimeout, useAsync, useSearchParam } from '../../hooks/index.js'
import type { FileModel, VersionId } from '../../services/index.js'
import { checkVersion, createMockFileModel, fetchPreset, fetchRegistries, getSnippet, shareSnippet } from '../../services/index.js'
import { Store } from '../../Store.js'
import { cleanUrl, deepEqual, genPath } from '../../Utils.js'
import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileRenaming, Footer, HasPreview, Octicon, PreviewPanel, ProjectCreation, ProjectDeletion, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../index.js'
@@ -22,7 +20,7 @@ interface Props {
export function SchemaGenerator({ gen, allowedVersions }: Props) {
const { locale } = useLocale()
const { version, changeVersion, changeTargetVersion } = useVersion()
const { projects, project, file, updateProject, updateFile, closeFile } = useProject()
const { projects, project, file, updateProject, closeFile } = useProject()
const [error, setError] = useState<Error | string | null>(null)
const [errorBoundary, errorRetry] = useErrorBoundary()
if (errorBoundary) {
@@ -34,16 +32,15 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const [currentPreset, setCurrentPreset] = useSearchParam('preset')
const [sharedSnippetId, setSharedSnippetId] = useSearchParam(SHARE_KEY)
const ignoreChange = useRef(false)
const backup = useMemo(() => Store.getBackup(gen.id), [gen.id])
const loadBackup = () => {
if (backup !== undefined) {
model?.reset(DataModel.wrapLists(backup), false)
// TODO: implement
}
}
const { value } = useAsync(async () => {
const {} = useAsync(async () => {
let data: unknown = undefined
if (currentPreset && sharedSnippetId) {
setSharedSnippetId(undefined)
@@ -81,81 +78,65 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
}
data = file.data
}
const [model, blockStates] = await Promise.all([
getModel(version, gen.id),
getBlockStates(version),
])
if (data) {
ignoreChange.current = true
model.reset(DataModel.wrapLists(data), false)
// TODO: set file contents to data
}
Analytics.setGenerator(gen.id)
return { model, blockStates }
return {}
}, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id])
const model = value?.model
const blockStates = value?.blockStates
const model: FileModel = createMockFileModel()
useModel(model, model => {
if (!ignoreChange.current) {
setCurrentPreset(undefined, true)
setSharedSnippetId(undefined, true)
}
if (file && model && blockStates) {
const data = getOutput(model, blockStates)
updateFile(gen.id, file.id, { id: file.id, data })
}
ignoreChange.current = false
Store.setBackup(gen.id, DataModel.unwrapLists(model.data))
setError(null)
}, [gen.id, setCurrentPreset, setSharedSnippetId, blockStates, file?.id])
// TODO: when contents of file change:
// - remove preset and share id from url
// - update project
// - store backup
const reset = () => {
Analytics.resetGenerator(gen.id, model?.historyIndex ?? 1, 'menu')
model?.reset(DataModel.wrapLists(model.schema.default()), true)
Analytics.resetGenerator(gen.id, 1, 'menu')
// TODO
}
const undo = (e: MouseEvent) => {
e.stopPropagation()
Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'menu')
model?.undo()
Analytics.undoGenerator(gen.id, 1, 'menu')
// TODO
}
const redo = (e: MouseEvent) => {
e.stopPropagation()
Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'menu')
model?.redo()
Analytics.redoGenerator(gen.id, 1, 'menu')
// TODO
}
const onKeyUp = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'z') {
Analytics.undoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey')
model?.undo()
} else if (e.ctrlKey && e.key === 'y') {
Analytics.redoGenerator(gen.id, model?.historyIndex ?? 1, 'hotkey')
model?.redo()
}
}
const onKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 's') {
setFileSaving('hotkey')
e.preventDefault()
e.stopPropagation()
}
}
useEffect(() => {
const onKeyUp = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'z') {
Analytics.undoGenerator(gen.id, 1, 'hotkey')
// TODO
} else if (e.ctrlKey && e.key === 'y') {
Analytics.redoGenerator(gen.id, 1, 'hotkey')
// TODO
}
}
const onKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 's') {
setFileSaving('hotkey')
e.preventDefault()
e.stopPropagation()
}
}
document.addEventListener('keyup', onKeyUp)
document.addEventListener('keydown', onKeyDown)
return () => {
document.removeEventListener('keyup', onKeyUp)
document.removeEventListener('keydown', onKeyDown)
}
}, [model, blockStates, file])
}, [gen.id])
const [presets, setPresets] = useState<string[]>([])
useEffect(() => {
getCollections(version).then(collections => {
setPresets(collections.get(gen.id).map(p => p.startsWith('minecraft:') ? p.slice(10) : p))
})
.catch(e => { console.error(e); setError(e) })
const { value: presets } = useAsync(async () => {
const registries = await fetchRegistries(version)
const entries = registries.get(gen.id) ?? []
return entries.map(e => e.startsWith('minecraft:') ? e.slice(10) : e)
}, [version, gen.id])
const selectPreset = (id: string) => {
@@ -168,13 +149,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const loadPreset = async (id: string) => {
try {
const preset = await fetchPreset(version, genPath(gen, version), id)
const seed = model?.get(new Path(['generator', 'seed']))
if (preset?.generator?.seed !== undefined && seed !== undefined) {
preset.generator.seed = seed
if (preset.generator.biome_source?.seed !== undefined) {
preset.generator.biome_source.seed = seed
}
}
// TODO: sync random seed
return preset
} catch (e) {
setError(`Cannot load preset ${id} in ${version}`)
@@ -203,14 +178,14 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
setShareUrl(`${location.origin}/${gen.url}/?version=${version}&preset=${currentPreset}`)
setShareShown(true)
copySharedId()
} else if (model && blockStates) {
const output = getOutput(model, blockStates)
if (deepEqual(output, model.schema.default())) {
} else {
// TODO: get contents from file, and compare to default of type
if (deepEqual(model.data, {})) {
setShareUrl(`${location.origin}/${gen.url}/?version=${version}`)
setShareShown(true)
} else {
setShareLoading(true)
shareSnippet(gen.id, version, output, previewShown)
shareSnippet(gen.id, version, model.data, previewShown)
.then(({ id, length, compressed, rate }) => {
Analytics.createSnippet(gen.id, id, version, length, compressed, rate)
const url = `${location.origin}/${gen.url}/?${SHARE_KEY}=${id}`
@@ -298,21 +273,12 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
const [projectDeleting, setprojectDeleting] = useState(false)
const [fileSaving, setFileSaving] = useState<string | undefined>(undefined)
const [fileRenaming, setFileRenaming] = useState<{ type: string, id: string } | undefined>(undefined)
const [newFileQueued, setNewFileQueued] = useState(false)
const onNewFile = useCallback(() => {
closeFile()
// Need to queue reset because otherwise the useModel hook will update the old file
setNewFileQueued(true)
// TODO: create new file with default contents
}, [closeFile])
useEffect(() => {
if (file === undefined && newFileQueued) {
model?.reset(DataModel.wrapLists(model.schema.default()), true)
setNewFileQueued(false)
}
}, [model, newFileQueued, file])
return <>
<main class={`generator${previewShown ? ' has-preview' : ''}${projectShown ? ' has-project' : ''}`}>
{!gen.tags?.includes('partners') && <Ad id="data-pack-generator" type="text" />}
@@ -335,7 +301,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
</BtnMenu>
</div>
{error && <ErrorPanel error={error} onDismiss={() => setError(null)} />}
<Tree {...{model, version, blockStates}} onError={setError} />
<Tree model={model} onError={setError} />
<Footer donate={!gen.tags?.includes('partners')} />
</main>
<div class="popup-actions right-actions" style={`--offset: -${8 + actionsShown * 50}px;`}>
@@ -356,10 +322,10 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
</div>
</div>
<div class={`popup-preview${previewShown ? ' shown' : ''}`}>
<PreviewPanel {...{model, version, id: gen.id}} shown={previewShown} onError={setError} />
<PreviewPanel model={model} id={gen.id} shown={previewShown} onError={setError} />
</div>
<div class={`popup-source${sourceShown ? ' shown' : ''}`}>
<SourcePanel {...{model, blockStates, doCopy, doDownload, doImport}} name={gen.schema ?? 'data'} copySuccess={copySuccess} onError={setError} />
<SourcePanel {...{model, doCopy, doDownload, doImport}} name={gen.schema ?? 'data'} copySuccess={copySuccess} onError={setError} />
</div>
<div class={`popup-share${shareShown ? ' shown' : ''}`}>
<TextInput value={shareUrl} readonly />
@@ -371,7 +337,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
</div>
</div>
<div class={`popup-project${projectShown ? ' shown' : ''}`}>
<ProjectPanel {...{model, version, id: gen.id}} onError={setError} onDeleteProject={() => setprojectDeleting(true)} onRename={setFileRenaming} onCreate={() => setProjectCreating(true)} />
<ProjectPanel onError={setError} onDeleteProject={() => setprojectDeleting(true)} onRename={setFileRenaming} onCreate={() => setProjectCreating(true)} />
</div>
{projectCreating && <ProjectCreation onClose={() => setProjectCreating(false)} />}
{projectDeleting && <ProjectDeletion onClose={() => setprojectDeleting(false)} />}

View File

@@ -1,9 +1,7 @@
import { DataModel } from '@mcschema/core'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useLocalStorage, useModel } from '../../hooks/index.js'
import { getOutput } from '../../schema/transformOutput.js'
import type { BlockStateRegistry } from '../../services/index.js'
import { useLocalStorage } from '../../hooks/index.js'
import type { FileModel } from '../../services/index.js'
import { getSourceFormats, getSourceIndent, getSourceIndents, parseSource, sortData, stringifySource } from '../../services/index.js'
import { Store } from '../../Store.js'
import { message } from '../../Utils.js'
@@ -18,15 +16,14 @@ interface Editor {
type SourcePanelProps = {
name: string,
model: DataModel | undefined,
blockStates: BlockStateRegistry | undefined,
model: FileModel | undefined,
doCopy?: number,
doDownload?: number,
doImport?: number,
copySuccess: () => unknown,
onError: (message: string | Error) => unknown,
}
export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) {
export function SourcePanel({ name, model, doCopy, doDownload, doImport, copySuccess, onError }: SourcePanelProps) {
const { locale } = useLocale()
const [indent, setIndent] = useState(Store.getIndent())
const [format, setFormat] = useState(Store.getFormat())
@@ -40,8 +37,8 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
const textarea = useRef<HTMLTextAreaElement>(null)
const editor = useRef<Editor>()
const getSerializedOutput = useCallback((model: DataModel, blockStates: BlockStateRegistry) => {
let data = getOutput(model, blockStates)
const getSerializedOutput = useCallback((model: FileModel) => {
let data = model.data
if (sort === 'alphabetically') {
data = sortData(data)
}
@@ -51,9 +48,9 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
useEffect(() => {
retransform.current = () => {
if (!editor.current) return
if (!model || !blockStates) return
if (!model) return
try {
const output = getSerializedOutput(model, blockStates)
const output = getSerializedOutput(model)
editor.current.setValue(output)
} catch (e) {
if (e instanceof Error) {
@@ -72,8 +69,8 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
const value = editor.current.getValue()
if (value.length === 0) return
try {
const data = await parseSource(value, format)
model?.reset(DataModel.wrapLists(data), false)
await parseSource(value, format)
// TODO: import
} catch (e) {
if (e instanceof Error) {
e.message = `Error importing: ${e.message}`
@@ -84,7 +81,7 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
console.error(e)
}
}
}, [model, blockStates, indent, format, sort, highlighting])
}, [model, indent, format, sort, highlighting])
useEffect(() => {
if (highlighting) {
@@ -145,10 +142,7 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
}
}, [highlighting])
useModel(model, () => {
if (!retransform.current) return
retransform.current()
})
// TODO: when file contents change, retransform
useEffect(() => {
if (!retransform.current) return
if (model) retransform.current()
@@ -163,16 +157,16 @@ export function SourcePanel({ name, model, blockStates, doCopy, doDownload, doIm
}, [indent, format, sort, highlighting, braceLoaded])
useEffect(() => {
if (doCopy && model && blockStates) {
navigator.clipboard.writeText(getSerializedOutput(model, blockStates)).then(() => {
if (doCopy && model) {
navigator.clipboard.writeText(getSerializedOutput(model)).then(() => {
copySuccess()
})
}
}, [doCopy])
useEffect(() => {
if (doDownload && model && blockStates && download.current) {
const content = encodeURIComponent(getSerializedOutput(model, blockStates))
if (doDownload && model && download.current) {
const content = encodeURIComponent(getSerializedOutput(model))
download.current.setAttribute('href', `data:text/json;charset=utf-8,${content}`)
const fileName = name === 'pack_mcmeta' ? 'pack.mcmeta' : `${name}.${format}`
download.current.setAttribute('download', fileName)

View File

@@ -1,19 +1,14 @@
import type { DataModel } from '@mcschema/core'
import { useErrorBoundary, useState } from 'preact/hooks'
import { useErrorBoundary } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useModel } from '../../hooks/index.js'
import { FullNode } from '../../schema/renderHtml.js'
import type { BlockStateRegistry, VersionId } from '../../services/index.js'
import type { FileModel } from '../../services/index.js'
type TreePanelProps = {
version: VersionId,
model: DataModel | undefined,
blockStates: BlockStateRegistry | undefined,
model: FileModel | undefined,
onError: (message: string) => unknown,
}
export function Tree({ version, model, blockStates, onError }: TreePanelProps) {
export function Tree({ model, onError }: TreePanelProps) {
const { lang } = useLocale()
if (!model || !blockStates || lang === 'none') return <></>
if (!model || lang === 'none') return <></>
const [error] = useErrorBoundary(e => {
onError(`Error rendering the tree: ${e.message}`)
@@ -21,12 +16,7 @@ export function Tree({ version, model, blockStates, onError }: TreePanelProps) {
})
if (error) return <></>
const [, setState] = useState(0)
useModel(model, () => {
setState(state => state + 1)
})
return <div class="tree" data-cy="tree">
<FullNode {...{model, lang, version, blockStates}}/>
{/* TODO: render tree */}
</div>
}

View File

@@ -1,8 +1,7 @@
import { DataModel } from '@mcschema/core'
import { clampedMap } from 'deepslate'
import { mat3 } from 'gl-matrix'
import { useCallback, useRef, useState } from 'preact/hooks'
import { getProjectData, useLocale, useProject, useStore } from '../../contexts/index.js'
import { getProjectData, useLocale, useProject, useStore, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/index.js'
import { checkVersion } from '../../services/Schemas.js'
import { Store } from '../../Store.js'
@@ -21,8 +20,9 @@ type Layer = typeof LAYERS[number]
const DETAIL_DELAY = 300
const DETAIL_SCALE = 2
export const BiomeSourcePreview = ({ data, shown, version }: PreviewProps) => {
export const BiomeSourcePreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const { project } = useProject()
const { biomeColors } = useStore()
const [seed, setSeed] = useState(randomSeed())
@@ -31,13 +31,13 @@ export const BiomeSourcePreview = ({ data, shown, version }: PreviewProps) => {
const [focused, setFocused] = useState<string[]>([])
const [focused2, setFocused2] = useState<string[]>([])
const state = JSON.stringify(data)
const type: string = data?.generator?.biome_source?.type?.replace(/^minecraft:/, '') ?? ''
const state = JSON.stringify(model.data)
const type: string = model.data?.generator?.biome_source?.type?.replace(/^minecraft:/, '') ?? ''
const hasRandomness = type === 'multi_noise' || type === 'the_end'
const { value } = useAsync(async function loadBiomeSource() {
await DEEPSLATE.loadVersion(version, getProjectData(project))
await DEEPSLATE.loadChunkGenerator(DataModel.unwrapLists(data?.generator?.settings), DataModel.unwrapLists(data?.generator?.biome_source), seed)
await DEEPSLATE.loadChunkGenerator(model.data?.generator?.settings, model.data?.generator?.biome_source, seed)
return {
biomeSource: { loaded: true },
noiseRouter: checkVersion(version, '1.19') ? DEEPSLATE.getNoiseRouter() : undefined,

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { BlockDefinition, Identifier, Structure, StructureRenderer } from 'deepslate/render'
import type { mat4 } from 'gl-matrix'
import { useCallback, useRef } from 'preact/hooks'
@@ -11,14 +10,14 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
const PREVIEW_ID = Identifier.parse('misode:preview')
export const BlockStatePreview = ({ data, shown }: PreviewProps) => {
export const BlockStatePreview = ({ model, shown }: PreviewProps) => {
const { version } = useVersion()
const serializedData = JSON.stringify(data)
const serializedData = JSON.stringify(model.data)
const { value: resources } = useAsync(async () => {
if (!shown) return AsyncCancel
const resources = await getResources(version)
const definition = BlockDefinition.fromJson(DataModel.unwrapLists(data))
const definition = BlockDefinition.fromJson(model.data)
const wrapper = new ResourceWrapper(resources, {
getBlockDefinition(id) {
if (id.equals(PREVIEW_ID)) return definition

View File

@@ -1,9 +1,8 @@
import { DataModel } from '@mcschema/core'
import type { BlockPos, ChunkPos, PerlinNoise, Random } from 'deepslate/worldgen'
import type { Color } from '../../Utils.js'
import { clamp, isObject, stringToColor } from '../../Utils.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion } from '../../services/index.js'
import type { Color } from '../../Utils.js'
import { clamp, isObject, stringToColor } from '../../Utils.js'
export type Placement = [BlockPos, number]
@@ -38,9 +37,9 @@ export const featureColors: Color[] = [
export function decorateChunk(pos: ChunkPos, state: any, ctx: PlacementContext): PlacedFeature[] {
if (checkVersion(ctx.version, undefined, '1.17')) {
getPlacements([pos[0] * 16, 0, pos[1] * 16], DataModel.unwrapLists(state), ctx)
getPlacements([pos[0] * 16, 0, pos[1] * 16], state, ctx)
} else {
modifyPlacement([pos[0] * 16, 0, pos[1] * 16], DataModel.unwrapLists(state.placement), ctx)
modifyPlacement([pos[0] * 16, 0, pos[1] * 16], state.placement, ctx)
}
return ctx.placements.map(([pos, i]) => {

View File

@@ -1,7 +1,7 @@
import { BlockPos, ChunkPos, LegacyRandom, PerlinNoise } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useLocale, useVersion } from '../../contexts/index.js'
import { computeIfAbsent, iterateWorld2D, randomSeed } from '../../Utils.js'
import { Btn } from '../index.js'
import type { PlacedFeature, PlacementContext } from './Decorator.js'
@@ -9,10 +9,11 @@ import { decorateChunk } from './Decorator.js'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
export const DecoratorPreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const [seed, setSeed] = useState(randomSeed())
const state = JSON.stringify(data)
const state = JSON.stringify(model.data)
const { context, chunkFeatures } = useMemo(() => {
const random = new LegacyRandom(seed)
@@ -51,7 +52,7 @@ export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
iterateWorld2D(imageData.current, transform, (x, y) => {
const pos = ChunkPos.create(Math.floor(x / 16), Math.floor(-y / 16))
const features = computeIfAbsent(chunkFeatures, `${pos[0]} ${pos[1]}`, () => decorateChunk(pos, data, context))
const features = computeIfAbsent(chunkFeatures, `${pos[0]} ${pos[1]}`, () => decorateChunk(pos, model.data, context))
return features.find(f => f.pos[0] === x && f.pos[2] == -y) ?? { pos: BlockPos.create(x, 0, -y) }
}, (feature) => {
if ('color' in feature) {

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import type { Voxel } from 'deepslate/render'
import { clampedMap, VoxelRenderer } from 'deepslate/render'
import type { mat3, mat4 } from 'gl-matrix'
@@ -19,7 +18,7 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
const MODES = ['side', 'top', '3d'] as const
export const DensityFunctionPreview = ({ data, shown }: PreviewProps) => {
export const DensityFunctionPreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { project } = useProject()
const { version } = useVersion()
@@ -29,11 +28,11 @@ export const DensityFunctionPreview = ({ data, shown }: PreviewProps) => {
const [seed, setSeed] = useState(randomSeed())
const [minY] = useState(0)
const [height] = useState(256)
const serializedData = JSON.stringify(data)
const serializedData = JSON.stringify(model.data)
const { value: df } = useAsync(async () => {
await DEEPSLATE.loadVersion(version, getProjectData(project))
const df = DEEPSLATE.loadDensityFunction(DataModel.unwrapLists(data), minY, height, seed)
const df = DEEPSLATE.loadDensityFunction(model.data, minY, height, seed)
return df
}, [version, project, minY, height, seed, serializedData])

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { Identifier } from 'deepslate'
import { useMemo, useRef, useState } from 'preact/hooks'
import { useLocale, useVersion } from '../../contexts/index.js'
@@ -12,7 +11,7 @@ import type { PreviewProps } from './index.js'
import { generateLootTable } from './LootTable.js'
import { generateLootTable as generateLootTable1204 } from './LootTable1204.js'
export const LootTablePreview = ({ data }: PreviewProps) => {
export const LootTablePreview = ({ model }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const use1204 = !checkVersion(version, '1.20.5')
@@ -35,7 +34,7 @@ export const LootTablePreview = ({ data }: PreviewProps) => {
])
}, [version])
const table = DataModel.unwrapLists(data)
const table = model.data
const state = JSON.stringify(table)
const items = useMemo(() => {
if (dependencies === undefined || loading) {

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { BlockDefinition, BlockModel, Identifier, Structure, StructureRenderer } from 'deepslate/render'
import type { mat4 } from 'gl-matrix'
import { useCallback, useRef } from 'preact/hooks'
@@ -12,22 +11,22 @@ import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
const PREVIEW_ID = Identifier.parse('misode:preview')
const PREVIEW_DEFINITION = new BlockDefinition({ '': { model: PREVIEW_ID.toString() }}, undefined)
export const ModelPreview = ({ data, shown }: PreviewProps) => {
export const ModelPreview = ({ model, shown }: PreviewProps) => {
const { version } = useVersion()
const serializedData = JSON.stringify(data)
const serializedData = JSON.stringify(model.data)
const { value: resources } = useAsync(async () => {
if (!shown) return AsyncCancel
const resources = await getResources(version)
const model = BlockModel.fromJson(DataModel.unwrapLists(data))
model.flatten(resources)
const blockModel = BlockModel.fromJson(model.data)
blockModel.flatten(resources)
const wrapper = new ResourceWrapper(resources, {
getBlockDefinition(id) {
if (id.equals(PREVIEW_ID)) return PREVIEW_DEFINITION
return null
},
getBlockModel(id) {
if (id.equals(PREVIEW_ID)) return model
if (id.equals(PREVIEW_ID)) return blockModel
return null
},
})

View File

@@ -1,4 +1,3 @@
import { DataModel } from '@mcschema/core'
import { clampedMap, NoiseParameters, NormalNoise, XoroshiroRandom } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
@@ -12,14 +11,14 @@ import { ColormapSelector } from './ColormapSelector.jsx'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const NoisePreview = ({ data, shown }: PreviewProps) => {
export const NoisePreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const [seed, setSeed] = useState(randomSeed())
const state = JSON.stringify(data)
const state = JSON.stringify(model.data)
const noise = useMemo(() => {
const random = XoroshiroRandom.create(seed)
const params = NoiseParameters.fromJson(DataModel.unwrapLists(data))
const params = NoiseParameters.fromJson(model.data)
return new NormalNoise(random, params)
}, [state, seed])

View File

@@ -1,31 +1,31 @@
import { DataModel } from '@mcschema/core'
import { clampedMap } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { vec2 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { useCallback, useRef, useState } from 'preact/hooks'
import { getProjectData, useLocale, useProject, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/index.js'
import { fetchRegistries } from '../../services/index.js'
import { Store } from '../../Store.js'
import { iterateWorld2D, randomSeed } from '../../Utils.js'
import { getProjectData, useLocale, useProject } from '../../contexts/index.js'
import { useAsync } from '../../hooks/index.js'
import { CachedCollections } from '../../services/index.js'
import { Btn, BtnInput, BtnMenu, ErrorPanel } from '../index.js'
import type { ColormapType } from './Colormap.js'
import { getColormap } from './Colormap.js'
import { ColormapSelector } from './ColormapSelector.jsx'
import { DEEPSLATE } from './Deepslate.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const NoiseSettingsPreview = ({ data, shown, version }: PreviewProps) => {
export const NoiseSettingsPreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const { project } = useProject()
const [seed, setSeed] = useState(randomSeed())
const [biome, setBiome] = useState('minecraft:plains')
const [layer, setLayer] = useState('terrain')
const state = JSON.stringify(data)
const state = JSON.stringify(model.data)
const { value, error } = useAsync(async () => {
const unwrapped = DataModel.unwrapLists(data)
const unwrapped = model.data
await DEEPSLATE.loadVersion(version, getProjectData(project))
const biomeSource = { type: 'fixed', biome }
await DEEPSLATE.loadChunkGenerator(unwrapped, biomeSource, seed)
@@ -86,7 +86,10 @@ export const NoiseSettingsPreview = ({ data, shown, version }: PreviewProps) =>
}
}, [noiseSettings, finalDensity])
const allBiomes = useMemo(() => CachedCollections?.get('worldgen/biome') ?? [], [version])
const { value: allBiomes } = useAsync(async () => {
const registries = await fetchRegistries(version)
return registries.get('worldgen/biome')
}, [version])
if (error) {
return <ErrorPanel error={error} prefix="Failed to initialize preview: " />

View File

@@ -1,7 +1,6 @@
import { DataModel } from '@mcschema/core'
import { Identifier, ItemStack } from 'deepslate'
import { useEffect, useMemo, useRef, useState } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useLocale, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/useAsync.js'
import type { VersionId } from '../../services/index.js'
import { checkVersion, fetchAllPresets } from '../../services/index.js'
@@ -12,8 +11,9 @@ import type { PreviewProps } from './index.js'
const ANIMATION_TIME = 1000
export const RecipePreview = ({ data, version }: PreviewProps) => {
export const RecipePreview = ({ model }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const [advancedTooltips, setAdvancedTooltips] = useState(true)
const [animation, setAnimation] = useState(0)
const overlay = useRef<HTMLDivElement>(null)
@@ -29,7 +29,7 @@ export const RecipePreview = ({ data, version }: PreviewProps) => {
return () => clearInterval(interval)
}, [])
const recipe = DataModel.unwrapLists(data)
const recipe = model.data
const state = JSON.stringify(recipe)
const items = useMemo<Map<Slot, ItemStack>>(() => {
return placeItems(version, recipe, animation, itemTags ?? new Map())

View File

@@ -1,26 +1,26 @@
import { DataModel } from '@mcschema/core'
import type { Identifier } from 'deepslate'
import { ChunkPos } from 'deepslate'
import type { mat3 } from 'gl-matrix'
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
import { useLocale, useVersion } from '../../contexts/index.js'
import { useAsync } from '../../hooks/useAsync.js'
import type { Color } from '../../Utils.js'
import { computeIfAbsent, iterateWorld2D, randomSeed, stringToColor } from '../../Utils.js'
import { useLocale } from '../../contexts/index.js'
import { useAsync } from '../../hooks/useAsync.js'
import { Btn } from '../index.js'
import { featureColors } from './Decorator.js'
import { DEEPSLATE } from './Deepslate.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
import type { PreviewProps } from './index.js'
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
export const StructureSetPreview = ({ data, version, shown }: PreviewProps) => {
export const StructureSetPreview = ({ model, shown }: PreviewProps) => {
const { locale } = useLocale()
const { version } = useVersion()
const [seed, setSeed] = useState(randomSeed())
const state = JSON.stringify(data)
const state = JSON.stringify(model.data)
const { value: structureSet } = useAsync(async () => {
await DEEPSLATE.loadVersion(version)
const structureSet = DEEPSLATE.loadStructureSet(DataModel.unwrapLists(data), seed)
const structureSet = DEEPSLATE.loadStructureSet(model.data, seed)
return structureSet
}, [state, version, seed])

View File

@@ -1,5 +1,4 @@
import type { DataModel } from '@mcschema/core'
import type { VersionId } from '../../services/index.js'
import type { FileModel } from '../../services/index.js'
export * from './BiomeSourcePreview.js'
export * from './BlockStatePreview.jsx'
@@ -12,9 +11,7 @@ export * from './NoiseSettingsPreview.js'
export * from './RecipePreview.jsx'
export * from './StructureSetPreview.jsx'
export type PreviewProps = {
model: DataModel,
data: any,
shown: boolean,
version: VersionId,
export interface PreviewProps {
model: FileModel
shown: boolean
}

View File

@@ -46,13 +46,7 @@ async function loadLocale(language: string) {
const langConfig = config.languages.find(lang => lang.code === language)
if (!langConfig) return
const data = await import(`../../locales/${language}.json`)
const schema = langConfig.schemas !== false
&& await import(`../../../node_modules/@mcschema/locales/src/${language}.json`)
let partners = { default: {} }
if (language === 'en') {
partners = await import('../partners/locales/en.json')
}
Locales[language] = { ...data.default, ...schema.default, ...partners.default }
Locales[language] = data.default
}
export function useLocale() {

View File

@@ -5,6 +5,5 @@ export * from './useFocus.js'
export * from './useHash.js'
export * from './useLocalStorage.js'
export * from './useMediaQuery.js'
export * from './useModel.js'
export * from './useSearchParam.js'
export * from './useTags.js'

View File

@@ -1,20 +0,0 @@
import type { DataModel } from '@mcschema/core'
import type { Inputs } from 'preact/hooks'
import { useEffect } from 'preact/hooks'
export function useModel(model: DataModel | undefined | null, invalidated: (model: DataModel) => unknown, inputs?: Inputs) {
const listener = {
invalidated() {
if (model) {
invalidated(model)
}
},
}
useEffect(() => {
model?.addListener(listener)
return () => {
model?.removeListener(listener)
}
}, [model, ...inputs ?? []])
}

View File

@@ -1,210 +0,0 @@
import type { CollectionRegistry, ResourceType, SchemaRegistry } from '@mcschema/core'
import { BooleanNode, Case, ChoiceNode, ListNode, MapNode, NumberNode, ObjectNode, Opt, Reference as RawReference, StringNode as RawStringNode, Switch } from '@mcschema/core'
const ID = 'immersive_weathering'
export function initImmersiveWeathering(schemas: SchemaRegistry, collections: CollectionRegistry) {
const Reference = RawReference.bind(undefined, schemas)
const StringNode = RawStringNode.bind(undefined, collections)
const Tag = (id: Exclude<ResourceType, `$tag/${string}`>) => ChoiceNode([
{
type: 'string',
node: StringNode({ validator: 'resource', params: { pool: id, allowTag: true } }),
change: (v: unknown) => {
if (Array.isArray(v) && typeof v[0] === 'string' && !v[0].startsWith('#')) {
return v[0]
}
return undefined
},
},
{
type: 'list',
node: ListNode(
StringNode({ validator: 'resource', params: { pool: id } })
),
change: (v: unknown) => {
if (typeof v === 'string' && !v.startsWith('#')) {
return [v]
}
return []
},
},
], { choiceContext: 'tag' })
schemas.register(`${ID}:block_growth`, ObjectNode({
area_condition: Reference(`${ID}:area_condition`),
position_predicates: Opt(ListNode(
Reference(`${ID}:position_test`)
)),
growth_chance: NumberNode({ min: 0, max: 1 }),
growth_for_face: ListNode(
ObjectNode({
direction: Opt(StringNode({ enum: 'direction' })),
weight: Opt(NumberNode({ integer: true })),
growth: ListNode(
ObjectNode({
data: Reference(`${ID}:block_pair`),
weight: NumberNode({ integer: true }),
})
),
}, { category: 'pool' })
),
owners: ListNode(
StringNode({ validator: 'resource', params: { pool: 'block' } })
),
replacing_target: Reference(`${ID}:rule_test`),
target_self: Opt(BooleanNode()),
destroy_target: Opt(BooleanNode()),
}, { context: `${ID}.block_growth` }))
schemas.register(`${ID}:area_condition`, ObjectNode({
type: StringNode({ enum: ['generate_if_not_too_many', 'neighbor_based_generation'] }),
[Switch]: [{ push: 'type' }],
[Case]: {
generate_if_not_too_many: {
radiusX: NumberNode({ integer: true }),
radiusY: NumberNode({ integer: true }),
radiusZ: NumberNode({ integer: true }),
requiredAmount: NumberNode({ integer: true }),
yOffset: Opt(NumberNode({ integer: true })),
must_have: Opt(Reference(`${ID}:rule_test`)),
must_not_have: Opt(Reference(`${ID}:rule_test`)),
includes: Opt(Tag('block')),
},
neighbor_based_generation: {
must_have: Reference(`${ID}:rule_test`),
must_not_have: Opt(Reference(`${ID}:rule_test`)),
required_amount: Opt(NumberNode({ integer: true })),
directions: ListNode(
StringNode({ enum: 'direction' })
),
},
},
}, { context: `${ID}.area_condition` }))
schemas.register(`${ID}:block_pair`, ObjectNode({
block: Reference(`${ID}:block_state`),
above_block: Opt(Reference(`${ID}:block_state`)),
}, { context: `${ID}.block_pair` }))
schemas.register(`${ID}:block_state`, ObjectNode({
Name: StringNode({ validator: 'resource', params: { pool: 'block' } }),
Properties: Opt(MapNode(
StringNode(),
StringNode(),
)),
}, { context: 'block_state' }))
schemas.register(`${ID}:position_test`, ObjectNode({
predicate_type: StringNode({ enum: ['biome_match', 'day_test', 'nand', 'precipitation_test', 'temperature_range'] }),
[Switch]: [{ push: 'predicate_type' }],
[Case]: {
biome_match: {
biomes: Tag('$worldgen/biome'),
},
day_test: {
day: BooleanNode(),
},
nand: {
predicates: ListNode(
Reference(`${ID}:position_test`)
),
},
precipitation_test: {
precipitation: StringNode({ enum: ['none', 'rain', 'snow']}),
},
temperature_range: {
min: NumberNode(),
max: NumberNode(),
use_local_pos: Opt(BooleanNode()),
},
},
}, { context: `${ID}.position_test`, category: 'predicate' }))
collections.register(`${ID}:rule_test`, [
...collections.get('rule_test'),
'immersive_weathering:block_set_match',
'immersive_weathering:fluid_match',
'immersive_weathering:tree_log',
])
schemas.register(`${ID}:rule_test`, ObjectNode({
predicate_type: StringNode({ validator: 'resource', params: { pool: `${ID}:rule_test` as any } }),
[Switch]: [{ push: 'predicate_type' }],
[Case]: {
'minecraft:block_match': {
block: StringNode({ validator: 'resource', params: { pool: 'block' } }),
},
'minecraft:blockstate_match': {
block_state: Reference('block_state'),
},
'minecraft:random_block_match': {
block: StringNode({ validator: 'resource', params: { pool: 'block' } }),
probability: NumberNode({ min: 0, max: 1 }),
},
'minecraft:random_blockstate_match': {
block_state: Reference('block_state'),
probability: NumberNode({ min: 0, max: 1 }),
},
'minecraft:tag_match': {
tag: StringNode({ validator: 'resource', params: { pool: '$tag/block' }}),
},
'immersive_weathering:block_set_match': {
blocks: Tag('block'),
probability: Opt(NumberNode({ min: 0, max: 1 })),
},
'immersive_weathering:fluid_match': {
fluid: StringNode({ validator: 'resource', params: { pool: 'fluid' } }),
},
},
}, { context: 'rule_test', disableSwitchContext: true }))
collections.register('block_growth', [
'immersive_weathering:brain_coral',
'immersive_weathering:bubble_coral',
'immersive_weathering:cracked_mud_rivers',
'immersive_weathering:crimson_nylium',
'immersive_weathering:cryosol',
'immersive_weathering:farmland_rare_weeds',
'immersive_weathering:farmland_weeds',
'immersive_weathering:fire_coral',
'immersive_weathering:fire_soot',
'immersive_weathering:fluvisol',
'immersive_weathering:grass_base',
'immersive_weathering:grass_block_badlands',
'immersive_weathering:grass_block_bamboo_jungle',
'immersive_weathering:grass_block_birch_forest',
'immersive_weathering:grass_block_dark_forest',
'immersive_weathering:grass_block_flower_forest',
'immersive_weathering:grass_block_forest',
'immersive_weathering:grass_block_jungle',
'immersive_weathering:grass_block_lush_caves',
'immersive_weathering:grass_block_old_growth_spruce',
'immersive_weathering:grass_block_plains',
'immersive_weathering:grass_block_sunflower_plains',
'immersive_weathering:grass_block_swamp',
'immersive_weathering:grass_block_taiga',
'immersive_weathering:grass_block_wooded_badlands',
'immersive_weathering:hanging_roots',
'immersive_weathering:horn_coral',
'immersive_weathering:humus',
'immersive_weathering:icicle_growth',
'immersive_weathering:large_fern',
'immersive_weathering:magma',
'immersive_weathering:mycelium',
'immersive_weathering:podzol',
'immersive_weathering:red_sand_weathering',
'immersive_weathering:rooted_dirt',
'immersive_weathering:rooted_grass',
'immersive_weathering:sand_weathering',
'immersive_weathering:sapling',
'immersive_weathering:sapling_nether',
'immersive_weathering:silt',
'immersive_weathering:tall_grass',
'immersive_weathering:tall_seagrass',
'immersive_weathering:tube_coral',
'immersive_weathering:vertisol',
'immersive_weathering:warped_nylium',
])
}

View File

@@ -1,239 +0,0 @@
import type { CollectionRegistry, ResourceType, SchemaRegistry } from '@mcschema/core'
import { BooleanNode, Case, ChoiceNode, ListNode, Mod, NumberNode, ObjectNode, Opt, Reference as RawReference, StringNode as RawStringNode, Switch } from '@mcschema/core'
import type { VersionId } from '../services/Schemas.js'
const ID = 'lithostitched'
export function initLithostitched(schemas: SchemaRegistry, collections: CollectionRegistry, version: VersionId) {
const Reference = RawReference.bind(undefined, schemas)
const StringNode = RawStringNode.bind(undefined, collections)
const Tag = (id: Exclude<ResourceType, `$tag/${string}`>) =>
ChoiceNode(
[
{
type: 'string',
node: StringNode({
validator: 'resource',
params: { pool: id, allowTag: true },
}),
change: (v: unknown) => {
if (
Array.isArray(v) &&
typeof v[0] === 'string' &&
!v[0].startsWith('#')
) {
return v[0]
}
return undefined
},
},
{
type: 'list',
node: ListNode(
StringNode({ validator: 'resource', params: { pool: id } })
),
change: (v: unknown) => {
if (typeof v === 'string' && !v.startsWith('#')) {
return [v]
}
return []
},
},
],
{ choiceContext: 'tag' }
)
// Worldgen Modifiers
const MobCategorySpawnSettings = Mod(
ObjectNode({
type: StringNode({
validator: 'resource',
params: { pool: 'entity_type' },
}),
weight: NumberNode({ integer: true }),
minCount: NumberNode({ integer: true }),
maxCount: NumberNode({ integer: true }),
}),
{
category: () => 'pool',
default: () => [
{
type: 'minecraft:bat',
weight: 1,
},
],
}
)
collections.register(`${ID}:modifier_type`, [
'lithostitched:add_biome_spawns',
'lithostitched:add_features',
...(version === '1.20.5' || version === '1.21')
? ['lithostitched:add_pool_aliases']
: [],
'lithostitched:add_structure_set_entries',
'lithostitched:add_surface_rule',
'lithostitched:add_template_pool_elements',
'lithostitched:no_op',
'lithostitched:redirect_feature',
'lithostitched:remove_biome_spawns',
'lithostitched:remove_features',
'lithostitched:remove_structures_from_structure_set',
'lithostitched:replace_climate',
'lithostitched:replace_effects',
])
collections.register(`${ID}:modifier_predicate_type`, [
'lithostitched:all_of',
'lithostitched:any_of',
'lithostitched:mod_loaded',
'lithostitched:not',
'lithostitched:true',
])
schemas.register(`${ID}:worldgen_modifier`, Mod(ObjectNode({
type: StringNode({ validator: 'resource', params: { pool: `${ID}:modifier_type` as any } }),
predicate: Mod(Opt(Reference(`${ID}:modifier_predicate`)), {
enabled: () => version !== '1.21',
}),
[Switch]: [{ push: 'type' }],
[Case]: {
'lithostitched:add_biome_spawns': {
biomes: Tag('$worldgen/biome'),
spawners: ChoiceNode([
{
type: 'object',
node: MobCategorySpawnSettings,
change: (v: any) => v[0],
},
{
type: 'list',
node: ListNode(MobCategorySpawnSettings),
change: (v: any) => Array(v),
},
]),
},
'lithostitched:add_features': {
biomes: Tag('$worldgen/biome'),
features: Tag('$worldgen/configured_feature'),
step: StringNode({ enum: 'decoration_step' }),
},
'lithostitched:add_pool_aliases': {
structure: StringNode({ validator: 'resource', params: { pool: '$worldgen/structure' } }),
pool_aliases: Reference('pool_alias_binding'),
},
'lithostitched:add_structure_set_entries': {
structure_set: StringNode({ validator: 'resource', params: { pool: '$worldgen/structure_set' } }),
entries: ListNode(
ObjectNode({
structure: StringNode({ validator: 'resource', params: { pool: '$worldgen/structure' } }),
weight: NumberNode({ integer: true, min: 1 }),
})
),
},
'lithostitched:add_surface_rule': {
levels: ListNode(StringNode({ validator: 'resource', params: { pool: '$dimension' } })),
surface_rule: Reference('material_rule'),
},
'lithostitched:add_template_pool_elements': {
template_pool: StringNode({ validator: 'resource', params: { pool: '$worldgen/template_pool' } }),
elements: ListNode(
Reference('template_weighted_element')
),
},
'lithostitched:redirect_feature': {
placed_feature: StringNode({ validator: 'resource', params: { pool: '$worldgen/placed_feature' } }),
redirect_to: StringNode({ validator: 'resource', params: { pool: '$worldgen/configured_feature' } }),
},
'lithostitched:remove_biome_spawns': {
biomes: Tag('$worldgen/biome'),
mobs: Tag('entity_type'),
},
'lithostitched:remove_features': {
biomes: Tag('$worldgen/biome'),
features: Tag('$worldgen/configured_feature'),
step: StringNode({ enum: 'decoration_step' }),
},
'lithostitched:remove_structures_from_structure_set': {
structure_set: StringNode({ validator: 'resource', params: { pool: '$worldgen/structure_set' } }),
structures: ListNode(
StringNode({ validator: 'resource', params: { pool: '$worldgen/structure' } })
),
},
'lithostitched:replace_climate': {
biomes: Tag('$worldgen/biome'),
climate: ObjectNode({
temperature: NumberNode(),
downfall: NumberNode(),
has_precipitation: BooleanNode(),
temperature_modifier: Opt(StringNode({ enum: ['none', 'frozen'] })),
}),
},
'lithostitched:replace_effects': {
biomes: Tag('$worldgen/biome'),
effects: ObjectNode({
sky_color: Opt(NumberNode({ color: true })),
fog_color: Opt(NumberNode({ color: true })),
water_color: Opt(NumberNode({ color: true })),
water_fog_color: Opt(NumberNode({ color: true })),
grass_color: Opt(NumberNode({ color: true })),
foliage_color: Opt(NumberNode({ color: true })),
grass_color_modifier: Opt(StringNode({ enum: ['none', 'dark_forest', 'swamp'] })),
ambient_sound: Opt(StringNode()),
mood_sound: Opt(ObjectNode({
sound: StringNode(),
tick_delay: NumberNode({ integer: true }),
block_search_extent: NumberNode({ integer: true }),
offset: NumberNode(),
})),
additions_sound: Opt(ObjectNode({
sound: StringNode(),
tick_chance: NumberNode({ min: 0, max: 1 }),
})),
music: Opt(ObjectNode({
sound: StringNode(),
min_delay: NumberNode({ integer: true, min: 0 }),
max_delay: NumberNode({ integer: true, min: 0 }),
replace_current_music: BooleanNode(),
})),
particle: Opt(ObjectNode({
options: ObjectNode({
type: StringNode(),
}),
probability: NumberNode({ min: 0, max: 1 }),
})),
}),
},
},
}, { context: `${ID}.worldgen_modifier`, disableSwitchContext: true }), {
default: () => ({
type: `${ID}:add_features`,
biomes: '#minecraft:is_overworld',
features: 'example:ore_ruby',
step: 'underground_ores',
}),
}))
schemas.register(`${ID}:modifier_predicate`, ObjectNode({
type: StringNode({ validator: 'resource', params: { pool: `${ID}:modifier_predicate_type` as any } }),
[Switch]: [{ push: 'type' }],
[Case]: {
'lithostitched:all_of': {
predicates: ListNode(Reference(`${ID}:modifier_predicate`)),
},
'lithostitched:any_of': {
predicates: ListNode(Reference(`${ID}:modifier_predicate`)),
},
'lithostitched:mod_loaded': {
mod_id: StringNode(),
},
'lithostitched:not': {
predicate: Reference(`${ID}:modifier_predicate`),
},
},
}, {
context: `${ID}.modifier_predicate`, disableSwitchContext: true,
}))
}

View File

@@ -1,492 +0,0 @@
import type { CollectionRegistry, INode, ResourceType, SchemaRegistry } from '@mcschema/core'
import { BooleanNode, Case, ChoiceNode, ListNode, MapNode, Mod, NumberNode, ObjectNode, Opt, StringNode as RawStringNode, Switch } from '@mcschema/core'
import type { VersionId } from '../services/Schemas.js'
const ID = 'neoforge'
export function initNeoForge(schemas: SchemaRegistry, collections: CollectionRegistry, _version: VersionId) {
const StringNode = RawStringNode.bind(undefined, collections)
// Homogenous list (ref, list of refs, tag)
const Tag = (id: Exclude<ResourceType, `$tag/${string}`>) =>
ChoiceNode(
[
{
type: 'string',
node: StringNode({
validator: 'resource',
params: { pool: id, allowTag: true },
}),
change: (v: unknown) => {
if (
Array.isArray(v) &&
typeof v[0] === 'string' &&
!v[0].startsWith('#')
) {
return v[0]
}
return undefined
},
},
{
type: 'list',
node: ListNode(
StringNode({ validator: 'resource', params: { pool: id } })
),
change: (v: unknown) => {
if (typeof v === 'string' && !v.startsWith('#')) {
return [v]
}
return []
},
},
],
{ choiceContext: 'tag' }
)
// Spawner data
const MobCategorySpawnSettings = Mod(
ObjectNode({
type: StringNode({ validator: 'resource', params: { pool: 'entity_type' }}),
weight: NumberNode({ integer: true, min: 0 }),
minCount: NumberNode({ integer: true, min: 1 }),
maxCount: NumberNode({ integer: true, min: 1 }),
}),
{
category: () => 'pool',
default: () => [
{
type: 'minecraft:bat',
weight: 1,
},
],
}
)
// Generation step carving
const CarvingStep = StringNode({ enum: [ 'air', 'liquid' ] })
// Mob category
const MobCategory = StringNode({ enum: [ 'monster', 'creature', 'ambient', 'axolotls', 'underground_water_creature', 'water_creature', 'water_ambient', 'misc' ], additional: true })
// Biome modifier types
collections.register(`${ID}:biome_modifier_type`, [
'neoforge:none',
'neoforge:add_features',
'neoforge:remove_features',
'neoforge:add_spawns',
'neoforge:remove_spawns',
'neoforge:add_carvers',
'neoforge:remove_carvers',
'neoforge:add_spawn_costs',
'neoforge:remove_spawn_costs',
])
// Biome modifiers
schemas.register(`${ID}:biome_modifier`, Mod(
ObjectNode({
type: StringNode({ validator: 'resource', params: { pool: `${ID}:biome_modifier_type` as any }}),
[Switch]: [{ push: 'type' }],
[Case]: {
'neoforge:none': {},
'neoforge:add_features': {
biomes: Tag('$worldgen/biome'),
features: Tag('$worldgen/placed_feature'),
step: StringNode({ enum: 'decoration_step' }),
},
'neoforge:remove_features': {
biomes: Tag('$worldgen/biome'),
features: Tag('$worldgen/placed_feature'),
steps: Opt(ChoiceNode([
{
type: 'string',
node: StringNode({ enum: 'decoration_step' }),
change: (v: any) => v[0],
},
{
type: 'list',
node: ListNode(StringNode({ enum: 'decoration_step' })),
change: (v: any) => Array(v),
},
])),
},
'neoforge:add_spawns': {
biomes: Tag('$worldgen/biome'),
spawners: ChoiceNode([
{
type: 'object',
node: MobCategorySpawnSettings,
change: (v: any) => v[0],
},
{
type: 'list',
node: MobCategorySpawnSettings,
change: (v: any) => Array(v),
},
]),
},
'neoforge:remove_spawns': {
biomes: Tag('$worldgen/biome'),
entity_types: Tag('entity_type'),
},
'neoforge:add_carvers': {
biomes: Tag('$worldgen/biome'),
carvers: Tag('$worldgen/configured_carver'),
step: CarvingStep,
},
'neoforge:remove_carvers': {
biomes: Tag('$worldgen/biome'),
carvers: Tag('$worldgen/configured_carver'),
steps: Opt(ChoiceNode([
{
type: 'string',
node: CarvingStep,
change: (v: any) => v[0],
},
{
type: 'list',
node: ListNode(CarvingStep),
change: (v: any) => Array(v),
},
])),
},
'neoforge:add_spawn_costs': {
biomes: Tag('$worldgen/biome'),
entity_types: Tag('entity_type'),
spawn_cost: ObjectNode({
energy_budget: NumberNode(),
charge: NumberNode(),
}),
},
'neoforge:remove_spawn_costs': {
biomes: Tag('$worldgen/biome'),
entity_types: Tag('entity_type'),
},
},
}, { context: `${ID}.biome_modifier`, disableSwitchContext: true }),
{
default: () => ({
type: `${ID}:add_features`,
biomes: '#minecraft:is_overworld',
features: 'minecraft:ore_iron_small',
step: 'underground_ores',
}),
}
))
// Structure modifier types
collections.register(`${ID}:structure_modifier_type`, [
'neoforge:none',
'neoforge:add_spawns',
'neoforge:remove_spawns',
'neoforge:clear_spawns',
])
// Structure modifiers
schemas.register(`${ID}:structure_modifier`, Mod(
ObjectNode({
type: StringNode({ validator: 'resource', params: { pool: `${ID}:structure_modifier_type` as any }}),
[Switch]: [{ push: 'type' }],
[Case]: {
'neoforge:none': {},
'neoforge:add_spawns': {
structures: Tag('$worldgen/structure'),
spawners: ChoiceNode([
{
type: 'object',
node: MobCategorySpawnSettings,
change: (v: any) => v[0],
},
{
type: 'list',
node: MobCategorySpawnSettings,
change: (v: any) => Array(v),
},
]),
},
'neoforge:remove_spawns': {
structures: Tag('$worldgen/structure'),
entity_types: Tag('entity_type'),
},
'neoforge:clear_spawns': {
structures: Tag('$worldgen/structure'),
categories: ChoiceNode([
{
type: 'string',
node: MobCategory,
change: (v: any) => v[0],
},
{
type: 'list',
node: MobCategory,
change: (v: any) => Array(v),
},
]),
},
},
}, { context: `${ID}.structure_modifier`, disableSwitchContext: true }),
{
default: () => ({
type: `${ID}:add_spawns`,
structures: '#minecraft:village',
spawners: {
type: 'minecraft:bat',
weight: 1,
},
}),
}
))
// Data maps
createDataMap(schemas, collections, 'compostables', 'item', ChoiceNode([
{
type: 'number',
node: NumberNode({
min: 0,
max: 1,
}),
change: (v: any) => v?.chance,
},
{
type: 'object',
node: ObjectNode({
chance: NumberNode({
min: 0,
max: 1,
}),
can_villager_compost: Opt(BooleanNode()),
}),
change: (v: any) => ({
chance: v,
can_villager_compost: false,
}),
},
]), (values) => values['minecraft:apple'] = {
chance: 1,
can_villager_compost: true,
}
)
createDataMap(schemas, collections, 'furnace_fuels', 'item', ChoiceNode([
{
type: 'number',
node: NumberNode({
min: 1,
integer: true,
}),
change: (v: any) => v?.burn_time,
},
{
type: 'object',
node: ObjectNode({
burn_time: NumberNode({
min: 1,
integer: true,
}),
}),
change: (v: any) => ({
burn_time: v,
}),
},
]), (values) => values['minecraft:chest'] = {
burn_time: 300,
}
)
createDataMap(schemas, collections, 'monster_room_mobs', 'entity_type', ChoiceNode([
{
type: 'number',
node: NumberNode({
min: 0,
integer: true,
}),
change: (v: any) => v?.weight,
},
{
type: 'object',
node: ObjectNode({
weight: NumberNode({
min: 0,
integer: true,
}),
}),
change: (v: any) => ({
weight: v,
}),
},
]), (values) => values['minecraft:bat'] = {
weight: 5,
})
createDataMap(schemas, collections, 'oxidizables', 'block', ChoiceNode([
{
type: 'string',
node: StringNode({
validator: 'resource',
params: { pool: 'block' },
}),
change: (v: any) => v?.next_oxidation_stage,
},
{
type: 'object',
node: ObjectNode({
next_oxidation_stage: StringNode({
validator: 'resource',
params: { pool: 'block' },
}),
}),
change: (v: any) => ({
next_oxidation_stage: v,
}),
},
]), (values) => values['minecraft:grass_block'] = {
next_oxidation_stage: 'minecraft:dirt',
})
createDataMap(schemas, collections, 'parrot_imitations', 'entity_type', ChoiceNode([
{
type: 'string',
node: StringNode({
validator: 'resource',
params: { pool: 'sound_event' as any },
}),
change: (v: any) => v?.sound,
},
{
type: 'object',
node: ObjectNode({
sound: StringNode({
validator: 'resource',
params: { pool: 'sound_event' as any },
}),
}),
change: (v: any) => ({
sound: v,
}),
},
]), (values) => values['minecraft:allay'] = {
sound: 'minecraft:entity.allay.ambient_without_item',
})
createDataMap(schemas, collections, 'raid_hero_gifts', 'villager_profession', ChoiceNode([
{
type: 'string',
node: StringNode({
validator: 'resource',
params: { pool: '$loot_table' },
}),
change: (v: any) => v?.loot_table,
},
{
type: 'object',
node: ObjectNode({
loot_table: StringNode({
validator: 'resource',
params: { pool: '$loot_table' },
}),
}),
change: (v: any) => ({
loot_table: v,
}),
},
]), (values) => values['minecraft:cleric'] = {
loot_table: 'minecraft:empty',
})
createDataMap(schemas, collections, 'vibration_frequencies', 'game_event', ChoiceNode([
{
type: 'number',
node: NumberNode({
min: 1,
max: 15,
integer: true,
}),
change: (v: any) => v?.frequency,
},
{
type: 'object',
node: ObjectNode({
frequency: NumberNode({
min: 1,
max: 15,
integer: true,
}),
}),
change: (v: any) => ({
frequency: v,
}),
},
]), (values) => values['minecraft:block_change'] = {
frequency: 5,
})
createDataMap(schemas, collections, 'waxables', 'block', ChoiceNode([
{
type: 'string',
node: StringNode({
validator: 'resource',
params: { pool: 'block' },
}),
change: (v: any) => v?.waxed,
},
{
type: 'object',
node: ObjectNode({
waxed: StringNode({
validator: 'resource',
params: { pool: 'block' },
}),
}),
change: (v: any) => ({
waxed: v,
}),
},
]), (values) => values['minecraft:dirt'] = {
waxed: 'minecraft:coarse_dirt',
})
}
function createDataMap(schemas: SchemaRegistry, collections: CollectionRegistry, dataMap: string, registry: ResourceType, valueNode: INode<any>, def: (values: any) => void) {
const StringNode = RawStringNode.bind(undefined, collections)
// Ref or tag
const Tag = StringNode({
validator: 'resource',
params: { pool: registry, allowTag: true },
})
// Create data map
schemas.register(`${ID}:data_map_${dataMap}`, Mod(
ObjectNode({
replace: Opt(BooleanNode()),
values: MapNode(
Tag,
ChoiceNode([
{
type: 'direct',
match: () => true,
node: valueNode,
change: (v: any) => v?.value,
},
{
type: 'replaceable',
match: (v: any) => typeof v === 'object' && v?.value !== undefined,
priority: 1,
node: ObjectNode({
replace: Opt(BooleanNode()),
value: valueNode,
}),
change: (v: any) => ({
replace: true,
value: v,
}),
},
]),
),
remove: Opt(ListNode(Tag)),
}, {context: `${ID}.data_map_${dataMap}`, disableSwitchContext: true}),
{
default: () => {
const result = {
values: {},
}
def(result.values)
return result
},
}
))
}

View File

@@ -1,442 +0,0 @@
import type { CollectionRegistry, SchemaRegistry } from '@mcschema/core'
import { BooleanNode, Case, ListNode, MapNode, Mod, NumberNode, ObjectNode, Opt, Reference as RawReference, StringNode as RawStringNode, Switch } from '@mcschema/core'
const ID = 'obsidian'
export function initObsidian(schemas: SchemaRegistry, collections: CollectionRegistry) {
const Reference = RawReference.bind(undefined, schemas)
const StringNode = RawStringNode.bind(undefined, collections)
// ITEMS
schemas.register(`${ID}:item`, Mod(ObjectNode({
information: Opt(Reference(`${ID}:item_information`)),
display: Opt(ObjectNode({
model: Opt(Reference(`${ID}:model`)),
item_model: Opt(Reference(`${ID}:model`)),
lore: Opt(ListNode(
ObjectNode({
text: Reference(`${ID}:name_information`),
}),
)),
})),
use_action: Opt(ObjectNode({
action: Opt(StringNode({ enum: ['none', 'eat', 'drink', 'block', 'bow', 'spear', 'crossbow', 'spyglass'] })),
right_click_action: Opt(StringNode({ enum: ['open_gui', 'run_command', 'open_url']})) ,
command: Opt(StringNode()),
url: Opt(StringNode()),
gui_size: Opt(NumberNode({ integer: true, min: 1, max: 6 })),
gui_title: Opt(Reference(`${ID}:name_information`)),
})),
}, { context: `${ID}:item` }), {
default: () => ({}),
}))
schemas.register(`${ID}:item_information`, ObjectNode({
rarity: Opt(StringNode({ enum: ['common', 'uncommon', 'rare', 'epic']})),
creative_tab: Opt(StringNode()),
max_stack_size: Opt(NumberNode({ integer: true, min: 1 })),
name: Opt(Reference(`${ID}:name_information`)),
has_enchantment_glint: Opt(BooleanNode()),
is_enchantable: Opt(BooleanNode()),
enchantability: Opt(NumberNode({ integer: true })),
use_duration: Opt(NumberNode({ integer: true })),
can_place_block: Opt(BooleanNode()),
placable_block: Opt(StringNode({ validator: 'resource', params: { pool: 'block' } })),
wearable: Opt(BooleanNode()),
default_color: Opt(NumberNode({ color: true })),
wearable_slot: Opt(StringNode()),
custom_render_mode: Opt(BooleanNode()),
render_mode_models: Opt(ListNode(
ObjectNode({
model: Reference('model_identifier'),
modes: ListNode(StringNode()),
})
)),
}, { context: `${ID}:item_information` }))
schemas.register(`${ID}:item_model`, ObjectNode({
textures: Opt(MapNode(
StringNode(),
StringNode({ validator: 'resource', params: { pool: '$texture' } }),
)),
parent: StringNode({ validator: 'resource', params: { pool: '$model'} }),
}, { context: `${ID}:item_model` }))
schemas.register(`${ID}:block`, Mod(ObjectNode({
block_type: Opt(StringNode({ enum: `${ID}:block_type`})),
information: Opt(Reference(`${ID}:block_information`)),
display: Opt(ObjectNode({
model: Opt(Reference(`${ID}:model`)),
item_model: Opt(Reference(`${ID}:model`)),
block_model: Opt(Reference(`${ID}:model`)),
lore: ListNode(
ObjectNode({
text: Reference(`${ID}:item_name_information`),
}),
),
})),
additional_information: Opt(ObjectNode({
extraBlocksName: Opt(StringNode()),
slab: Opt(BooleanNode()),
stairs: Opt(BooleanNode()),
walls: Opt(BooleanNode()),
fence: Opt(BooleanNode()),
fenceGate: Opt(BooleanNode()),
button: Opt(BooleanNode()),
pressurePlate: Opt(BooleanNode()),
door: Opt(BooleanNode()),
trapdoor: Opt(BooleanNode()),
path: Opt(BooleanNode()),
lantern: Opt(BooleanNode()),
barrel: Opt(BooleanNode()),
leaves: Opt(BooleanNode()),
plant: Opt(BooleanNode()),
chains: Opt(BooleanNode()),
cake_like: Opt(BooleanNode()),
waterloggable: Opt(BooleanNode()),
dyable: Opt(BooleanNode()),
defaultColor: Opt(NumberNode({ color: true })),
sittable: Opt(BooleanNode()),
isConvertible: Opt(BooleanNode()),
convertible: Opt(ObjectNode({
drops_item: Opt(BooleanNode()),
reversible: Opt(BooleanNode()),
parent_block: Opt(StringNode({ validator: 'resource', params: { pool: 'block' } })),
transformed_block: Opt(StringNode({ validator: 'resource', params: { pool: 'block' } })),
dropped_item: Opt(StringNode({ validator: 'resource', params: { pool: 'item' } })),
sound: Opt(StringNode()),
conversionItem: Opt(ObjectNode({
item: StringNode({ validator: 'resource', params: { pool: 'item' } }),
tag: StringNode({ validator: 'resource', params: { pool: '$tag/item' } }),
})),
reversalItem: Opt(ObjectNode({
item: StringNode({ validator: 'resource', params: { pool: 'item' } }),
tag: StringNode({ validator: 'resource', params: { pool: '$tag/item' } }),
})),
})),
})),
functions: Opt(ObjectNode({
random_tick: Opt(Reference(`${ID}:function`)),
scheduled_tick: Opt(Reference(`${ID}:function`)),
random_display_tick: Opt(Reference(`${ID}:function`)),
place: Opt(Reference(`${ID}:function`)),
break: Opt(Reference(`${ID}:function`)),
use: Opt(Reference(`${ID}:function`)),
walk_on: Opt(Reference(`${ID}:function`)),
})),
ore_information: Opt(ObjectNode({
test_type: Opt(StringNode({ enum: ['tag', 'always', 'block_match', 'block_state_match', 'random_block_match', 'random_block_state_match']})),
target_state: Opt(ObjectNode({
block: Opt(StringNode({ validator: 'resource', params: { pool: 'block' }})),
tag: Opt(StringNode({ validator: 'resource', params: { pool: '$tag/block' } })),
properties: Opt(MapNode(
StringNode(),
StringNode(),
)),
probability: Opt(NumberNode({ min: 0, max: 1 })),
})),
triangleRange: Opt(BooleanNode()),
plateau: Opt(NumberNode({ integer: true, min: 0 })),
spawnPredicate: Opt(StringNode({ enum: ['built_in', 'vanilla', 'overworld', 'the_nether', 'the_end', 'categories', 'biomes'] })),
biomeCategories: ListNode(StringNode()),
biomes: ListNode(
StringNode({ validator: 'resource', params: { pool: '$worldgen/biome' }})
),
size: Opt(NumberNode({ integer: true })),
chance: Opt(NumberNode({ integer: true, min: 1 })),
discardOnAirChance: Opt(NumberNode({ min: 0, max: 1 })),
topOffset: Reference(`${ID}:block_y_offset`),
bottomOffset: Reference(`${ID}:block_y_offset`),
})),
food_information: Opt(ObjectNode({
hunger: Opt(NumberNode({ integer: true, min: 0 })),
saturation: Opt(NumberNode({ integer: true, min: 0 })),
effects: ListNode(
ObjectNode({
effect: StringNode({ validator: 'resource', params: { pool: 'mob_effect' } }),
duration: Opt(NumberNode({ integer: true, min: 0 })),
amplifier: Opt(NumberNode({ integer: true, min: 0 })),
chance: Opt(NumberNode({ integer: true, min: 0, max: 1 })),
})
),
})),
cake_slices: Opt(NumberNode({integer: true, min: 1})),
campfire_properties: Opt(ObjectNode({
emits_particles: Opt(BooleanNode()),
fire_damage: Opt(NumberNode({ integer: true })),
luminance: Opt(NumberNode({ integer: true })),
})),
particle_type: Opt(StringNode()),
can_plant_on: Opt(ListNode(
StringNode({ validator: 'resource', params: { pool: 'block' } })
)),
growable: Opt(ObjectNode({
min_age: Opt(NumberNode({ integer: true, min: 1 })),
max_age: Opt(NumberNode({ integer: true })),
})),
oxidizable_properties: Opt(ObjectNode({
stages: Opt(ListNode(
ObjectNode({
can_be_waxed: Opt(BooleanNode()),
stairs: Opt(BooleanNode()),
slab: Opt(BooleanNode()),
blocks: Opt(ListNode(
ObjectNode({
name: Opt(Reference(`${ID}:name_information`)),
display: Opt(Reference(`${ID}:model`)),
})
)),
})
)),
})),
events: Opt(ListNode(Reference(`${ID}:event`))),
drop_information: Opt(ObjectNode({
drops: Opt(ListNode(
ObjectNode({
name: StringNode({ validator: 'resource', params: { pool: 'item' } }),
drops_if_silk_touch: Opt(BooleanNode()),
})
)),
survives_explosion: Opt(BooleanNode()),
xp_drop_amount: Opt(NumberNode({ integer: true })),
})),
is_multi_block: Opt(BooleanNode()),
multiblock_information: Opt(ObjectNode({
width: Opt(NumberNode({ integer: true })),
height: Opt(NumberNode({ integer: true })),
})),
placable_feature: Opt(StringNode({ validator: 'resource', params: { pool: '$worldgen/configured_feature' } })),
}, { context: `${ID}:block` }), {
default: () => ({}),
}))
schemas.register(`${ID}:block_information`, ObjectNode({
rarity: Opt(StringNode({ enum: ['common', 'uncommon', 'rare', 'epic']})),
creative_tab: Opt(StringNode()),
collidable: Opt(BooleanNode()),
max_stack_size: Opt(NumberNode({ integer: true, min: 1 })),
name: Opt(Reference(`${ID}:name_information`)),
vanilla_sound_group: Opt(StringNode()),
custom_sound_group: Opt(Reference(`${ID}:sound_group`)),
vanilla_material: Opt(StringNode()),
custom_material: Opt(Reference(`${ID}:material`)),
has_glint: Opt(BooleanNode()),
is_enchantable: Opt(BooleanNode()),
enchantability: Opt(NumberNode({ integer: true })),
fireproof: Opt(BooleanNode()),
translucent: Opt(BooleanNode()),
dynamic_boundaries: Opt(BooleanNode()),
has_item: Opt(BooleanNode()),
dyeable: Opt(BooleanNode()),
defaultColor: Opt(NumberNode({ color: true })),
wearable: Opt(BooleanNode()),
wearble_slot: Opt(StringNode()),
custom_render_mode: Opt(BooleanNode()),
render_mode_models: Opt(ListNode(
ObjectNode({
model: Reference('model_identifier'),
modes: ListNode(StringNode()),
})
)),
}, { context: `${ID}:block_information` }))
schemas.register(`${ID}:block_y_offset`, ObjectNode({
type: Opt(StringNode({enum: ['fixed', 'above_bottom', 'below_top', 'bottom', 'top']})),
offset: Opt(NumberNode({ integer: true })),
}))
schemas.register(`${ID}:function`, ObjectNode({
predicate: Opt(Reference(`${ID}:predicate`)),
function_type: StringNode({ enum: ['NONE', 'REQUIRES_SHIFTING', 'REQUIRES_ITEM', 'REQUIRES_SHIFTING_AND_ITEM'] }),
[Switch]: [{ push: 'function_type' }],
[Case]: {
NONE: {},
REQUIRES_SHIFTING: {},
REQUIRES_ITEM: { item: StringNode({ validator: 'resource', params: { pool: 'item' } }) },
REQUIRES_SHIFTING_AND_ITEM: { item: StringNode({ validator: 'resource', params: { pool: 'item' } }) },
},
function_file: StringNode(),
}, { context: `${ID}:function` }))
schemas.register(`${ID}:predicate`, ObjectNode({
predicate_type: Opt(StringNode({ enum: ['ALWAYS', 'EQUALS', 'NOT_EQUALS', 'CONTAINS', 'NOT_CONTAINS', 'BEGINS_WITH', 'ENDS_WITH', 'REGEX'] })),
[Switch]: [{ push: 'predicate_type' }],
[Case]: {
ALWAYS: {},
EQUALS: { left: StringNode(), right: StringNode() },
NOT_EQUALS: { left: StringNode(), right: StringNode() },
CONTAINS: { left: StringNode(), right: StringNode() },
NOT_CONTAINS: { left: StringNode(), right: StringNode() },
BEGINS_WITH: { left: StringNode(), right: StringNode() },
ENDS_WITH: { left: StringNode(), right: StringNode() },
REGEX: { left: StringNode(), right: StringNode() },
},
}, { context: `${ID}:predicate` }))
schemas.register(`${ID}:model`, ObjectNode({
textures: Opt(MapNode(
StringNode(),
StringNode({ validator: 'resource', params: { pool: '$texture' } }),
)),
parent: StringNode({ validator: 'resource', params: { pool: '$model'} }),
}, { context: `${ID}:model` }))
schemas.register(`${ID}:sound_group`, ObjectNode({
id: Opt(StringNode()),
break_sound: Opt(StringNode()),
step_sound: Opt(StringNode()),
place_sound: Opt(StringNode()),
hit_sound: Opt(StringNode()),
}, { context: `${ID}:sound_group` }))
schemas.register(`${ID}:material`, ObjectNode({
id: Opt(StringNode()),
map_color: Opt(StringNode()),
allows_movement: Opt(BooleanNode()),
burnable: Opt(BooleanNode()),
liquid: Opt(BooleanNode()),
allows_light: Opt(BooleanNode()),
replacable: Opt(BooleanNode()),
solid: Opt(BooleanNode()),
piston_behaviour: Opt(StringNode({ enum: ['NORMAL', 'DESTROY', 'BLOCK', 'IGNORE', 'PUSH_ONLY'] })),
}, { context: `${ID}:material` }))
// COMMON
schemas.register(`${ID}:name_information`, ObjectNode({
id: StringNode(),
text: Opt(StringNode()),
type: Opt(StringNode({ enum: ['literal', 'translatable'] })),
translated: Opt(MapNode(
StringNode(),
StringNode(),
)),
color: Opt(StringNode()),
formatting: Opt(ListNode(StringNode())),
}))
schemas.register(`${ID}:display_information`, ObjectNode({
// TODO
}))
schemas.register(`${ID}:event`, ObjectNode({
activations: StringNode({enum: ['use', 'shift_use', 'collide', 'walk_on']}),
predicate: Opt(StringNode({ validator: 'resource', params: { pool: '$predicate' } })),
type: StringNode({ enum: ['give_effect', 'damage', 'decrement_stack', 'kill', 'play_sound', 'remove_effect', 'run_command', 'set_block', 'set_block_at_pos', 'set_block_property', 'spawn_loot', 'spawn_entity'] }),
[Switch]: [{ push: 'type' }],
[Case]: {
give_effect: {
amplifier: NumberNode({ integer: true }),
duration: NumberNode(),
effect: StringNode({ validator: 'resource', params: { pool: 'mob_effect' } }),
target: StringNode(),
},
damage: {
amount: NumberNode({ integer: true }),
target: StringNode(),
damage_type: StringNode(),
},
decrement_stack: {
amount: NumberNode({ integer: true }),
},
kill: {
target: StringNode(),
},
remove_effect: {
effect: StringNode({ validator: 'resource', params: { pool: 'mob_effect' } }),
target: StringNode(),
},
run_command: {
command: StringNode(),
target: StringNode(),
},
set_block: {
block: StringNode({ validator: 'resource', params: { pool: 'block' } }),
x_pos: NumberNode(),
y_pos: NumberNode(),
z_pos: NumberNode(),
},
set_block_at_pos: {
block: StringNode({ validator: 'resource', params: { pool: 'block' } }),
x_pos: NumberNode(),
y_pos: NumberNode(),
z_pos: NumberNode(),
},
set_block_property: {
block: StringNode({ validator: 'resource', params: { pool: 'block' } }),
property: StringNode(),
value: StringNode(),
},
spawn_loot: {
loot_table: StringNode({ validator: 'resource', params: { pool: '$loot_table' } }),
x_pos: NumberNode(),
y_pos: NumberNode(),
z_pos: NumberNode(),
},
spawn_entity: {
entity_type: StringNode({ validator: 'resource', params: { pool: 'entity_type' } }),
x_pos: NumberNode(),
y_pos: NumberNode(),
z_pos: NumberNode(),
amount: NumberNode({ integer: true }),
},
},
}))
// COLLECTIONS
collections.register(`${ID}:block_type`, [
'BLOCK',
'HORIZONTAL_FACING_BLOCK',
'ROTATABLE_BLOCK',
'CAMPFIRE',
'STAIRS',
'SLAB',
'WALL',
'FENCE',
'FENCE_GATE',
'CAKE',
'BED',
'TRAPDOOR',
'METAL_DOOR',
'WOODEN_DOOR',
'LOG',
'STEM',
'WOOD',
'OXIDIZING_BLOCK',
'PLANT',
'PILLAR',
'HORIZONTAL_FACING_PLANT',
'SAPLING',
'TORCH',
'BEEHIVE',
'LEAVES',
'LADDER',
'PATH',
'WOODEN_BUTTON',
'STONE_BUTTON',
'DOUBLE_PLANT',
'HORIZONTAL_FACING_DOUBLE_PLANT',
'HANGING_DOUBLE_LEAVES',
'EIGHT_DIRECTIONAL_BLOCK',
'LANTERN',
'CHAIN',
'PANE',
'DYEABLE',
'LOOM',
'GRINDSTONE',
'CRAFTING_TABLE',
'PISTON',
'NOTEBLOCK',
'JUKEBOX',
'SMOKER',
'FURNACE',
'BLAST_FURNACE',
'LECTERN',
'FLETCHING_TABLE',
'BARREL',
'COMPOSTER',
'RAILS',
'CARTOGRAPHY_TABLE',
'CARPET',
])
}

View File

@@ -1,114 +0,0 @@
import type { CollectionRegistry, SchemaRegistry } from '@mcschema/core'
import { Case, ListNode, Mod, NumberNode, ObjectNode, Opt, Reference as RawReference, StringNode as RawStringNode, Switch } from '@mcschema/core'
export function initOhTheTreesYoullGrow(schemas: SchemaRegistry, collections: CollectionRegistry) {
const Reference = RawReference.bind(undefined, schemas)
const StringNode = RawStringNode.bind(undefined, collections)
collections.register('ohthetreesyoullgrow:feature', [
'ohthetreesyoullgrow:tree_from_nbt_v1',
])
const BlockSet = ListNode(
StringNode({ validator: 'resource', params: { pool: 'block' } })
)
schemas.register('ohthetreesyoullgrow:configured_feature', Mod(ObjectNode({
type: StringNode({ validator: 'resource', params: { pool: 'ohthetreesyoullgrow:feature' as any } }),
config: ObjectNode({
[Switch]: ['pop', { push: 'type' }],
[Case]: {
'ohthetreesyoullgrow:tree_from_nbt_v1': {
base_location: StringNode({ validator: 'resource', params: { pool: '$structure' } }),
canopy_location: StringNode({ validator: 'resource', params: { pool: '$structure' } }),
can_grow_on_filter: Reference('block_predicate_worldgen'),
can_leaves_place_filter: Reference('block_predicate_worldgen'),
decorators: Opt(ListNode(
ObjectNode({
type: StringNode({ validator: 'resource', params: { pool: 'worldgen/tree_decorator_type' } }),
[Switch]: [{ push: 'type' }],
[Case]: {
'minecraft:alter_ground': {
provider: Reference('block_state_provider'),
},
'minecraft:attached_to_leaves': {
probability: NumberNode({ min: 0, max: 1 }),
exclusion_radius_xz: NumberNode({ integer: true, min: 0, max: 16 }),
exclusion_radius_y: NumberNode({ integer: true, min: 0, max: 16 }),
required_empty_blocks: NumberNode({ integer: true, min: 1, max: 16 }),
block_provider: Reference('block_state_provider'),
directions: ListNode(
StringNode({ enum: 'direction' })
),
},
'minecraft:beehive': {
probability: NumberNode({ min: 0, max: 1 }),
},
'minecraft:cocoa': {
probability: NumberNode({ min: 0, max: 1 }),
},
'minecraft:leave_vine': {
probability: NumberNode({ min: 0, max: 1 }),
},
},
}, { context: 'tree_decorator' })
)),
height: Reference('int_provider'),
leaves_provider: Reference('block_state_provider'),
leaves_target: BlockSet,
log_provider: Reference('block_state_provider'),
log_target: BlockSet,
max_log_depth: Opt(NumberNode({ integer: true })),
place_from_nbt: BlockSet,
},
},
}, { disableSwitchContext: true }),
}, { context: 'ohthetreesyoullgrow.configured_feature' }), {
default: () => ({
type: 'ohthetreesyoullgrow:tree_from_nbt_v1',
config: {
can_grow_on_filter: {
type: 'minecraft:matching_block_tag',
tag: 'minecraft:dirt',
},
can_leaves_place_filter: {
type: 'minecraft:replaceable',
},
height: {
type: 'minecraft:uniform',
value: {
min_inclusive: 5,
max_inclusive: 10,
},
},
leaves_provider: {
type: 'minecraft:simple_state_provider',
state: {
Name: 'minecraft:acacia_leaves',
Properties: {
distance: '7',
persistent: 'false',
waterlogged: 'false',
},
},
},
leaves_target: [
'minecraft:oak_leaves',
],
log_provider: {
type: 'minecraft:simple_state_provider',
state: {
Name: 'minecraft:acacia_log',
Properties: {
axis: 'y',
},
},
},
log_target: [
'minecraft:oak_log',
],
place_from_nbt: [],
},
}),
}))
}

View File

@@ -1,18 +0,0 @@
import type { CollectionRegistry, SchemaRegistry } from '@mcschema/core'
import type { VersionId } from '../services/Schemas.js'
import { initImmersiveWeathering } from './ImmersiveWeathering.js'
import { initLithostitched } from './Lithostitched.js'
import { initNeoForge } from './NeoForge.js'
import { initObsidian } from './Obsidian.js'
import { initOhTheTreesYoullGrow } from './OhTheTreesYoullGrow.js'
export * from './ImmersiveWeathering.js'
export * from './Lithostitched.js'
export function initPartners(schemas: SchemaRegistry, collections: CollectionRegistry, version: VersionId) {
initImmersiveWeathering(schemas, collections)
initLithostitched(schemas, collections, version)
initNeoForge(schemas, collections, version)
initObsidian(schemas, collections)
initOhTheTreesYoullGrow(schemas, collections)
}

View File

@@ -1,176 +0,0 @@
{
"immersive_weathering.area_condition.type": "Type",
"immersive_weathering.area_condition.type.generate_if_not_too_many": "Generate if not too many",
"immersive_weathering.area_condition.type.neighbor_based_generation": "Neighbor based generation",
"immersive_weathering.area_condition.generate_if_not_too_many.radiusX": "Radius X",
"immersive_weathering.area_condition.generate_if_not_too_many.radiusY": "Radius Y",
"immersive_weathering.area_condition.generate_if_not_too_many.radiusZ": "Radius Z",
"immersive_weathering.area_condition.generate_if_not_too_many.requiredAmount": "Required amount",
"immersive_weathering.area_condition.generate_if_not_too_many.yOffset": "Y offset",
"immersive_weathering.area_condition.generate_if_not_too_many.must_have": "Must have",
"immersive_weathering.area_condition.generate_if_not_too_many.must_not_have": "Must not have",
"immersive_weathering.area_condition.generate_if_not_too_many.includes": "Includes",
"immersive_weathering.area_condition.neighbor_based_generation.must_have": "Must have",
"immersive_weathering.area_condition.neighbor_based_generation.must_not_have": "Must not have",
"immersive_weathering.area_condition.neighbor_based_generation.required_amount": "Required amount",
"immersive_weathering.area_condition.neighbor_based_generation.directions": "Directions",
"immersive_weathering.area_condition.neighbor_based_generation.directions.entry": "Direction",
"immersive_weathering.block_growth.area_condition": "Area conditions",
"immersive_weathering.block_growth.position_predicates": "Position predicates",
"immersive_weathering.block_growth.position_predicates.entry": "Position test",
"immersive_weathering.block_growth.growth_chance": "Growth chance",
"immersive_weathering.block_growth.growth_for_face": "Growth for face",
"immersive_weathering.block_growth.growth_for_face.entry": "Face",
"immersive_weathering.block_growth.growth_for_face.entry.direction": "Direction",
"immersive_weathering.block_growth.growth_for_face.entry.weight": "Weight",
"immersive_weathering.block_growth.growth_for_face.entry.growth": "Growth",
"immersive_weathering.block_growth.growth_for_face.entry.growth.entry.data": "Block pair",
"immersive_weathering.block_growth.growth_for_face.entry.growth.entry.weight": "Weight",
"immersive_weathering.block_growth.owners": "Owners",
"immersive_weathering.block_growth.owners.entry": "Block",
"immersive_weathering.block_growth.replacing_target": "Replacing target",
"immersive_weathering.block_growth.target_self": "Target self",
"immersive_weathering.block_growth.destroy_target": "Destroy target",
"immersive_weathering.block_pair.block": "Block",
"immersive_weathering.block_pair.above_block": "Above block",
"immersive_weathering.position_test.predicate_type": "Predicate type",
"immersive_weathering.position_test.predicate_type.biome_match": "Biome match",
"immersive_weathering.position_test.predicate_type.day_test": "Day test",
"immersive_weathering.position_test.predicate_type.nand": "NAND",
"immersive_weathering.position_test.predicate_type.precipitation_test": "Precipitation test",
"immersive_weathering.position_test.predicate_type.temperature_range": "Temperature range",
"immersive_weathering.position_test.biome_match.biomes": "Biomes",
"immersive_weathering.position_test.day_test.day": "Day",
"immersive_weathering.position_test.nand.predicates": "Predicates",
"immersive_weathering.position_test.precipitation_test.precipitation": "Precipitation",
"immersive_weathering.position_test.temperature_range.min": "Min",
"immersive_weathering.position_test.temperature_range.max": "Max",
"immersive_weathering.position_test.temperature_range.use_local_pos": "Use local pos",
"immersive_weathering:rule_test.always_true": "Always true",
"immersive_weathering:rule_test.block_match": "Block match",
"immersive_weathering:rule_test.blockstate_match": "Block state match",
"immersive_weathering:rule_test.random_block_match": "Random block match",
"immersive_weathering:rule_test.random_blockstate_match": "Random block state match",
"immersive_weathering:rule_test.tag_match": "Tag match",
"immersive_weathering:rule_test.immersive_weathering:block_set_match": "Block set match",
"immersive_weathering:rule_test.immersive_weathering:fluid_match": "Fluid match",
"immersive_weathering:rule_test.immersive_weathering:tree_log": "Tree log",
"lithostitched:modifier_type.lithostitched:add_biome_spawns": "Add biome spawns",
"lithostitched:modifier_type.lithostitched:add_features": "Add features",
"lithostitched:modifier_type.lithostitched:add_pool_aliases": "Add pool aliases",
"lithostitched:modifier_type.lithostitched:add_structure_set_entries": "Add structure set entries",
"lithostitched:modifier_type.lithostitched:add_surface_rule": "Add surface rule",
"lithostitched:modifier_type.lithostitched:add_template_pool_elements": "Add template pool elements",
"lithostitched:modifier_type.lithostitched:no_op": "Nothing",
"lithostitched:modifier_type.lithostitched:redirect_feature": "Redirect feature",
"lithostitched:modifier_type.lithostitched:remove_biome_spawns": "Remove biome spawns",
"lithostitched:modifier_type.lithostitched:remove_features": "Remove features",
"lithostitched:modifier_type.lithostitched:remove_structures_from_structure_set": "Remove structures from set",
"lithostitched:modifier_type.lithostitched:replace_climate": "Replace climate",
"lithostitched:modifier_type.lithostitched:replace_effects": "Replace effects",
"lithostitched:modifier_predicate_type.lithostitched:all_of": "All of",
"lithostitched:modifier_predicate_type.lithostitched:any_of": "Any of",
"lithostitched:modifier_predicate_type.lithostitched:mod_loaded": "Mod loaded",
"lithostitched:modifier_predicate_type.lithostitched:not": "Not",
"lithostitched:modifier_predicate_type.lithostitched:true": "True",
"neoforge:biome_modifier_type.neoforge:none": "Disable Biome Modifier",
"neoforge:biome_modifier_type.neoforge:add_features": "Add Features",
"neoforge:biome_modifier_type.neoforge:remove_features": "Remove Features",
"neoforge:biome_modifier_type.neoforge:add_spawns": "Add Mob Spawns",
"neoforge:biome_modifier_type.neoforge:remove_spawns": "Remove Mob Spawns",
"neoforge:biome_modifier_type.neoforge:add_carvers": "Add World Carvers",
"neoforge:biome_modifier_type.neoforge:remove_carvers": "Remove World Carvers",
"neoforge:biome_modifier_type.neoforge:add_spawn_costs": "Add Mob Spawn Costs",
"neoforge:biome_modifier_type.neoforge:remove_spawn_costs": "Remove Mob Spawn Costs",
"neoforge:structure_modifier_type.neoforge:none": "Disable Structure Modifier",
"neoforge:structure_modifier_type.neoforge:add_spawns": "Add Mob Spawns",
"neoforge:structure_modifier_type.neoforge:remove_spawns": "Remove Mob Spawns",
"neoforge:structure_modifier_type.neoforge:clear_spawns": "Clear Mob Spawns",
"obsidian:item.information": "Item Information",
"obsidian:item_information.rarity": "Rarity",
"obsidian:item_information.creative_tab": "Creative Tab",
"obsidian:item_information.max_stack_size": "Max Stack Size",
"obsidian:item_information.name": "Item Name",
"obsidian:item_information.has_enchantment_glint": "Has Enchantment Glint",
"obsidian:item_information.is_enchantable": "Is Enchantable",
"obsidian:item_information.enchantability": "Enchantability",
"obsidian:item_information.use_duration": "Item Duration",
"obsidian:item_information.can_place_block": "Can Place Block",
"obsidian:item_information.placable_block": "Placable Block",
"obsidian:item_information.wearable": "Wearable",
"obsidian:item_information.default_color": "Default Color",
"obsidian:item_information.wearable_slot": "Wearable Slot",
"obsidian:item_information.custom_render_mode": "Has display-based models",
"obsidian:item_information.render_mode_models": "Display-based Models",
"obsidian:item.display": "Display",
"obsidian:item.display.model": "Model",
"obsidian:item.display.item_model": "Item Model",
"obsidian:item.display.lore": "Lore",
"obsidian:item.use_action": "Use Action",
"obsidian:item.use_action.action": "Action",
"obsidian:item.use_action.right_click_action": "Right Click Action",
"obsidian:item.use_action.command": "Command",
"obsidian:item.use_action.url": "URL",
"obsidian:item.use_action.gui_size": "GUI Size",
"obsidian:item.use_action.gui_title": "GUI Title",
"obsidian:block.block_type": "Block Type",
"obsidian:block.information": "Block Information",
"obsidian:block_information.rarity": "Rarity",
"obsidian:block_information.creative_tab": "Creative Tab",
"obsidian:block_information.max_stack_size": "Max Stack Size",
"obsidian:block_information.name": "Item Name",
"obsidian:block_information.sound_group_type": "Sound Group Type",
"obsidian:block_information.vanilla_sound_group": "Vanilla Sound Group",
"obsidian:block_information.custom_sound_group": "Custom Sound Group",
"obsidian:block_information.material_type": "Material Type",
"obsidian:block_information.vanilla_material": "Vanilla Material",
"obsidian:block_information.custom_material": "Custom Material",
"obsidian:block.display": "Display",
"obsidian:block.additional_information": "Additional Information",
"obsidian:block.ore_information": "Ore Information",
"obsidian:block.food_information": "Food Information",
"obsidian:block.block_type.CAMPFIRE.campfire_properties": "Campfire Properties",
"obsidian:block.can_plant_on": "Can Plant On",
"obsidian:block.particle_type": "Particle Type",
"obsidian:block.growable": "Growable",
"obsidian:block.oxidizable_properties": "Oxidizable Properties",
"obsidian:block.events": "Events",
"obsidian:block.drop_information": "Drop Information",
"obsidian:block.is_multi_block": "Is Multi-Block",
"obsidian:block.multiblock_information": "Multiblock Information",
"obsidian:block.placable_feature": "Placable Feature",
"obsidian:block.display.model": "Model",
"obsidian:block.display.item_model": "Item Model",
"obsidian:block.display.block_model": "Block Model",
"obsidian:block.display.lore": "Lore",
"ohthetreesyoullgrow:feature.ohthetreesyoullgrow:tree_from_nbt_v1": "Tree from NBT v1",
"ohthetreesyoullgrow.configured_feature.type": "Type",
"ohthetreesyoullgrow.configured_feature.config": "Config",
"ohthetreesyoullgrow.configured_feature.config.base_location": "Base location",
"ohthetreesyoullgrow.configured_feature.config.base_location.help": "The path to the trunk structure piece.",
"ohthetreesyoullgrow.configured_feature.config.canopy_location": "Canopy location",
"ohthetreesyoullgrow.configured_feature.config.canopy_location.help": "The path to the canopy structure piece.",
"ohthetreesyoullgrow.configured_feature.config.can_grow_on_filter": "Can grow on filter",
"ohthetreesyoullgrow.configured_feature.config.can_grow_on_filter.help": "Block filter for which this tree is allowed to grow on. Checks all of the red wool positions defined by the trunk.",
"ohthetreesyoullgrow.configured_feature.config.can_leaves_place_filter": "Can leaves place filter",
"ohthetreesyoullgrow.configured_feature.config.can_leaves_place_filter.help": "Block filter for which this tree's leaves are allowed to place.",
"ohthetreesyoullgrow.configured_feature.config.decorators": "Decorators",
"ohthetreesyoullgrow.configured_feature.config.decorators.entry": "Decorator",
"ohthetreesyoullgrow.configured_feature.config.height": "Height",
"ohthetreesyoullgrow.configured_feature.config.height.help": "Int provider defining the height of the tree.",
"ohthetreesyoullgrow.configured_feature.config.leaves_provider": "Leaves provider",
"ohthetreesyoullgrow.configured_feature.config.leaves_target": "Leaves target",
"ohthetreesyoullgrow.configured_feature.config.leaves_target.entry": "Block",
"ohthetreesyoullgrow.configured_feature.config.log_provider": "Log provider",
"ohthetreesyoullgrow.configured_feature.config.log_target": "Log target",
"ohthetreesyoullgrow.configured_feature.config.log_target.entry": "Block",
"ohthetreesyoullgrow.configured_feature.config.max_log_depth": "Max log depth",
"ohthetreesyoullgrow.configured_feature.config.place_from_nbt": "Place from NBT",
"ohthetreesyoullgrow.configured_feature.config.place_from_nbt.help": "Additional blocks from the structure pieces that should be placed in the world.",
"ohthetreesyoullgrow.configured_feature.config.place_from_nbt.entry": "Block"
}

View File

@@ -1,25 +0,0 @@
import type { INode, Path } from '@mcschema/core'
import { DataModel } from '@mcschema/core'
export class ModelWrapper extends DataModel {
constructor(
schema: INode<any>,
private readonly mapper: (path: Path) => Path,
private readonly getter: (path: Path) => any,
private readonly setter: (path: Path, value: any, silent?: boolean) => any,
) {
super(schema)
}
map(path: Path) {
return this.mapper(path)
}
get(path: Path) {
return this.getter(path)
}
set(path: Path, value: any, silent?: boolean) {
return this.setter(path, value, silent)
}
}

View File

@@ -1,719 +0,0 @@
import type { BooleanHookParams, EnumOption, Hook, INode, ListHookParams, NodeChildren, NumberHookParams, StringHookParams, ValidationOption } from '@mcschema/core'
import { DataModel, ListNode, MapNode, ModelPath, ObjectNode, Path, relativePath, StringNode } from '@mcschema/core'
import { Identifier, ItemStack } from 'deepslate/core'
import type { ComponentChildren, JSX } from 'preact'
import { memo } from 'preact/compat'
import { useState } from 'preact/hooks'
import { Btn, Octicon } from '../components/index.js'
import { ItemDisplay } from '../components/ItemDisplay.jsx'
import { VanillaColors } from '../components/previews/BiomeSourcePreview.jsx'
import config from '../Config.js'
import { localize, useLocale, useStore } from '../contexts/index.js'
import { useFocus } from '../hooks/index.js'
import type { BlockStateRegistry, VersionId } from '../services/index.js'
import { CachedDecorator, CachedFeature, checkVersion } from '../services/index.js'
import { deepClone, deepEqual, generateColor, generateUUID, hexId, hexToRgb, isObject, newSeed, rgbToHex, stringToColor } from '../Utils.js'
import { ModelWrapper } from './ModelWrapper.js'
const selectRegistries = ['loot_table.type', 'loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'recipe.type', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'dimension.generator.biome_source.preset', 'carver.type', 'feature.type', 'decorator.type', 'feature.tree.minimum_size.type', 'block_state_provider.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'int_provider.type', 'float_provider.type', 'height_provider.type', 'structure_feature.type', 'surface_builder.type', 'processor.processor_type', 'rule_test.predicate_type', 'pos_rule_test.predicate_type', 'template_element.element_type', 'block_placer.type', 'block_predicate.type', 'material_rule.type', 'material_condition.type', 'structure_placement.type', 'density_function.type', 'root_placer.type', 'entity.type_specific.cat.variant', 'entity.type_specific.frog.variant', 'rule_block_entity_modifier.type', 'pool_alias_binding.type', 'lithostitched.worldgen_modifier.type', 'lithostitched.modifier_predicate.type', 'ohthetreesyoullgrow.configured_feature.type', 'enchantment_provider.type', 'enchantment_value_effect.type', 'level_based_value.type', 'neoforge.biome_modifier.type', 'neoforge.structure_modifier.type']
const datalistEnums = ['item_stack.components', 'function.set_components.components']
const hiddenFields = ['number_provider.type', 'score_provider.type', 'nbt_provider.type', 'int_provider.type', 'float_provider.type', 'height_provider.type', 'level_based_value.type']
const flattenedFields = ['feature.config', 'decorator.config', 'int_provider.value', 'float_provider.value', 'block_state_provider.simple_state_provider.state', 'block_state_provider.rotated_block_provider.state', 'block_state_provider.weighted_state_provider.entries.entry.data', 'rule_test.block_state', 'structure_feature.config', 'surface_builder.config', 'template_pool.elements.entry.element', 'decorator.block_survives_filter.state', 'material_rule.block.result_state', 'enchantment.effects.entry.effect']
const inlineFields = ['loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'feature.type', 'decorator.type', 'block_state_provider.type', 'feature.tree.minimum_size.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'block_placer.type', 'rule_test.predicate_type', 'processor.processor_type', 'template_element.element_type', 'nbt_operation.op', 'number_provider.value', 'score_provider.name', 'score_provider.target', 'nbt_provider.source', 'nbt_provider.target', 'generator_biome.biome', 'block_predicate.type', 'material_rule.type', 'material_condition.type', 'density_function.type', 'root_placer.type', 'entity.type_specific.type', 'glyph_provider.type', 'sprite_source.type', 'rule_block_entity_modifier.type', 'immersive_weathering.area_condition.type', 'immersive_weathering.block_growth.growth_for_face.entry.direction', 'immersive_weathering.position_test.predicate_type', 'pool_alias_binding.type', 'item_stack.id', 'data_component.container.entry.slot', 'map_decoration.type', 'suspicious_stew_effect_instance.id', 'enchantment_value_effect.type', 'enchantment_effect.type', 'particle.type']
const nbtFields = ['function.set_nbt.tag', 'advancement.display.icon.nbt', 'text_component_object.nbt', 'entity.nbt', 'block.nbt', 'item.nbt']
const fixedLists = ['generator_biome.parameters.temperature', 'generator_biome.parameters.humidity', 'generator_biome.parameters.continentalness', 'generator_biome.parameters.erosion', 'generator_biome.parameters.depth', 'generator_biome.parameters.weirdness', 'feature.end_spike.crystal_beam_target', 'feature.end_gateway.exit', 'decorator.block_filter.offset', 'block_predicate.has_sturdy_face.offset', 'block_predicate.inside_world_bounds.offset', 'block_predicate.matching_block_tag.offset', 'block_predicate.matching_blocks.offset', 'block_predicate.matching_fluids.offset', 'block_predicate.would_survive.offset', 'model_element.from', 'model_element.to', 'model_element.rotation.origin', 'model_element.faces.uv', 'item_transform.rotation', 'item_transform.translation', 'item_transform.scale', 'generator_structure.random_spread.locate_offset', 'pack_overlay.formats', 'data_component.profile.id', 'data_component.lodestone_tracker.tracker.pos', 'attribute_modifier.uuid']
const collapsedFields = ['noise_settings.surface_rule', 'noise_settings.noise.terrain_shaper']
const collapsableFields = ['density_function.argument', 'density_function.argument1', 'density_function.argument2', 'density_function.input', 'density_function.when_in_range', 'density_function.when_out_of_range']
const itemPreviewFields = ['loot_pool.entries.entry', 'loot_entry.alternatives.children.entry', 'loot_entry.group.children.entry', 'loot_entry.sequence.children.entry', 'function.set_contents.entries.entry']
const forceEnumContexts: Record<string, string> = { 'loot_table.type': 'loot_table.type', 'condition.condition': 'loot_condition_type', 'function.function': 'loot_function_type' }
const findGenerator = (id: string) => {
return config.generators.find(g => g.id === id.replace(/^\$/, ''))
}
/**
* Secondary model used to remember the keys of a map
*/
const keysModel = new DataModel(MapNode(
StringNode(),
StringNode()
), { historyMax: 0 })
type JSXTriple = [JSX.Element | null, JSX.Element | null, JSX.Element | null]
type RenderHook = Hook<[any, string, VersionId, BlockStateRegistry, Record<string, any>], JSXTriple>
type NodeProps<T> = T & {
node: INode<any>,
path: ModelPath,
value: any,
lang: string,
version: VersionId,
states: BlockStateRegistry,
ctx: Record<string, any>,
}
export function FullNode({ model, lang, version, blockStates }: { model: DataModel, lang: string, version: VersionId, blockStates: BlockStateRegistry }) {
const path = new ModelPath(model)
const [prefix, suffix, body] = model.schema.hook(renderHtml, path, deepClone(model.data), lang, version, blockStates, {})
return suffix?.props?.children.some((c: any) => c) ? <div class={`node ${model.schema.type(path)}-node`} data-category={model.schema.category(path)}>
<div class="node-header">{prefix}{suffix}</div>
<div class="node-body">{body}</div>
</div> : body
}
const renderHtml: RenderHook = {
base() {
return [null, null, null]
},
boolean(params, path, value, lang, version, states, ctx) {
return [null, <BooleanSuffix {...{...params, path, value, lang, version, states, ctx}} />, null]
},
choice({ choices, config, switchNode }, path, value, lang, version, states, ctx) {
const choice = switchNode.activeCase(path, true) as typeof choices[number]
const contextPath = (config?.context) ? new ModelPath(path.getModel(), new Path(path.getArray(), [config.context])) : path
const [prefix, suffix, body] = choice.node.hook(this, contextPath, value, lang, version, states, ctx)
if (choices.length === 1) {
return [prefix, suffix, body]
}
const choiceContextPath = config?.choiceContext ? new Path([], [config.choiceContext]) : config?.context ? new Path([], [config.context]) : path
const set = (type: string) => {
const c = choices.find(c => c.type === type) ?? choice
const def = c.node.default()
const newValue = c.change
? c.change(DataModel.unwrapLists(value))
: config.choiceContext === 'feature' && def?.type === 'minecraft:decorated' ? def.config.feature : def
path.model.set(path, DataModel.wrapLists(newValue))
}
const inject = <select value={choice.type} onChange={(e) => set((e.target as HTMLSelectElement).value)}>
{choices.map(c => <option value={c.type}>
{pathLocale(lang, choiceContextPath.contextPush(c.type))}
</option>)}
</select>
return [prefix, <>{inject}{suffix}</>, body]
},
list({ children, config, node }, path, value, lang, version, states, ctx) {
const context = path.getContext().join('.')
if (fixedLists.includes(context)) {
const prefix = <>
{[...Array(config.maxLength!)].map((_, i) =>
<ErrorPopup lang={lang} path={path.modelPush(i)} />)}
<div class="fixed-list"></div>
</>
const suffix = <>{[...Array(config.maxLength)].map((_, i) => {
const child = children.hook(this, path.modelPush(i), value?.[i]?.node, lang, version, states, ctx)
return child[1]
})}</>
return [prefix, suffix, null]
}
const onAdd = () => {
if (!Array.isArray(value)) value = []
const node = DataModel.wrapLists(children.default())
path.model.set(path, [{ node, id: hexId() }, ...value])
}
const suffix = <button class="add tooltipped tip-se" aria-label={localize(lang, 'add_top')} onClick={onAdd}>{Octicon.plus_circle}</button>
return [null, suffix, <ListBody {...{children, config, node, path, value, lang, version, states, ctx}}/>]
},
map({ children, keys, config }, path, value, lang, version, states, ctx) {
const { expand, collapse, isToggled } = useToggles()
const keyPath = new ModelPath(keysModel, new Path([hashString(path.toString())], path.contextArr))
const onAdd = () => {
const key = keyPath.get()
if (path.model.get(path.push(key)) === undefined) {
path.model.set(path.push(key), DataModel.wrapLists(children.default()))
}
keyPath.set('')
}
const blockState = config.validation?.validator === 'block_state_map' ? states?.[relativePath(path, config.validation.params.id).get()] : null
const keysSchema = blockState?.properties
? StringNode(null!, { enum: Object.keys(blockState.properties ?? {}) })
: keys
if (blockState && path.last() === 'Properties') {
if (typeof value !== 'object') value = {}
const properties = Object.entries(blockState.properties ?? {})
.map(([key, values]) => [key, StringNode(null!, { enum: values })])
Object.entries(blockState.properties ?? {}).forEach(([key, values]) => {
if (typeof value[key] !== 'string') {
path.model.errors.add(path.push(key), 'error.expected_string')
} else if (!values.includes(value[key])) {
path.model.errors.add(path.push(key), 'error.invalid_enum_option', value[key])
}
})
return ObjectNode(Object.fromEntries(properties)).hook(this, path, value, lang, version, states, ctx)
}
const suffix = <>
{keysSchema.hook(this, keyPath, keyPath.get() ?? '', lang, version, states, ctx)[1]}
<button class="add tooltipped tip-se" aria-label={localize(lang, 'add')} onClick={onAdd}>{Octicon.plus_circle}</button>
</>
const body = <>
{typeof value === 'object' && Object.entries(value).map(([key, cValue]) => {
const pathWithContext = (config?.context) ? new ModelPath(path.getModel(), new Path(path.getArray(), [config.context])) : path
const cPath = pathWithContext.modelPush(key)
const canToggle = children.type(cPath) === 'object'
const toggle = isToggled(key)
if (canToggle && (toggle === false || (toggle === undefined && value.length > 20))) {
return <div class="node node-header" data-category={children.category(cPath)}>
<ErrorPopup lang={lang} path={cPath} nested />
<button class="toggle tooltipped tip-se" aria-label={`${localize(lang, 'expand')}\n${localize(lang, 'expand_all', 'Ctrl')}`} onClick={expand(key)}>{Octicon.chevron_right}</button>
<label>{key}</label>
<Collapsed key={key} path={cPath} value={cValue} schema={children} />
</div>
}
const cSchema = blockState
? StringNode(null!, { enum: blockState.properties?.[key] ?? [] })
: children
if (blockState?.properties?.[key] && typeof cValue === 'string'
&& !blockState.properties?.[key].includes(cValue)) {
path.model.errors.add(cPath, 'error.invalid_enum_option', cValue)
}
const onRemove = () => cPath.set(undefined)
return <MemoedTreeNode key={key} schema={cSchema} path={cPath} value={cValue} {...{lang, version, states, ctx}} label={key}>
{canToggle && <button class="toggle tooltipped tip-se" aria-label={`${localize(lang, 'collapse')}\n${localize(lang, 'collapse_all', 'Ctrl')}`} onClick={collapse(key)}>{Octicon.chevron_down}</button>}
<button class="remove tooltipped tip-se" aria-label={localize(lang, 'remove')} onClick={onRemove}>{Octicon.trashcan}</button>
</MemoedTreeNode>
})}
</>
return [null, suffix, body]
},
number(params, path, value, lang, version, states, ctx) {
return [null, <NumberSuffix {...{...params, path, value, lang, version, states, ctx}} />, null]
},
object({ node, config, getActiveFields, getChildModelPath }, path, value, lang, version, states, ctx) {
const { expand, collapse, isToggled } = useToggles()
if (path.getArray().length == 0 && isDecorated(config.context, value)) {
const { wrapper, fields } = createDecoratorsWrapper(getActiveFields(path), path, value)
value = wrapper.data
getActiveFields = () => fields
getChildModelPath = (path, key) => new ModelPath(wrapper, new Path(path.getArray(), ['feature'])).push(key)
}
let prefix: JSX.Element | null = null
let suffix: JSX.Element | null = null
let body: JSX.Element | null = null
if (node.optional()) {
if (value === undefined) {
const onExpand = () => path.set(DataModel.wrapLists(node.default()))
suffix = <button class="node-collapse closed tooltipped tip-se" aria-label={localize(lang, 'expand')} onClick={onExpand}>{Octicon.plus_circle}</button>
} else if (typeof value === 'object' && value !== null){
const onCollapse = () => path.set(undefined)
suffix = <button class="node-collapse open tooltipped tip-se" aria-label={localize(lang, 'remove')} onClick={onCollapse}>{Octicon.trashcan}</button>
}
}
const context = path.getContext().join('.')
if (collapsableFields.includes(context) || collapsedFields.includes(context)) {
const toggled = isToggled('')
const expanded = collapsedFields.includes(context) ? toggled : !toggled
prefix = <>
<button class="toggle tooltipped tip-se" aria-label={localize(lang, expanded ? 'collapse' : 'expand')} onClick={toggled ? collapse('') : expand('')}>{expanded ? Octicon.chevron_down : Octicon.chevron_right}</button>
</>
if (!expanded) {
return [prefix, suffix, null]
}
}
if (!(node.optional() && value === undefined)) {
if (typeof value === 'object' && value !== null) {
const newCtx = (typeof value === 'object' && value !== null && node.default()?.pools)
? { ...ctx, loot: value?.type } : ctx
body = <>{Object.entries(getActiveFields(path))
.filter(([_, child]) => child.enabled(path))
.map(([key, child]) => {
const cPath = getChildModelPath(path, key)
const context = cPath.getContext().join('.')
if (hiddenFields.includes(context)) return null
const [cPrefix, cSuffix, cBody] = child.hook(this, cPath, value[key], lang, version, states, newCtx)
const isFlattened = child.type(cPath) === 'object' && flattenedFields.includes(context)
const isInlined = inlineFields.includes(context)
if (isFlattened || isInlined) {
prefix = <>{prefix}<ErrorPopup lang={lang} path={cPath} /><HelpPopup lang={lang} path={cPath} />{cPrefix}</>
suffix = <>{suffix}{cSuffix}</>
return isFlattened ? cBody : null
}
return <MemoedTreeNode key={key} schema={child} path={cPath} value={value[key]} {...{lang, version, states, ctx: newCtx}} />
})}</>
} else {
const onReset = () => path.set(DataModel.wrapLists(node.default()))
suffix = <>{suffix}<button class="add tooltipped tip-se" aria-label={localize(lang, 'reset')} onClick={onReset}>{Octicon.history}</button></>
}
}
return [prefix, suffix, body]
},
string(params, path, value, lang, version, states, ctx) {
return [null, <StringSuffix {...{...params, path, value, lang, version, states, ctx}} />, null]
},
}
function Collapsed({ path, value }: { path: ModelPath, value: any, schema: INode<any> }) {
const { locale } = useLocale()
const context = path.getContext().join('.')
switch (context) {
case 'loot_table.pools.entry':
const count = value?.entries?.length ?? 0
return <label>{count} {count == 1 ? 'entry' : 'entries'}</label>
case 'function.set_contents.entries.entry':
case 'loot_pool.entries.entry':
const name = value?.name?.replace(/^minecraft:/, '') ?? value?.type?.replace(/^minecraft:/, '')
const weight = value?.weight || undefined
return <>
<label>{name}</label>
{weight !== undefined && <label class="tooltipped tip-se" aria-label={locale('weight')}>{weight}</label>}
</>
}
for (const child of Object.values(value ?? {})) {
if (typeof child === 'string') {
return <label>{child.replace(/^minecraft:/, '')}</label>
}
}
return null
}
function useToggles() {
const [toggleState, setToggleState] = useState(new Map<string, boolean>())
const [toggleAll, setToggleAll] = useState<boolean | undefined>(undefined)
const expand = (key: string) => (evt: MouseEvent) => {
if (evt.ctrlKey) {
setToggleState(new Map())
setToggleAll(true)
} else {
setToggleState(state => new Map(state.set(key, true)))
}
}
const collapse = (key: string) => (evt: MouseEvent) => {
if (evt.ctrlKey) {
setToggleState(new Map())
setToggleAll(false)
} else {
setToggleState(state => new Map(state.set(key, false)))
}
}
const isToggled = (key: string) => {
if (!(toggleState instanceof Map)) return false
return toggleState.get(key) ?? toggleAll
}
return { expand, collapse, isToggled }
}
function ListBody({ path, value, lang, config, children, version, states, ctx }: NodeProps<ListHookParams>) {
const { expand, collapse, isToggled } = useToggles()
const [maxShown, setMaxShown] = useState(50)
const onAddBottom = () => {
if (!Array.isArray(value)) value = []
const node = DataModel.wrapLists(children.default())
path.model.set(path, [...value, { node, id: hexId() }])
}
return <>
{(value && Array.isArray(value)) && value.map(({ node: cValue, id: cId }, index) => {
if (index === maxShown) {
return <div class="node node-header">
<label>{localize(lang, 'entries_hidden', `${value.length - maxShown}`)}</label>
<button onClick={() => setMaxShown(Math.min(maxShown + 50, value.length))}>{localize(lang, 'entries_hidden.more', '50')}</button>
<button onClick={() => setMaxShown(value.length)}>{localize(lang, 'entries_hidden.all')}</button>
</div>
}
if (index > maxShown) {
return null
}
const pathWithContext = (config?.context) ? new ModelPath(path.getModel(), new Path(path.getArray(), [config.context])) : path
const cPath = pathWithContext.push(index).contextPush('entry')
const canToggle = children.type(cPath) === 'object'
const toggle = isToggled(cId)
let label: undefined | string | JSX.Element
if (itemPreviewFields.includes(cPath.getContext().join('.'))) {
if (isObject(cValue) && typeof cValue.type === 'string' && cValue.type.replace(/^minecraft:/, '') === 'item' && typeof cValue.name === 'string') {
let itemStack: ItemStack | undefined
try {
itemStack = new ItemStack(Identifier.parse(cValue.name), 1)
} catch (e) {}
if (itemStack !== undefined) {
label = <ItemDisplay item={itemStack} />
}
}
}
if (canToggle && (toggle === false || (toggle === undefined && value.length > 20))) {
return <div class="node node-header" data-category={children.category(cPath)}>
<ErrorPopup lang={lang} path={cPath} nested />
<button class="toggle tooltipped tip-se" aria-label={`${localize(lang, 'expand')}\n${localize(lang, 'expand_all', 'Ctrl')}`} onClick={expand(cId)}>{Octicon.chevron_right}</button>
<label>{label ?? pathLocale(lang, cPath, `${index}`)}</label>
<Collapsed key={cId} path={cPath} value={cValue} schema={children} />
</div>
}
const onRemove = () => cPath.set(undefined)
const onMoveUp = () => {
const v = [...path.get()];
[v[index - 1], v[index]] = [v[index], v[index - 1]]
path.model.set(path, v)
}
const onMoveDown = () => {
const v = [...path.get()];
[v[index + 1], v[index]] = [v[index], v[index + 1]]
path.model.set(path, v)
}
const actions: MenuAction[] = [
{
icon: 'duplicate',
label: 'duplicate',
onSelect: () => {
const v = [...path.get()]
v.splice(index, 0, { id: hexId(), node: deepClone(cValue) })
path.model.set(path, v)
},
},
]
return <MemoedTreeNode key={cId} label={label} path={cPath} schema={children} value={cValue} {...{lang, version, states, actions}} ctx={{...ctx, index: (index === 0 ? 1 : 0) + (index === value.length - 1 ? 2 : 0)}}>
{canToggle && <button class="toggle tooltipped tip-se" aria-label={`${localize(lang, 'collapse')}\n${localize(lang, 'collapse_all', 'Ctrl')}`} onClick={collapse(cId)}>{Octicon.chevron_down}</button>}
<button class="remove tooltipped tip-se" aria-label={localize(lang, 'remove')} onClick={onRemove}>{Octicon.trashcan}</button>
{value.length > 1 && <div class="node-move">
<button class="move tooltipped tip-se" aria-label={localize(lang, 'move_up')} onClick={onMoveUp} disabled={index === 0}>{Octicon.chevron_up}</button>
<button class="move tooltipped tip-se" aria-label={localize(lang, 'move_down')} onClick={onMoveDown} disabled={index === value.length - 1}>{Octicon.chevron_down}</button>
</div>}
</MemoedTreeNode>
})}
{(value && value.length > 0 && value.length <= maxShown) && <div class="node node-header">
<button class="add tooltipped tip-se" aria-label={localize(lang, 'add_bottom')} onClick={onAddBottom}>{Octicon.plus_circle}</button>
</div>}
</>
}
function BooleanSuffix({ path, node, value, lang }: NodeProps<BooleanHookParams>) {
const set = (target: boolean) => {
path.model.set(path, node.optional() && value === target ? undefined : target)
}
return <>
<button class={value === false ? 'selected' : ''} onClick={() => set(false)}>{localize(lang, 'false')}</button>
<button class={value === true ? 'selected' : ''} onClick={() => set(true)}>{localize(lang, 'true')}</button>
</>
}
function NumberSuffix({ path, config, integer, value, lang }: NodeProps<NumberHookParams>) {
const onChange = (evt: Event) => {
const value = (evt.target as HTMLInputElement).value
const parsed = integer ? parseInt(value) : parseFloat(value)
path.model.set(path, parsed)
}
const onColor = (evt: Event) => {
const value = (evt.target as HTMLInputElement).value
const parsed = parseInt(value.slice(1), 16)
path.model.set(path, parsed)
}
return <>
<input type="text" value={value ?? ''} onBlur={onChange} onKeyDown={evt => {if (evt.key === 'Enter') onChange(evt)}} />
{config?.color && <input type="color" value={'#' + (value?.toString(16).padStart(6, '0') ?? '000000')} onChange={onColor} />}
{config?.color && <button onClick={() => path.set(generateColor())} class="tooltipped tip-se" aria-label={localize(lang, 'generate_new_color')}>{Octicon.sync}</button>}
{['dimension.generator.seed', 'dimension.generator.biome_source.seed', 'world_settings.seed', 'structure_placement.salt'].includes(path.getContext().join('.')) && <button onClick={() => newSeed(path.model)} class="tooltipped tip-se" aria-label={localize(lang, 'generate_new_seed')}>{Octicon.sync}</button>}
</>
}
function StringSuffix({ path, getValues, config, node, value, lang, version, states }: NodeProps<StringHookParams>) {
const context = path.getContext().join('.')
const onChange = (evt: Event) => {
evt.stopPropagation()
const newValue = (evt.target as HTMLSelectElement).value
if (newValue === value) return
// Hackfix to support switching between checkerboard and multi_noise biome sources
if (context === 'dimension.generator.biome_source.type') {
const biomeSourceType = newValue.replace(/^minecraft:/, '')
const biomePath = path.pop().push('biomes')
const biomes = biomePath.get()
if (biomeSourceType === 'multi_noise') {
const newBiomes = Array.isArray(biomes)
? biomes.flatMap((b: any) => {
if (typeof b.node !== 'string') return []
return [{ node: { biome: b.node }}]
})
: [{ node: { biome: 'minecraft:plains' } }]
path.model.set(biomePath, newBiomes, true)
} else if (biomeSourceType === 'checkerboard') {
const newBiomes = typeof biomes === 'string'
? biomes
: Array.isArray(biomes)
? biomes.flatMap((b: any) => {
if (typeof b.node !== 'object' || b.node === null || typeof b.node.biome !== 'string') return []
return [{ node: b.node.biome }]
})
: [{ node: 'minecraft:plains' }]
path.model.set(biomePath, newBiomes, true)
}
}
path.model.set(path, newValue.length === 0 ? undefined : newValue)
}
const values = getValues()
const id = !isEnum(config) && config?.validator === 'resource' && typeof config.params.pool === 'string' ? config.params.pool : undefined
if (nbtFields.includes(context)) {
return <textarea value={value ?? ''} onBlur={onChange}></textarea>
} else if ((isEnum(config) && !config.additional && !datalistEnums.includes(context)) || selectRegistries.includes(context)) {
let childPath = new Path([])
if (isEnum(config) && typeof config.enum === 'string') {
childPath = childPath.contextPush(config.enum)
} else if (id) {
childPath = childPath.contextPush(id)
} else if (isEnum(config)) {
childPath = path
} else if (Object.hasOwn(forceEnumContexts, context)) {
childPath = childPath.contextPush(forceEnumContexts[context])
}
return <select value={value ?? ''} onChange={onChange}>
{node.optional() && <option value="">{localize(lang, 'unset')}</option>}
{values.map(v => <option value={v}>
{pathLocale(lang, childPath.contextPush(v.replace(/^minecraft:/, '')))}
</option>)}
</select>
} else if (!isEnum(config) && config?.validator === 'block_state_key') {
const blockState = states?.[relativePath(path, config.params.id).get()]
const values = Object.keys(blockState?.properties ?? {})
return <select value={value ?? ''} onChange={onChange}>
{values.map(v => <option>{v}</option>)}
</select>
} else {
const { biomeColors, setBiomeColor } = useStore()
const fullId = typeof value === 'string' ? value.includes(':') ? value : 'minecraft:' + value : 'unknown'
const datalistId = hexId()
const gen = id ? findGenerator(id) : undefined
return <>
<input value={value ?? ''} onBlur={onChange} onKeyDown={evt => {if (evt.key === 'Enter') onChange(evt)}}
list={values.length > 0 ? datalistId : ''} />
{values.length > 0 && <datalist id={datalistId}>
{values.map(v => <option value={v} />)}
</datalist>}
{['generator_biome.biome'].includes(context) && <input type="color" value={rgbToHex(biomeColors[fullId] ?? VanillaColors[fullId] ?? stringToColor(fullId))} onChange={v => setBiomeColor(fullId, hexToRgb(v.currentTarget.value))}></input>}
{(['text_component_object.hoverEvent.show_entity.contents.id', 'enchantment.effects.entry.uuid'].includes(context) || ('attribute_modifier.id' === context && !checkVersion(version, '1.21'))) && <button onClick={() => path.set(generateUUID())} class="tooltipped tip-se" aria-label={localize(lang, 'generate_new_uuid')}>{Octicon.sync}</button>}
{gen && values.includes(value) && value.startsWith('minecraft:') &&
<a href={`/${gen.url}/?version=${version}&preset=${value.replace(/^minecraft:/, '')}`} class="tooltipped tip-se" aria-label={localize(lang, 'follow_reference')}>{Octicon.link_external}</a>}
</>
}
}
type MenuAction = {
label: string,
description?: string,
icon: keyof typeof Octicon,
onSelect: () => unknown,
}
type TreeNodeProps = {
schema: INode<any>,
path: ModelPath,
value: any,
lang: string,
version: VersionId,
states: BlockStateRegistry,
ctx: Record<string, any>,
compare?: any,
label?: string | ComponentChildren,
actions?: MenuAction[],
children?: ComponentChildren,
}
function TreeNode({ label, schema, path, value, lang, version, states, ctx, actions, children }: TreeNodeProps) {
const type = schema.type(path)
const category = schema.category(path)
const context = path.getContext().join('.')
const [active, setActive] = useFocus()
const onContextMenu = (evt: MouseEvent) => {
evt.preventDefault()
setActive()
}
const newCtx: Record<string, any> = { ...ctx, depth: (ctx.depth ?? 0) + 1 }
delete newCtx.index
const [prefix, suffix, body] = schema.hook(renderHtml, path, value, lang, version, states, newCtx)
return <div class={`node ${type}-node`} data-category={category}>
<div class="node-header" onContextMenu={onContextMenu}>
<ErrorPopup lang={lang} path={path} />
<HelpPopup lang={lang} path={path} />
{children}
{prefix}
<label>
{label ?? pathLocale(lang, path, `${path.last()}`)}
{active && <div class="node-menu">
{actions?.map(a => <div key={a.label} class="menu-item">
<Btn icon={a.icon} tooltip={localize(lang, a.label)} tooltipLoc="se" onClick={() => a.onSelect()}/>
<span>{a.description ?? localize(lang, a.label)}</span>
</div>)}
<div class="menu-item">
<Btn icon="copy" tooltip={localize(lang, 'copy_context')} tooltipLoc="se" onClick={() => navigator.clipboard.writeText(context)} />
<span>{context}</span>
</div>
</div>}
</label>
{suffix}
</div>
{body && <div class="node-body">{body}</div>}
</div>
}
const MemoedTreeNode = memo(TreeNode, (prev, next) => {
return prev.schema === next.schema
&& prev.lang === next.lang
&& prev.path.equals(next.path)
&& deepEqual(prev.ctx, next.ctx)
&& deepEqual(prev.value, next.value)
})
function isEnum(value?: ValidationOption | EnumOption): value is EnumOption {
return !!(value as any)?.enum
}
function hashString(str: string) {
var hash = 0, i, chr
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i)
hash = ((hash << 5) - hash) + chr
hash |= 0
}
return hash
}
function pathLocale(lang: string, path: Path, ...params: string[]) {
const ctx = path.getContext()
for (let i = 0; i < ctx.length; i += 1) {
const key = ctx.slice(i).join('.')
const result = localize(lang, key, ...params)
if (key !== result) {
return result
}
}
return ctx[ctx.length - 1]
}
function ErrorPopup({ lang, path, nested }: { lang: string, path: ModelPath, nested?: boolean }) {
if (path.model instanceof ModelWrapper) {
path = path.model.map(path).withModel(path.model)
}
const e = nested
? path.model.errors.getAll().filter(e => e.path.startsWith(path))
: path.model.errors.get(path, true)
if (e.length === 0) return null
const message = localize(lang, e[0].error, ...(e[0].params ?? []))
return popupIcon('node-error', 'issue_opened', message)
}
function HelpPopup({ lang, path }: { lang: string, path: Path }) {
const key = path.contextPush('help').getContext().join('.')
const message = localize(lang, key)
if (message === key) return null
return popupIcon('node-help', 'info', message)
}
const popupIcon = (type: string, icon: keyof typeof Octicon, popup: string) => {
const [active, setActive] = useFocus()
return <div class={`node-icon ${type}${active ? ' show' : ''}`} onClick={() => setActive()}>
{Octicon[icon]}
<span class="icon-popup">{popup}</span>
</div>
}
function isDecorated(context: string | undefined, value: any) {
return context === 'feature'
&& value?.type?.replace(/^minecraft:/, '') === 'decorated'
&& isObject(value?.config)
}
function createDecoratorsWrapper(originalFields: NodeChildren, path: ModelPath, value: any) {
const decorators: any[] = []
const feature = iterateNestedDecorators(value, decorators)
const fields = {
type: originalFields.type,
config: ObjectNode({
decorators: ListNode(CachedDecorator),
feature: CachedFeature,
}, { context: 'feature.decorated' }),
}
const schema = ObjectNode(fields, { context: 'feature' })
const featurePath = new Path(['config', 'feature'])
const decoratorsPath = new Path(['config', 'decorators'])
const model = path.getModel()
const wrapper: ModelWrapper = new ModelWrapper(schema, path => {
if (path.startsWith(featurePath)) {
return new Path([...[...Array(decorators.length - 1)].flatMap(() => ['config', 'feature']), ...path.modelArr])
} else if (path.startsWith(decoratorsPath)) {
if (path.modelArr.length === 2) {
return new Path([])
}
const index = path.modelArr[2]
if (typeof index === 'number') {
return new Path([...[...Array(index)].flatMap(() => ['config', 'feature']), 'config', 'decorator', ...path.modelArr.slice(3)])
}
}
return path
}, path => {
if (path.equals(decoratorsPath)) {
const newDecorators: any[] = []
iterateNestedDecorators(model.data, newDecorators)
return newDecorators
}
return model.get(wrapper.map(path))
}, (path, value, silent) => {
if (path.startsWith(featurePath)) {
const newDecorators: any[] = []
iterateNestedDecorators(model.data, newDecorators)
const newPath =new Path([...[...Array(newDecorators.length - 1)].flatMap(() => ['config', 'feature']), ...path.modelArr])
return model.set(newPath, value, silent)
} else if (path.startsWith(decoratorsPath)) {
const index = path.modelArr[2]
if (path.modelArr.length === 2) {
const feature = wrapper.get(featurePath)
return model.set(new Path(), produceNestedDecorators(feature, value), silent)
} else if (typeof index === 'number') {
if (path.modelArr.length === 3 && value === undefined) {
const feature = wrapper.get(featurePath)
const newDecorators: any[] = []
iterateNestedDecorators(model.data, newDecorators)
newDecorators.splice(index, 1)
const newValue = produceNestedDecorators(feature, newDecorators)
return model.set(new Path(), newValue, silent)
} else {
const newPath = new Path([...[...Array(index)].flatMap(() => ['config', 'feature']), 'config', 'decorator', ...path.modelArr.slice(3)])
return model.set(newPath, value, silent)
}
}
}
model.set(path, value, silent)
})
wrapper.data = {
type: model.data.type,
config: {
decorators,
feature,
},
}
wrapper.errors = model.errors
return { fields, wrapper }
}
function iterateNestedDecorators(value: any, decorators: any[]): any {
if (value?.type?.replace(/^minecraft:/, '') !== 'decorated') {
return value
}
if (!isObject(value?.config)) {
return value
}
decorators.push({ id: decorators.length, node: value.config.decorator })
return iterateNestedDecorators(value.config.feature ?? '', decorators)
}
function produceNestedDecorators(feature: any, decorators: any[]): any {
if (decorators.length === 0) return feature
return {
type: 'minecraft:decorated',
config: {
decorator: decorators.shift().node,
feature: produceNestedDecorators(feature, decorators),
},
}
}

View File

@@ -1,67 +0,0 @@
import type { DataModel, Hook } from '@mcschema/core'
import { ModelPath, relativePath } from '@mcschema/core'
import type { BlockStateRegistry } from '../services/index.js'
export function getOutput(model: DataModel, blockStates: BlockStateRegistry): any {
return model.schema.hook(transformOutput, new ModelPath(model), model.data, { blockStates })
}
export type OutputProps = {
blockStates: BlockStateRegistry,
}
export const transformOutput: Hook<[any, OutputProps], any> = {
base({}, _, value) {
return value
},
choice({ switchNode }, path, value, props) {
return switchNode.hook(this, path, value, props)
},
list({ children }, path, value, props) {
if (!Array.isArray(value)) return value
const res = value.map((obj, index) =>
children.hook(this, path.push(index), obj.node, props)
)
for (const a of Object.getOwnPropertySymbols(value)) {
res[a as any] = value[a as any]
}
return res
},
map({ children, config }, path, value, props) {
if (value === undefined) return undefined
const blockState = config.validation?.validator === 'block_state_map'? props.blockStates?.[relativePath(path, config.validation.params.id).get()] : null
const res: any = {}
Object.keys(value).forEach(f => {
if (blockState) {
if (!Object.keys(blockState.properties ?? {}).includes(f)) return
}
res[f] = children.hook(this, path.push(f), value[f], props)
})
for (const a of Object.getOwnPropertySymbols(value)) {
res[a as any] = value[a]
}
return res
},
object({ getActiveFields }, path, value, props) {
if (value === undefined || value === null || typeof value !== 'object') {
return value
}
const res: any = {}
const activeFields = getActiveFields(path)
Object.keys(activeFields)
.filter(k => activeFields[k].enabled(path))
.forEach(f => {
const out = activeFields[f].hook(this, path.push(f), value[f], props)
if (out !== undefined && out !== null) {
res[f] = out
}
})
for (const a of Object.getOwnPropertySymbols(value)) {
res[a as any] = value[a]
}
return res
},
}

View File

@@ -1,20 +1,13 @@
import type { CollectionRegistry } from '@mcschema/core'
import config from '../Config.js'
import { Store } from '../Store.js'
import { message } from '../Utils.js'
import type { BlockStateRegistry, VersionId } from './Schemas.js'
import type { VersionId } from './Schemas.js'
import { checkVersion } from './Schemas.js'
const CACHE_NAME = 'misode-v2'
const CACHE_LATEST_VERSION = 'cached_latest_version'
const CACHE_PATCH = 'misode_cache_patch'
type Version = {
id: string,
ref?: string,
dynamic?: boolean,
}
declare var __LATEST_VERSION__: string
export const latestVersion = __LATEST_VERSION__ ?? ''
const mcmetaUrl = 'https://raw.githubusercontent.com/misode/mcmeta'
@@ -46,51 +39,10 @@ async function validateCache(version: RefInfo) {
}
}
export async function fetchData(versionId: string, collectionTarget: CollectionRegistry, blockStateTarget: BlockStateRegistry) {
const version = config.versions.find(v => v.id === versionId) as Version | undefined
if (!version) {
console.error(`[fetchData] Unknown version ${version} in ${JSON.stringify(config.versions)}`)
return
}
await validateCache(version)
await Promise.all([
_fetchRegistries(version, collectionTarget),
_fetchBlockStateMap(version, blockStateTarget),
])
}
async function _fetchRegistries(version: Version, target: CollectionRegistry) {
console.debug(`[fetchRegistries] ${version.id}`)
try {
const data = await cachedFetch<any>(`${mcmeta(version, 'summary')}/registries/data.min.json`)
for (const id in data) {
target.register(id, data[id].map((e: string) => 'minecraft:' + e))
}
} catch (e) {
console.warn('Error occurred while fetching registries:', message(e))
}
}
async function _fetchBlockStateMap(version: Version, target: BlockStateRegistry) {
console.debug(`[fetchBlockStateMap] ${version.id}`)
try {
const data = await cachedFetch<any>(`${mcmeta(version, 'summary')}/blocks/data.min.json`)
for (const id in data) {
target['minecraft:' + id] = {
properties: data[id][0],
default: data[id][1],
}
}
} catch (e) {
console.warn('Error occurred while fetching block state map:', message(e))
}
}
export async function fetchRegistries(versionId: VersionId) {
console.debug(`[fetchRegistries] ${versionId}`)
const version = config.versions.find(v => v.id === versionId)!
await validateCache(version)
try {
const data = await cachedFetch<any>(`${mcmeta(version, 'summary')}/registries/data.min.json`)
const result = new Map<string, string[]>()
@@ -103,10 +55,16 @@ export async function fetchRegistries(versionId: VersionId) {
}
}
export interface BlockStateData {
properties: Record<string, string[]>
default: Record<string, string>
}
export async function fetchBlockStates(versionId: VersionId) {
console.debug(`[fetchBlockStates] ${versionId}`)
const version = config.versions.find(v => v.id === versionId)!
const result = new Map<string, {properties: Record<string, string[]>, default: Record<string, string>}>()
const result = new Map<string, BlockStateData>()
await validateCache(version)
try {
const data = await cachedFetch<any>(`${mcmeta(version, 'summary')}/blocks/data.min.json`)
for (const id in data) {
@@ -128,6 +86,7 @@ export async function fetchItemComponents(versionId: VersionId) {
if (!checkVersion(versionId, '1.20.5')) {
return result
}
await validateCache(version)
try {
const data = await cachedFetch<Record<string, Record<string, unknown>>>(`${mcmeta(version, 'summary')}/item_components/data.min.json`)
for (const [id, components] of Object.entries(data)) {
@@ -152,6 +111,7 @@ export async function fetchItemComponents(versionId: VersionId) {
export async function fetchPreset(versionId: VersionId, registry: string, id: string) {
console.debug(`[fetchPreset] ${versionId} ${registry} ${id}`)
const version = config.versions.find(v => v.id === versionId)!
await validateCache(version)
try {
let url
if (id.startsWith('immersive_weathering:')) {

View File

@@ -1,77 +1,27 @@
import type { CollectionRegistry, INode, SchemaRegistry } from '@mcschema/core'
import { ChoiceNode, DataModel, Reference, StringNode } from '@mcschema/core'
import config from '../Config.js'
import { initPartners } from '../partners/index.js'
import { message } from '../Utils.js'
import { fetchData } from './DataFetcher.js'
import type { BlockStateData } from './DataFetcher.js'
import { fetchBlockStates, fetchRegistries } from './DataFetcher.js'
export const VersionIds = ['1.15', '1.16', '1.17', '1.18', '1.18.2', '1.19', '1.19.3', '1.19.4', '1.20', '1.20.2', '1.20.3', '1.20.5', '1.21', '1.21.2'] as const
export type VersionId = typeof VersionIds[number]
export const DEFAULT_VERSION: VersionId = '1.21'
export type BlockStateRegistry = {
[block: string]: {
properties?: {
[key: string]: string[],
},
default?: {
[key: string]: string,
},
},
interface VersionData {
registries: Map<string, string[]>
blockStates: Map<string, BlockStateData>
}
type VersionData = {
collections: CollectionRegistry,
schemas: SchemaRegistry,
blockStates: BlockStateRegistry,
}
const Versions: Record<string, VersionData | Promise<VersionData>> = {}
type ModelData = {
model: DataModel,
version: VersionId,
}
const Models: Record<string, ModelData> = {}
const versionGetter: {
[versionId in VersionId]: () => Promise<{
getCollections: () => CollectionRegistry,
getSchemas: (collections: CollectionRegistry) => SchemaRegistry,
}>
} = {
1.15: () => import('@mcschema/java-1.15'),
1.16: () => import('@mcschema/java-1.16'),
1.17: () => import('@mcschema/java-1.17'),
1.18: () => import('@mcschema/java-1.18'),
'1.18.2': () => import('@mcschema/java-1.18.2'),
1.19: () => import('@mcschema/java-1.19'),
'1.19.3': () => import('@mcschema/java-1.19.3'),
'1.19.4': () => import('@mcschema/java-1.19.4'),
'1.20': () => import('@mcschema/java-1.20'),
'1.20.2': () => import('@mcschema/java-1.20.2'),
'1.20.3': () => import('@mcschema/java-1.20.3'),
'1.20.5': () => import('@mcschema/java-1.20.5'),
1.21: () => import('@mcschema/java-1.21'),
'1.21.2': () => import('@mcschema/java-1.21.2'),
}
export let CachedDecorator: INode<any>
export let CachedFeature: INode<any>
export let CachedCollections: CollectionRegistry
export let CachedSchemas: SchemaRegistry
async function getVersion(id: VersionId): Promise<VersionData> {
if (!Versions[id]) {
Versions[id] = (async () => {
try {
const mcschema = await versionGetter[id]()
const collections = mcschema.getCollections()
const blockStates: BlockStateRegistry = {}
await fetchData(id, collections, blockStates)
const schemas = mcschema.getSchemas(collections)
initPartners(schemas, collections, id)
Versions[id] = { collections, schemas, blockStates }
const registries = await fetchRegistries(id)
const blockStates= await fetchBlockStates(id)
Versions[id] = { registries, blockStates }
return Versions[id]
} catch (e) {
throw new Error(`Cannot get version "${id}": ${message(e)}`)
@@ -82,65 +32,26 @@ async function getVersion(id: VersionId): Promise<VersionData> {
return Versions[id]
}
export async function getModel(version: VersionId, id: string): Promise<DataModel> {
if (!Models[id] || Models[id].version !== version) {
const versionData = await getVersion(version)
CachedDecorator = Reference(versionData.schemas, 'configured_decorator')
CachedFeature = ChoiceNode([
{
type: 'string',
node: StringNode(versionData.collections, { validator: 'resource', params: { pool: '$worldgen/configured_feature' } }),
},
{
type: 'object',
node: Reference(versionData.schemas, 'configured_feature'),
},
], { choiceContext: 'feature' })
const schemaName = config.generators.find(g => g.id === id)?.schema
if (!schemaName) {
throw new Error(`Cannot find model ${id}`)
}
try {
const schema = versionData.schemas.get(schemaName)
const model = new DataModel(schema, { wrapLists: true })
if (Models[id]) {
model.reset(Models[id].model.data, false)
} else {
model.validate(true)
model.history = [JSON.stringify(model.data)]
}
Models[id] = { model, version }
} catch (e) {
const err = new Error(`Cannot get generator "${id}" for version "${version}": ${message(e)}`)
if (e instanceof Error) err.stack = e.stack
throw err
}
}
return Models[id].model
}
export async function getCollections(version: VersionId): Promise<CollectionRegistry> {
const versionData = await getVersion(version)
CachedCollections = versionData.collections
return versionData.collections
}
export async function getBlockStates(version: VersionId): Promise<BlockStateRegistry> {
export async function getBlockStates(version: VersionId): Promise<Map<string, BlockStateData>> {
const versionData = await getVersion(version)
return versionData.blockStates
}
export async function getSchemas(version: VersionId): Promise<SchemaRegistry> {
const versionData = await getVersion(version)
CachedSchemas = versionData.schemas
return versionData.schemas
}
export function checkVersion(versionId: string, minVersionId: string | undefined, maxVersionId?: string) {
const version = config.versions.findIndex(v => v.id === versionId)
const minVersion = minVersionId ? config.versions.findIndex(v => v.id === minVersionId) : 0
const maxVersion = maxVersionId ? config.versions.findIndex(v => v.id === maxVersionId) : config.versions.length - 1
return minVersion <= version && version <= maxVersion
}
export interface FileModel {
get text(): string
get data(): any
}
export function createMockFileModel(): FileModel {
return {
text: '{}',
data: {},
}
}