diff --git a/src/main/kotlin/com/demonwav/mcdev/i18n/I18nAnnotator.kt b/src/main/kotlin/com/demonwav/mcdev/i18n/I18nAnnotator.kt index cc7169b52..748af410c 100644 --- a/src/main/kotlin/com/demonwav/mcdev/i18n/I18nAnnotator.kt +++ b/src/main/kotlin/com/demonwav/mcdev/i18n/I18nAnnotator.kt @@ -15,7 +15,7 @@ import com.demonwav.mcdev.i18n.intentions.RemoveUnmatchedEntryIntention import com.demonwav.mcdev.i18n.intentions.TrimKeyIntention import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes -import com.demonwav.mcdev.util.mcDomain +import com.demonwav.mcdev.util.resourceDomain import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.openapi.util.TextRange @@ -50,7 +50,7 @@ class I18nAnnotator : Annotator { } private fun checkEntryMatchesDefault(entry: I18nEntry, annotations: AnnotationHolder) { - if (entry.project.findDefaultLangEntries(domain = entry.containingFile.virtualFile.mcDomain).any { it.key == entry.key }) { + if (entry.project.findDefaultLangEntries(domain = entry.containingFile.virtualFile.resourceDomain).any { it.key == entry.key }) { return } annotations.createWarningAnnotation(entry.textRange, "Translation key not included in default localization file.") diff --git a/src/main/kotlin/com/demonwav/mcdev/i18n/I18nEditorNotificationProvider.kt b/src/main/kotlin/com/demonwav/mcdev/i18n/I18nEditorNotificationProvider.kt index 7ab523d2a..ea1482cbb 100644 --- a/src/main/kotlin/com/demonwav/mcdev/i18n/I18nEditorNotificationProvider.kt +++ b/src/main/kotlin/com/demonwav/mcdev/i18n/I18nEditorNotificationProvider.kt @@ -16,7 +16,7 @@ import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes import com.demonwav.mcdev.i18n.sorting.I18nSorter import com.demonwav.mcdev.i18n.sorting.Ordering import com.demonwav.mcdev.util.applyWriteAction -import com.demonwav.mcdev.util.mcDomain +import com.demonwav.mcdev.util.resourceDomain import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.editor.colors.EditorColorsManager import com.intellij.openapi.fileEditor.FileEditor @@ -80,7 +80,7 @@ class I18nEditorNotificationProvider(private val project: Project) : EditorNotif } private fun getMissingEntries(file: VirtualFile): Map { - val defaultEntries = project.findDefaultLangEntries(scope = Scope.PROJECT, domain = file.mcDomain) + val defaultEntries = project.findDefaultLangEntries(scope = Scope.PROJECT, domain = file.resourceDomain) val entries = project.findLangEntries(file = file, scope = Scope.PROJECT) val keys = entries.map { it.key } val missingEntries = defaultEntries.associate { it.key to it }.toMutableMap() diff --git a/src/main/kotlin/com/demonwav/mcdev/i18n/I18nElementFactory.kt b/src/main/kotlin/com/demonwav/mcdev/i18n/I18nElementFactory.kt index 51cf3810d..f02d260ee 100644 --- a/src/main/kotlin/com/demonwav/mcdev/i18n/I18nElementFactory.kt +++ b/src/main/kotlin/com/demonwav/mcdev/i18n/I18nElementFactory.kt @@ -15,7 +15,7 @@ import com.demonwav.mcdev.i18n.lang.I18nFileType import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes import com.demonwav.mcdev.util.applyWriteAction -import com.demonwav.mcdev.util.mcDomain +import com.demonwav.mcdev.util.resourceDomain import com.intellij.ide.DataManager import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.module.Module @@ -50,7 +50,7 @@ object I18nElementFactory { val files = FileTypeIndex.getFiles(I18nFileType, GlobalSearchScope.moduleScope(module)) if (files.count { it.nameWithoutExtension.toLowerCase(Locale.ROOT) == I18nConstants.DEFAULT_LOCALE } > 1) { - val choices = files.mapNotNull { it.mcDomain }.distinct().sorted() + val choices = files.mapNotNull { it.resourceDomain }.distinct().sorted() val swingList = JBList(choices) DataManager.getInstance().dataContextFromFocus.doWhenDone(Consumer { JBPopupFactory.getInstance() diff --git a/src/main/kotlin/com/demonwav/mcdev/i18n/i18n-utils.kt b/src/main/kotlin/com/demonwav/mcdev/i18n/i18n-utils.kt index a2fd15d3c..908bea4ad 100644 --- a/src/main/kotlin/com/demonwav/mcdev/i18n/i18n-utils.kt +++ b/src/main/kotlin/com/demonwav/mcdev/i18n/i18n-utils.kt @@ -13,7 +13,7 @@ package com.demonwav.mcdev.i18n import com.demonwav.mcdev.i18n.lang.I18nFile import com.demonwav.mcdev.i18n.lang.I18nFileType import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry -import com.demonwav.mcdev.util.mcDomain +import com.demonwav.mcdev.util.resourceDomain import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager @@ -47,7 +47,7 @@ fun Project.findLangEntries(scope: Scope = Scope.GLOBAL, key: String? = null, fi { it.virtualFile != null && (file == null || it.virtualFile.path == file.path) - && (domain == null || it.virtualFile.mcDomain == domain) + && (domain == null || it.virtualFile.resourceDomain == domain) }, { key == null || it.key == key } ) @@ -58,7 +58,7 @@ fun Project.findDefaultLangEntries(scope: Scope = Scope.GLOBAL, key: String? = n { it.virtualFile != null && it.virtualFile.nameWithoutExtension.toLowerCase(Locale.ROOT) == I18nConstants.DEFAULT_LOCALE && (file == null || it.virtualFile.path == file.path) - && (domain == null || it.virtualFile.mcDomain == domain) + && (domain == null || it.virtualFile.resourceDomain == domain) }, { key == null || it.key == key } ) @@ -69,4 +69,4 @@ fun Project.findDefaultLangFile(domain: String? = null) = I18nConstants.DEFAULT_LOCALE_FILE, false, GlobalSearchScope.projectScope(this) - ).firstOrNull { domain == null || it.mcDomain == domain } + ).firstOrNull { domain == null || it.resourceDomain == domain } diff --git a/src/main/kotlin/com/demonwav/mcdev/i18n/lang/I18nCompletionContributor.kt b/src/main/kotlin/com/demonwav/mcdev/i18n/lang/I18nCompletionContributor.kt index 5bb69be11..e4c69bfec 100644 --- a/src/main/kotlin/com/demonwav/mcdev/i18n/lang/I18nCompletionContributor.kt +++ b/src/main/kotlin/com/demonwav/mcdev/i18n/lang/I18nCompletionContributor.kt @@ -17,7 +17,7 @@ import com.demonwav.mcdev.i18n.findDefaultLangEntries import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes import com.demonwav.mcdev.util.getSimilarity -import com.demonwav.mcdev.util.mcDomain +import com.demonwav.mcdev.util.resourceDomain import com.intellij.codeInsight.completion.CompletionContributor import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet @@ -55,7 +55,7 @@ class I18nCompletionContributor : CompletionContributor() { if (KEY_PATTERN.accepts(position) || DUMMY_PATTERN.accepts(position)) { val text = position.text.let { it.substring(0, it.length - CompletionUtil.DUMMY_IDENTIFIER.length) } - val domain = file.mcDomain + val domain = file.resourceDomain handleKey(text, position, domain, result) } } diff --git a/src/main/kotlin/com/demonwav/mcdev/i18n/sorting/I18nSorter.kt b/src/main/kotlin/com/demonwav/mcdev/i18n/sorting/I18nSorter.kt index ab1fd538c..5ab3bf1d3 100644 --- a/src/main/kotlin/com/demonwav/mcdev/i18n/sorting/I18nSorter.kt +++ b/src/main/kotlin/com/demonwav/mcdev/i18n/sorting/I18nSorter.kt @@ -17,7 +17,7 @@ import com.demonwav.mcdev.i18n.findDefaultLangFile import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes import com.demonwav.mcdev.util.lexicographical -import com.demonwav.mcdev.util.mcDomain +import com.demonwav.mcdev.util.resourceDomain import com.demonwav.mcdev.util.runWriteAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement @@ -32,7 +32,7 @@ object I18nSorter { private val descendingComparator = ascendingComparator.reversed() fun query(project: Project, file: PsiFile, defaultSelection: Ordering = Ordering.ASCENDING) { - val defaultFileMissing = project.findDefaultLangFile(file.virtualFile.mcDomain ?: return) == null + val defaultFileMissing = project.findDefaultLangFile(file.virtualFile.resourceDomain ?: return) == null val isDefaultFile = file.name == I18nConstants.DEFAULT_LOCALE_FILE val (order, comments) = TranslationSortOrderDialog.show(defaultFileMissing || isDefaultFile, defaultSelection) if (order == null) { @@ -47,7 +47,7 @@ object I18nSorter { Ordering.ASCENDING -> I18nElementFactory.assembleElements(project, it.sortedWith(ascendingComparator), keepComments) Ordering.DESCENDING -> I18nElementFactory.assembleElements(project, it.sortedWith(descendingComparator), keepComments) Ordering.TEMPLATE -> sortByTemplate(project, TemplateManager.getProjectTemplate(project), it, keepComments) - else -> sortByTemplate(project, buildDefaultTemplate(project, file.virtualFile.mcDomain) ?: return, it, keepComments) + else -> sortByTemplate(project, buildDefaultTemplate(project, file.virtualFile.resourceDomain) ?: return, it, keepComments) } } diff --git a/src/main/kotlin/com/demonwav/mcdev/json/schema_providers.kt b/src/main/kotlin/com/demonwav/mcdev/json/schema_providers.kt new file mode 100644 index 000000000..a8e49f419 --- /dev/null +++ b/src/main/kotlin/com/demonwav/mcdev/json/schema_providers.kt @@ -0,0 +1,58 @@ +/* + * Minecraft Dev for IntelliJ + * + * https://minecraftdev.org + * + * Copyright (c) 2018 minecraft-dev + * + * MIT License + */ + +package com.demonwav.mcdev.json + +import com.demonwav.mcdev.util.resourceDomain +import com.demonwav.mcdev.util.resourcePath +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider +import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory +import com.jetbrains.jsonSchema.extension.SchemaType + +class SchemaProviderFactory : JsonSchemaProviderFactory { + override fun getProviders(project: Project) = + listOf( + SoundsSchemaProvider(), + PathBasedSchemaProvider("Minecraft Blockstates JSON", "blockstates", "blockstates/"), + PathBasedSchemaProvider("Minecraft Item Model JSON", "model_item", "models/item/"), + PathBasedSchemaProvider("Minecraft Block Model JSON", "model_block", "models/block/"), + PathBasedSchemaProvider("Minecraft Loot Table JSON", "loot_table", "loot_tables/"), + PathBasedSchemaProvider("Minecraft Advancement JSON", "advancement", "advancements/") + ) +} + +class SoundsSchemaProvider : JsonSchemaFileProvider { + companion object { + val FILE = JsonSchemaProviderFactory.getResourceFile(SchemaProviderFactory::class.java, "/jsonSchemas/sounds.schema.json") + } + + override fun getName() = "Minecraft Sounds JSON" + + override fun isAvailable(file: VirtualFile) = file.resourceDomain != null && file.resourcePath == "sounds.json" + + override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema + + override fun getSchemaFile(): VirtualFile = FILE +} + +class PathBasedSchemaProvider(name: String, schema: String, private val path: String) : JsonSchemaFileProvider { + private val _name = name + private val file = JsonSchemaProviderFactory.getResourceFile(SchemaProviderFactory::class.java, "/jsonSchemas/$schema.schema.json") + + override fun getName() = this._name + + override fun isAvailable(file: VirtualFile) = file.resourceDomain != null && file.resourcePath?.startsWith(path) == true + + override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema + + override fun getSchemaFile(): VirtualFile = file +} diff --git a/src/main/kotlin/com/demonwav/mcdev/util/files.kt b/src/main/kotlin/com/demonwav/mcdev/util/files.kt index 520b2a301..d7d7fe210 100644 --- a/src/main/kotlin/com/demonwav/mcdev/util/files.kt +++ b/src/main/kotlin/com/demonwav/mcdev/util/files.kt @@ -29,10 +29,14 @@ val VirtualFile.manifest: Manifest? } // Technically resource domains are much more restricted ([a-z0-9_-]+) in modern versions, but we want to support as much as possible -private val DOMAIN_PATTERN = Regex("^.*?/assets/([^/]+)/lang.*?$") +private val RESOURCE_PATTERN = Regex("^.*?/assets/([^/]+)/(.*?)\$") + +val VirtualFile.resourceDomain: String? + get() = RESOURCE_PATTERN.matchEntire(this.path)?.groupValues?.get(1) + +val VirtualFile.resourcePath: String? + get() = RESOURCE_PATTERN.matchEntire(this.path)?.groupValues?.get(2) -val VirtualFile.mcDomain: String? - get() = DOMAIN_PATTERN.matchEntire(this.path)?.groupValues?.get(1) operator fun Manifest.get(attribute: String): String? = mainAttributes.getValue(attribute) operator fun Manifest.get(attribute: Attributes.Name): String? = mainAttributes.getValue(attribute) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 336e40868..9c4d094d5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -701,6 +701,10 @@ + + + + com.demonwav.mcdev.i18n.I18nFileListener diff --git a/src/main/resources/jsonSchemas/advancement.schema.json b/src/main/resources/jsonSchemas/advancement.schema.json new file mode 100644 index 000000000..e6aaf2c6b --- /dev/null +++ b/src/main/resources/jsonSchemas/advancement.schema.json @@ -0,0 +1,112 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Advancement JSON", + "type": "object", + "properties": { + "display": { + "type": "object", + "properties": { + "icon": { + "type": "object", + "properties": { + "item": { "type": "string" }, + "data": { "type": "integer", "minimum": 0, "maximum": 32767 } + }, + "required": [ "item" ] + }, + "title": { "$ref": "common.json#/textComponent" }, + "frame": { + "anyOf": [ + { "type": "string" }, + { "enum": [ "task", "goal", "challenge" ], "default": "task" } + ] + }, + "background": { "type": "string" }, + "description": { "$ref": "common.json#/textComponent" }, + "show_toast": { "type": "boolean" }, + "announce_to_chat": { "type": "boolean" }, + "hidden": { "type": "boolean" } + }, + "required": [ "title", "description", "icon" ] + }, + "parent": { "type": "string" }, + "criteria": { + "type": "object", + "additionalProperties": { "$ref": "#/definitions/trigger" }, + "minProperties": 1 + }, + "requirements": { + "type": "array", + "items": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + } + }, + "rewards": { + "type": "object", + "properties": { + "recipes": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "loot": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "experience": { "type": "integer" }, + "function": { "type": "string" } + } + } + }, + "require": [ "criteria" ], + "definitions": { + "trigger": { + "type": "object", + "properties": { + "trigger": { + "anyOf": [ + { "type": "string" }, + { + "type": "string", + "enum": [ + "minecraft:bred_animals", + "minecraft:brewed_potion", + "minecraft:changed_dimension", + "minecraft:channeled_lightning", + "minecraft:construct_beacon", + "minecraft:consume_item", + "minecraft:cured_zombie_villager", + "minecraft:effects_changed", + "minecraft:enchanted_item", + "minecraft:enter_block", + "minecraft:entity_hurt_player", + "minecraft:entity_killed_player", + "minecraft:filled_bucket", + "minecraft:fishing_rod_hooked", + "minecraft:impossible", + "minecraft:inventory_changed", + "minecraft:item_durability_changed", + "minecraft:levitation", + "minecraft:location", + "minecraft:nether_travel", + "minecraft:nether_travel", + "minecraft:placed_block", + "minecraft:player_hurt_entity", + "minecraft:player_killed_entity", + "minecraft:recipe_unlocked", + "minecraft:slept_in_bed", + "minecraft:summoned_entity", + "minecraft:tame_animal", + "minecraft:tick", + "minecraft:used_ender_eye", + "minecraft:used_totem", + "minecraft:villager_trade" + ] + } + ] + } + }, + "required": [ "trigger" ] + } + } +} diff --git a/src/main/resources/jsonSchemas/blockstates.schema.json b/src/main/resources/jsonSchemas/blockstates.schema.json new file mode 100644 index 000000000..9989a4103 --- /dev/null +++ b/src/main/resources/jsonSchemas/blockstates.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Blockstates JSON", + "oneOf": [ + { + "allOf": [ + { "required": [ "forge_marker" ] }, + { "$ref": "blockstates_forge.schema.json" } + ] + }, + { + "allOf": [ + { "not": { "required": [ "forge_marker" ] } }, + { "$ref": "blockstates_vanilla.schema.json" } + ] + } + ] +} diff --git a/src/main/resources/jsonSchemas/blockstates_common.schema.json b/src/main/resources/jsonSchemas/blockstates_common.schema.json new file mode 100644 index 000000000..63ab4f5b1 --- /dev/null +++ b/src/main/resources/jsonSchemas/blockstates_common.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Blockstates JSON", + "baseVariant": { + "type": "object", + "properties": { + "model": { "type": "string" }, + "textures": { + "type": "object", + "additionalProperties": { "type": "string" } + }, + "x": { + "type": "number", + "multipleOf": 22.5 + }, + "y": { + "type": "number", + "multipleOf": 22.5 + }, + "uvlock": { "type": "boolean" } + } + } +} diff --git a/src/main/resources/jsonSchemas/blockstates_forge.schema.json b/src/main/resources/jsonSchemas/blockstates_forge.schema.json new file mode 100644 index 000000000..a21c28df9 --- /dev/null +++ b/src/main/resources/jsonSchemas/blockstates_forge.schema.json @@ -0,0 +1,171 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Forge Blockstates JSON", + "type": "object", + "properties": { + "forge_marker": { + "type": "integer", + "enum": [ 1 ] + }, + "variants": { "$ref": "#/definitions/variants" }, + "defaults": { "$ref": "#/definitions/variantObject" } + }, + "required": [ "forge_marker" ], + "definitions": { + "variants": { + "type": "object", + "patternProperties": { + "^([a-z0-9_]+=[^,]*,)*([a-z0-9_]+=[^,]*)$": { + "$ref": "#/definitions/variant" + }, + "^[a-z0-9_]+$": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { "$ref": "#/definitions/variant" } + }, + { + "type": "array", + "items": { "$ref": "#/definitions/variantObject" } + } + ] + } + }, + "additionalProperties": false + }, + "variant": { + "anyOf": [ + { "$ref": "#/definitions/variantObject" }, + { + "type": "array", + "items": { "$ref": "#/definitions/variantObject" } + } + ] + }, + "variantObject": { + "allOf": [ + { "$ref": "blockstates_common.schema.json#/baseVariant" }, + { + "properties": { + "transform": { "$ref": "#/definitions/rootTransform" }, + "weight": { "type": "number" }, + "submodel": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/variant" + } + } + ] + }, + "custom": { + "type": "object", + "additionalProperties": true + } + } + } + ] + }, + "rootTransform": { + "oneOf": [ + { "$ref": "#/definitions/transform" }, + { + "type": "object", + "patternProperties": { + "(third|first)person_(left|right)hand": { "$ref": "#/definitions/transform" }, + "gui|head|ground|fixed": { "$ref": "#/definitions/transform" } + }, + "additionalProperties": false + } + ] + }, + "transform": { + "oneOf": [ + { + "type": "string", + "enum": [ "identity", "forge:default-block", "forge:default-item", "forge:default-tool" ] + }, + { + "type": "object", + "required": [ "matrix" ], + "properties": { + "matrix": { "$ref": "#/definitions/transformMatrix" } + }, + "additionalProperties": false + }, + { "$ref": "#/definitions/transformMatrix" }, + { + "type": "object", + "properties": { + "translation": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { "type": "number" } + }, + "scale": { + "oneOf": [ + { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { "type": "number" } + }, + { "type": "number" } + ] + }, + "rotation": { "$ref": "#/definitions/transformRotation" }, + "post-rotation": { "$ref": "#/definitions/transformRotation" } + }, + "additionalProperties": false + } + ] + }, + "transformMatrix": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { "type": "number" } + } + }, + "transformRotation": { + "oneOf": [ + { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { "type": "number" } + }, + { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "x": { "type": "number" }, + "y": { "type": "number" }, + "z": { "type": "number" } + } + }, + { + "type": "array", + "items": { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "properties": { + "x": { "type": "number" }, + "y": { "type": "number" }, + "z": { "type": "number" } + } + } + } + ] + } + } +} diff --git a/src/main/resources/jsonSchemas/blockstates_vanilla.schema.json b/src/main/resources/jsonSchemas/blockstates_vanilla.schema.json new file mode 100644 index 000000000..830425cd0 --- /dev/null +++ b/src/main/resources/jsonSchemas/blockstates_vanilla.schema.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Vanilla Blockstates JSON", + "type": "object", + "properties": { + "variants": { + "type": "object", + "patternProperties": { + "^([a-z0-9_]+=[^,]*,)*([a-z0-9_]+=[^,]*)$": { "$ref": "#/definitions/variant" } + }, + "additionalProperties": false + }, + "multipart": { + "type": "array", + "items": { "$ref": "#/definitions/multipartCase" } + } + }, + "dependencies": { + "variants": { + "required": [ "variants" ], + "not": { "title": "Multipart and full variant definitions are mutually exclusive", "required": [ "multipart" ] } + }, + "multipart": { + "required": [ "multipart" ], + "not": { "title": "Multipart and full variant definitions are mutually exclusive", "required": [ "variants" ] } + } + }, + "definitions": { + "variant": { + "anyOf": [ + { + "allOf": [ + { "$ref": "blockstates_common.schema.json#/baseVariant" }, + { "required": [ "model" ] } + ] + }, + { + "type": "array", + "items": { + "allOf": [ + { "$ref": "blockstates_common.schema.json#/baseVariant" }, + { "required": [ "model" ] }, + { + "properties": { + "weight": { + "type": "number" + } + } + } + ] + } + } + ] + }, + "multipartCase": { + "type": "object", + "properties": { + "apply": { + "$ref": "#/definitions/variant" + }, + "when": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + "^[a-z0-9_]*$": { } + }, + "additionalProperties": false, + "not": { "required": [ "OR" ] } + }, + { + "type": "object", + "properties": { + "OR": { + "type": "array", + "items": { + "type": "object", + "patternProperties": { + "^[a-z0-9_]*$": { } + }, + "additionalProperties": false + } + } + }, + "required": [ "OR" ], + "additionalProperties": false + } + ] + } + }, + "required": [ "apply" ] + } + } +} diff --git a/src/main/resources/jsonSchemas/common.schema.json b/src/main/resources/jsonSchemas/common.schema.json new file mode 100644 index 000000000..3eaa88183 --- /dev/null +++ b/src/main/resources/jsonSchemas/common.schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "integerRange": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "properties": { + "min": { "type": "integer" }, + "max": { "type": "integer" } + }, + "required": [ "min", "max" ], + "additionalProperties": false + } + ] + }, + "floatRange": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "object", + "properties": { + "min": { "type": "number" }, + "max": { "type": "number" } + }, + "required": [ "min", "max" ], + "additionalProperties": false + } + ] + }, + "equipmentSlot": { "type": "string", "enum": [ "mainhand", "offhand", "feet", "legs", "chest", "head" ] }, + "equipmentSlots": { + "oneOf": [ + { "$ref": "#/equipmentSlot" }, + { "type": "array", "items": { "$ref": "#/equipmentSlot" } } + ] + }, + "textComponent": { + "anyOf": [ + { "type": "string" }, + { "type": "object" } + ] + } +} diff --git a/src/main/resources/jsonSchemas/loot_table.schema.json b/src/main/resources/jsonSchemas/loot_table.schema.json new file mode 100644 index 000000000..d892aef55 --- /dev/null +++ b/src/main/resources/jsonSchemas/loot_table.schema.json @@ -0,0 +1,210 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Loot Table JSON", + "type": "object", + "properties": { + "pools": { + "type": "array", + "items": { + "type": "object", + "properties": { + "conditions": { "$ref": "#/definitions/conditions" }, + "rolls": { "$ref": "common.schema.json#/integerRange" }, + "bonusRolls": { "$ref": "common.schema.json#/integerRange" }, + "entries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "conditions": { "$ref": "#/definitions/conditions" }, + "type": { "type": "string" }, + "weight": { "type": "number" }, + "quality": { "type": "number" } + }, + "required": [ "type" ], + "anyOf": [ + { + "allOf": [ + { "properties": { "type": { "enum": [ "item" ] } } }, + { + "properties": { + "name": { "type": "string" }, + "functions": { "$ref": "#/definitions/functions" } + }, + "required": [ "name" ] + } + ] + }, + { + "allOf": [ + { "properties": { "type": { "enum": [ "loot_table" ] } } }, + { + "properties": { + "name": { "type": "string" } + }, + "required": [ "name" ] + } + ] + }, + { } + ] + } + } + }, + "required": [ "entries" ] + } + } + }, + "definitions": { + "conditions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "condition": { + "anyOf": [ + { "type": "string" }, + { + "type": "string", + "enum": [ + "entity_properties", + "entity_scores", + "killed_by_player", + "random_chance", + "random_chance_with_looting" + ] + } + ] + } + }, + "required": [ "condition" ], + "anyOf": [ + { + "allOf": [ + { "properties": { "condition": { "enum": [ "entity_properties" ] } } }, + { "$ref": "loot_table_conditions.schema.json#/entityProperties" } + ] + }, + { + "allOf": [ + { "properties": { "condition": { "enum": [ "entity_scores" ] } } }, + { "$ref": "loot_table_conditions.schema.json#/entityScores" } + ] + }, + { + "allOf": [ + { "properties": { "condition": { "enum": [ "killed_by_player" ] } } }, + { "$ref": "loot_table_conditions.schema.json#/killedByPlayer" } + ] + }, + { + "allOf": [ + { "properties": { "condition": { "enum": [ "random_chance" ] } } }, + { "$ref": "loot_table_conditions.schema.json#/randomChance" } + ] + }, + { + "allOf": [ + { "properties": { "condition": { "enum": [ "random_chance_with_looting" ] } } }, + { "$ref": "loot_table_conditions.schema.json#/randomChanceWithLooting" } + ] + }, + { } + ] + } + }, + "functions": { + "type": "array", + "items": { "$ref": "#/definitions/function" } + }, + "function": { + "type": "object", + "properties": { + "function": { + "anyOf": [ + { "type": "string" }, + { + "type": "string", + "enum": [ + "enchant_randomly", + "enchant_with_levels", + "exploration_map", + "furnace_smelt", + "looting_enchant", + "set_attributes", + "set_count", + "set_damage", + "set_data", + "set_nbt" + ] + } + ] + }, + "conditions": { "$ref": "#/definitions/conditions" } + }, + "required": [ "function" ], + "anyOf": [ + { + "allOf": [ + { "properties": { "function": { "enum": [ "enchant_randomly" ] } } }, + { "$ref": "loot_table_functions.schema.json#/enchantRandomly" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "enchant_with_levels" ] } } }, + { "$ref": "loot_table_functions.schema.json#/enchantWithLevels" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "exploration_map" ] } } }, + { "$ref": "loot_table_functions.schema.json#/explorationMap" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "furnace_smelt" ] } } } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "looting_enchant" ] } } }, + { "$ref": "loot_table_functions.schema.json#/lootingEnchant" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "set_attributes" ] } } }, + { "$ref": "loot_table_functions.schema.json#/setAttributes" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "set_count" ] } } }, + { "$ref": "loot_table_functions.schema.json#/setCount" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "set_damage" ] } } }, + { "$ref": "loot_table_functions.schema.json#/setDamage" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "set_data" ] } } }, + { "$ref": "loot_table_functions.schema.json#/setData" } + ] + }, + { + "allOf": [ + { "properties": { "function": { "enum": [ "set_nbt" ] } } }, + { "$ref": "loot_table_functions.schema.json#/setNbt" } + ] + }, + { } + ] + } + } +} diff --git a/src/main/resources/jsonSchemas/loot_table_conditions.schema.json b/src/main/resources/jsonSchemas/loot_table_conditions.schema.json new file mode 100644 index 000000000..b50483727 --- /dev/null +++ b/src/main/resources/jsonSchemas/loot_table_conditions.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "entityProperties": { + "properties": { + "entity": { + "type": "string", + "enum": [ "this", "killer", "killer_player" ] + }, + "properties": { + "type": "object", + "properties": { + "on_fire": { "type": "boolean" } + } + } + } + }, + "entityScores": { + "properties": { + "entity": { + "type": "string", + "enum": [ "this", "killer", "killer_player" ] + }, + "scores": { + "type": "object", + "additionalProperties": { "$ref": "common.schema.json#/integerRange" } + } + } + }, + "killedByPlayer": { + "properties": { + "on_fire": { "type": "inverse" } + } + }, + "randomChance": { + "properties": { + "chance": { "type": "number", "minimum": 0, "maximum": 1 } + } + }, + "randomChanceWithLooting": { + "properties": { + "chance": { "type": "number", "minimum": 0, "maximum": 1 }, + "looting_multiplier": { "type": "number" } + } + } +} diff --git a/src/main/resources/jsonSchemas/loot_table_functions.schema.json b/src/main/resources/jsonSchemas/loot_table_functions.schema.json new file mode 100644 index 000000000..55ae4b996 --- /dev/null +++ b/src/main/resources/jsonSchemas/loot_table_functions.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "enchantRandomly": { + "properties": { + "enchantments": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "enchantWithLevels": { + "properties": { + "treasure": { "type": "boolean" }, + "levels": { "$ref": "common.schema.json#/integerRange" } + } + }, + "explorationMap": { + "properties": { + "destination": { "type": "string" }, + "decoration": { "type": "string" }, + "zoom": { "type": "integer" }, + "search_radius": { "type": "integer" }, + "skip_existing_chunks": { "type": "boolean" } + } + }, + "lootingEnchant": { + "properties": { + "limit": { "type": "integer", "minimum": 0 }, + "count": { "$ref": "common.schema.json#/integerRange" } + } + }, + "setAttributes": { + "properties": { + "modifiers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "attribute": { "type": "string" }, + "operation": { "type": "string", "enum": [ "addition", "multiply_base", "multiply_total" ] }, + "amount": { "$ref": "common.schema.json#/floatRange" }, + "id": { "type": "string" }, + "slot": { "$ref": "common.schema.json#/equipmentSlots" } + }, + "additionalProperties": false, + "required": [ "name", "attribute", "operation", "amount" ] + } + } + } + }, + "setCount": { + "properties": { + "count": { "$ref": "common.schema.json#/integerRange" } + }, + "required": [ "count" ] + }, + "setDamage": { + "properties": { + "damage": { "$ref": "common.schema.json#/floatRange" } + }, + "required": [ "damage" ] + }, + "setData": { + "properties": { + "data": { "$ref": "common.schema.json#/integerRange" } + }, + "required": [ "data" ] + }, + "setNbt": { + "properties": { + "tag": { "type": "string" } + }, + "required": [ "tag" ] + } +} diff --git a/src/main/resources/jsonSchemas/model_block.schema.json b/src/main/resources/jsonSchemas/model_block.schema.json new file mode 100644 index 000000000..464aac96b --- /dev/null +++ b/src/main/resources/jsonSchemas/model_block.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Block Model JSON", + "type": "object", + "properties": { + "parent": { "type": "string" }, + "textures": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "ambientocclusion": { "type": "boolean" }, + "elements": { "$ref": "model_common.schema.json#/elements" }, + "display": { "$ref": "model_common.schema.json#/transforms" } + } +} diff --git a/src/main/resources/jsonSchemas/model_common.schema.json b/src/main/resources/jsonSchemas/model_common.schema.json new file mode 100644 index 000000000..0bd9bfd3f --- /dev/null +++ b/src/main/resources/jsonSchemas/model_common.schema.json @@ -0,0 +1,108 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "elements": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { "$ref": "#/elementCoord" }, + "to": { "$ref": "#/elementCoord" }, + "rotation": { + "type": "object", + "properties": { + "origin": { "$ref": "#/elementCoord" }, + "axis": { + "type": "string", + "enum": [ "x", "y", "z" ] + }, + "angle": { + "type": "number", + "multipleOf": 22.5, + "minimum": -45, + "maximum": 45 + } + }, + "additionalProperties": false, + "required": [ "axis" ] + }, + "faces": { + "type": "object", + "patternProperties": { + "down|up|north|south|west|east": { + "type": "object", + "properties": { + "uv": { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { + "type": "number" + } + }, + "texture": { + "type": "string", + "pattern": "^#.*?" + }, + "cullface": { + "type": "string", + "enum": [ "down", "up", "north", "south", "west", "east" ] + }, + "rotation": { + "type": "integer", + "enum": [ 0, 90, 180, 270 ] + }, + "tintindex": { + "type": "integer" + } + } + } + }, + "additionalProperties": false + } + }, + "required": [ "from", "to" ] + } + }, + "elementCoord": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "number", + "minimum": -16, + "maximum": 32 + } + }, + "transforms": { + "type": "object", + "patternProperties": { + "(third|first)person_(left|right)hand": { "$ref": "#/transform" }, + "gui|head|ground|fixed": { "$ref": "#/transform" } + }, + "additionalProperties": false + }, + "transform": { + "type": "object", + "properties": { + "rotation": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { "type": "number" } + }, + "translation": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { "type": "number", "minimum": -80, "maximum": 80 } + }, + "scale": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { "type": "number", "minimum": -4, "maximum": 4 } + } + }, + "additionalProperties": false + } +} diff --git a/src/main/resources/jsonSchemas/model_item.schema.json b/src/main/resources/jsonSchemas/model_item.schema.json new file mode 100644 index 000000000..adf2780b6 --- /dev/null +++ b/src/main/resources/jsonSchemas/model_item.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Minecraft Item Model JSON", + "type": "object", + "properties": { + "parent": { "type": "string" }, + "textures": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "elements": { "$ref": "model_common.schema.json#/elements" }, + "display": { "$ref": "model_common.schema.json#/transforms" }, + "overrides": { + "type": "array", + "items": { + "type": "object", + "properties": { + "predicate": { + "type": "object", + "additionalProperties": { "type": "number" } + }, + "model": { "type": "string" } + }, + "additionalProperties": false, + "required": [ "predicate", "model" ] + } + } + } +} diff --git a/src/main/resources/jsonSchemas/sounds.schema.json b/src/main/resources/jsonSchemas/sounds.schema.json new file mode 100644 index 000000000..f41341851 --- /dev/null +++ b/src/main/resources/jsonSchemas/sounds.schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Minecraft Sounds JSON", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "category": { "type": "string" }, + "replace": { "type": "boolean" }, + "subtitle": { "type": "string" }, + "sounds": { + "type": "array", + "items": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "name": { "type": "string" }, + "volume": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 1 + }, + "pitch": { + "type": "number", + "default": 1 + }, + "weight": { + "type": "number", + "default": 1 + }, + "stream": { + "type": "boolean", + "default": false + }, + "type": { + "enum": ["sound", "event"], + "default": "sound" + } + } + } + ] + }, + "uniqueItems": true + } + } + } +}