Fix minecraft-data as proper submodule

This commit is contained in:
adman
2026-01-13 15:37:52 +01:00
parent f97538fe19
commit ac2713433e
1083 changed files with 1 additions and 13761996 deletions
-5
View File
@@ -1,5 +0,0 @@
/node_modules
1.*
bun.lock
package-lock.json
__*
@@ -1,119 +0,0 @@
/**
* This is a utility script that converts the YAML here into ProtoDef schema code
* You can run this with `npm run build`
*/
const fs = require('fs')
const { join } = require('path')
const { parse, compile } = require('protodef-yaml')
function getJSON (path) {
return JSON.parse(fs.readFileSync(path, 'utf-8'))
}
// Parse the YML files and turn to JSON
function genProtoSchema (createPacketMap) {
// Create the packet_map.yml from proto.yml
const parsed = parse('./proto.yml')
const version = parsed['!version']
if (createPacketMap) {
// On bedrock the packet maps are created automatically here
const packets = []
for (const key in parsed) {
if (key.startsWith('%container')) {
const [, name] = key.split(',')
if (name.startsWith('packet_')) {
const children = parsed[key]
const packetName = name.replace('packet_', '')
const packetID = children['!id']
packets.push([packetID, packetName, name])
}
}
}
let l1 = ''
let l2 = ''
for (const [id, name, fname] of packets) {
l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n`
l2 += ` if ${name}: ${fname}\n`
}
// TODO: skip creating packet_map.yml and just generate the ProtoDef map JSON directly
const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}`
fs.writeFileSync('./packet_map.yml', t)
}
const json = compile('./proto.yml')
fs.rmSync('./packet_map.yml', { force: true }) // temp file
return [version, json]
}
function visitor (key, value) {
// Convert decimal to hex in protocol.json for packet mapper fields
if ((key === 'packet') && (value?.[1]?.[0]?.name === 'name')) {
const mapper = value[1][0].type[1].mappings
for (const key in mapper) {
if (key.startsWith('0x')) continue
mapper['0x' + Number(key).toString(16).padStart(2, '0')] = mapper[key]
delete mapper[key]
}
}
return value
}
function convert (edition, ver, path) {
process.chdir(path || join(__dirname, `../../data/${edition}/${ver}`))
const [version, json] = genProtoSchema(edition === 'bedrock')
fs.mkdirSync(`../${version}`, { recursive: true })
if (edition === 'bedrock') {
fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: json }, visitor, 2))
} else if (edition === 'pc') {
fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify(json, visitor, 2))
}
return version
}
function validate (edition, ver, path) {
process.chdir(path || join(__dirname, `../../data/${edition}/${ver}`))
console.log(process.cwd())
const [version, json] = genProtoSchema(edition === 'bedrock')
const expected = edition === 'bedrock'
? JSON.stringify({ types: json }, visitor, 2)
: JSON.stringify(json, visitor, 2)
// If you crash here, no protocol.json was proto_generated - run `npm run build`
const actual = JSON.stringify(getJSON(`../${version}/protocol.json`), null, 2)
// Make sure the protocol_expected.json file equals the protocol.json file; otherwise the JSON must be rebuilt
if (expected !== actual) {
// console.log(expected.length, actual.length, expected, actual)
throw Error(`${ver} (${version}) / protocol.json is desynced from yaml, please run 'npm run build'`)
}
console.log('ok', `../${version}/protocol.json`)
return version
}
const dataPaths = require('../../data/dataPaths.json')
function all (fn) {
for (const edition in dataPaths) {
const versions = dataPaths[edition]
for (const versionId in versions) {
console.log('⏳', fn.name, 'protocol for', edition, versionId)
const paths = versions[versionId]
if (paths.proto) {
fn(edition, versionId, join(__dirname, '../../data/' + paths.proto))
}
}
}
}
// If no argument, build everything
if (!module.parent) {
if (!process.argv[2]) {
all(convert)
} else { // build the specified version
convert(process.argv[2], process.argv[3])
}
}
module.exports = { convert, validate, all }
@@ -1,65 +0,0 @@
const { genYAML } = require('protodef-yaml')
const { join } = require('path')
const fs = require('fs')
function addDataPath (version, key, value) {
const dataPaths = require('../../data/dataPaths.json')
if (dataPaths.pc[version][key]) return
dataPaths.pc[version][key] = value
console.log('Updating datapath for', version, key, value)
fs.writeFileSync(join(__dirname, '../../data/dataPaths.json'), JSON.stringify(dataPaths, null, 2))
}
function convert (version) {
let text = '!version: ' + version + '\n\n'
const protocol = require(`../../data/pc/${version}/protocol.json`)
for (const state in protocol) {
const data = protocol[state]
if (state === 'types') {
text += '^types:\n'
text += genYAML(data, 1)
}
// 0.30c (stateless)
if (state === 'toClient') {
text += '\n^toClient.types:\n'
text += genYAML(data.types, 1)
}
if (state === 'toServer') {
text += '\n^toServer.types:\n'
text += genYAML(data.types, 1)
}
// Rest of versions have states
if (data.toClient) {
text += `\n^${state}.toClient.types:\n`
text += genYAML(data.toClient.types, 1)
}
if (data.toServer) {
text += `\n^${state}.toServer.types:\n`
text += genYAML(data.toServer.types, 1)
}
}
const outFile = `../../data/pc/${version}/proto.yml`
const absPath = fs.realpathSync(`../../data/pc/${version}/`) + '/proto.yml'
console.log(`Wrote to ${absPath}`)
fs.writeFileSync(outFile, text)
addDataPath(version, 'proto', 'pc/' + version)
}
if (process.argv.length < 3) {
console.log('Usage: node convertMcpcProtocolJsonToYaml.js <version>')
process.exit(1)
}
const version = process.argv[2]
if (version === 'all') {
const dataPaths = require('../../data/dataPaths.json')
for (const version in dataPaths.pc) {
try {
convert(version)
} catch (e) {
console.log('Failed for', version, e)
}
}
} else {
convert(version)
}
@@ -1,233 +0,0 @@
#!/usr/bin/env node
const fs = require('fs')
const cp = require('child_process')
const { globSync } = require('glob')
/**
* Normalize decompiler output into an array of "statements"/lines.
* @param {string} raw
* @returns {string[]}
*/
function prepLines (raw) {
const lines = raw.replaceAll(' {\n', ' {;\n').split(';')
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim()
if (line.includes('static final') && !line.includes(' = ')) {
const varName = line.split(' ').pop()
for (let j = 0; j < lines.length; j++) {
const line2 = lines[j].trim()
if (line2.startsWith(varName + ' = ')) {
lines[i] = line.replace(varName, line2)
lines[j] = ''
}
}
}
}
return lines
}
/**
* Read entity types from the client EntityType.java and return mappings.
* @param {string} versionDir
* @returns {[Record<string,string>, Record<string,string>]}
*/
function getEntityTypes (versionDir) {
const entityTypes = fs.readFileSync(`${versionDir}/client/net/minecraft/world/entity/EntityType.java`, 'utf8')
const entityTypesLines = prepLines(entityTypes)
const classNameTo = {}
const nameToClass = {}
for (const line of entityTypesLines) {
if (line.includes('= register(')) {
// Given the line: public static final EntityType<Allay> ALLAY = register( "allay", EntityType.Builder.<Allay>of(Allay::new, MobCategory.CREATURE).sized(0.35F, 0.6F).clientTrackingRange(8).updateInterval(2) );
// we extract Allay and "allay"
const regex = line.match(/EntityType<(.*)> (.*) = register\(\W*"([a-z0-9_]+)"/)
if (regex) {
const [, type, , name] = regex
classNameTo[type] = name
nameToClass[name] = type
}
}
}
return [classNameTo, nameToClass]
}
/**
* Read EntityDataSerializers.java and return serializer names in order.
* @param {string} versionDir
* @returns {string[]}
*/
function getEntityMetadataSerializers (versionDir) {
const output = []
const entityTypes = fs.readFileSync(`${versionDir}/client/net/minecraft/network/syncher/EntityDataSerializers.java`, 'utf8')
const entityTypesLines = prepLines(entityTypes)
for (const line of entityTypesLines) {
if (line.includes('registerSerializer')) {
// From: registerSerializer(BYTE);
// We extract: BYTE
const regex = line.match(/ {2}registerSerializer\((.*)\)/)
if (regex) {
const [, type] = regex
output.push(type)
}
}
}
return output.map(e => e.toLowerCase())
}
/**
* Main function to extract and optionally update mcdata files.
* @param {string} version - directory name for extracted src (e.g. "1.20")
* @param {string} [mcdataVersion] - mcdata version to update (defaults to version)
* @param {{write?:boolean, cloneIfMissing?:boolean}} [opts]
* @returns {{tree:object, flat:object, serializers:string[], classNameToRegistryName:object, entityNameToClass:object, metadatas:object}}
*/
function extractPcEntityMetadata (version, mcdataVersion = version, opts = {}) {
if (!version) {
throw new Error('Usage: extractPcEntityMetadata(version, [mcdataVersion])')
}
const write = opts.write !== false
const cloneIfMissing = opts.cloneIfMissing !== false
if (!fs.existsSync(version)) {
if (!cloneIfMissing) {
throw new Error(`Version directory "${version}" does not exist`)
}
cp.execSync(`git clone -b client${version} https://github.com/extremeheat/extracted_minecraft_data.git ${version} --depth 1`, { stdio: 'inherit' })
}
const serializers = getEntityMetadataSerializers(version)
const [classNameToRegistryName, entityNameToClass] = getEntityTypes(version)
const allEntityFiles = globSync(`${version}/**/entity/**/*.java`)
const allEntityFileCodes = Object.fromEntries(allEntityFiles.map(file => [
file.split(/\\|\//g).pop().replace('.java', ''),
fs.readFileSync(file, 'utf8')
]))
const entityPreTree = []
const metadatas = {}
for (const file in allEntityFileCodes) {
const code = allEntityFileCodes[file]
const lines = prepLines(code)
let lastClass
for (const line of lines) {
let lineWithoutGenerics = line.replace(/<.*>/g, '')
const bloatMods = ['abstract', 'static']
for (const mod of bloatMods) lineWithoutGenerics = lineWithoutGenerics.replace(` ${mod} `, ' ')
if (lineWithoutGenerics.includes('public class ')) {
const className = lineWithoutGenerics.split('public class ')[1]?.split(' ')[0]
lastClass = className
const extend = lineWithoutGenerics.split('extends ')[1]?.split(' ')[0]
if (className === file) {
if (extend) entityPreTree.push([extend, file])
else entityPreTree.push([file])
} else if (extend === file) {
lastClass = `${file}.${className}`
entityPreTree.push([file, lastClass])
}
}
if (line.match(/SynchedEntityData\..*defineId\(/)) {
// from: private static final EntityDataAccessor<Sniffer.State> DATA_STATE = SynchedEntityData.defineId(Sniffer.class, EntityDataSerializers.SNIFFER_STATE);
// extract: DATA_STATE, SNIFFER_STATE
const r = line.match(/> ([A-Z_0-9]+) = .*EntityDataSerializers.([A-Z_0-9]+)/s)
if (r) {
const [, data, serializer] = r
; (metadatas[lastClass] ??= []).push([data, serializer])
} else {
throw new Error('Failed to parse line: ' + line)
}
}
}
}
const tree = {}
const flat = {}
const handledNames = []
function build (ele, root = tree, arr = []) {
const name = classNameToRegistryName[ele]
const metadata = metadatas[ele]
root[ele] = { children: {}, name, metadata }
if (name) flat[name] = arr.concat(metadata ?? [])
handledNames.push(ele)
for (const [key, val] of entityPreTree) {
if (key === ele) {
build(val, root[key].children, arr.concat(metadata ?? []))
}
}
}
build('Entity')
for (const key in classNameToRegistryName) {
if (!handledNames.includes(key)) throw new Error('Unhandled entity ' + key)
}
function updateMcDataEntitiesJSON () {
const presentMcDataPath = `../../data/pc/${mcdataVersion}/entities.json`
const presentMcData = require(presentMcDataPath)
for (const entry of presentMcData) {
const cls = entityNameToClass[entry.name]
const regName = classNameToRegistryName[cls]
entry.metadataKeys = (flat[regName] || []).map(e => e[0].replace('DATA_', '').replace('_ID', '').replace('ID_', '').toLowerCase())
}
fs.writeFileSync(presentMcDataPath, JSON.stringify(presentMcData, null, 2))
}
function updateMcDataProtocolJSON () {
const presentMcDataPath = `../../data/pc/${mcdataVersion}/protocol.json`
const mcdata = require(presentMcDataPath)
const mapper = { type: 'varint', mappings: {} }
for (let i = 0; i < serializers.length; i++) {
mapper.mappings[i] = serializers[i].toLowerCase()
}
mcdata.types.entityMetadata[1].type[1] = [
{ name: 'key', type: 'u8' },
{ name: 'type', type: ['mapper', mapper] },
{ name: 'value', type: ['entityMetadataItem', { compareTo: 'type' }] }
]
fs.writeFileSync(presentMcDataPath, JSON.stringify(mcdata, null, 2))
}
if (write) {
updateMcDataEntitiesJSON()
updateMcDataProtocolJSON()
}
return {
tree,
flat,
serializers,
classNameToRegistryName,
entityNameToClass,
metadatas,
entityPreTree
}
}
// Expose functions for use as a library
module.exports = {
extractPcEntityMetadata,
prepLines,
getEntityTypes,
getEntityMetadataSerializers
}
// If run directly from the CLI, call the function with process.argv
if (require.main === module) {
const version = process.argv[2]
const mcdataVersion = process.argv[3] || process.argv[2]
if (!version) {
console.log('Usage: node extractEntityMetadata.js <codeVersion> [mcdataVersion]')
process.exit(1)
}
try {
extractPcEntityMetadata(version, mcdataVersion, { write: true, cloneIfMissing: true })
// finished successfully
} catch (err) {
console.error('Error:', err)
process.exit(1)
}
}
@@ -1,58 +0,0 @@
const fs = require('fs')
const cp = require('child_process')
const version = process.argv[2]
if (!version) {
console.log('Usage: node extractPcFoods.js <version>')
process.exit(1)
}
if (!fs.existsSync(version)) {
cp.execSync(`git clone -b client${version} https://github.com/extremeheat/extracted_minecraft_data.git ${version} --depth 1`, { stdio: 'inherit' })
}
const mcDataItems = require(`../../data/pc/${version}/items.json`)
const outputItems = []
function getItemDataFor (itemName) {
return mcDataItems.find(e => e.name === itemName)
}
const foodsFile = fs.readFileSync(`./${version}/client/net/minecraft/world/food/Foods.java`, 'utf8')
function add (name, satMod, nut) {
console.log(name, satMod, nut)
// for correct order, we assign undefineds
const entry = {
id: undefined,
name: name.toLowerCase(),
stackSize: null,
displayName: undefined,
foodPoints: parseFloat(nut),
saturation: undefined,
effectiveQuality: undefined,
// There is a ratio*2 modifier as of 1.20.5 in https://github.com/extremeheat/extracted_minecraft_data/blob/dc3974eeb6bea61fb26f8f1feece761d0b812d6a/client/net/minecraft/world/food/FoodConstants.java#L35
// also done in https://github.com/PrismarineJS/minecraft-data-generator-server/blob/44f49b4ab51fb92f72209d7fdf7c6e42030de8dd/1.7/src/main/java/dev/u9g/minecraftdatagenerator/generators/FoodsDataGenerator.java#L24
saturationRatio: parseFloat(satMod).toFixed(2) * 2
}
Object.assign(entry, getItemDataFor(entry.name))
entry.saturation = Number((entry.foodPoints * entry.saturationRatio).toFixed(2)) // round by 2 decimal digits
entry.effectiveQuality = entry.foodPoints + entry.saturation
outputItems.push(entry)
}
for (const line of foodsFile.replaceAll('\n', '').split(';')) {
if (!line.includes('final FoodProperties')) continue
if (line.includes('stew(')) {
// special case for stew
const name = line.match(/ ([A-Z0-9+_]+) =/)[1]
const satMod = 0.6
const nut = line.match(/stew\(([0-9.fFeE]+)\)/)[1]
add(name, satMod, nut)
} else {
const name = line.match(/ ([A-Z0-9+_]+) =/)[1]
const satMod = line.match(/saturationModifier\(([0-9.fFeE]+)\)/)[1]
const nut = line.match(/nutrition\(([0-9.fFeE]+)\)/)[1]
add(name, satMod, nut)
}
}
outputItems.sort((a, b) => a.id - b.id)
fs.writeFileSync(`../../data/pc/${version}/foods.json`, JSON.stringify(outputItems, null, 2))
@@ -1,23 +0,0 @@
const fs = require('fs')
const cp = require('child_process')
const version = process.argv[2]
const extractTypes = process.argv[3] === '-types'
if (!version) {
console.log('Usage: node extractEntityMetadata.js <codeVersion> [-types]')
process.exit(1)
}
if (!fs.existsSync(version)) {
cp.execSync(`git clone -b client${version} https://github.com/extremeheat/extracted_minecraft_data.git ${version} --depth 1`, { stdio: 'inherit' })
}
const componentsFile = fs.readFileSync(`./${version}/client/net/minecraft/core/component/DataComponents.java`, 'utf8')
const nameRe = /register\("(.+)",/gm
const typeRe = /\((.+?)\).*?;/gm
const nameMatches = componentsFile.matchAll(nameRe)
const typeMatches = componentsFile.matchAll(typeRe)
typeMatches.next()
for (const match of nameMatches) {
console.log(`- ${match[1]}${extractTypes ? ' : ' + typeMatches.next().value?.[1] : ''}`)
}
@@ -1,126 +0,0 @@
const fs = require('fs')
const { join } = require('path')
const rootPath = join(__dirname, '..', '..')
const data = join(__dirname, '..', '..', 'data')
function getJSON (path) {
return JSON.parse(fs.readFileSync(path, 'utf-8'))
}
function writeJSON (path, data) {
fs.writeFileSync(path, JSON.stringify(data, null, 2), 'utf-8')
}
// Reads and updates the JSON after processing in callback
function alterJSON (path, callback) {
const jsonContents = getJSON(path)
if (callback(jsonContents) !== false) {
writeJSON(path, jsonContents)
}
}
function bumpReadmeVersion (edition, version) {
const readmePath = join(rootPath, 'README.md')
let readmeContents = fs.readFileSync(readmePath, 'utf-8')
if (!readmeContents.includes(version)) {
if (edition === 'pc') readmeContents = readmeContents.replace('\n<!--NEXT PC-->', `, ${version}\n<!--NEXT PC-->`)
if (edition === 'bedrock') readmeContents = readmeContents.replace('\n<!--NEXT BEDROCK-->', `, ${version}\n<!--NEXT BEDROCK-->`)
}
fs.writeFileSync(readmePath, readmeContents, 'utf-8')
}
function updateProtocol (edition, version, protocolVersionNumber) {
const dataRoot = join(data, edition)
const protocolVersionsPath = join(dataRoot, 'common', 'protocolVersions.json')
const versionsPath = join(dataRoot, 'common', 'versions.json')
const versionPath = join(dataRoot, version, 'version.json')
const protocolVersionsEntry = {
version: parseInt(protocolVersionNumber),
minecraftVersion: version,
majorVersion: version.split('.').slice(0, 2).join('.'),
releaseType: 'release'
}
let didUpdateProtocol = false
// Move the latest/proto.yml to its associated folder defined in the !version meta field
const latestProtoPath = join(dataRoot, 'latest', 'proto.yml')
const protoYml = fs.readFileSync(latestProtoPath, 'utf-8')
const oldProtoVersion = protoYml.match(/!version: ([0-9.]+)/)[1]
// Check if $oldVersion/proto.yml already exists
const oldProtoPath = join(dataRoot, oldProtoVersion, 'proto.yml')
if (fs.existsSync(oldProtoPath)) {
console.log(`Can't move ${latestProtoPath} to ${oldProtoPath} because the file already exists.`)
} else {
fs.copyFileSync(latestProtoPath, oldProtoPath)
if (edition === 'bedrock') {
fs.copyFileSync(join(dataRoot, 'latest', 'types.yml'), join(dataRoot, oldProtoVersion, 'types.yml'))
}
didUpdateProtocol = true
// Update references to the old version in the dataPaths.json file
alterJSON(join(data, 'dataPaths.json'), dataPaths => {
let latestVersionData
for (const v in dataPaths[edition]) {
const e = dataPaths[edition][v]
// if .proto points to latest, update it to the old version
if (e.proto === `${edition}/latest`) {
e.proto = `${edition}/${oldProtoVersion}`
}
latestVersionData = structuredClone(e) // eslint-disable-line
}
if (!dataPaths[edition][version]) {
dataPaths[edition][version] = latestVersionData
}
latestVersionData.proto = `${edition}/latest`
latestVersionData.protocol = `${edition}/${version}`
latestVersionData.version = `${edition}/${version}`
})
}
// Update commons/protocolVersions.json (pc does this auto with cron)
alterJSON(protocolVersionsPath, protoVers => {
if (!protoVers.find(v => v.version === protocolVersionsEntry.version)) {
protoVers.unshift(protocolVersionsEntry)
}
})
// Update commons/version.json
alterJSON(versionsPath, versions => {
if (!versions.includes(version)) {
versions.push(version)
}
})
// Update the $version/version.json file
fs.mkdirSync(join(dataRoot, version), { recursive: true })
if (!fs.existsSync(versionPath)) {
writeJSON(versionPath, protocolVersionsEntry)
}
// Update the '!version' meta field in the latest/proto.yml
if (didUpdateProtocol) {
const protoYmlUpdate = protoYml.replace(/!version: [0-9.]+/, `!version: ${version}`)
fs.writeFileSync(join(dataRoot, 'latest', 'proto.yml'), protoYmlUpdate, 'utf-8')
}
}
const platforms = {
bedrock: updateProtocol,
pc: updateProtocol
}
if (process.argv.length !== 5) {
console.log(`Usage: npm run version <${Object.keys(platforms).join('|')}> {version} {protocol_version}`)
process.exit(1)
}
const [,, platform, version, protocol] = process.argv
if (platforms[platform] === undefined) {
console.log(`Received platform "${platform}", expected one of (${Object.keys(platforms).join(', ')})`)
process.exit(1)
}
platforms[platform](platform, version, protocol)
bumpReadmeVersion(platform, version)
-23
View File
@@ -1,23 +0,0 @@
{
"name": "minecraft-data-tools",
"private": true,
"description": "minecraft-data tools",
"scripts": {
"test": "mocha --reporter spec",
"lint": "standard && standard ../../.github",
"fix": "standard --fix && standard --fix ../../.github",
"pretest": "npm run lint",
"build": "node ./compileProtocol.js",
"version": "node ./incrementVersion.js"
},
"dependencies": {
"ajv": "^6.5.4",
"glob": "^10.2.6",
"mocha": "^10.2.0",
"protodef-validator": "^1.2.2",
"standard": "^17.1.2"
},
"devDependencies": {
"protodef-yaml": "^1.5.0"
}
}
@@ -1,23 +0,0 @@
// Sync particle IDs between pc/particles.json and pc/protocol.json
const fs = require('fs')
const oldParticleData = require('../../data/pc/1.19.3/particles.json')
const oldProtocolData = require('../../data/pc/1.19.3/protocol.json')
const particleData = require('../../data/pc/1.19.4/particles.json')
const protocolData = require('../../data/pc/1.19.4/protocol.json')
// const oldParticleDataByName = Object.fromEntries(oldParticleData.map(e => [e.name, e.id]))
const newParticleDataByName = Object.fromEntries(particleData.map(e => [e.name, e.id]))
const oldIdToNew = Object.fromEntries(oldParticleData.map(e => [e.id, newParticleDataByName[e.name]]))
const ppData = oldProtocolData.types.particleData[1]
const newFields = {}
for (const fieldId in ppData.fields) {
const field = ppData.fields[fieldId]
newFields[oldIdToNew[fieldId]] = field
}
protocolData.types.particleData[1].fields = newFields
const json = JSON.stringify(protocolData, null, 2)
// Matching the custom formatting on the JSON is annoying, let's just keep it JS/vscode default 2-space formatting for consistency
// json = json.replace(/\[\s+"option",\s+"string"\s+\],/g, '["option", "string"],').replace(/\[\s+"option",\s+"string"\s+\]/g, '["option", "string"]')
fs.writeFileSync('./protocol.json', json)
@@ -1,48 +0,0 @@
/* eslint-env mocha */
const fs = require('fs')
const path = require('path')
require('./version_iterator')(function (p, versionString) {
describe('audit blockitems ' + versionString, function () {
it('audit blockitems', function () {
const itemsFile = path.join(p, 'items.json')
if (!fs.existsSync(itemsFile)) return
const items = require(itemsFile)
const blocksFile = path.join(p, 'blocks.json')
if (!fs.existsSync(blocksFile)) return
const blocks = require(blocksFile)
if (items[0].id === 0) return
let rewriteItems = false
for (const block of blocks) {
let blockitem = null
for (const item of items) {
if (item.name === block.name) {
blockitem = item
break
}
}
if (!blockitem) {
// console.log('Missing item for block ' + block.name)
rewriteItems = false
items.push({
id: block.id,
displayName: block.displayName,
name: block.name,
stackSize: 64
})
}
}
items.sort((a, b) => { return a.id - b.id })
// Automatically fix item data, if necessary
if (rewriteItems) {
fs.writeFileSync(itemsFile, JSON.stringify(items, null, 2))
}
})
})
})
@@ -1,58 +0,0 @@
/* eslint-env mocha */
const fs = require('fs')
const path = require('path')
// checks for duplicates names and jumps in ids
require('./version_iterator')(function (p, versionString) {
describe('audit blocks ' + versionString, function () {
it('audit blocks', function () {
let blocks
const pFile = path.join(p, 'blocks.json')
if (fs.existsSync(pFile)) {
blocks = require(pFile)
} else {
// console.log('No blocks for version ' + versionString)
}
if (blocks) {
const all = []
blocks.forEach(block => {
all[block.id] = block
})
const displayNames = {}
const names = {}
for (let i = 0; i < all.length; ++i) {
const block = all[i]
if (block) {
if (block.displayName == null) {
// console.log('Missing displayName:', i)
} else {
const otherBlock = displayNames[block.displayName]
if (otherBlock) {
// console.log('Duplicate displayName:', otherBlock.id, 'and', block.id,
// 'both share', block.displayName)
} else {
displayNames[block.displayName] = block
}
}
if (block.name == null) {
console.log('Missing name:', i)
} else {
const otherBlock = names[block.name]
if (otherBlock) {
// console.log('Duplicate name:', otherBlock.id, 'and', block.id,
// 'both share', block.name)
} else {
names[block.name] = block
}
}
} else {
// console.log('Missing:', i)
}
}
}
})
})
})
@@ -1,33 +0,0 @@
/* eslint-env mocha */
const fs = require('fs')
const { join } = require('path')
const assert = require('assert')
describe('audit dataPaths', function () {
const dataPaths = require('../../../data/dataPaths.json')
it('should have a dataPath for each file', function () {
require('./version_iterator')(function (p, versionString) {
const [type, version] = versionString.split(' ')
const dp = dataPaths[type][version]
const files = fs.readdirSync(p).map(f => f.split('.')[0])
for (const file of files) {
assert(dp[file], `missing dataPath for ${type} ${version} ${file}`)
}
})
})
it('dataPath should point to valid files', function () {
for (const version in dataPaths) {
for (const type in dataPaths[version]) {
const dp = dataPaths[version][type]
for (const file in dp) {
const path = dp[file]
const p = join(__dirname, '../../../data/' + path + '/' + file)
const exists = fs.existsSync(p + '.json') || fs.existsSync(p + '.yml')
assert(exists, `missing file for ${type} ${version} ${file}, path: ${p}`)
}
}
}
})
})
@@ -1,54 +0,0 @@
/* eslint-env mocha */
const fs = require('fs')
const path = require('path')
// checks for duplicates names and jumps in ids
require('./version_iterator')(function (p, versionString) {
describe('audit items ' + versionString, function () {
it('audit items', function () {
let items
const pFile = path.join(p, 'items.json')
if (fs.existsSync(pFile)) {
items = require(pFile)
} else {
// console.log('No items for version ' + versionString)
}
if (items) {
const displayNames = {}
const names = {}
let lastItemId = 0
items.forEach(item => {
if (item.displayName == null) {
console.log('Missing displayName:', item.id)
} else {
const otherBlock = displayNames[item.displayName]
if (otherBlock) {
// console.log('Duplicate displayName:', otherBlock.id, 'and', item.id,
// 'both share', item.displayName)
} else {
displayNames[item.displayName] = item
}
}
if (item.name == null) {
console.log('Missing name:', item.id)
} else {
const otherBlock = names[item.name]
if (otherBlock) {
// console.log('Duplicate name:', otherBlock.id, 'and', item.id,
// 'both share', item.name)
} else {
names[item.name] = item
}
}
const delta = item.id - lastItemId
if (delta !== 1) {
// console.log('jump from', lastItemId, 'to', item.id)
}
lastItemId = item.id
})
}
})
})
})
@@ -1,136 +0,0 @@
/* eslint-env mocha */
const fs = require('fs')
const path = require('path')
const assert = require('assert')
// counts the number of recipes with a shape, without one and with an outShape
function getIfExist (path) {
if (fs.existsSync(path)) {
return require(path)
} else {
return null
}
}
require('./version_iterator')(function (p, versionString) {
describe('audit recipes ' + versionString, function () {
it('audit recipes', function () {
let recipes
const pFile = path.join(p, 'recipes.json')
if (fs.existsSync(pFile)) {
recipes = require(pFile)
} else {
console.log('No recipes for version ' + versionString)
}
if (recipes) {
let shapeCount = 0
let shapelessCount = 0
let outShapeCount = 0
Object.keys(recipes).forEach(key => {
const list = recipes[key]
for (let i = 0; i < list.length; ++i) {
const recipe = list[i]
if (recipe.inShape != null) {
shapeCount += 1
} else if (recipe.ingredients != null) {
shapelessCount += 1
} else {
console.log('inShape or ingredients required:', key)
}
if (recipe.outShape) outShapeCount += 1
}
})
console.log('normal recipes:', shapeCount)
console.log('shapeless recipes:', shapelessCount)
console.log('how many have an outShape:', outShapeCount)
}
})
it('pickaxe not upside-down', () => {
const recipes = getIfExist(path.join(p, 'recipes.json'))
const items = getIfExist(path.join(p, 'items.json'))
if (recipes && items) {
const pickaxe = items.find(x => x.name === 'diamond_pickaxe')
const stick = items.find(x => x.name === 'stick')
const diamond = items.find(x => x.name === 'diamond')
const recipe = recipes[pickaxe.id]
// Uncomment to fix upside-down recipes
/* if (recipe[0].inShape[0][0] !== diamond.id) {
for (const item of Object.values(recipes)) {
for (const rep of item) {
if (rep.inShape) rep.inShape.reverse()
}
}
fs.writeFileSync(path.join(p, 'recipes.json'), JSON.stringify(recipes, null, 2))
} */
// Those 2 versions doesnt contain diamond pickaxe recipe, to be fixed...
if (versionString === 'pc 1.9' || versionString === 'pc 1.10' || !recipe[0]) return
assert.deepStrictEqual(recipe[0].inShape, [
[
diamond.id,
diamond.id,
diamond.id
],
[
null,
stick.id,
null
],
[
null,
stick.id,
null
]
])
}
})
it('iron door not rotated', () => {
const recipes = getIfExist(path.join(p, 'recipes.json'))
const items = getIfExist(path.join(p, 'items.json'))
if (recipes && items) {
const ironDoor = items.find(x => x.name === 'iron_door')
const iron = items.find(x => x.name === 'iron_ingot')
const recipe = recipes[ironDoor.id]
if (!recipe[0]) return
assert.deepStrictEqual(recipe[0].inShape, [
[
iron.id,
iron.id
],
[
iron.id,
iron.id
],
[
iron.id,
iron.id
]
])
}
})
it('crafting benches has multiple recipes', () => {
const recipes = getIfExist(path.join(p, 'recipes.json'))
const items = getIfExist(path.join(p, 'items.json'))
if (recipes && items) {
const craftingTable = items.find(x => x.name === 'crafting_table')
const oakPlanks = items.find(x => x.name === 'oak_planks')
if (!oakPlanks) return // Bail if version doesn't have seperately defined planks, this prevents the test failing on versions that use metadata
const recipe = recipes[craftingTable.id]
if (!recipe[0]) return
// remove the if after fixing https://github.com/PrismarineJS/minecraft-data/issues/917
if (versionString !== 'pc 1.20.5' && versionString !== 'pc 1.20.6') {
assert.notEqual(recipe.length, 1) // Check that crafting table has multiple recipes.
}
}
})
})
})
@@ -1,94 +0,0 @@
/* eslint-env mocha */
const fs = require('fs')
const path = require('path')
// checks if bounding boxes data in blocks.json is consistent with blockCollisionShapes.json
require('./version_iterator')(function (p, versionString) {
describe('audit block shapes ' + versionString, function () {
const blockFile = path.join(p, 'blocks.json')
if (!fs.existsSync(blockFile)) {
return // Only audit shape where block exists
}
it('audit bb', function () {
const shapeFile = path.join(p, 'blockCollisionShapes.json')
if (!fs.existsSync(shapeFile)) {
// console.log('No collision shapes for version ' + versionString)
return
}
const blocks = require(blockFile)
const shapes = require(shapeFile)
const blockByName = {}
// Check bijection between blocks and shapes
// Check that every block have a shape
blocks.forEach(block => {
blockByName[block.name] = block
const shape = shapes.blocks[block.name]
if (shape === undefined) {
// console.log('missing shape for block: ' + block.name)
}
})
// Check that every shape have a block
for (const key of Object.keys(shapes.blocks)) {
const block = blockByName[key]
if (block === undefined) {
// console.log('missing block for shape: ' + key)
}
}
// Check every shape is present
const usedShapes = {}
for (let value of Object.values(shapes.blocks)) {
if (!(value instanceof Array)) value = [value]
for (const shape of value) {
if (shapes.shapes[shape] === undefined) {
// console.log('missing shape: ' + shape)
}
usedShapes[shape] = true
}
}
// Check every shape is used
for (const key of Object.keys(shapes.shapes)) {
if (!usedShapes[key]) {
// console.log('unused shape: ' + key)
}
}
// Check block bounding box is consistent with shape
let rewriteBlocks = false
blocks.forEach(block => {
blockByName[block.name] = block
const shape = shapes.blocks[block.name]
if (shape !== undefined) {
if (block.boundingBox === 'empty') {
if (shape !== 0) {
// console.log('Inconsistent BB for block ' + block.name + ' (expected empty got ' + shape + ')')
block.boundingBox = 'block'
rewriteBlocks = false
}
} else if (block.boundingBox === 'block') {
if (shape === 0) {
// console.log('Inconsistent BB for block ' + block.name + ' (expected block got ' + shape + ')')
block.boundingBox = 'empty'
rewriteBlocks = false
}
} else {
// console.log('Unknown BB: ' + block.boundingBox + ' for block ' + block.name)
}
}
})
// Automatically fix block data, if necessary
if (rewriteBlocks) {
fs.writeFileSync(blockFile, JSON.stringify(blocks, null, 2))
}
})
})
})
@@ -1,95 +0,0 @@
/* eslint-env mocha */
const assert = require('assert')
/**
* Gets the chronological index of a version from the versions.json file
* Returns -1 if version is not found, or null for non-version strings like "latest"
*/
function getVersionIndex (platform, version) {
// Skip non-version entries like "latest"
if (version === 'latest') return null
try {
const versions = require(`../../../data/${platform}/common/versions.json`)
return versions.indexOf(version)
} catch (error) {
return -1
}
}
describe('audit version regression in dataPaths', function () {
const dataPaths = require('../../../data/dataPaths.json')
const platforms = ['pc', 'bedrock']
platforms.forEach(function (platform) {
if (!dataPaths[platform]) return
describe(`${platform} version regression`, function () {
it('should not have newer versions pointing to older data than previously seen', function () {
const platformData = dataPaths[platform]
// Load the chronological version order from versions.json
const versions = require(`../../../data/${platform}/common/versions.json`)
// Track the highest version index seen for each data type
const highestVersionIndexForDataType = {}
// Iterate through versions in chronological order, but only process ones in dataPaths
for (const version of versions) {
if (!platformData[version]) continue
const versionData = platformData[version]
for (const [dataType, dataPath] of Object.entries(versionData)) {
// Skip non-version data paths (like "latest" or cross-platform refs)
const pathParts = dataPath.split('/')
if (pathParts.length !== 2) continue
const [dataPathPlatform, dataPathVersion] = pathParts
// Skip cross-platform references (e.g., bedrock data pointing to pc data)
if (dataPathPlatform !== platform) continue
// Get the chronological index of the data path version
const dataPathIndex = getVersionIndex(platform, dataPathVersion)
// Skip if version not found in versions.json (like "latest")
if (dataPathIndex === null || dataPathIndex === -1) continue
// Initialize tracking for this data type if not seen before
if (!highestVersionIndexForDataType[dataType]) {
highestVersionIndexForDataType[dataType] = {
version: dataPath,
index: dataPathIndex
}
continue
}
// Check if current data path is older than the highest we've seen
const previousHighest = highestVersionIndexForDataType[dataType]
if (dataPathIndex < previousHighest.index) {
assert.fail(
`Version regression detected in ${platform}:\n` +
` Version: ${version}\n` +
` Data type: ${dataType}\n` +
` Current path: ${dataPath} (index: ${dataPathIndex})\n` +
` Expected at least: ${previousHighest.version} (index: ${previousHighest.index})\n` +
' A newer version should not point to older data than previously seen.'
)
}
// Update the highest version if current is newer
if (dataPathIndex > previousHighest.index) {
highestVersionIndexForDataType[dataType] = {
version: dataPath,
index: dataPathIndex
}
}
}
}
})
})
})
})
@@ -1,48 +0,0 @@
/* eslint-env mocha */
const assert = require('assert')
const path = require('path')
const fs = require('fs')
describe('dataPaths.json consistency with versions.json', function () {
const dataPathsFile = path.join(__dirname, '../../../data/dataPaths.json')
const dataPaths = require(dataPathsFile)
const editions = ['pc', 'bedrock']
editions.forEach(function (edition) {
describe(`${edition} versions consistency`, function () {
const versionsFile = path.join(
__dirname,
`../../../data/${edition}/common/versions.json`
)
assert.ok(
fs.existsSync(versionsFile),
`Missing versions.json for ${edition}`
)
const versionsList = require(versionsFile)
const manifestVersions = Object.keys(dataPaths[edition] || {})
it('all manifest versions are present in versions.json', function () {
const missing = manifestVersions.filter(
(v) => !versionsList.includes(v)
)
assert.deepStrictEqual(
missing,
[],
`The following ${edition} versions are in dataPaths.json but not in versions.json: ${missing.join(', ')}`
)
})
it('no extra versions exist in versions.json compared to manifest', function () {
const extra = versionsList.filter((v) => !manifestVersions.includes(v))
assert.deepStrictEqual(
extra,
[],
`The following ${edition} versions are in versions.json but not in dataPaths.json: ${extra.join(', ')}`
)
})
})
})
})
@@ -1,8 +0,0 @@
/* eslint-env mocha */
const protocolTools = require('../compileProtocol')
describe('protocol yaml', () => {
it('should be valid and in sync with json', () => {
protocolTools.all(protocolTools.validate)
}).timeout(9000)
})
-83
View File
@@ -1,83 +0,0 @@
/* eslint-env mocha */
const assert = require('assert')
const fs = require('fs')
const path = require('path')
const Ajv = require('ajv')
const v = new Ajv({ verbose: true })
const Validator = require('protodef-validator')
Error.stackTraceLimit = 0
const data = ['attributes', 'biomes', 'commands', 'instruments', 'items', 'materials', 'blocks', 'blockCollisionShapes', 'recipes', 'windows', 'entities', 'protocol', 'version', 'effects', 'enchantments', 'language', 'foods', 'particles', 'blockLoot', 'entityLoot', 'mapIcons', 'tints', 'blockMappings', 'sounds']
require('./version_iterator')(function (p, versionString) {
describe('minecraft-data schemas ' + versionString, function () {
this.timeout(60 * 1000)
data.forEach(function (dataName) {
let instance
const pFile = path.join(p, dataName + '.json')
if (fs.existsSync(pFile)) {
instance = require(pFile)
}
if (instance) {
it(dataName + '.json is valid', function () {
// Skip tints schema validation for PC 1.21.4, as it doesn't meet the
// maxItems: 1 check for the constant tints.
if (dataName === 'tints' && versionString === 'pc 1.21.4') {
this.skip()
}
if (dataName === 'protocol') {
const validator = new Validator()
instance.types.LatinString = 'native' // TODO: Update protodef validator
validator.addType('entityMetadataItem', require('../../../schemas/protocol_types/entity_metadata_item.json'))
validator.addType('entityMetadataLoop', require('../../../schemas/protocol_types/entity_metadata_loop.json'))
validator.validateProtocol(instance)
} else {
const schema = require('../../../schemas/' + dataName + '_schema.json')
const valid = v.validate(schema, instance)
assert.ok(valid, JSON.stringify(v.errors, null, 2))
}
})
}
})
})
})
const commonData = ['protocolVersions', 'features']
const minecraftTypes = ['pc', 'bedrock']
minecraftTypes.forEach(function (type) {
describe('minecraft-data schemas of common data of ' + type, function () {
this.timeout(60 * 1000)
commonData.forEach(function (dataName) {
it(dataName + '.json is valid', function () {
const instance = require('../../../data/' + type + '/common/' + dataName + '.json')
const schema = require('../../../schemas/' + dataName + '_schema.json')
const valid = v.validate(schema, instance)
assert.ok(valid, JSON.stringify(v.errors, null, 2))
})
})
})
describe('features.json quality is good', function () {
it('there is no duplicate feature in features.json', () => {
const features = require('../../../data/' + type + '/common/features.json')
const countPerFeature = {}
for (const feature of features) {
countPerFeature[feature.name] = countPerFeature[feature.name] ? countPerFeature[feature.name] + 1 : 1
}
let duplicateCount = 0
for (const [name, count] of Object.entries(countPerFeature)) {
if (count > 1) {
console.log(`feature ${name} is duplicated ${count} times, please remove ${count - 1}`)
duplicateCount += 1
}
}
assert.equal(duplicateCount, 0, `${duplicateCount} duplicates found. Please remove them.`)
})
})
})
@@ -1,14 +0,0 @@
module.exports = forEach
const path = require('path')
const minecraftTypes = ['pc', 'bedrock']
function forEach (f) {
minecraftTypes.forEach(function (type) {
const versions = require('../../../data/' + type + '/common/versions')
versions.forEach(function (version) {
f(path.join(__dirname, '../../../data', type, version), type + ' ' + version)
})
})
}