From 5dfe97bc147dffcaa425750dd0bdf2eed10aa605 Mon Sep 17 00:00:00 2001 From: mastriel Date: Sat, 18 Jan 2025 23:41:00 -0700 Subject: [PATCH 01/10] work on 1.21.4 item model support, add automatic builds --- .github/workflows/build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81a4c2c..439d8b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,10 +39,8 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - with: - gradle-version: '8.8' - - name: Build with Gradle 8.8 + - name: Build with Gradle 8.12 run: gradle -Pversion=${{steps.version.outputs.version}} build - name: Upload a Build Artifact From b423ce4a11c95d4a4d8ce48749dcb133991495f4 Mon Sep 17 00:00:00 2001 From: mastriel Date: Fri, 31 Jan 2025 20:13:45 -0700 Subject: [PATCH 02/10] finish 1.21.4 model support --- .../mastriel/cutapi/item/ItemDescriptor.kt | 34 +++++++--- .../cutapi/item/behaviors/VanillaTool.kt | 8 +-- .../xyz/mastriel/cutapi/resources/Resource.kt | 11 ++- .../cutapi/resources/ResourcePackManager.kt | 4 +- .../mastriel/cutapi/resources/ResourceRef.kt | 3 +- .../mastriel/cutapi/resources/VanillaRef.kt | 8 +++ .../cutapi/resources/builtin/Texture2D.kt | 9 +-- .../cutapi/resources/builtin/TextureLike.kt | 4 ++ .../resources/process/Texture2DProcessor.kt | 67 ++++++++++++++----- .../utils/serializers/LocationSerializer.kt | 2 +- 10 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/ItemDescriptor.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/ItemDescriptor.kt index 81eae0c..734f23f 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/ItemDescriptor.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/ItemDescriptor.kt @@ -114,11 +114,15 @@ public sealed class ItemTexture { public abstract fun isAvailable(): Boolean public abstract fun getRef(): ResourceRef<*> public abstract fun getItemModelId(): Identifier + public abstract val showSwapAnimation: Boolean - public data class Texture(val texture: ResourceRef) : ItemTexture() { + public data class Texture(val texture: ResourceRef, override val showSwapAnimation: Boolean) : + ItemTexture() { override fun getItemModelId(): Identifier { - return texture.getResource()?.getItemModel()?.toIdentifier() ?: unknownID() + val id = texture.getResource()?.getItemModel()?.toIdentifier() ?: unknownID() + val swapMode = if (showSwapAnimation) "__swap" else "__noswap" + return id("${id}${swapMode}") } override fun isAvailable(): Boolean = texture.isAvailable() @@ -128,12 +132,14 @@ public sealed class ItemTexture { } } - public data class Model(val model: ResourceRef) : ItemTexture() { + public data class Model(val model: ResourceRef, override val showSwapAnimation: Boolean) : ItemTexture() { override fun getItemModelId(): Identifier { - return model.getResource()?.getItemModel()?.toIdentifier() ?: unknownID() + val id = model.getResource()?.getItemModel()?.toIdentifier() ?: unknownID() + val swapMode = if (showSwapAnimation) "__swap" else "__noswap" + return id("${id}${swapMode}") } - + override fun isAvailable(): Boolean = model.isAvailable() override fun getRef(): ResourceRef<*> { @@ -142,17 +148,23 @@ public sealed class ItemTexture { } } -public fun itemTexture(texture: ResourceRef): ItemTexture.Texture = ItemTexture.Texture(texture) +public fun itemTexture(texture: ResourceRef, showSwapAnimation: Boolean = true): ItemTexture.Texture = + ItemTexture.Texture(texture, showSwapAnimation) -public fun itemModel(model: ResourceRef): ItemTexture.Model = ItemTexture.Model(model) +public fun itemModel(model: ResourceRef, showSwapAnimation: Boolean = true): ItemTexture.Model = + ItemTexture.Model(model, showSwapAnimation) -public fun itemTexture(plugin: CuTPlugin, path: String): ItemTexture.Texture = ItemTexture.Texture(ref(plugin, path)) +public fun itemTexture(plugin: CuTPlugin, path: String, showSwapAnimation: Boolean = true): ItemTexture.Texture = + ItemTexture.Texture(ref(plugin, path), showSwapAnimation) -public fun itemModel(plugin: CuTPlugin, path: String): ItemTexture.Model = ItemTexture.Model(ref(plugin, path)) +public fun itemModel(plugin: CuTPlugin, path: String, showSwapAnimation: Boolean = true): ItemTexture.Model = + ItemTexture.Model(ref(plugin, path), showSwapAnimation) -public fun itemTexture(stringPath: String): ItemTexture.Texture = ItemTexture.Texture(ref(stringPath)) +public fun itemTexture(stringPath: String, showSwapAnimation: Boolean = true): ItemTexture.Texture = + ItemTexture.Texture(ref(stringPath), showSwapAnimation) -public fun itemModel(stringPath: String): ItemTexture.Model = ItemTexture.Model(ref(stringPath)) +public fun itemModel(stringPath: String, showSwapAnimation: Boolean = true): ItemTexture.Model = + ItemTexture.Model(ref(stringPath), showSwapAnimation) /** * A class for creating dynamic displays (lore, name, etc.) for [CuTItemStack]s. diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/VanillaTool.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/VanillaTool.kt index de89a3a..c2638aa 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/VanillaTool.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/VanillaTool.kt @@ -42,7 +42,7 @@ public class VanillaTool( val minToolTier = ToolTier.fromVanillaMaterial(material) val correctToolForDrops = minToolTier.breakingLevel <= tool.tier.breakingLevel - val holder = BuiltInRegistries.BLOCK.createIntrusiveHolder(blockType) + val holder = BuiltInRegistries.BLOCK.wrapAsHolder(blockType) materialData += ToolMaterialData(holder, correctToolForDrops, special ?: tool.toolSpeed.speed) } } @@ -73,7 +73,7 @@ public class VanillaTool( @TemporaryAPI @UsesNMS private fun optimize(materialData: List): List { - val map = mutableMapOf>>() + val map = mutableMapOf>>() for (data in materialData) { val properties = ToolRuleProperties(data.correctForBlocks, data.speed) @@ -93,7 +93,7 @@ private fun optimize(materialData: List): List, + val holder: Holder, val correctForBlocks: Boolean, val speed: Float ) @@ -108,7 +108,7 @@ private data class ToolRuleProperties( @TemporaryAPI @UsesNMS private data class PlainToolComponentRule( - val blocks: List>, + val blocks: List>, val speed: Optional, val correctForDrops: Optional ) { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt index 5eee608..27ab0d3 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.* import xyz.mastriel.cutapi.* import xyz.mastriel.cutapi.resources.data.* import java.io.* +import kotlin.contracts.* public open class Resource( public open val ref: ResourceRef<*>, @@ -15,7 +16,7 @@ public open class Resource( public fun isSubresource(): Boolean = "#" in ref.name init { - inspector.single("Resource Type") { this::class.simpleName ?: "Unknown" } + inspector.single("Resource Type") { this::class.simpleName ?: "" } inspector.single("Is Serializable") { this is ByteArraySerializable } inspector.single("Plugin") { ref.plugin.namespace } inspector.map("Subresources") { @@ -96,7 +97,13 @@ public fun T.saveWithMetadata( saveTo(file) } -public fun Resource.isSerializable(): Boolean = this is ByteArraySerializable +@OptIn(ExperimentalContracts::class) +public fun Resource.isSerializable(): Boolean { + contract { + returns(true) implies (this@isSerializable is ByteArraySerializable) + } + return this is ByteArraySerializable +} @OptIn(ExperimentalSerializationApi::class) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt index 8e04cf9..4327f30 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt @@ -33,7 +33,7 @@ public class ResourcePackManager { } /** - * Gets the textures folder of a plugin in the resource pack. + * Gets the models folder of a plugin in the resource pack. * * @param namespace The namespace of the plugin. */ @@ -47,7 +47,7 @@ public class ResourcePackManager { * @param namespace The namespace of the plugin. */ public fun getItemModelFolder(namespace: String): File { - return File(tempFolder, "assets/$namespace/models/item/") + return File(tempFolder, "assets/$namespace/items/") } /** diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt index 48f08cf..f2c4f67 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt @@ -60,7 +60,7 @@ public data class ResourceRef internal constructor( withRootAlias: Boolean = withNamespace, withNamespaceAsFolder: Boolean = false, withName: Boolean = true, - fixInvalids: Boolean = true + fixInvalids: Boolean = false ): String { val sb = StringBuilder("") if (withNamespace) { @@ -141,7 +141,6 @@ public fun ref(stringPath: String): ResourceRef { val (start, path) = stringPath.split("://", limit = 2) val startSplit = start.split(Locator.ROOT_SEPARATOR, limit = 2) val namespace = startSplit[0] - val root = startSplit.getOrNull(1) val plugin = CuTAPI.getPluginFromNamespace(namespace) return ref(plugin, path) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt new file mode 100644 index 0000000..4e927a2 --- /dev/null +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt @@ -0,0 +1,8 @@ +package xyz.mastriel.cutapi.resources + +import kotlinx.serialization.* + +@JvmInline +@Serializable +public value class VanillaRef(public val ref: String) { +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Texture2D.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Texture2D.kt index d7f4eb1..94d559a 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Texture2D.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Texture2D.kt @@ -113,12 +113,12 @@ public open class Texture2D( override fun createItemModelData(): JsonObject { val internalJson = CuTAPI.toml.encodeToTomlElement(metadata.itemModelData).asTomlTable().toJson() var jsonObject: JsonObject = internalJson - if (metadata.modelFile != null && metadata.modelFile!!.isAvailable()) { - val (fileJson) = metadata.modelFile?.getResource()!! + if (metadata.modelFile != null && metadata.modelFile.isAvailable()) { + val (fileJson) = metadata.modelFile.getResource()!! jsonObject = internalJson.combine(fileJson) } - val path = ref.path(withExtension = false, withNamespaceAsFolder = false).fixInvalidResourcePath() + val path = ref.path(withExtension = false, withNamespaceAsFolder = false, fixInvalids = true) val texturesObject = jsonObject["textures"]?.jsonObject val textures = texturesObject?.toMutableMap() ?: mutableMapOf() @@ -134,7 +134,8 @@ public open class Texture2D( "${ref.namespace}:${ ref.path( withExtension = false, - withNamespaceAsFolder = false + withNamespaceAsFolder = false, + fixInvalids = true ) }" ) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt index a2598a7..5497ca0 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt @@ -20,6 +20,10 @@ public interface TextureLike { @JvmInline public value class VanillaItemModel(public val location: String) { + public fun getLocationWithItemFolder(): String { + return "${toIdentifier().namespace}:item/${toIdentifier().key}" + } + public fun toIdentifier(): Identifier { if (":" !in location) { return id(MinecraftAssets, location) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt index 9f84834..7ab558f 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt @@ -14,13 +14,17 @@ import kotlin.math.* public val TextureAndModelProcessor: ResourceProcessor = resourceProcessor { val textures = this.resources.filterIsInstance().filter { !it.metadata.transient } val models = this.resources.filterIsInstance() - val resourcePackManager = CuTAPI.resourcePackManager - - val namespaces = textures.map { it.ref.namespace }.distinct() + // puts the textures in the right places generateTexturesInPack(textures) + + // puts the models in the right places generateModelsInPack(models) + + // generates the item model and model files generateItemAndModelJsonFiles(textures) + + // generates the glyphs generateGlyphs(textures) } @@ -55,7 +59,7 @@ internal fun BitmapFontProvider( ): MinecraftFontProvider { // why does this need the extension exactly?? // it wont work without it. - val path = ref.path(withExtension = true, withNamespaceAsFolder = false).fixInvalidResourcePath() + val path = ref.path(withExtension = true, withNamespaceAsFolder = false, fixInvalids = true) val filePath = "${ref.namespace}:item/$path" return MinecraftFontProvider("bitmap", filePath, ascent, height, chars) } @@ -124,11 +128,11 @@ internal fun generateGlyphs(textures: List) { ascent = min(ascent, finalHeight) val privateUseChar = Character.toChars(privateUseCharIndex).joinToString("") - privateUseCharIndex += 1; + privateUseCharIndex += 1 if (fontSettings.advance != null) { val advance = fontSettings.advance val spaceChar = Character.toChars(privateUseCharIndex).joinToString("") - privateUseCharIndex += 1; + privateUseCharIndex += 1 spaceProvider.advances!![spaceChar] = advance texture.glyphChars[size] = spaceChar + privateUseChar } else { @@ -155,7 +159,7 @@ internal fun generateTexturesInPack(textures: List) { val texturesFolder = CuTAPI.resourcePackManager.getTexturesFolder(texture.ref.plugin) applyPostProcessing(texture) - texture.saveTo(texturesFolder appendPath texture.ref.path(withExtension = true).fixInvalidResourcePath()) + texture.saveTo(texturesFolder appendPath texture.ref.path(withExtension = true, fixInvalids = true)) val metadata = texture.metadata val animationData = metadata.animation @@ -173,9 +177,10 @@ internal fun generateTexturesInPack(textures: List) { val path = texture.ref.path( withExtension = false, withNamespaceAsFolder = false, - withNamespace = false - ).fixInvalidResourcePath() - val modelFile = CuTAPI.resourcePackManager.getItemModelFolder(texture.root.namespace) appendPath "/$path.json" + withNamespace = false, + fixInvalids = true + ) + val modelFile = CuTAPI.resourcePackManager.getModelFolder(texture.root.namespace) appendPath "/$path.json" modelFile.mkdirsOfParent() modelFile.createAndWrite(jsonString) @@ -185,8 +190,8 @@ internal fun generateTexturesInPack(textures: List) { internal fun generateModelsInPack(models: List) { // for this one we only need to put the models in the correct folder. models.forEach { model -> - val modelsFolder = CuTAPI.resourcePackManager.getItemModelFolder(model.ref.root.namespace) - model.saveTo(modelsFolder appendPath model.ref.path(withExtension = false).fixInvalidResourcePath() + ".json") + val modelsFolder = CuTAPI.resourcePackManager.getModelFolder(model.ref.root.namespace) + model.saveTo(modelsFolder appendPath model.ref.path(withExtension = false, fixInvalids = true) + ".json") } } @@ -206,7 +211,7 @@ private fun applyPostProcessing(texture: Texture2D) { } private fun generateModelFile(texture: Texture2D) { - val location = texture.getItemModel().location + val location = texture.getItemModel().getLocationWithItemFolder() val itemModelJson = texture.metadata.itemModelData?.copy( textures = texture.metadata.itemModelData.textures + mapOf("layer0" to location) @@ -214,7 +219,7 @@ private fun generateModelFile(texture: Texture2D) { val jsonString = CuTAPI.json.encodeToString(itemModelJson) - val path = texture.ref.path(withExtension = false) + val path = texture.ref.path(withExtension = false, fixInvalids = true) val file = CuTAPI.resourcePackManager.getModelFolder(texture.ref.namespace) appendPath "${path}.json" @@ -226,19 +231,49 @@ private fun generateModelFile(texture: Texture2D) { private data class ItemModelFile( @SerialName("hand_animation_on_swap") val handAnimationOnSwap: Boolean, + val model: ModelData +) { + + @Serializable + enum class ModelType { + @SerialName("minecraft:model") + Model + } + @Serializable + data class ModelData( + val type: ModelType, + val model: VanillaRef ) +} // we generate 2 different item models, one with no hand swap animation and one with it -private fun generateItemModelFile(texture: Texture2D) { +private fun generateItemModelFiles(texture: Texture2D) { + + val modelData = ItemModelFile.ModelData( + ItemModelFile.ModelType.Model, + VanillaRef(texture.getItemModel().location) + ) + + val noSwap = ItemModelFile(false, modelData) + val swap = ItemModelFile(true, modelData) + val folder = CuTAPI.resourcePackManager.getItemModelFolder(texture.ref.namespace) + val path = texture.ref.path(withExtension = false) + val noSwapFile = folder appendPath "${path}__noswap.json" + val swapFile = folder appendPath "${path}__swap.json" + + noSwapFile.parentFile.mkdirs() + swapFile.parentFile.mkdirs() + noSwapFile.createAndWrite(CuTAPI.json.encodeToString(noSwap)) + swapFile.createAndWrite(CuTAPI.json.encodeToString(swap)) } private fun generateItemAndModelJsonFiles(textures: Collection) { for (texture in textures) { generateModelFile(texture) - + generateItemModelFiles(texture) } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/utils/serializers/LocationSerializer.kt b/src/main/kotlin/xyz/mastriel/cutapi/utils/serializers/LocationSerializer.kt index 6fe8014..46cabc0 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/utils/serializers/LocationSerializer.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/utils/serializers/LocationSerializer.kt @@ -30,7 +30,7 @@ public object LocationSerializer : KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("Location") { + buildClassSerialDescriptor("cutapi:location") { element("x") element("y") element("z") From e2909f751c6bd46d7918512320b33f15414390bb Mon Sep 17 00:00:00 2001 From: Zac Date: Sat, 26 Jul 2025 14:47:06 -0600 Subject: [PATCH 03/10] resource management updates --- build.gradle.kts | 5 +- gradle.properties | 3 +- .../xyz/mastriel/cutapi/CuTAPIPlugin.kt | 7 +- .../block/breaklogic/BlockBreakManager.kt | 9 +- .../block/breaklogic/PlayerBlockBreaker.kt | 7 +- .../xyz/mastriel/cutapi/item/CustomItem.kt | 4 +- .../mastriel/cutapi/item/PacketItemHandler.kt | 124 ++++++--- .../{BlankNameBehavior.kt => HideTooltip.kt} | 2 +- .../cutapi/item/recipe/ShapedRecipeBuilder.kt | 5 +- .../item/recipe/ShapelessRecipeBuilder.kt | 6 +- .../xyz/mastriel/cutapi/resources/Resource.kt | 7 + .../cutapi/resources/ResourceGenerator.kt | 2 +- .../cutapi/resources/ResourceManager.kt | 254 +++++++++++++----- .../mastriel/cutapi/resources/ResourceRef.kt | 2 +- .../cutapi/resources/builtin/Model3D.kt | 10 +- .../resources/builtin/TemplateMetadata.kt | 3 +- .../cutapi/resources/builtin/TextureLike.kt | 2 + .../mastriel/cutapi/resources/data/CuTMeta.kt | 2 +- .../resources/data/minecraft/ItemModelData.kt | 20 +- .../resources/process/GenerateProcessor.kt | 69 ++++- .../HorizontalAtlasTextureGenerator.kt | 6 +- .../process/InventoryTextureGenerator.kt | 38 +++ .../resources/process/Texture2DProcessor.kt | 21 +- .../xyz/mastriel/cutapi/utils/ConfigUtils.kt | 11 +- .../xyz/mastriel/cutapi/utils/PlayerUtils.kt | 16 ++ .../mastriel/cutapi/utils/TomlJsonUtils.kt | 4 +- .../mastriel/cutapi/utils/WeakPlayerMap.kt | 66 +++++ .../pack/ui/inventory_bg.model3d.json | 207 ++++++++++++-- .../pack/ui/inventory_part.model3d.json | 6 - .../pack/ui/inventory_part.model3d.json.meta | 3 - .../pack/ui/inventory_texture.template | 4 + src/main/resources/paper-plugin.yml | 2 +- 32 files changed, 734 insertions(+), 193 deletions(-) rename src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/{BlankNameBehavior.kt => HideTooltip.kt} (81%) create mode 100644 src/main/kotlin/xyz/mastriel/cutapi/resources/process/InventoryTextureGenerator.kt create mode 100644 src/main/kotlin/xyz/mastriel/cutapi/utils/WeakPlayerMap.kt delete mode 100644 src/main/resources/pack/ui/inventory_part.model3d.json delete mode 100644 src/main/resources/pack/ui/inventory_part.model3d.json.meta create mode 100644 src/main/resources/pack/ui/inventory_texture.template diff --git a/build.gradle.kts b/build.gradle.kts index 101e5f6..ad091c9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,7 @@ repositories { val kotlinVersion: String by properties group = "xyz.mastriel" version = properties["version"]!! +val minecraftVersion: String by properties @@ -66,7 +67,7 @@ dependencies { shadow("net.lingala.zip4j:zip4j:2.11.5") shadow("com.jhlabs:filters:2.0.235-1") - paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT") + paperweight.paperDevBundle("${minecraftVersion}-R0.1-SNAPSHOT") } @@ -75,7 +76,7 @@ tasks { runServer { pluginJars("../CuTAPI/build/libs/CuTAPI-0.1.0a-reobf.jar") - minecraftVersion("1.21.4") + minecraftVersion(minecraftVersion) } } diff --git a/gradle.properties b/gradle.properties index 202092a..c46256d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ kotlin.code.style=official kotlinVersion=2.0.20 -version=0.1.1 \ No newline at end of file +version=0.1.1 +minecraftVersion=1.21.4 \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt b/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt index 8d62f69..120f944 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt @@ -52,7 +52,7 @@ public class CuTAPIPlugin : JavaPlugin(), CuTPlugin { ) CustomItem.register(CustomItem.Unknown) TexturePostProcessor.registerBuiltins() - + TexturePostProcessor.register(GrayscalePostProcessor) TexturePostProcessor.register(PaletteSwapPostProcessor) TexturePostProcessor.register(MultiplyOpaquePixelsProcessor) @@ -102,13 +102,16 @@ public class CuTAPIPlugin : JavaPlugin(), CuTPlugin { private fun registerResourceLoaders() { ResourceGenerator.register(HorizontalAtlasTextureGenerator) + ResourceGenerator.register(InventoryTextureGenerator) ResourceFileLoader.register(FolderApplyResourceLoader) - ResourceFileLoader.register(TemplateResourceLoader) ResourceFileLoader.register(Texture2DResourceLoader) ResourceFileLoader.register(Model3DResourceLoader) ResourceFileLoader.register(MetadataResource.Loader) ResourceFileLoader.register(PostProcessDefinitionsResource.Loader) + ResourceFileLoader.register(GenerateResource.Loader) + + ResourceFileLoader.register(TemplateResourceLoader) } private fun registerEvents() { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/BlockBreakManager.kt b/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/BlockBreakManager.kt index b48738e..eeb5a4f 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/BlockBreakManager.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/BlockBreakManager.kt @@ -22,7 +22,7 @@ import java.util.logging.* @OptIn(UsesNMS::class) public class BlockBreakManager : Listener, PacketListener { - private val breakers = ConcurrentHashMap() + private val breakers = ConcurrentHashMap() @Periodic(1) @@ -49,7 +49,7 @@ public class BlockBreakManager : Listener, PacketListener { breakers.remove(player) } } catch (ex: Exception) { - Plugin.logger.log(Level.SEVERE, "Error while ticking block breaker for ${breaker.player.name}", ex) + Plugin.logger.log(Level.SEVERE, "Error while ticking block breaker for ${breaker.player?.name}", ex) } } @@ -113,13 +113,14 @@ public class BlockBreakManager : Listener, PacketListener { } - val breaker = PlayerBlockBreaker(blockPosition.bukkitBlock(player.world), player, activeItem.toAgnostic()) + val breaker = + PlayerBlockBreaker(blockPosition.bukkitBlock(player.world), player.playerUUID, activeItem.toAgnostic()) // creative does not send stop or abort, plus we only need // to trigger the first tick since everything should break // instantly in creative mode. if (player.gameMode != GameMode.CREATIVE) { - breakers[player] = breaker + breakers[player.playerUUID] = breaker } } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/PlayerBlockBreaker.kt b/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/PlayerBlockBreaker.kt index 004f4cb..c6474b8 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/PlayerBlockBreaker.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/PlayerBlockBreaker.kt @@ -22,9 +22,10 @@ import xyz.mastriel.cutapi.utils.* @UsesNMS public open class PlayerBlockBreaker( public val block: Block, - public val player: Player, + public val playerUUID: PlayerUUID, public val item: AgnosticItemStack ) { + public val player: Player? by playerUUID protected val soundGroup: SoundGroup = block.blockSoundGroup protected val material: Material = block.type @@ -86,6 +87,8 @@ public open class PlayerBlockBreaker( @Suppress("DEPRECATION") public open fun calculateDamageDealt(): Float { + val player = this.player ?: return 0f + // blocks under 0 are unbreakable if (hardness < 0f) return 0f @@ -116,7 +119,7 @@ public open class PlayerBlockBreaker( // deprecation is suppressed here because isOnGround can be // easily spoofed. it doesn't really matter that much since // it's used for a minor benefit when mining while flying. - // (which fly hacks should be pretty obvious to spot. + // (also, fly hacks should be pretty obvious to spot) if (!player.isOnGround) totalSpeedMultiplier /= 5f val correctToolIsUsed = when (item) { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt index 2706907..e48aeb6 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt @@ -100,7 +100,7 @@ public open class CustomItem( id = id(Plugin, "inventory_background"), Material.GLISTERING_MELON_SLICE ) { - behavior(BlankNameBehavior) + behavior(HideTooltip) display { texture = itemModel(Plugin, "ui/inventory_bg.model3d.json") @@ -144,7 +144,7 @@ public sealed class AgnosticMaterial { } return false } - + public infix fun materialIs(value: CustomItem<*>): Boolean { if (this is Custom) { return this.custom() == value diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/PacketItemHandler.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/PacketItemHandler.kt index eec5a31..de71798 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/PacketItemHandler.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/PacketItemHandler.kt @@ -1,6 +1,9 @@ package xyz.mastriel.cutapi.item +import com.github.shynixn.mccoroutine.bukkit.* import com.mojang.datafixers.util.* +import it.unimi.dsi.fastutil.ints.* +import kotlinx.coroutines.* import net.minecraft.core.* import net.minecraft.core.component.* import net.minecraft.network.protocol.game.* @@ -9,6 +12,7 @@ import net.minecraft.network.syncher.SynchedEntityData.DataValue import net.minecraft.world.item.trading.* import org.bukkit.* import org.bukkit.craftbukkit.inventory.* +import org.bukkit.craftbukkit.util.* import org.bukkit.entity.* import org.bukkit.event.* import org.bukkit.event.inventory.* @@ -20,6 +24,7 @@ import xyz.mastriel.cutapi.nms.* import xyz.mastriel.cutapi.pdc.tags.converters.* import xyz.mastriel.cutapi.periodic.* import xyz.mastriel.cutapi.utils.* +import xyz.mastriel.cutapi.utils.personalized.* import java.util.* @@ -113,7 +118,7 @@ internal object PacketItemHandler : Listener, PacketListener { val hotbar = mainContents.take(9) val storedInventory = mainContents.subList(9, 36) - return listOf(craftingSlots, armorContents, storedInventory, hotbar, offhand /* cursor */) + return listOf(craftingSlots, armorContents, storedInventory, hotbar, offhand) .flatten() .map { it.nms() } .toNonNullList() @@ -150,6 +155,24 @@ internal object PacketItemHandler : Listener, PacketListener { return ClientboundSetEquipmentPacket(event.packet.entity, slots) } + private fun getServerSideStack(item: MojangItemStack): MojangItemStack { + val itemStack = item.bukkit().wrap() ?: return item + return itemStack.getPrerenderItemStack(item.count)?.nms() ?: item + } + + @PacketHandler + fun clickInventory(event: PacketEvent): ServerboundContainerClickPacket { + return ServerboundContainerClickPacket( + event.packet.containerId, + event.packet.stateId, + event.packet.slotNum, + event.packet.buttonNum, + event.packet.clickType, + getServerSideStack(event.packet.carriedItem), + event.packet.changedSlots.mapValuesTo(Int2ObjectArrayMap()) { (_, item) -> getServerSideStack(item) } + ) + } + @PacketHandler fun handleCreativeSetSlot(event: PacketEvent): ServerboundSetCreativeModeSlotPacket { val item = event.packet.itemStack @@ -175,6 +198,19 @@ internal object PacketItemHandler : Listener, PacketListener { } } + if (event.packet.itemStack.item == CraftMagicNumbers.getItem(Material.AIR)) { + Plugin.launch { + delay(1.ticks) + + ClientboundContainerSetSlotPacket( + 0, + 0, + event.packet.slotNum.toInt(), + net.minecraft.world.item.ItemStack.EMPTY + ).sendTo(event.player) + } + } + return ServerboundSetCreativeModeSlotPacket( event.packet.slotNum, renderIfNeeded(event.player, item.bukkit().toAgnostic()).nms() @@ -183,46 +219,7 @@ internal object PacketItemHandler : Listener, PacketListener { @Periodic(ticks = 5, asyncThread = false) fun updatePlayerItems() { - for (player in Bukkit.getOnlinePlayers().filterNotNull()) { - - // only send the packet to update the whole inventory if the player is not in creative mode - // in creative mode, the client handles the cursor slot in its entirety. we can't see what item - // is in the cursor slot at any given time since the client doesn't send that information to the server. - // https://github.com/PaperMC/Paper/issues/7797#issuecomment-1120472278 - if (player.gameMode != GameMode.CREATIVE) { - - val packet = ClientboundContainerSetContentPacket( - 0, - // having no revision/stateid works, however it sends unnecessary packets - // prs are welcome to fix this - // https://wiki.vg/Protocol#Click_Container - 0, - renderPlayerInventory(player), - customCursorItem(player).nms() - ) - - packet.sendTo(player) - } else { - // so here's what's up. we have to send a separate packet for each slot in the player's inventory - // because the client doesn't send the cursor slot to the server in creative mode. - // this stupid as fuck. - - val inventory = renderPlayerInventory(player) - - inventory.map { it.bukkit() }.forEachIndexed { index, item -> - - if (item.wrap() != null) { - val packet = ClientboundContainerSetSlotPacket( - 0, - 0, - index, - item.nms() - ) - packet.sendTo(player) - } - } - } - + for (player in onlinePlayers()) { // this is responsible for updating the inventory of the player's open window. if (player.openWindowId != null) { @@ -257,6 +254,42 @@ internal object PacketItemHandler : Listener, PacketListener { if (bottomInventory !is PlayerInventory && topInventory.type != InventoryType.CRAFTING) { sendInventory(bottomInventory) } + return + } + + // only send the packet to update the whole inventory if the player is not in creative mode + // in creative mode, the client handles the cursor slot in its entirety. we can't see what item + // is in the cursor slot at any given time since the client doesn't send that information to the server. + // https://github.com/PaperMC/Paper/issues/7797#issuecomment-1120472278 + if (player.gameMode != GameMode.CREATIVE) { + + val packet = ClientboundContainerSetContentPacket( + 0, + 0, + renderPlayerInventory(player), + customCursorItem(player).nms() + ) + + packet.sendTo(player) + } else { + // we have to send a separate packet for each slot in the player's inventory + // because the client doesn't send the cursor slot to the server in creative mode. + // this stupid as fuck. + + val inventory = renderPlayerInventory(player) + + inventory.map { it.bukkit() }.forEachIndexed { index, item -> + + if (item.wrap() != null) { + val packet = ClientboundContainerSetSlotPacket( + 0, + 0, + index, + item.nms() + ) + packet.sendTo(player) + } + } } @@ -265,7 +298,10 @@ internal object PacketItemHandler : Listener, PacketListener { private fun customCursorItem(player: Player): ItemStack { // Bukkit.broadcast("Cursor: ${player.openInventory.cursor}".colored) - player.itemOnCursor.wrap()?.withViewer(player)?.let { return it } + val item = player.itemOnCursor.wrap()?.withViewer(player) + if (item != null) { + return item + } return player.itemOnCursor } @@ -289,7 +325,7 @@ internal object PacketItemHandler : Listener, PacketListener { return ClientboundSetEntityDataPacket(packet.id, newItems) } - fun renderIfNeeded(viewer: Player, stack: AgnosticItemStack): ItemStack { + fun renderIfNeeded(viewer: Player?, stack: AgnosticItemStack): ItemStack { return when (stack) { is AgnosticItemStack.Custom -> stack.custom() withViewer viewer is AgnosticItemStack.Vanilla -> stack.vanilla() @@ -321,7 +357,7 @@ internal object PacketItemHandler : Listener, PacketListener { private val windowIds = mutableMapOf() - internal val Player.openWindowId get() = windowIds[player] + internal val Player.openWindowId get() = windowIds[this] internal fun CuTItemStack.setPrerenderItemStack(prerender: ItemStack) { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/BlankNameBehavior.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/HideTooltip.kt similarity index 81% rename from src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/BlankNameBehavior.kt rename to src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/HideTooltip.kt index f98a9e7..cc7ec05 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/BlankNameBehavior.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/HideTooltip.kt @@ -5,7 +5,7 @@ import xyz.mastriel.cutapi.* import xyz.mastriel.cutapi.item.* import xyz.mastriel.cutapi.registry.* -public object BlankNameBehavior : ItemBehavior(id(Plugin, "blank_name")) { +public object HideTooltip : ItemBehavior(id(Plugin, "blank_name")) { override fun onRender(viewer: Player?, item: CuTItemStack) { item.vanilla().editMeta { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt index f6dcfdf..638f47c 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt @@ -21,7 +21,8 @@ public class CustomShapedRecipeIngredient( material: Material, quantity: Int = 1, itemRequirement: Computable, - onCraft: IngredientCraftContext.() -> Unit + onCraft: IngredientCraftContext.() -> Unit, + public val placeholderItem: CustomItem<*> ) : ShapedRecipeIngredient(char, material, quantity, itemRequirement, onCraft) @@ -158,7 +159,7 @@ public class ShapedRecipeBuilder( ) { repeat(slotsRequired) { val predicate = IngredientPredicates.isItem(item) - ingredients[char] = CustomShapedRecipeIngredient(char, item.type, quantity, predicate, onCraft) + ingredients[char] = CustomShapedRecipeIngredient(char, item.type, quantity, predicate, onCraft, item) } } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt index cd1108b..9853ba5 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt @@ -17,7 +17,8 @@ public class CustomShapelessRecipeIngredient( material: Material, quantity: Int = 1, itemRequirement: Computable, - onCraft: IngredientCraftContext.() -> Unit + onCraft: IngredientCraftContext.() -> Unit, + public val placeholderItem: CustomItem<*> ) : ShapelessRecipeIngredient(material, quantity, itemRequirement, onCraft) public data class CustomShapelessRecipe( @@ -73,7 +74,8 @@ public class ShapelessRecipeBuilder( item.type, quantity, IngredientPredicates.isItem(item), - onCraft + onCraft, + item ) } } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt index 27ab0d3..4fcf28f 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt @@ -34,6 +34,13 @@ public open class Resource( public open var subResources: List = emptyList() protected set + protected fun subResource(resource: Resource) { + if (resource.ref.root != this.ref.root) { + throw IllegalArgumentException("Sub-resource must have the same root as the parent resource.") + } + subResources = subResources + resource + } + // these are set by the resource manager // these are also purely for the clone block // and processing cloning resources. diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt index ad56e3d..57b42ae 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt @@ -37,7 +37,7 @@ public abstract class ResourceGenerator( public data class ResourceGeneratorContext( val resource: T, val generateBlock: GenerateBlock, - val suppliedSubId: String, + val ref: ResourceRef, val register: (Resource) -> Unit ) { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceManager.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceManager.kt index b9c7b77..a162bdc 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceManager.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceManager.kt @@ -21,6 +21,14 @@ public class ResourceManager { private val resourceRoots = mutableListOf() + /** + * Registers a resource in the resource manager. + * + * @param resource The resource to register. + * @param overwrite Whether to overwrite an existing resource with the same reference. + * @param log Whether to log the registration process. + * @throws IllegalStateException if the resource is already registered and overwrite is false. + */ public fun register(resource: Resource, overwrite: Boolean = false, log: Boolean = true) { if (resource.ref in resources.keys && !overwrite) { error("Resource '${resource.ref}' already registered.") @@ -34,6 +42,11 @@ public class ResourceManager { resource.onRegister() } + /** + * Registers all parent folders of a given resource reference. + * + * @param ref The resource reference whose parent folders will be registered. + */ private fun registerFolders(ref: ResourceRef<*>) { val folders = mutableListOf() var currentRef: Locator = ref @@ -46,20 +59,45 @@ public class ResourceManager { this.folders.addAll(folders) } - + /** + * Retrieves a resource by its reference. + * + * @param ref The reference of the resource to retrieve. + * @return The resource associated with the reference. + * @throws IllegalStateException if the resource is not found. + */ public fun getResource(ref: ResourceRef): T { return getResourceOrNull(ref) ?: error("$ref is not a valid resource.") } + /** + * Retrieves a resource by its reference or returns null if not found. + * + * @param ref The reference of the resource to retrieve. + * @return The resource associated with the reference, or null if not found. + */ @Suppress("UNCHECKED_CAST") public fun getResourceOrNull(ref: ResourceRef): T? { return resources[ref] as? T } + /** + * Checks if a resource is available and loaded. + * + * @param ref The reference of the resource to check. + * @return True if the resource is available, false otherwise. + */ public fun isAvailable(ref: ResourceRef<*>): Boolean { return resources[ref] != null } + /** + * Retrieves the contents of a folder. + * + * @param root The root of the folder. + * @param folderRef The reference to the folder. + * @return A list of locators representing the folder's contents. + */ public fun getFolderContents(root: ResourceRoot, folderRef: FolderRef): List { if (folderRef.isRoot) { return locators.filter { it.root == root } @@ -69,6 +107,11 @@ public class ResourceManager { .filter { it.parent == folderRef } } + /** + * Retrieves all resources currently registered in the resource manager. + * + * @return A list of all registered resources. + */ public fun getAllResources(): List { return resources.values.toList() } @@ -76,11 +119,19 @@ public class ResourceManager { private val tempFolder = Plugin.dataFolder.appendPath("resources-tmp/").absoluteFile + /** + * Clears the temporary folder used for resource operations. + */ internal fun clearTemp() { tempFolder.deleteRecursively() tempFolder.mkdir() } + /** + * Dumps plugin resources to a temporary folder. + * + * @param plugin The plugin whose resources will be dumped. + */ internal fun dumpPluginResourcesToTemp(plugin: CuTPlugin) { val descriptor = CuTAPI.getDescriptor(plugin) val options = descriptor.options @@ -103,33 +154,48 @@ public class ResourceManager { } /** - * Create a new resource root for your plugin. Resources are loaded from here - * whenever the resource pack is generated. + * Registers a new resource root for a plugin. + * + * @param resourceRoot The resource root to register. */ public fun registerResourceRoot(resourceRoot: ResourceRoot) { resourceRoots += resourceRoot } + /** + * Retrieves a resource root by its namespace. + * + * @param root The namespace of the resource root. + * @return The resource root, or null if not found. + */ public fun getResourceRoot(root: String): ResourceRoot? { return resourceRoots.find { it.namespace == root } } /** - * Removes a resource root from your plugin. + * Unregisters a resource root by its namespace. * - * @return true if a resource root was unregistered, false otherwise. + * @param root The namespace of the resource root. + * @return True if the resource root was unregistered, false otherwise. */ public fun unregisterResourceRoot(root: ResourceRoot): Boolean { return resourceRoots.removeIf { it == root } } /** - * Remove all resource roots from your plugin. + * Unregisters all resource roots associated with a plugin. + * + * @param plugin The plugin whose resource roots will be unregistered. */ public fun unregisterAllResourceRoots(plugin: CuTPlugin) { resourceRoots.removeIf { it.cutPlugin == plugin } } + /** + * Loads all resources from a resource root. + * + * @param root The resource root to load resources from. + */ internal fun loadRootResources(root: ResourceRoot) { val namespace = root.namespace @@ -152,7 +218,7 @@ public class ResourceManager { } /** - * Converts a [FolderRef] to a filesystem file, given a root to be based off of. + * Converts a [FolderRef] to a filesystem file handle, given a root to be based off of. * * @param root The root folder that this is loading from. * @param folderRef The [FolderRef] of this folder. @@ -180,15 +246,11 @@ public class ResourceManager { } /** - * Loads all resources from a folder [root] recursively. + * Recursively finds resources in a folder and adds them to a list. * - * @param root The root folder this is loading from. Used for relative pathing - * @param folder The current [FolderRef]. - * - * Example: - * Loading from /CuTAPI/custom, where custom is the root, and contains a folder - * named 'abc' which itself has a file `texture.png`, [folder] could be 'cutapi://abc', and that - * would load the `texture.png` file into 'cutapi://abc/texture.png' + * @param root The root folder to search in. + * @param folder The current folder reference. + * @param found The list to add found resources to. */ private fun findResourcesInFolder(root: File, folder: FolderRef, found: MutableList>>) { folderRefToFile(root, folder).listFiles()?.toList() @@ -207,10 +269,13 @@ public class ResourceManager { } /** - * Load a resource from a file into a ResourceRef, and registers it. + * Loads a resource from a file and registers it. * - * @param resourceFile The file being loaded - * @param ref The resource ref this file will be associated with + * @param resourceFile The file to load the resource from. + * @param ref The reference of the resource. + * @param withLoader The loader to use for loading the resource. + * @param options Additional options for loading the resource. + * @return The result of the resource loading process. */ public fun loadResource( resourceFile: File, @@ -251,7 +316,11 @@ public class ResourceManager { /** * Loads a resource from a file without registering it. * - * A null return value means that the resource was a metadata file. + * @param resourceFile The file to load the resource from. + * @param ref The reference of the resource. + * @param loader The loader to use for loading the resource. + * @param options Additional options for loading the resource. + * @return The result of the resource loading process. */ @OptIn(InternalSerializationApi::class) public fun loadResourceWithoutRegistering( @@ -281,6 +350,13 @@ public class ResourceManager { } } + /** + * Loads metadata for a resource from a file. + * + * @param metadataFile The file containing the metadata. + * @param ref The reference of the resource. + * @return The metadata as a byte array, or null if not found. + */ private fun loadMetadata(metadataFile: File, ref: ResourceRef<*>): ByteArray? { // TODO we're deserializing this a lot. we should cache it or something. return try { @@ -294,7 +370,8 @@ public class ResourceManager { else TomlTable() - val newTable = folderTable.combine(metadataTable, false) + val newTable = folderTable.combine(metadataTable, true) + println("${ref}: " + CuTAPI.toml.encodeToString(newTable)) CuTAPI.toml.encodeToString(newTable).toByteArray(Charsets.UTF_8) } } catch (e: Exception) { @@ -311,19 +388,31 @@ public class ResourceManager { Plugin.error("Metadata extensions for $ref excessively (or infinitely) recurse.") break } - table = processTemplates(table) + table = processTemplates(ref, table) } CuTAPI.toml.encodeToString(table).toByteArray(Charsets.UTF_8) } } + /** + * Checks if a metadata table needs to process extend blocks. + * + * @param table The metadata table to check. + * @return True if extensions need to be processed, false otherwise. + */ private fun metadataNeedsToProcessExtensions(table: TomlTable): Boolean { return table["extends"]?.asTomlTable() != null } - - private fun processTemplates(table: TomlTable): TomlTable { + /** + * Processes templates in a metadata table. + * + * @param ref The reference of the resource. + * @param table The metadata 'extends' table to process. + * @return The processed metadata table. + */ + private fun processTemplates(ref: ResourceRef<*>, table: TomlTable): TomlTable { var metadata = table val extensions = metadata["extends"]?.asTomlTable() ?: return metadata @@ -340,7 +429,7 @@ public class ResourceManager { val newMetadata = refs.fold(metadata) { acc, r -> val templateTable = r.first.getResource() ?: return@fold acc - val patchedTemplates = r.second.map { templateTable.getPatchedTable(it) } + val patchedTemplates = r.second.map { templateTable.getPatchedTable(ref, it) } patchedTemplates.fold(acc) { acc2, t -> acc2.combine(t, true) @@ -351,7 +440,14 @@ public class ResourceManager { } /** - * Loads a resource from just a ref, resourceBytes, and metadataBytes. + * Attempts to load a resource from its reference, resource bytes, and metadata bytes. + * + * @param ref The reference of the resource. + * @param resourceBytes The resource data as a byte array. + * @param metadataBytes The metadata as a byte array. + * @param loader The loader to use for loading the resource. + * @param options Additional options for loading the resource. + * @return The result of the resource loading process. */ @Suppress("UNCHECKED_CAST") private fun tryLoadResource( @@ -378,6 +474,14 @@ public class ResourceManager { } } + /** + * Creates clones of a resource based on its metadata. + * + * @param resource The resource to clone. + * @param resourceBytes The resource data as a byte array. + * @param metadataBytes The metadata as a byte array. + * @param loader The loader to use for loading the clones. + */ @Suppress("UNCHECKED_CAST") private fun createClones( resource: Resource, @@ -386,58 +490,68 @@ public class ResourceManager { loader: ResourceFileLoader<*> ) { if (metadataBytes == null) return - val originMetadataTable = CuTAPI.toml.parseToTomlTable(metadataBytes.toString(Charsets.UTF_8)) - val cloneBlocks = try { - originMetadataTable.getArrayOrNull("clone") ?: listOf() - } catch (ex: Exception) { - ex.printStackTrace() - listOf() - } + val originMetadataTable = CuTAPI.toml.parseToTomlTable(metadataBytes.toString(Charsets.UTF_8)) + val cloneBlocks = extractCloneBlocks(originMetadataTable) - for (cloneBlock in cloneBlocks.mapNotNull { it as? TomlTable }) { + for (cloneBlock in cloneBlocks) { try { - val originMetadataMap = originMetadataTable.toMutableMap() - // we don't want infinite cloning - originMetadataMap.remove("clone") - - val patchedMetadata = TomlTable(originMetadataMap) - - val newTable = patchedMetadata.combine(cloneBlock, false) - - val newRefSubId = newTable.getStringOrNull("clone_sub_id") - ?: throw SerializationException("Clone block must have a 'clone_sub_id' field.") - - val newMetadataBytes = CuTAPI.toml.encodeToString(newTable).toByteArray(Charsets.UTF_8) - val newRef = resource.ref.cloneSubId(newRefSubId) - - when (val result = - tryLoadResource(newRef, resourceBytes, newMetadataBytes, loader as ResourceFileLoader)) { - is ResourceLoadResult.Success -> { - register(result.resource, overwrite = true) - } - - is ResourceLoadResult.Failure -> { - Plugin.error("Failed to clone resource ${resource.ref}. (wrong type)") - checkResourceLoading(resource.plugin) - } - - is ResourceLoadResult.WrongType -> { - Plugin.error("Failed to clone resource ${resource.ref}. (wrong type)") - checkResourceLoading(resource.plugin) - } - - else -> {} - } + val newMetadataBytes = generateCloneMetadata(originMetadataTable, cloneBlock) + val newRef = createCloneReference(resource, newMetadataBytes) + loadAndRegisterClone(newRef, resourceBytes, newMetadataBytes, loader) } catch (ex: Exception) { Plugin.error("Failed to clone resource ${resource.ref}.") ex.printStackTrace() } + } + } + private fun extractCloneBlocks(originMetadataTable: TomlTable): List { + return try { + originMetadataTable.getArrayOrNull("clone")?.filterIsInstance() ?: emptyList() + } catch (ex: Exception) { + ex.printStackTrace() + emptyList() + } + } + + private fun generateCloneMetadata(originMetadataTable: TomlTable, cloneBlock: TomlTable): ByteArray { + val patchedMetadata = TomlTable(originMetadataTable.filterKeys { it != "clone" }) + val combinedMetadata = patchedMetadata.combine(cloneBlock, false) + return CuTAPI.toml.encodeToString(combinedMetadata).toByteArray(Charsets.UTF_8) + } + + private fun createCloneReference(resource: Resource, newMetadataBytes: ByteArray): ResourceRef<*> { + val newTable = CuTAPI.toml.parseToTomlTable(newMetadataBytes.toString(Charsets.UTF_8)) + val newSubId = newTable.getStringOrNull("clone_sub_id") + ?: throw SerializationException("Clone block must have a 'clone_sub_id' field.") + return resource.ref.cloneSubId(newSubId) + } + + @Suppress("UNCHECKED_CAST") + private fun loadAndRegisterClone( + newRef: ResourceRef<*>, + resourceBytes: ByteArray, + newMetadataBytes: ByteArray, + loader: ResourceFileLoader<*> + ) { + when (val result = + tryLoadResource(newRef, resourceBytes, newMetadataBytes, loader as ResourceFileLoader)) { + is ResourceLoadResult.Success -> register(result.resource, overwrite = true) + is ResourceLoadResult.Failure -> Plugin.error("Failed to clone resource $newRef. (loading failure)") + is ResourceLoadResult.WrongType -> Plugin.error("Failed to clone resource $newRef. (wrong type)") } } + /** + * Writes a resource and its metadata to a temporary folder if needed. + * + * @param plugin The plugin associated with the resource. + * @param ref The reference of the resource. + * @param resourceFile The file containing the resource data. + * @param metadataFile The file containing the metadata. + */ public fun writeToResourceTmpIfNeeded( plugin: CuTPlugin, ref: ResourceRef<*>, @@ -458,6 +572,12 @@ public class ResourceManager { } + /** + * Retrieves the default metadata table for a folder. + * + * @param ref The reference of the folder. + * @return The default metadata table, or null if not found. + */ private fun getFolderDefaultTable(ref: ResourceRef<*>): TomlTable? { val parent = ref.parent ?: return null @@ -467,7 +587,9 @@ public class ResourceManager { /** - * Runs [Resource.check] on all resources. + * Runs checks on all registered resources. + * + * @throws ResourceCheckException if a resource fails its check. */ internal fun checkAllResources() { resources.forEach { (ref, res) -> diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt index f2c4f67..cef450a 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt @@ -47,7 +47,7 @@ public data class ResourceRef internal constructor( return CuTAPI.resourceManager.isAvailable(this) } - override fun getValue(thisRef: Any?, property: KProperty<*>): T? { + override operator fun getValue(thisRef: Any?, property: KProperty<*>): T? { return getResource() } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Model3D.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Model3D.kt index 3306945..b086e48 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Model3D.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/Model3D.kt @@ -23,7 +23,6 @@ public open class Model3D( inspector.single("Materials") { materials.joinToString() } inspector.single("Block Strategies") { metadata.blockStrategies.joinToString() } inspector.map("Textures") { metadata.textures.mapValues { (_, v) -> v.toString() } } - } @Serializable @@ -41,10 +40,11 @@ public open class Model3D( override fun getItemModel(): VanillaItemModel { return VanillaItemModel( - "${ref.namespace}:/items/${ + "${ref.namespace}:${ ref.path( withExtension = false, - withNamespaceAsFolder = false + withNamespaceAsFolder = false, + fixInvalids = true ) }" ) @@ -81,8 +81,6 @@ public val Model3DResourceLoader: ResourceFileLoader = resourceLoader( val json = jsonObject.toMutableMap() json["textures"] = JsonObject(textures) - println(JsonObject(json)) - val structure = CuTAPI.json.decodeFromJsonElement(JsonObject(json)) success(Model3D(ref, structure, metadata)) } catch (ex: Exception) { @@ -122,6 +120,8 @@ public data class Model3DDisplay( val scale: VoxelVector? = null, @EncodeDefault(EncodeDefault.Mode.NEVER) val rotation: VoxelVector? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) + val translation: VoxelVector? = null, ) @Serializable diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt index 9cb3b7f..6f7fa8a 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt @@ -17,11 +17,12 @@ public class TemplateResource( @Serializable public class Metadata : CuTMeta() - public fun getPatchedTable(map: Map): TomlTable { + public fun getPatchedTable(baseRef: ResourceRef<*>, map: Map): TomlTable { var stringTable = stringData for ((key, value) in map) { stringTable = stringTable.replace("{{${key}}}", value.toString()) } + stringTable = stringTable.replace("{{@ref}}", baseRef.toString()) return CuTAPI.toml.parseToTomlTable(stringTable) } } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt index 5497ca0..b218dfd 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TextureLike.kt @@ -11,6 +11,8 @@ public interface TextureLike { public fun getItemModel(): VanillaItemModel + public val ref: ResourceRef<*> + public val materials: List public val resource: Resource diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/data/CuTMeta.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/data/CuTMeta.kt index f357789..d0ae7b7 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/data/CuTMeta.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/data/CuTMeta.kt @@ -10,7 +10,7 @@ import xyz.mastriel.cutapi.utils.* @Serializable public open class CuTMeta { @SerialName("generate") - public open val generateBlock: List = listOf() + public open val generateBlocks: List = listOf() /** * Clone blocks will create a whole new resource, diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt index 02a58be..d0c7e81 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt @@ -1,19 +1,33 @@ package xyz.mastriel.cutapi.resources.data.minecraft import kotlinx.serialization.* +import xyz.mastriel.cutapi.resources.* +import xyz.mastriel.cutapi.resources.builtin.* + @Serializable public data class ItemModelData( - val parent: String = "minecraft:item/generated", + @SerialName("parent") + public val parent: String = "minecraft:item/generated", @OptIn(ExperimentalSerializationApi::class) @EncodeDefault(EncodeDefault.Mode.NEVER) - val textures: Map = mapOf(), + @SerialName("textures") + private val _textures: Map = mapOf(), @OptIn(ExperimentalSerializationApi::class) @EncodeDefault(EncodeDefault.Mode.NEVER) val overrides: List = listOf() -) +) { + @Transient + public val textures: Map = _textures.toMutableMap().also { + for ((key, value) in it) { + if ("://" in value) { + it[key] = ref(value).toMinecraftLocator() + } + } + } +} @Serializable public data class ItemOverrides( diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/GenerateProcessor.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/GenerateProcessor.kt index 0bf4665..36cd50e 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/GenerateProcessor.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/GenerateProcessor.kt @@ -1,16 +1,78 @@ package xyz.mastriel.cutapi.resources.process +import kotlinx.serialization.* +import net.peanuuutz.tomlkt.* import xyz.mastriel.cutapi.* +import xyz.mastriel.cutapi.registry.* import xyz.mastriel.cutapi.resources.* +import xyz.mastriel.cutapi.resources.builtin.* +import xyz.mastriel.cutapi.resources.data.* +public class GenerateResource( + override val ref: ResourceRef, + override val metadata: Metadata +) : MetadataResource(ref, metadata) { + + @Serializable + public data class Metadata( + @SerialName("gen_id") + val generatorId: Identifier, + @SerialName("base_id") + val baseId: ResourceRef<@Contextual Resource>, + val options: TomlTable + ) : CuTMeta() + + public companion object { + public val Loader: ResourceFileLoader = metadataResourceLoader( + extensions = listOf("gen.toml"), + resourceTypeId = id(Plugin, "generate"), + serializer = Metadata.serializer(), + ) { + success(GenerateResource(ref, CuTAPI.toml.decodeFromString(dataAsString))) + } + } +} + // processes all resources and generates new resources based on the generate blocks internal fun generateResources(resources: List, stage: ResourceGenerationStage) { for (resource in resources) { - val generators = resource.metadata?.generateBlock ?: continue + if (resource is GenerateResource) { + // If the resource is a GenerateResource, we handle it separately + val generator = ResourceGenerator.getOrNull(resource.metadata.generatorId) + ?: error("'${resource.metadata.generatorId}' is not a valid Resource Generator for ${resource::class.simpleName}.") + if (generator.stage != stage) continue + + val ref = resource.ref.toString().removeSuffix("gen.toml") + resource.ref.extension + + val generateBlock = object : GenerateBlock() { + override val generatorId: Identifier = resource.metadata.generatorId + override val subId: String? = null + override val options: TomlTable = resource.metadata.options + } + + val newResources = mutableListOf() + fun register(resource: Resource) { + CuTAPI.resourceManager.register(resource) + newResources.add(resource) + } + + val baseResource = resource.metadata.baseId.getResource() + ?: error("Base resource for GenerateResource '${resource.ref}' not found.") + + val ctx = ResourceGeneratorContext(baseResource, generateBlock, ref(ref), ::register) + + generator.generate(ctx) + + for (newResource in newResources) { + generateResources(newResources, stage) + } + continue + } + + val generators = resource.metadata?.generateBlocks ?: continue for (generateBlock in generators) { try { - val generator = ResourceGenerator.getOrNull(generateBlock.generatorId) ?: error("'${generateBlock.generatorId}' is not a valid Resource Generator for ${resource::class.simpleName}.") if (generator.stage != stage) return @@ -22,7 +84,8 @@ internal fun generateResources(resources: List, stage: ResourceGenerat newResources.add(resource) } - val ctx = ResourceGeneratorContext(resource, generateBlock, subId, ::register) + val ctx = + ResourceGeneratorContext(resource, generateBlock, resource.ref.generatedSubId(subId), ::register) generator.generate(ctx) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/HorizontalAtlasTextureGenerator.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/HorizontalAtlasTextureGenerator.kt index 4dba7d5..0cad6a5 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/HorizontalAtlasTextureGenerator.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/HorizontalAtlasTextureGenerator.kt @@ -34,18 +34,18 @@ public val HorizontalAtlasTextureGenerator: ResourceGenerator = resourceGenerato val amountOfTextures = texture.data.width / width - val subIdTemplate = this.suppliedSubId + val template = this.ref.toString() for (i in 0 until amountOfTextures) { val subTexture = texture.data.getSubimage(i * width, 0, width, texture.data.height).copy() - val subId = subIdTemplate.replace("#", i.toString()) + val ref = template.replace("*", i.toString()) var metadata = options.generatedMetadata.copy() if (options.specificMetadata.containsKey(i)) { metadata = metadata.apply(options.specificMetadata[i]!!, Texture2D.Metadata.serializer()) } val subTextureResource = Texture2D( - ref = texture.ref.generatedSubId(subId), + ref = ref(ref), data = subTexture, metadata = metadata ) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/InventoryTextureGenerator.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/InventoryTextureGenerator.kt new file mode 100644 index 0000000..007c037 --- /dev/null +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/InventoryTextureGenerator.kt @@ -0,0 +1,38 @@ +package xyz.mastriel.cutapi.resources.process + +import kotlinx.serialization.* +import xyz.mastriel.cutapi.* +import xyz.mastriel.cutapi.registry.* +import xyz.mastriel.cutapi.resources.* +import xyz.mastriel.cutapi.resources.builtin.* + +@Serializable +private data class InventoryTextureGeneratorOptions( + val texture: ResourceRef<@Contextual Texture2D> +) + +public val InventoryTextureGenerator: ResourceGenerator = resourceGenerator( + id(Plugin, "inventory_texture"), + ResourceGenerationStage.BeforeProcessors +) { + + val options = castOptions(InventoryTextureGeneratorOptions.serializer()) + + val model = ref(Plugin, "ui/inventory_bg.model3d.json").getResource()!! + + val newModel = Model3D( + ref = ref.cast(), + modelJson = model.modelJson.copy( + textures = model.modelJson.textures.toMutableMap().also { + it["2"] = options.texture.toMinecraftLocator() + } + ), + metadata = model.metadata.copy( + textures = model.metadata.textures.toMutableMap().also { + it["2"] = options.texture + } + ) + ) + + register(newModel) +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt index 7ab558f..881eddb 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/process/Texture2DProcessor.kt @@ -18,11 +18,12 @@ public val TextureAndModelProcessor: ResourceProcessor = resourceProcessor) { +private fun generateTextureItemAndModelJsonFiles(textures: Collection) { for (texture in textures) { generateModelFile(texture) generateItemModelFiles(texture) } +} + +private fun generateModelItemFiles(textures: Collection) { + for (texture in textures) { + generateItemModelFiles(texture) + } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/utils/ConfigUtils.kt b/src/main/kotlin/xyz/mastriel/cutapi/utils/ConfigUtils.kt index 177b408..e8c2916 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/utils/ConfigUtils.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/utils/ConfigUtils.kt @@ -9,15 +9,20 @@ import kotlin.reflect.* public fun configValue(plugin: Plugin, path: String, default: T): ConfigDelegate { return ConfigDelegate(plugin, path, default) } -internal fun cutConfigValue(path: String, default: T): ConfigDelegate { + +internal fun cutConfigValue(path: String, default: T): ConfigDelegate { return ConfigDelegate(Plugin, path, default) } -public class ConfigDelegate internal constructor(public val plugin: Plugin, public val path: String, public val default: T) : +public class ConfigDelegate internal constructor( + public val plugin: Plugin, + public val path: String, + public val default: T +) : ReadOnlyProperty { @Suppress("UNCHECKED_CAST") - override fun getValue(thisRef: Any?, property: KProperty<*>): T { + override operator fun getValue(thisRef: Any?, property: KProperty<*>): T { return try { (plugin.config.get(path) as? T?) ?: default } catch (ex: ClassCastException) { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/utils/PlayerUtils.kt b/src/main/kotlin/xyz/mastriel/cutapi/utils/PlayerUtils.kt index 25230ab..b95fb3f 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/utils/PlayerUtils.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/utils/PlayerUtils.kt @@ -2,6 +2,9 @@ package xyz.mastriel.cutapi.utils import org.bukkit.* import org.bukkit.entity.* +import java.util.* +import kotlin.properties.* +import kotlin.reflect.* public fun onlinePlayers(): List = Bukkit.getOnlinePlayers() @@ -9,3 +12,16 @@ public fun onlinePlayers(): List = Bukkit.getOnlinePlayers() .toList() public fun playerNameList(): List = onlinePlayers().map(Player::getName) + + +@JvmInline +public value class PlayerUUID(public val uuid: UUID) : ReadOnlyProperty { + public fun toPlayer(): Player? = Bukkit.getPlayer(uuid) + + override operator fun getValue(thisRef: Any?, property: KProperty<*>): Player? { + return toPlayer() + } + +} + +public val Player.playerUUID: PlayerUUID get() = PlayerUUID(uniqueId) \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/utils/TomlJsonUtils.kt b/src/main/kotlin/xyz/mastriel/cutapi/utils/TomlJsonUtils.kt index 5a0e3c4..b3cbfc8 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/utils/TomlJsonUtils.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/utils/TomlJsonUtils.kt @@ -67,7 +67,9 @@ public fun TomlTable.combine(other: TomlTable, combineArrays: Boolean = true): T if (map[key] is TomlArray && other[key] is TomlArray) { val newArray = mutableListOf() val (currentArray, otherArray) = map[key]!!.asTomlArray() to other[key]!!.asTomlArray() - if (combineArrays) newArray.addAll(currentArray) + if (combineArrays || otherArray.isEmpty()) { + newArray.addAll(currentArray) + } newArray.addAll(otherArray) map[key] = TomlArray(newArray) continue diff --git a/src/main/kotlin/xyz/mastriel/cutapi/utils/WeakPlayerMap.kt b/src/main/kotlin/xyz/mastriel/cutapi/utils/WeakPlayerMap.kt new file mode 100644 index 0000000..edd7aa7 --- /dev/null +++ b/src/main/kotlin/xyz/mastriel/cutapi/utils/WeakPlayerMap.kt @@ -0,0 +1,66 @@ +package xyz.mastriel.cutapi.utils + +import org.bukkit.* +import org.bukkit.entity.* + +// A map that only allows for online players to be stored. +public class WeakPlayerMap internal constructor(private val base: MutableMap) : MutableMap { + override fun put(key: Player, value: V): V? { + purgeOfflinePlayers() + return base.put(key, value) + } + + + private fun purgeOfflinePlayers() { + base.keys.removeIf { !it.isOnline } + } + + override val entries: MutableSet> + get() = purgeOfflinePlayers().let { base.entries } + override val keys: MutableSet + get() = purgeOfflinePlayers().let { base.keys } + override val size: Int + get() = purgeOfflinePlayers().let { base.size } + override val values: MutableCollection + get() = purgeOfflinePlayers().let { base.values } + + override fun clear() { + return purgeOfflinePlayers().let { base.clear() } + } + + override fun isEmpty(): Boolean { + return purgeOfflinePlayers().let { base.isEmpty() } + } + + override fun remove(key: Player): V? { + return purgeOfflinePlayers().let { base.remove(key) } + } + + override fun putAll(from: Map) { + return purgeOfflinePlayers().let { base.putAll(from) } + } + + override fun get(key: Player): V? { + return purgeOfflinePlayers().let { base[key] } + } + + override fun containsValue(value: V): Boolean { + return purgeOfflinePlayers().let { base.containsValue(value) } + } + + override fun containsKey(key: Player): Boolean { + return purgeOfflinePlayers().let { base.containsKey(key) } + } +} + +public fun weakPlayerMapOf(): WeakPlayerMap = WeakPlayerMap(mutableMapOf()) + +public fun weakPlayerMapOf(vararg pairs: Pair): WeakPlayerMap = WeakPlayerMap(mutableMapOf(*pairs)) + +public fun weakPlayerMapOf(map: Map): WeakPlayerMap = WeakPlayerMap(map.toMutableMap()) + +public fun MutableMap.toWeakPlayerMap(): WeakPlayerMap = WeakPlayerMap(this) + +@JvmName("toWeakPlayerMapFromOfflinePlayer") +public fun MutableMap.toWeakPlayerMap(): WeakPlayerMap = + WeakPlayerMap(this.filterKeys { it.isOnline }.mapKeys { it.key.player!! }.toMutableMap()) diff --git a/src/main/resources/pack/ui/inventory_bg.model3d.json b/src/main/resources/pack/ui/inventory_bg.model3d.json index 5d3d80f..9032f45 100644 --- a/src/main/resources/pack/ui/inventory_bg.model3d.json +++ b/src/main/resources/pack/ui/inventory_bg.model3d.json @@ -1,56 +1,211 @@ { "credit": "Made with Blockbench", "textures": { - "2": "item/inventory_part" }, "elements": [ { - "from": [0, 0, -1], - "to": [16, 16, 0], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 8, 7]}, + "from": [ + 1.0435, + 1.0435, + -1 + ], + "to": [ + 14.95654, + 14.95654, + 0 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 8, + 8, + 7 + ] + }, "faces": { - "north": {"uv": [0, 0, 16, 16], "texture": "#1"}, - "east": {"uv": [0, 0, 16, 1], "texture": "#1"}, - "south": {"uv": [0, 0, 16, 16], "texture": "#1"}, - "west": {"uv": [0, 0, 0, 0], "texture": "#1"}, - "up": {"uv": [0, 0, 0, 0], "texture": "#1"}, - "down": {"uv": [0, 0, 0, 0], "texture": "#1"} + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#1" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1 + ], + "texture": "#1" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#1" + }, + "west": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": "#1" + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": "#1" + }, + "down": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": "#1" + } } }, { - "from": [0, 0, 0], - "to": [16, 16, 16], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 8, 23]}, + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 8, + 8, + 23 + ] + }, "faces": { - "north": {"uv": [0, 0, 16, 16], "texture": "#2"}, - "east": {"uv": [0, 0, 16, 1], "texture": "#2"}, - "south": {"uv": [0, 0, 16, 1], "texture": "#2"}, - "west": {"uv": [0, 0, 16, 1], "texture": "#2"}, - "up": {"uv": [0, 0, 0, 0], "texture": "#2"}, - "down": {"uv": [0, 0, 16, 16], "texture": "#2"} + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#2" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 1 + ], + "texture": "#2" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 1 + ], + "texture": "#2" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 1 + ], + "texture": "#2" + }, + "up": { + "uv": [ + 0, + 0, + 0, + 0 + ], + "texture": "#2" + }, + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#2" + } } } ], "gui_light": "front", "display": { "thirdperson_righthand": { - "scale": [0, 0, 0] + "scale": [ + 0, + 0, + 0 + ] }, "thirdperson_lefthand": { - "scale": [0, 0, 0] + "scale": [ + 0, + 0, + 0 + ] }, "firstperson_righthand": { - "scale": [0, 0, 0] + "scale": [ + 0, + 0, + 0 + ] }, "firstperson_lefthand": { - "scale": [0, 0, 0] + "scale": [ + 0, + 0, + 0 + ] }, "ground": { - "scale": [0.3, 0.3, 0.3] + "scale": [ + 0.3, + 0.3, + 0.3 + ] }, "gui": { - "rotation": [-180, -180, 0], - "scale": [1.15, -1.15, -1.15] + "rotation": [ + -180, + -180, + 0 + ], + "scale": [ + 1.15, + -1.15, + -1.15 + ] } } } \ No newline at end of file diff --git a/src/main/resources/pack/ui/inventory_part.model3d.json b/src/main/resources/pack/ui/inventory_part.model3d.json deleted file mode 100644 index ba74fa2..0000000 --- a/src/main/resources/pack/ui/inventory_part.model3d.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "item/icon/template", - "textures": { - "1": "item/inventory_part" - } -} \ No newline at end of file diff --git a/src/main/resources/pack/ui/inventory_part.model3d.json.meta b/src/main/resources/pack/ui/inventory_part.model3d.json.meta deleted file mode 100644 index 14f4e9f..0000000 --- a/src/main/resources/pack/ui/inventory_part.model3d.json.meta +++ /dev/null @@ -1,3 +0,0 @@ - -id = "cutapi:model3d" -materials = ["glistering_melon_slice"] diff --git a/src/main/resources/pack/ui/inventory_texture.template b/src/main/resources/pack/ui/inventory_texture.template new file mode 100644 index 0000000..cd2d161 --- /dev/null +++ b/src/main/resources/pack/ui/inventory_texture.template @@ -0,0 +1,4 @@ +[item_model_data] +parent = "cutapi:ui/inventory_bg" +textures.1 = "{{@ref}}" +textures.2 = "cutapi://ui/inventory_bg.png" diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 4906b27..cf15339 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -2,7 +2,7 @@ name: CuTAPI version: '$version' main: xyz.mastriel.cutapi.CuTAPIPlugin description: An API designed to make creating cool things in plugins possible! -api-version: '1.20' +api-version: '1.21' loader: xyz.mastriel.cutapi.CuTAPILoader permissions: cutapi.admin.give: From 8070fa9165423298ef1aa4b080bcd4fab5154fce Mon Sep 17 00:00:00 2001 From: Zac Date: Sat, 26 Jul 2025 22:14:12 -0600 Subject: [PATCH 04/10] improve documentation and revamp identifier registries, requiring that items are registered inside of events. --- .idea/copilotDiffState.xml | 18 +++ .idea/gradle.xml | 1 + .idea/jarRepositories.xml | 15 +++ .idea/modules.xml | 14 -- ...sers.mastriel.IdeaProjects.CuTAPI.main.iml | 18 +++ .../mastriel.IdeaProjects.CuTAPI.test.iml | 18 +++ .idea/vcs.xml | 12 ++ src/main/kotlin/xyz/mastriel/cutapi/CuTAPI.kt | 5 + .../xyz/mastriel/cutapi/CuTAPIPlugin.kt | 82 +++++++++--- .../mastriel/cutapi/block/BlockItemPolicy.kt | 12 +- .../xyz/mastriel/cutapi/item/CustomItem.kt | 4 +- .../xyz/mastriel/cutapi/item/ItemBuilders.kt | 40 +++--- .../cutapi/item/behaviors/Equipable.kt | 67 ++++++++++ .../cutapi/item/behaviors/ModifyAttribute.kt | 44 ++++++ .../cutapi/item/recipe/ShapedRecipeBuilder.kt | 8 +- .../item/recipe/ShapelessRecipeBuilder.kt | 8 +- .../cutapi/registry/DeferredRegistry.kt | 89 +++++++++++++ .../cutapi/registry/IdentifierRegistry.kt | 96 +++++++++++++- .../mastriel/cutapi/resources/FolderRef.kt | 57 ++++++++ .../xyz/mastriel/cutapi/resources/Locator.kt | 50 ++++++- .../xyz/mastriel/cutapi/resources/Resource.kt | 89 ++++++++++++- .../cutapi/resources/ResourceFileLoader.kt | 70 +++++++++- .../cutapi/resources/ResourceGenerator.kt | 78 +++++++++-- .../cutapi/resources/ResourceInspector.kt | 125 +++++++++++++++++- .../cutapi/resources/ResourcePackManager.kt | 70 ++++++++-- .../cutapi/resources/ResourceProcessor.kt | 53 +++++--- .../mastriel/cutapi/resources/ResourceRef.kt | 109 +++++++++++++-- .../mastriel/cutapi/resources/ResourceRoot.kt | 37 +++++- .../mastriel/cutapi/resources/VanillaRef.kt | 6 + .../cutapi/utils/SimpleEventHandler.kt | 4 +- 30 files changed, 1161 insertions(+), 138 deletions(-) create mode 100644 .idea/copilotDiffState.xml create mode 100644 .idea/modules/Users.mastriel.IdeaProjects.CuTAPI.main.iml create mode 100644 .idea/modules/mastriel.IdeaProjects.CuTAPI.test.iml create mode 100644 src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/Equipable.kt create mode 100644 src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/ModifyAttribute.kt create mode 100644 src/main/kotlin/xyz/mastriel/cutapi/registry/DeferredRegistry.kt diff --git a/.idea/copilotDiffState.xml b/.idea/copilotDiffState.xml new file mode 100644 index 0000000..1e290a1 --- /dev/null +++ b/.idea/copilotDiffState.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ce1c62c..1fd1b27 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -10,6 +10,7 @@ + diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 70fcb5c..8e55d29 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -66,5 +66,20 @@ diff --git a/src/main/kotlin/xyz/mastriel/cutapi/CuTAPI.kt b/src/main/kotlin/xyz/mastriel/cutapi/CuTAPI.kt index 917d309..d9ae495 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/CuTAPI.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/CuTAPI.kt @@ -45,6 +45,11 @@ public object CuTAPI { public val packetEventManager: PacketEventManager = PacketEventManager() internal val blockBreakManager = BlockBreakManager() + /** + * Most registries should probably be initialized here. + */ + public val serverReady: EventHandlerList = EventHandlerList() + /** * Register a plugin with CuTAPI. This is used to namespace any items registered with the API, and * is used to hold some other additional information about the plugin. diff --git a/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt b/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt index 120f944..5b13548 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/CuTAPIPlugin.kt @@ -6,14 +6,18 @@ import com.github.shynixn.mccoroutine.bukkit.* import io.papermc.paper.plugin.lifecycle.event.types.* import kotlinx.coroutines.* import org.bukkit.* +import org.bukkit.event.* +import org.bukkit.event.server.* import org.bukkit.plugin.java.* import org.bukkit.scheduler.* +import xyz.mastriel.cutapi.block.* import xyz.mastriel.cutapi.commands.* import xyz.mastriel.cutapi.item.* import xyz.mastriel.cutapi.item.behaviors.* import xyz.mastriel.cutapi.item.bukkitevents.* import xyz.mastriel.cutapi.item.recipe.* import xyz.mastriel.cutapi.nms.* +import xyz.mastriel.cutapi.registry.* import xyz.mastriel.cutapi.resources.* import xyz.mastriel.cutapi.resources.builtin.* import xyz.mastriel.cutapi.resources.minecraft.* @@ -45,23 +49,57 @@ public class CuTAPIPlugin : JavaPlugin(), CuTPlugin { registerCommands() registerEvents() registerPeriodics() + CuTItemStack.registerType( id = ItemStackUtility.DEFAULT_ITEMSTACK_TYPE_ID, kClass = CuTItemStack::class, constructor = CuTItemStack.CONSTRUCTOR ) - CustomItem.register(CustomItem.Unknown) + CustomItem.modifyRegistry(RegistryPriority(Int.MAX_VALUE)) { + register(CustomItem.Unknown) + } TexturePostProcessor.registerBuiltins() - TexturePostProcessor.register(GrayscalePostProcessor) - TexturePostProcessor.register(PaletteSwapPostProcessor) - TexturePostProcessor.register(MultiplyOpaquePixelsProcessor) + TexturePostProcessor.modifyRegistry { + register(GrayscalePostProcessor) + register(PaletteSwapPostProcessor) + register(MultiplyOpaquePixelsProcessor) + } + ResourcePackProcessor.register(TextureAndModelProcessor, name = "Texture Processor") - MinecraftAssetDownloader.register(GithubMinecraftAssetDownloader()) - Uploader.register(BuiltinUploader()) + + MinecraftAssetDownloader.modifyRegistry { + register(GithubMinecraftAssetDownloader()) + } + + Uploader.modifyRegistry { + register(BuiltinUploader()) + } CuTAPI.packetEventManager.registerPacketListener(PacketItemHandler) + CustomItem.DeferredRegistry.commitRegistry() + + CuTAPI.serverReady { + ResourceFileLoader.initialize() + ResourceGenerator.initialize() + MinecraftAssetDownloader.initialize() + TexturePostProcessor.initialize() + Uploader.initialize() + + CustomItem.initialize() + CustomShapedRecipe.initialize() + CustomShapelessRecipe.initialize() + CustomFurnaceRecipe.initialize() + CustomSmithingTableRecipe.initialize() + + CustomTile.initialize() + CustomBlock.initialize() + CustomTileEntity.initialize() + + ToolCategory.initialize() + ToolTier.initialize() + } registerResourceLoaders() @@ -101,17 +139,22 @@ public class CuTAPIPlugin : JavaPlugin(), CuTPlugin { } private fun registerResourceLoaders() { - ResourceGenerator.register(HorizontalAtlasTextureGenerator) - ResourceGenerator.register(InventoryTextureGenerator) + ResourceGenerator.modifyRegistry { + register(HorizontalAtlasTextureGenerator) + register(InventoryTextureGenerator) + } + + ResourceFileLoader.modifyRegistry { + register(FolderApplyResourceLoader) + register(Texture2DResourceLoader) + register(Model3DResourceLoader) + register(MetadataResource.Loader) + register(PostProcessDefinitionsResource.Loader) + register(GenerateResource.Loader) + register(TemplateResourceLoader) + } - ResourceFileLoader.register(FolderApplyResourceLoader) - ResourceFileLoader.register(Texture2DResourceLoader) - ResourceFileLoader.register(Model3DResourceLoader) - ResourceFileLoader.register(MetadataResource.Loader) - ResourceFileLoader.register(PostProcessDefinitionsResource.Loader) - ResourceFileLoader.register(GenerateResource.Loader) - ResourceFileLoader.register(TemplateResourceLoader) } private fun registerEvents() { @@ -125,6 +168,15 @@ public class CuTAPIPlugin : JavaPlugin(), CuTPlugin { server.pluginManager.registerEvents(UploaderJoinEvents(), this) server.pluginManager.registerEvents(CuTAPI.playerPacketManager, this) + val serverReadyHandler = object : Listener { + @EventHandler + fun serverReady(event: ServerLoadEvent) { + if (event.type != ServerLoadEvent.LoadType.STARTUP) return + CuTAPI.serverReady.trigger(Unit) + } + } + server.pluginManager.registerEvents(serverReadyHandler, this) + val itemBehaviorEvents = ItemBehaviorEvents() server.pluginManager.registerEvents(itemBehaviorEvents, this) CuTAPI.periodicManager.register(this, itemBehaviorEvents) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/block/BlockItemPolicy.kt b/src/main/kotlin/xyz/mastriel/cutapi/block/BlockItemPolicy.kt index 0d2c008..cb72eaa 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/block/BlockItemPolicy.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/block/BlockItemPolicy.kt @@ -15,7 +15,7 @@ import xyz.mastriel.cutapi.item.behaviors.* */ public sealed class BlockItemPolicy { - internal abstract fun tileRegister(tileDescriptor: TileDescriptor, customTile: CustomTile<*>): CustomItem<*>? + internal abstract fun tileCreate(tileDescriptor: TileDescriptor, customTile: CustomTile<*>): CustomItem<*>? /** * Generate a new item and register it. You can supply your own item descriptor which will be combined @@ -25,13 +25,13 @@ public sealed class BlockItemPolicy { public constructor(descriptor: ItemDescriptorBuilder.() -> Unit) : this(ItemDescriptorBuilder().apply(descriptor).build()) - override fun tileRegister(tileDescriptor: TileDescriptor, customTile: CustomTile<*>): CustomItem<*> { + override fun tileCreate(tileDescriptor: TileDescriptor, customTile: CustomTile<*>): CustomItem<*> { val material = when (val strategy = customTile.descriptor.blockStrategy) { is BlockStrategy.Vanilla -> strategy.material else -> Material.STONE } - if (descriptor != null) return registerCustomItem(customTile.id / "item", material, descriptor) - return registerCustomItem(customTile.id / "item", material) { + if (descriptor != null) return customItem(customTile.id / "item", material, descriptor) + return customItem(customTile.id / "item", material) { behavior(BlockPlaceBehavior(customTile)) display { @@ -47,7 +47,7 @@ public sealed class BlockItemPolicy { * If it does already have one, a warning will be printed. You shouldn't use this with an item that has one! */ public data class Item(val item: CustomItem<*>, val consumesItem: Boolean = true) : BlockItemPolicy() { - override fun tileRegister(tileDescriptor: TileDescriptor, customTile: CustomTile<*>): CustomItem<*> { + override fun tileCreate(tileDescriptor: TileDescriptor, customTile: CustomTile<*>): CustomItem<*> { val behaviors = item.descriptor.itemBehaviors as? MutableList ?: error("${item.id} does not have its itemBehaviors as a MutableList!") @@ -61,7 +61,7 @@ public sealed class BlockItemPolicy { * This does nothing to create relationships between your block and any items. */ public data object None : BlockItemPolicy() { - override fun tileRegister(tileDescriptor: TileDescriptor, customTile: CustomTile<*>) = null + override fun tileCreate(tileDescriptor: TileDescriptor, customTile: CustomTile<*>) = null } } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt index e48aeb6..1b86895 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/CustomItem.kt @@ -84,6 +84,8 @@ public open class CustomItem( public companion object : IdentifierRegistry>("Custom Items") { + internal val DeferredRegistry = defer(RegistryPriority(Int.MAX_VALUE)) + public val Unknown: CustomItem = customItem( unknownID(), Material.ANVIL @@ -96,7 +98,7 @@ public open class CustomItem( } } - public val InventoryBackground: CustomItem = registerCustomItem( + public val InventoryBackground: CustomItem by DeferredRegistry.registerCustomItem( id = id(Plugin, "inventory_background"), Material.GLISTERING_MELON_SLICE ) { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/ItemBuilders.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/ItemBuilders.kt index ed16949..8c1f2bd 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/ItemBuilders.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/ItemBuilders.kt @@ -53,14 +53,14 @@ public fun typedCustomItem( } @JvmName("registerCustomItemWithStackType") -public inline fun registerCustomItem( +public inline fun DeferredRegistry>.registerCustomItem( id: Identifier, bukkitMaterial: Material, noinline block: (ItemDescriptorBuilder.() -> Unit)? -): CustomItem { +): Deferred> { val customItem = customItem(id, bukkitMaterial, block) - CustomItem.register(customItem) - return customItem + @Suppress("UNCHECKED_CAST") + return register { customItem } as Deferred> } public fun customItem( @@ -90,44 +90,44 @@ public fun customItem( } } -public fun registerCustomItem( +public fun DeferredRegistry>.registerCustomItem( id: Identifier, bukkitMaterial: Material, block: ItemDescriptorBuilder.() -> Unit -): CustomItem { +): Deferred> { val customItem = customItem(id, bukkitMaterial, block) - CustomItem.register(customItem) - return customItem + @Suppress("UNCHECKED_CAST") + return register { customItem } as Deferred> } -public fun registerCustomItem( +public fun DeferredRegistry>.registerCustomItem( id: Identifier, bukkitMaterial: Material, descriptor: ItemDescriptor -): CustomItem { +): Deferred> { val customItem = customItem(id, bukkitMaterial, descriptor) - CustomItem.register(customItem) - return customItem + @Suppress("UNCHECKED_CAST") + return register { customItem } as Deferred> } -public fun registerCustomItem( +public fun DeferredRegistry>.registerCustomItem( id: Identifier, bukkitMaterial: Material, name: PersonalizedWithDefault -): CustomItem { +): Deferred> { val customItem = customItem(id, bukkitMaterial, name) - CustomItem.register(customItem) - return customItem + @Suppress("UNCHECKED_CAST") + return register { customItem } as Deferred> } -public fun registerCustomItem( +public fun DeferredRegistry>.registerCustomItem( id: Identifier, bukkitMaterial: Material, name: PersonalizedWithDefault, behaviors: Collection -): CustomItem { +): Deferred> { val customItem = customItem(id, bukkitMaterial, name, behaviors) - CustomItem.register(customItem) - return customItem + @Suppress("UNCHECKED_CAST") + return register { customItem } as Deferred> } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/Equipable.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/Equipable.kt new file mode 100644 index 0000000..1ff481d --- /dev/null +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/Equipable.kt @@ -0,0 +1,67 @@ +package xyz.mastriel.cutapi.item.behaviors + +import org.bukkit.inventory.* +import xyz.mastriel.cutapi.* +import xyz.mastriel.cutapi.behavior.* +import xyz.mastriel.cutapi.item.* +import xyz.mastriel.cutapi.registry.* +import xyz.mastriel.cutapi.resources.* +import xyz.mastriel.cutapi.resources.builtin.* + +@RepeatableBehavior +public class Equipable private constructor( + private val slot: EquipmentSlot, + private val isSwappable: Boolean, + private val model: ResourceRef?, + private var damageItemWhenHurt: Boolean +) : ItemBehavior(id(Plugin, "equipable")) { + + public class Builder internal constructor(public val slot: EquipmentSlot) { + public var isSwappable: Boolean = true + public var model: ResourceRef? = null + public var damageItemWhenHurt: Boolean = false + } + + @Suppress("UnstableApiUsage") + override fun onCreate(item: CuTItemStack) { + item.handle.editMeta { meta -> + meta.setEquippable(meta.equippable.also { + it.slot = slot + it.model = model?.getResource()?.getItemModel()?.toIdentifier()?.toNamespacedKey() + it.isDamageOnHurt = damageItemWhenHurt + }) + } + } + + public companion object { + public fun of(slot: EquipmentSlot, builder: Builder.() -> Unit): Equipable { + val b = Builder(slot).apply(builder) + return Equipable( + slot, + b.isSwappable, + b.model, + b.damageItemWhenHurt + ) + } + + public fun head(builder: Builder.() -> Unit): Equipable = of( + EquipmentSlot.HEAD, + builder + ) + + public fun chest(builder: Builder.() -> Unit): Equipable = of( + EquipmentSlot.CHEST, + builder + ) + + public fun legs(builder: Builder.() -> Unit): Equipable = of( + EquipmentSlot.LEGS, + builder + ) + + public fun feet(builder: Builder.() -> Unit): Equipable = of( + EquipmentSlot.FEET, + builder + ) + } +} diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/ModifyAttribute.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/ModifyAttribute.kt new file mode 100644 index 0000000..adde096 --- /dev/null +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/behaviors/ModifyAttribute.kt @@ -0,0 +1,44 @@ +package xyz.mastriel.cutapi.item.behaviors + +import org.bukkit.attribute.* +import org.bukkit.inventory.* +import xyz.mastriel.cutapi.* +import xyz.mastriel.cutapi.behavior.* +import xyz.mastriel.cutapi.item.* +import xyz.mastriel.cutapi.registry.* + +@Suppress("UnstableApiUsage") +@RepeatableBehavior +public class ModifyAttribute( + public val key: Identifier, + public val slotGroup: EquipmentSlotGroup, + public val attribute: Attribute, + public val amount: Double, + public val operation: AttributeModifier.Operation = AttributeModifier.Operation.ADD_NUMBER +) : ItemBehavior(id(Plugin, "attribute")) { + + override fun onCreate(item: CuTItemStack) { + updateItem(item) + } + + public fun updateItem(item: CuTItemStack) { + item.handle.editMeta { meta -> + + val previous = meta.getAttributeModifiers(attribute)?.first { it.key == key.toNamespacedKey() } + if (previous != null) { + // If the attribute modifier is already present, remove it + meta.removeAttributeModifier(attribute, previous) + } + + meta.addAttributeModifier( + attribute, + AttributeModifier( + key.toNamespacedKey(), + amount, + operation, + slotGroup + ) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt index 638f47c..000f2ce 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapedRecipeBuilder.kt @@ -181,7 +181,9 @@ public fun ItemDescriptorBuilder.shapedRecipe( ) { onRegister += { val builder = ShapedRecipeBuilder(item.createItemStack(amount), id, size).apply(block) - CustomShapedRecipe.register(builder.build()) + CustomShapedRecipe.modifyRegistry { + register(builder.build()) + } } } @@ -195,9 +197,9 @@ public fun shapedRecipe( return builder.build() } -public fun registerShapedRecipe( +public fun DeferredRegistry.registerShapedRecipe( id: Identifier, size: CustomShapedRecipe.Size, result: ItemStack, block: ShapedRecipeBuilder.() -> Unit -): CustomShapedRecipe = CustomShapedRecipe.register(shapedRecipe(id, size, result, block)) \ No newline at end of file +): Deferred = register { shapedRecipe(id, size, result, block) } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt index 9853ba5..1f19fc8 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/item/recipe/ShapelessRecipeBuilder.kt @@ -94,7 +94,9 @@ public fun ItemDescriptorBuilder.shapelessRecipe( ) { onRegister += { val builder = ShapelessRecipeBuilder(item.createItemStack(amount), id).apply(block) - CustomShapelessRecipe.register(builder.build()) + CustomShapelessRecipe.modifyRegistry { + builder.build() + } } } @@ -107,8 +109,8 @@ public fun shapelessRecipe( return builder.build() } -public fun registerShapelessRecipe( +public fun DeferredRegistry.registerShapelessRecipe( id: Identifier, result: ItemStack, block: ShapelessRecipeBuilder.() -> Unit -): CustomShapelessRecipe = CustomShapelessRecipe.register(shapelessRecipe(id, result, block)) \ No newline at end of file +): Deferred = register { shapelessRecipe(id, result, block) } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/registry/DeferredRegistry.kt b/src/main/kotlin/xyz/mastriel/cutapi/registry/DeferredRegistry.kt new file mode 100644 index 0000000..fc83c26 --- /dev/null +++ b/src/main/kotlin/xyz/mastriel/cutapi/registry/DeferredRegistry.kt @@ -0,0 +1,89 @@ +package xyz.mastriel.cutapi.registry + +import kotlin.reflect.* + + +public interface Deferred { + public operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): T + + public fun get(): T +} + + +public class DeferredDelegate internal constructor( + private val idRegistry: IdentifierRegistry, +) : Deferred { + internal var id: Identifier? = null + public override operator fun getValue(thisRef: Any?, property: KProperty<*>): T { + return get(); + } + + public override fun get(): T { + if (id == null) { + error("Cannot get deferred item before it has been registered.") + } + return idRegistry.get(id!!) + } +} + + +public interface DeferredRegistry { + public val isOpen: Boolean + + /** + * Registers a deferred item. Note that this must return an Identifiable with a consistent Identifier. + */ + public fun register(producer: () -> T): Deferred + public fun getByProducer(producer: () -> T): Identifier + public fun associateId(producer: () -> T, id: Identifier) + public fun commitRegistry() +} + +public open class BasicDeferredRegistry internal constructor( + private val registry: IdentifierRegistry, + private val priority: RegistryPriority +) : DeferredRegistry { + protected data class DeferredItem( + val producer: () -> T, + val delegate: DeferredDelegate + ) + + private val items: MutableList> = mutableListOf() + private val producersToIds = mutableMapOf<() -> T, Identifier>() + final override var isOpen: Boolean = true + protected set + + /** + * Registers a deferred item. Note that this must return an Identifiable with a consistent Identifier. + */ + override fun register(producer: () -> T): Deferred { + if (!isOpen) error("Deferred registry is already closed") + return DeferredDelegate(registry).also { + items += DeferredItem(producer, it) + } + } + + override fun getByProducer(producer: () -> T): Identifier { + return producersToIds[producer] ?: error("Producer not registered") + } + + override fun associateId(producer: () -> T, id: Identifier) { + producersToIds[producer] = id + } + + + override fun commitRegistry() { + if (!isOpen) error("Deferred registry is already closed") + isOpen = false + + // Register all items in the registry + registry.modifyRegistry(priority) { + for ((producer, delegate) in items) { + + val item = producer() + delegate.id = item.id + register(item) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/registry/IdentifierRegistry.kt b/src/main/kotlin/xyz/mastriel/cutapi/registry/IdentifierRegistry.kt index b082a3e..49b6913 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/registry/IdentifierRegistry.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/registry/IdentifierRegistry.kt @@ -20,22 +20,109 @@ public data class HookContext( var preventRegister: Boolean ) +public data class RegistryEvent( + public val registry: IdentifierRegistry<*>, + private val registerFunction: (item: T) -> Unit, + private val replaceFunction: (id: Identifier, item: T) -> Unit +) { + public fun register(item: T): Unit = registerFunction(item) + public fun replace(id: Identifier, item: T): Unit = replaceFunction(id, item) +} + +@JvmInline +public value class RegistryPriority(public val value: Int) : Comparable { + public companion object { + public val High: RegistryPriority = RegistryPriority(1000) + public val Medium: RegistryPriority = RegistryPriority(0) + public val Low: RegistryPriority = RegistryPriority(-1000) + } + + override fun compareTo(other: RegistryPriority): Int { + return this.value.compareTo(other.value) + } +} + /** * A map of [Identifier] to [T]. This is used in keeping a registry of all items, blocks, etc. * * @param T The identifiable that is being tracked. */ public open class IdentifierRegistry(public val name: String) { + + protected data class Handler( + public val priority: RegistryPriority, + public val handler: RegistryEvent.() -> Unit + ) + protected val values: MutableMap = mutableMapOf() protected val hooks: MutableList.() -> Unit, HookPriority>> = mutableListOf() + protected val eventHandlers: MutableList> = mutableListOf() + + protected fun getSortedEventHandlers(): List> { + return eventHandlers.sortedByDescending { it.priority } + } + + public var isOpen: Boolean = true + private set + + public open fun modifyRegistry( + priority: RegistryPriority = RegistryPriority.Medium, + handler: RegistryEvent.() -> Unit + ) { + if (!isOpen) error("Registry '$name' is already initialized. Cannot add more handlers.") + eventHandlers += Handler(priority, handler) + } + + + /** + * Call this when the registry is ready to be used, and all handlers have already been added. + */ + public open fun initialize() { + if (!isOpen) error("Registry '$name' is already initialized.") + + // run all handlers + for (handler in getSortedEventHandlers()) { + val event = RegistryEvent(this, ::register, ::replace) + handler.handler(event) + } + + isOpen = false + Plugin.info("[REGISTRY] '$name' initialized with ${values.size} items.") + eventHandlers.clear() // we don't need to keep the handlers around anymore + } + + protected open fun replace(id: Identifier, item: T) { + if (!isOpen) error("Registry '$name' is already initialized. Cannot replace items.") + + if (values.containsKey(id)) { + unregister(id) + } else { + Plugin.warn("[REGISTRY] $id tried to be replaced in '$name', but it doesn't exist in this registry. Creating a new entry instead...") + } + register(item) + } + + /** + * Create a deferred registry for this registry. This allows you to group items together, to then be registered + * all at the same time when the registry is ready. + * + * @param priority The priority of the deferred registry. Defaults to [RegistryPriority.Medium]. + * @return A [DeferredRegistry] that can be used to register items later. + */ + public open fun defer(priority: RegistryPriority = RegistryPriority.Medium): DeferredRegistry { + if (!isOpen) error("Registry '$name' is already initialized. Cannot create a deferred registry.") + return BasicDeferredRegistry(this, priority) + } + + /** * Register an object with this map to allow for it to be identified. * * @param item The object which the association is being made for. */ - public open fun register(item: T): T { + protected open fun register(item: T): T { // add this to the list of used registries if it's not already there. keeps track of all registries // for when a plugin is disabled. if (!usedRegistries.any { it.get() == this }) { @@ -71,7 +158,7 @@ public open class IdentifierRegistry(public val name: String) * * @param item The object being removed. */ - public open fun unregister(item: T): Unit = unregister(item.id) + protected open fun unregister(item: T): Unit = unregister(item.id) /** * Remove an object from this registry. @@ -86,7 +173,7 @@ public open class IdentifierRegistry(public val name: String) * * @param id The object being removed. */ - public open fun unregister(id: Identifier) { + protected open fun unregister(id: Identifier) { if (!values.containsKey(id)) { Plugin.warn("[REGISTRY] $id tried to be removed from '${this.name}', but it doesn't exist in this registry.") return @@ -164,7 +251,7 @@ public open class IdentifierRegistry(public val name: String) hooks += func to priority } - internal companion object { + public companion object { // uses weak references, although registries should probably not be // garbage collected at any point and should always have a strong reference private val usedRegistries = mutableListOf>>() @@ -176,3 +263,4 @@ public open class IdentifierRegistry(public val name: String) } } } + diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/FolderRef.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/FolderRef.kt index 36e6c93..b4b65fb 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/FolderRef.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/FolderRef.kt @@ -2,17 +2,32 @@ package xyz.mastriel.cutapi.resources import xyz.mastriel.cutapi.* +/** + * Represents a reference to a folder within a [ResourceRoot]. + * + * @property root The root resource container this folder belongs to. + * @property pathList The path segments of the folder within the root. + */ @ConsistentCopyVisibility public data class FolderRef internal constructor( override val root: ResourceRoot, override val pathList: List ) : Locator { + /** + * Returns true if this folder is the root folder. + */ val isRoot: Boolean get() = pathList.isEmpty() + /** + * The path of this folder as a string, ending with a '/' if not root. + */ override val path: String get() = if (pathList.isEmpty()) "" else pathList.joinToString("/") + "/" + /** + * The parent folder reference, or null if this is the root. + */ override val parent: FolderRef? get() { val list = pathList.dropLast(1) @@ -20,38 +35,73 @@ public data class FolderRef internal constructor( return folderRef(root, list.joinToString("/")) } + /** + * Returns true if this folder has any children (files or folders). + */ public fun hasChildren(): Boolean { return getChildren().isNotEmpty() } + /** + * Returns a list of all children (files and folders) in this folder. + */ public fun getChildren(): List { return CuTAPI.resourceManager.getFolderContents(root, this) } + /** + * Returns the string representation of this folder reference in the format 'namespace://path/'. + */ override fun toString(): String { return "${root.namespace}://${path}" } + /** + * Returns a new [FolderRef] by appending the given path to this folder. + * + * @param path The path to append, split by '/'. + */ public operator fun div(path: String): FolderRef { return FolderRef(root, pathList + path.split("/")) } + /** + * Appends the given path to this folder and returns a new [FolderRef]. + * + * @param path The path to append. + */ public fun append(path: String): FolderRef { return this / path } + /** + * Returns a [ResourceRef] to a child resource with the given path. + * + * @param path The path to the child resource. + */ public fun child(path: String): ResourceRef { return ResourceRef(root, pathList + path.removePrefix("/").removeSuffix("/").split("/")) } } +/** + * Creates a [FolderRef] from a [ResourceRoot] and a path string. + * + * @param root The resource root. + * @param path The folder path. + * @return The [FolderRef] for the given root and path. + */ public fun folderRef(root: ResourceRoot, path: String): FolderRef { if (path == "") return FolderRef(root, emptyList()) return FolderRef(root, normalizeFolder(path).split("/").filterNot { it.isEmpty() }) } /** + * Creates a [FolderRef] from a string in the format 'namespace://path/'. + * + * @param stringPath The string path. * @throws IllegalStateException if the root is not found. + * @return The [FolderRef] for the given string path. */ public fun folderRef(stringPath: String): FolderRef { val (root, path) = stringPath.split("://") @@ -62,6 +112,13 @@ public fun folderRef(stringPath: String): FolderRef { return folderRef(resourceRoot, normalizeFolder(path)) } +/** + * Normalizes a folder path to use forward slashes, removes leading slashes, + * and ensures the path ends with a '/'. + * + * @param path The folder path to normalize. + * @return The normalized folder path. + */ public fun normalizeFolder(path: String): String { if (path.isEmpty()) return "" var newPath = path.replace("\\", "/") diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/Locator.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/Locator.kt index 3976e9c..696aa9e 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/Locator.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/Locator.kt @@ -3,36 +3,76 @@ package xyz.mastriel.cutapi.resources import xyz.mastriel.cutapi.* import java.io.* +/** + * Represents a location of a resource or folder within a resource root. + * Provides access to the resource's file, root, plugin, namespace, path, and parent. + */ public sealed interface Locator { /** - * The physical file in the tmp-resource folder for this resource. This may be null! - * This should only really be used when initally loading resources, and this will probably - * be already taken care of for you. + * Returns the physical file in the tmp-resource folder for this resource, or null if not applicable. + * This is mainly used during initial resource loading. + * + * @return The associated [File], or null if not available. */ public fun getFile(): File? = null + + /** + * The root resource container this locator belongs to. + */ public val root: ResourceRoot + /** + * The plugin that owns this locator. + */ public val plugin: CuTPlugin get() = root.cutPlugin + /** + * The namespace of this locator. + */ public val namespace: String get() = root.namespace /** - * The path of this resource/folder without a namespace. Useful for things like filesystem pathing. + * The path of this resource or folder, without the namespace. + * Useful for filesystem pathing. */ public val path: String /** - * A list of the path split at /. Includes the file name, or the current folder name in a [FolderRef] + * The path split at '/', including the file or folder name. */ public val pathList: List + + /** + * The parent folder reference, or null if this is the root. + */ public val parent: FolderRef? get() = null + /** + * Returns the namespaced path representation of this locator. + * + * @return The namespaced path as a [String]. + */ public fun toNamespacedPath(): String = toString() public companion object { + /** + * Separator for root in resource paths. + */ public const val ROOT_SEPARATOR: String = "@" + + /** + * Separator for generated resources in paths. + */ public const val GENERATED_SEPARATOR: String = "^" + + /** + * Separator for cloned resources in paths. + */ public const val CLONE_SEPARATOR: String = "+" + + /** + * Separator for subresources in paths. + */ public const val SUBRESOURCE_SEPARATOR: String = "#" } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt index 4fcf28f..1a9af8a 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/Resource.kt @@ -6,13 +6,28 @@ import xyz.mastriel.cutapi.resources.data.* import java.io.* import kotlin.contracts.* +@OptIn(ExperimentalSerializationApi::class) +/** + * Represents a resource in the CuTAPI system. + * + * @property ref The reference to this resource. + * @property metadata The metadata associated with this resource, if any. + */ public open class Resource( public open val ref: ResourceRef<*>, public open val metadata: CuTMeta? = null ) { + /** + * Inspector for this resource, used for debugging and introspection. + */ public val inspector: ResourceInspector = ResourceInspector() + /** + * Checks if this resource is a subresource (contains '#' in its name). + * + * @return true if this is a subresource, false otherwise. + */ public fun isSubresource(): Boolean = "#" in ref.name init { @@ -24,8 +39,14 @@ public open class Resource( } } + /** + * The root resource container for this resource. + */ public val root: ResourceRoot get() = ref.root + /** + * The plugin that owns this resource. + */ public val plugin: CuTPlugin get() = root.cutPlugin /** @@ -34,6 +55,12 @@ public open class Resource( public open var subResources: List = emptyList() protected set + /** + * Adds a subresource to this resource. + * + * @param resource The subresource to add. + * @throws IllegalArgumentException if the subresource does not share the same root. + */ protected fun subResource(resource: Resource) { if (resource.ref.root != this.ref.root) { throw IllegalArgumentException("Sub-resource must have the same root as the parent resource.") @@ -44,7 +71,14 @@ public open class Resource( // these are set by the resource manager // these are also purely for the clone block // and processing cloning resources. + /** + * The file from which this resource was loaded, if any. + */ internal var loadedFromFile: File? = null + + /** + * The metadata file from which this resource was loaded, if any. + */ internal val loadedFromMetadata: File? get() = if (loadedFromFile == null) null else File(loadedFromFile?.absolutePath + ".meta") @@ -58,7 +92,6 @@ public open class Resource( */ public open fun check() {} - /** * Called immediately when the resource is registered. * @@ -66,28 +99,56 @@ public open class Resource( */ public open fun onRegister() {} - /** * Throws a [ResourceCheckException] if a specified [ResourceRef] is not available. + * + * @param ref The resource reference to check. + * @param lazyReason Optional lambda to provide a reason for the exception. + * @throws ResourceCheckException if the reference is not available. */ protected fun requireValidRef(ref: ResourceRef<*>, lazyReason: (() -> String)? = null) { if (!ref.isAvailable()) throw ResourceCheckException(lazyReason?.invoke()) } } - +/** + * Exception thrown when a resource fails a logical check. + * + * @param reason The reason for the failure. + */ public class ResourceCheckException(reason: String?) : Exception(reason) +/** + * Interface for resources that can be serialized to a byte array. + */ public interface ByteArraySerializable { + /** + * Serializes the resource to a byte array. + * + * @return The serialized byte array. + */ public fun toBytes(): ByteArray } +/** + * Saves a serializable resource to a file. + * + * @receiver The resource to save. + * @param file The file to save to. + */ public fun T.saveTo(file: File) where T : ByteArraySerializable, T : Resource { file.parentFile.mkdirs() if (!file.exists()) file.createNewFile() file.writeBytes(toBytes()) } +/** + * Saves a serializable resource and its metadata to files. + * + * @receiver The resource to save. + * @param file The file to save the resource to. + * @param metadataSerializer The serializer for the metadata, or null to use the default. + */ public fun T.saveWithMetadata( file: File, metadataSerializer: KSerializer? = null @@ -104,6 +165,12 @@ public fun T.saveWithMetadata( saveTo(file) } +/** + * Checks if a resource is serializable as a [ByteArraySerializable]. + * + * @receiver The resource to check. + * @return true if the resource is serializable, false otherwise. + */ @OptIn(ExperimentalContracts::class) public fun Resource.isSerializable(): Boolean { contract { @@ -112,12 +179,24 @@ public fun Resource.isSerializable(): Boolean { return this is ByteArraySerializable } - -@OptIn(ExperimentalSerializationApi::class) +/** + * Serializes a resource to CBOR format using the provided serializer. + * + * @receiver The resource to serialize. + * @param serializer The serializer for the resource type. + * @return The CBOR-encoded byte array. + */ public fun T.cborSerialize(serializer: KSerializer): ByteArray { return CuTAPI.cbor.encodeToByteArray(serializer, this) } +/** + * Creates a subresource reference from this resource reference. + * + * @receiver The parent resource reference. + * @param refName The name of the subresource. + * @return The subresource reference. + */ public fun ResourceRef<*>.subRef(refName: String): ResourceRef { return ref(this.root, "${this.path(withExtension = true)}#${refName}") } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceFileLoader.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceFileLoader.kt index 00c0ced..7467bff 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceFileLoader.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceFileLoader.kt @@ -7,6 +7,10 @@ import xyz.mastriel.cutapi.registry.* import xyz.mastriel.cutapi.resources.data.* +/** + * Interface for loading resources from files. + * @param T The type of resource this loader handles. + */ public interface ResourceFileLoader : Identifiable { /** * The dependencies of this loader. These are the loaders that must run before this one. @@ -16,6 +20,15 @@ public interface ResourceFileLoader : Identifiable { */ public val dependencies: List> get() = listOf() + /** + * Loads a resource from the given data and metadata. + * + * @param ref The reference to the resource being loaded. + * @param data The raw data of the resource. + * @param metadata The metadata associated with the resource, if any. + * @param options Options for resource loading. + * @return The result of the resource loading attempt. + */ public fun loadResource( ref: ResourceRef, data: ByteArray, @@ -25,6 +38,10 @@ public interface ResourceFileLoader : Identifiable { public companion object : IdentifierRegistry>("Resource File Loaders") { + /** + * Returns all registered loaders sorted by their dependencies. + * Throws an exception if circular dependencies are detected. + */ public fun getDependencySortedLoaders(): List> { val sorted = mutableListOf>() val visited = mutableSetOf>() @@ -67,12 +84,13 @@ public interface ResourceFileLoader : Identifiable { */ public sealed class ResourceLoadResult { /** - * The resource has loaded successfully and can be registered! Hooray! + * The resource has loaded successfully and can be registered. Hooray! */ public data class Success(val resource: T) : ResourceLoadResult() /** * The resource failed to load, and we shouldn't try to make it into any other resource type. + * @param exception The exception that caused the failure, if any. */ public class Failure(public val exception: Throwable? = null) : ResourceLoadResult() { override fun toString(): String = "Failure" @@ -87,8 +105,22 @@ public sealed class ResourceLoadResult { } +/** + * Exception thrown when a resource is of the wrong type. + */ private class WrongResourceTypeException : Exception() +/** + * Context for resource file loading, containing all relevant data and helpers. + * + * @param T The resource type. + * @param M The metadata type. + * @property ref The reference to the resource. + * @property data The raw data of the resource. + * @property metadata The parsed metadata, if any. + * @property metadataBytes The raw metadata bytes, if any. + * @property options The resource loading options. + */ public class ResourceFileLoaderContext( public val ref: ResourceRef, public val data: ByteArray, @@ -96,19 +128,41 @@ public class ResourceFileLoaderContext( public val metadataBytes: ByteArray? = null, public val options: ResourceLoadOptions = ResourceLoadOptions() ) { + /** + * The resource data as a UTF-8 string. + */ public val dataAsString: String by lazy { data.toString(Charsets.UTF_8) } + /** + * Returns a successful resource load result. + * @param value The loaded resource. + */ public fun success(value: T): ResourceLoadResult.Success = ResourceLoadResult.Success(value) + + /** + * Returns a failed resource load result. + * @param exception The exception that caused the failure, if any. + */ public fun failure(exception: Throwable? = null): ResourceLoadResult.Failure = ResourceLoadResult.Failure(exception) + /** + * Returns a result indicating the resource was of the wrong type. + */ public fun wrongType(): ResourceLoadResult.WrongType = ResourceLoadResult.WrongType() } /** - * Create a resource loader that only processes resoruces with certain extensions. + * Create a resource loader that only processes resources with certain extensions. * Don't write the extensions with a dot at the beginning. If a resource has multiple - * extensions (such as tar.gz), write it with a period only in the middle + * extensions (such as tar.gz), write it with a period only in the middle. + * + * @param extensions The file extensions this loader should handle. + * @param resourceTypeId The identifier for the resource type. + * @param metadataSerializer The serializer for the resource metadata, if any. + * @param dependencies The dependencies of this loader. + * @param func The function to process the resource loading. + * @return A new ResourceFileLoader instance. */ public fun resourceLoader( extensions: Collection?, @@ -174,6 +228,11 @@ public fun resourceLoader( /** + * Checks if the resource type in the metadata matches the expected type or is unknown. + * + * @param ref The resource reference. + * @param metadataText The metadata as a string. + * @param resourceTypeId The expected resource type identifier. * @return true if it's the resource type or the resource type is unknown, false otherwise. */ internal fun checkIsResourceTypeOrUnknown( @@ -188,6 +247,11 @@ internal fun checkIsResourceTypeOrUnknown( return id(metadataId.toString()) == resourceTypeId } +/** + * Checks if strict resource loading is enabled for the plugin and disables the plugin if so. + * + * @param plugin The plugin to check and possibly disable. + */ internal fun checkResourceLoading(plugin: CuTPlugin) { if (CuTAPI.getDescriptor(plugin).options.strictResourceLoading) { Plugin.error("Strict resource loading is enabled for ${plugin.namespace}. Disabling ${plugin.namespace}...") diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt index 57b42ae..f54a8b3 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceGenerator.kt @@ -6,16 +6,39 @@ import xyz.mastriel.cutapi.registry.* import xyz.mastriel.cutapi.resources.data.* +/** + * Represents the stages of resource generation. + * These stages determine when a resource generator is executed during the resource lifecycle. + */ public enum class ResourceGenerationStage { + /** + * Executed before resource processors are run. + */ BeforeProcessors, + + /** + * Executed after resource processors are run. + */ AfterProcessors, + + /** + * Executed before resource pack processors are run. + */ BeforePackProcessors, + + /** + * Executed after resource pack processors are run. + */ AfterPackProcessors } /** - * This is called after the server has finished initialization, but before CuTAPI has run the ResourceProcessors. - * This should be used to + * Abstract base class for resource generators. + * Resource generators are responsible for generating resources during specific stages + * of the resource lifecycle. + * + * @property id The unique identifier for the resource generator. + * @property stage The stage at which the generator is executed. */ public abstract class ResourceGenerator( override val id: Identifier, @@ -23,17 +46,40 @@ public abstract class ResourceGenerator( ) : Identifiable { /** - * [context.resource] and resources generated through [ResourceGeneratorContext.register] can NOT have the same [ResourceRef]! - * Use subId to differentiate between them using [ResourceRef.subId] + * Generates or modifies resources during the specified stage. + * + * **Note:** [context.resource] and resources generated through [ResourceGeneratorContext.register] + * must not have the same [ResourceRef]. Use `subId` to differentiate between them using [ResourceRef.subId]. + * + * @param context The context for the resource generation, containing the resource and helper methods. */ public abstract fun generate(context: ResourceGeneratorContext) + /** + * Companion object for managing and retrieving registered resource generators. + */ public companion object : IdentifierRegistry("Resource Generators") { + + /** + * Retrieves all resource generators for a specific stage. + * + * @param stage The stage to filter resource generators by. + * @return A list of resource generators for the specified stage. + */ public fun getByStage(stage: ResourceGenerationStage): List = getAllValues().filter { it.stage == stage } } } +/** + * Context provided to resource generators during execution. + * + * @param T The type of resource being generated or modified. + * @property resource The resource being processed by the generator. + * @property generateBlock The block of metadata associated with the resource. + * @property ref The reference to the resource being processed. + * @property register A function to register newly generated resources. + */ public data class ResourceGeneratorContext( val resource: T, val generateBlock: GenerateBlock, @@ -42,10 +88,11 @@ public data class ResourceGeneratorContext( ) { /** - * Deserialize the options for this generator into a new object. + * Deserializes the options for this generator into a new object. * - * @param S The type you're deserializing into. - * @param serializer The serializer for [S]. + * @param S The type to deserialize into. + * @param serializer The serializer for the type [S]. + * @return The deserialized options object. */ public fun castOptions(serializer: KSerializer): S { return CuTAPI.toml.decodeFromTomlElement(serializer, generateBlock.options) @@ -53,7 +100,12 @@ public data class ResourceGeneratorContext( } /** - * Ran for ALL resources that exist. + * Creates a resource generator that is executed for all resources. + * + * @param id The unique identifier for the generator. + * @param priority The stage at which the generator is executed. + * @param block The logic to execute for each resource. + * @return A [ResourceGenerator] instance. */ public fun resourceGenerator( id: Identifier, @@ -68,7 +120,13 @@ public fun resourceGenerator( } /** - * Ran only for resources of a certain type that has [id] in its `generate` metadata section. + * Creates a resource generator that is executed only for resources of a specific type. + * + * @param T The type of resource to process. + * @param id The unique identifier for the generator. + * @param priority The stage at which the generator is executed. + * @param block The logic to execute for each resource of type [T]. + * @return A [ResourceGenerator] instance. */ @JvmName("resourceGeneratorWithType") public inline fun resourceGenerator( @@ -79,7 +137,7 @@ public inline fun resourceGenerator( return object : ResourceGenerator(id, priority) { override fun generate(context: ResourceGeneratorContext) { if (context.resource is T) { - // very nasty + // Cast the context to the specific type and execute the block. @Suppress("UNCHECKED_CAST") block(context as ResourceGeneratorContext) } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceInspector.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceInspector.kt index 417a443..cc6dfe3 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceInspector.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceInspector.kt @@ -4,54 +4,137 @@ import net.kyori.adventure.extra.kotlin.* import net.kyori.adventure.text.* import xyz.mastriel.cutapi.utils.* +/** + * A utility class for inspecting resources by adding and managing inspections. + * Inspections can be of various types, such as single values, lists, maps, or raw components. + */ public class ResourceInspector { + /** + * A mutable list of inspections added to the inspector. + */ private val _inspections = mutableListOf() + /** + * A read-only list of all inspections added to the inspector. + */ public val inspections: List get() = _inspections + /** + * Adds a new inspection to the inspector. + * + * @param inspection The inspection to add. + */ public fun addInspection(inspection: Inspection) { _inspections.add(inspection) } + /** + * Adds a single-value inspection. + * + * @param name The name of the inspection. + * @param value A lambda that provides the value to inspect. + */ public fun single(name: String, value: () -> Any) { addInspection(Inspection.Single(name, value)) } + /** + * Adds a list inspection. + * + * @param name The name of the inspection. + * @param values A lambda that provides the collection of values to inspect. + */ public fun list(name: String, values: () -> Collection) { addInspection(Inspection.List(name, values)) } + /** + * Adds a map inspection. + * + * @param name The name of the inspection. + * @param values A lambda that provides the map of key-value pairs to inspect. + */ public fun map(name: String, values: () -> Map) { addInspection(Inspection.Map(name, values)) } - public fun raw(name: String, component: Component) { + /** + * Adds a raw component inspection. + * + * @param name The name of the inspection. + * @param component The raw component to inspect. + */ + public fun raw(name: String, component: () -> Component) { addInspection(Inspection.Raw(name, component)) } + /** + * Companion object containing default colors for inspection components. + */ public companion object { + /** + * The color used for the inspector title. + */ public val InspectorTitle: Color = Color.of(0x77ffb8) + + /** + * The color used for property keys in inspections. + */ public val PropertyKey: Color = Color.of(0xc4ffb2) + + /** + * The color used for property values in inspections. + */ public val PropertyValue: Color = Color.of(0xf5ff97) } } +/** + * Appends an inspection to a text component builder. + * + * @param text The text of the inspection. + * @param value The value of the inspection (optional for values that require multiple lines, like maps or lists). + * @param spaces The number of spaces to prepend to the text (default is 0). + */ private fun TextComponent.Builder.appendInspection(text: String, value: Any = "", spaces: Int = 0) { appendLine((" ".repeat(spaces) + "&${ResourceInspector.PropertyKey}$text &8→ &${ResourceInspector.PropertyValue}$value").colored) } - +/** + * Represents an inspection that can be added to the [ResourceInspector]. + * Inspections can be of various types, such as single values, lists, maps, or raw components. + */ public sealed interface Inspection { + + /** + * The name of the inspection. + */ public abstract val name: String + /** + * Generates a component representation of the inspection. + * + * @return The component representing the inspection. + */ public fun getInspectionComponent(): Component + /** + * Represents a single-value inspection. + * + * @property name The name of the inspection. + * @property value A lambda that provides the value to inspect. + */ public data class Single( override val name: String, val value: () -> Any ) : Inspection { + /** + * Generates a component representation of the single-value inspection. + * + * @return The component representing the inspection. + */ override fun getInspectionComponent(): Component { return text { appendInspection(name, value()) @@ -59,11 +142,22 @@ public sealed interface Inspection { } } + /** + * Represents a list inspection. + * + * @property name The name of the inspection. + * @property values A lambda that provides the collection of values to inspect. + */ public data class List( override val name: String, val values: () -> Collection ) : Inspection { + /** + * Generates a component representation of the list inspection. + * + * @return The component representing the inspection. + */ override fun getInspectionComponent(): Component { return text { if (values().isEmpty()) { @@ -79,11 +173,22 @@ public sealed interface Inspection { } } + /** + * Represents a map inspection. + * + * @property name The name of the inspection. + * @property values A lambda that provides the map of key-value pairs to inspect. + */ public data class Map( override val name: String, val values: () -> kotlin.collections.Map ) : Inspection { + /** + * Generates a component representation of the map inspection. + * + * @return The component representing the inspection. + */ override fun getInspectionComponent(): Component { return text { if (values().isEmpty()) { @@ -99,12 +204,24 @@ public sealed interface Inspection { } } + /** + * Represents a raw component inspection. + * + * @property name The name of the inspection. + * @property component The raw component to inspect. + */ public data class Raw( override val name: String, - val component: Component + val component: () -> Component ) : Inspection { + + /** + * Generates a component representation of the raw inspection. + * + * @return The component representing the inspection. + */ override fun getInspectionComponent(): Component { - return component + return component() } } } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt index 4327f30..4c0631d 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourcePackManager.kt @@ -15,66 +15,89 @@ import kotlin.time.* public class ResourcePackManager { + /** + * The temporary folder used for resource pack generation. + */ public val tempFolder: File = Plugin.dataFolder.appendPath("pack-tmp/") + /** + * The name of the generated resource pack zip file. + */ private val zipName by cutConfigValue("generated-pack-name", "pack.zip") + + /** + * The file where the generated resource pack zip is stored. + */ public val zipFile: File = Plugin.dataFolder.appendPath(zipName) + /** + * Information about the generated resource pack, including its URL and hash. + */ public var packInfo: PackInfo? = null private set /** - * Gets the textures folder of a plugin in the resource pack. + * Gets the textures folder for a specific namespace in the resource pack. * * @param namespace The namespace of the plugin. + * @return The folder where textures are stored. */ public fun getTexturesFolder(namespace: String): File { return File(tempFolder, "assets/$namespace/textures/item") } /** - * Gets the models folder of a plugin in the resource pack. + * Gets the models folder for a specific namespace in the resource pack. * * @param namespace The namespace of the plugin. + * @return The folder where models are stored. */ public fun getModelFolder(namespace: String): File { return File(tempFolder, "assets/$namespace/models/") } /** - * Gets the item model folder of a plugin in the resource pack. + * Gets the item model folder for a specific namespace in the resource pack. * * @param namespace The namespace of the plugin. + * @return The folder where item models are stored. */ public fun getItemModelFolder(namespace: String): File { return File(tempFolder, "assets/$namespace/items/") } /** - * Gets the textures folder of a plugin in the resource pack. + * Gets the textures folder for a specific plugin in the resource pack. * * @param plugin The plugin. Will fail if this plugin is not registered in CuTAPI. + * @return The folder where textures are stored. */ public fun getTexturesFolder(plugin: CuTPlugin): File { val namespace = CuTAPI.getDescriptor(plugin).namespace return getTexturesFolder(namespace).also { it.mkdirs() } } - + /** + * The generator used to create the resource pack. + */ public val generator: ResourcePackGenerator get() { - // TODO("detect versions dynamically") + // TODO: Detect versions dynamically return PackVersion46Generator() } /** - * Checks if the zip file exists and is ready to be uploaded + * Checks if the zip file exists and is ready to be uploaded. + * + * @return `true` if the zip file exists, `false` otherwise. */ public fun zipReady(): Boolean = zipFile.exists() - /** - * Zip the resource pack and upload it. + * Zips the resource pack and uploads it using the active uploader. + * + * @return The generated [PackInfo] containing the URL and hash of the resource pack. + * @throws PackGenerationException If zipping or uploading fails. */ internal suspend fun zipAndUpload(): PackInfo { try { @@ -82,6 +105,7 @@ public class ResourcePackManager { } catch (e: Exception) { throw PackGenerationException("Failed to zip the resource pack.", e) } + var activeUploader = Uploader.getActive() if (activeUploader == null) { Plugin.warn("Could not find uploader with id '${Uploader.uploaderId}'. Using 'cutapi:internal' instead.") @@ -100,13 +124,21 @@ public class ResourcePackManager { return PackInfo(url, packHash).also { packInfo = it } } - + /** + * Zips the resource pack folder into a single zip file. + */ private fun zipPack() { if (zipFile.exists()) zipFile.delete() zipFolder(tempFolder, zipFile) } - + /** + * Regenerates the resource pack by clearing temporary resources, loading resources, + * running processors, and zipping the pack. + * + * @return The generated [PackInfo]. + * @throws PackGenerationException If resource loading or generation fails. + */ public suspend fun regenerate(): PackInfo = withContext(Plugin.minecraftDispatcher) { try { CuTAPI.resourceManager.clearTemp() @@ -133,9 +165,17 @@ public class ResourcePackManager { } } + /** + * Event triggered when the resource pack generation is finished. + */ public val onPackGenerateFinished: EventHandlerList = EventHandlerList() - + /** + * Sanitizes a resource name by replacing special characters with safe alternatives. + * + * @param name The resource name to sanitize. + * @return The sanitized name. + */ public fun sanitizeName(name: String): String { return name.replace(Locator.SUBRESOURCE_SEPARATOR, "__sre__") .replace(Locator.GENERATED_SEPARATOR, "__gen__") @@ -143,14 +183,16 @@ public class ResourcePackManager { .lowercase() } + /** + * Runs all registered resource processors on the resources in the [ResourceManager]. + */ private fun runResourceProcessors() { val executionTime = measureTime { generateResources(CuTAPI.resourceManager.getAllResources(), ResourceGenerationStage.BeforeProcessors) - // we always want the generate processor to run last. + // Ensure the generate processor runs last. ResourceProcessor.forEach { it.processResources(CuTAPI.resourceManager) } - generateResources(CuTAPI.resourceManager.getAllResources(), ResourceGenerationStage.AfterProcessors) } Plugin.info("Resource Processors (normal registry) ran in $executionTime.") diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceProcessor.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceProcessor.kt index 5e1b6a8..7d83032 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceProcessor.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceProcessor.kt @@ -2,44 +2,67 @@ package xyz.mastriel.cutapi.resources import xyz.mastriel.cutapi.registry.* - /** - * A resource processor will be executed when the server has finished loading, and all resources - * are registered. You should not modify the [ResourceManager], as this period is closed. - * If you add more resources anyway, they will not go through the processor. + * Represents a processor that is executed after the server has finished loading and all resources + * have been registered. This processor is used to perform operations on resources without modifying + * the [ResourceManager]. * - * If you want to add more resources using generators, see [ResourceGenerator]. Generated resources - * are also processed here. + * **Important:** Do not modify the [ResourceManager] during this phase. Any resources added will not + * go through the processor. * - * If you want to prepare your resources to be processed into a resource pack, register to [ResourcePackProcessor] - * instead. Both registries use the same type of [ResourceProcessor]. + * - To add resources using generators, see [ResourceGenerator]. + * - To prepare resources for a resource pack, use [ResourcePackProcessor]. * * @see [resourceProcessor] * @see [ResourcePackProcessor] */ public fun interface ResourceProcessor { + + /** + * Processes the resources managed by the [ResourceManager]. + * + * @param resources The [ResourceManager] containing all registered resources. + */ public fun processResources(resources: ResourceManager) + /** + * A registry for managing all registered [ResourceProcessor] instances. + * + * @property name The name of the registry, which is "Resource Processors". + */ public companion object : ListRegistry("Resource Processors") } - /** - * A resource pack processor will be executed while the server is trying to generate a resource pack. - * You should not modify the [ResourceManager], or make any unnecessary modifiacations to [Resource]s you're - * processing. + * Represents a processor that is executed while the server is generating a resource pack. + * This processor is used to process resources into a resource pack without modifying the + * [ResourceManager] or making unnecessary modifications to the [Resource]s being processed. * - * If you want to add more resources using generators, see [ResourceGenerator]. + * - To add resources using generators, see [ResourceGenerator]. * * @see [resourceProcessor] * @see [ResourceProcessor] */ public object ResourcePackProcessor : ListRegistry("Resource Pack Processors") - +/** + * Provides a context for processing a specific type of resource. + * + * @param T The type of resource being processed. + * @property resources The list of resources of type [T] to be processed. + */ public data class ResourceProcessorContext(val resources: List) - +/** + * Creates a [ResourceProcessor] for a specific type of resource. + * + * This function filters resources of type [T] from the [ResourceManager] and provides them + * to the given processing block. + * + * @param T The type of resource to process. + * @param block The processing logic to apply to the filtered resources. + * @return A [ResourceProcessor] that processes resources of type [T]. + */ public inline fun resourceProcessor(crossinline block: ResourceProcessorContext.() -> Unit): ResourceProcessor { return ResourceProcessor { resources -> val filteredResources = resources.getAllResources().filterIsInstance() diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt index cef450a..340b4dc 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRef.kt @@ -13,7 +13,7 @@ import kotlin.reflect.* /** * A reference to a resource that may or may not exist. - * Use getResource to get the actual resource this is referring to (if it exists) + * Use `getResource` to retrieve the actual resource this refers to (if it exists). */ @Serializable(with = ResourceRefSerializer::class) @ConsistentCopyVisibility @@ -22,38 +22,68 @@ public data class ResourceRef internal constructor( override val pathList: List ) : ReadOnlyProperty, Locator { - /** - * This doesn't actually ever hold anything of the type T, so this is a safe cast - * You might want to make sure that the resource is actually of type T before using this. + * Casts this `ResourceRef` to a different type. + * @return A `ResourceRef` of the specified type. */ @Suppress("UNCHECKED_CAST") public fun cast(): ResourceRef = this as ResourceRef + /** + * Retrieves the resource this reference points to, or null if it doesn't exist. + * @return The resource of type `T`, or null if unavailable. + */ public fun getResource(): T? { return CuTAPI.resourceManager.getResourceOrNull(this) } + /** + * Gets the type of the resource this reference points to. + * @return The `KClass` of the resource type, or null if unavailable. + */ val resourceType: KClass? get(): KClass? { return getResource()?.let { it::class } } + override val path: String get() = pathList.joinToString("/") + + /** + * Retrieves the metadata associated with the resource. + * @return The metadata, or null if unavailable. + */ public fun getMetadata(): CuTMeta? { return getResource()?.metadata } + /** + * Checks if the resource is available in the resource manager. + * @return `true` if the resource is available, `false` otherwise. + */ public fun isAvailable(): Boolean { return CuTAPI.resourceManager.isAvailable(this) } + /** + * Retrieves the resource this reference points to when used as a property delegate. + * @param thisRef The object containing the property. + * @param property The property being accessed. + * @return The resource of type `T`, or null if unavailable. + */ override operator fun getValue(thisRef: Any?, property: KProperty<*>): T? { return getResource() } - - override val path: String get() = pathList.joinToString("/") - + /** + * Constructs the path of the resource with optional formatting. + * @param withExtension Whether to include the file extension. + * @param withNamespace Whether to include the namespace. + * @param withRootAlias Whether to include the root alias. + * @param withNamespaceAsFolder Whether to include the namespace as a folder. + * @param withName Whether to include the resource name. + * @param fixInvalids Whether to fix invalid characters in the path. + * @return The formatted path as a string. + */ public fun path( withExtension: Boolean = false, withNamespace: Boolean = false, @@ -87,11 +117,17 @@ public data class ResourceRef internal constructor( return sb.toString().removeSuffix("/").fixInvalids() } + /** + * Gets the name of the resource (last segment of the path). + */ val name: String get() = path .split("/") .last() + /** + * Gets the file extension of the resource. + */ val extension: String get() = name .split(Locator.SUBRESOURCE_SEPARATOR, Locator.GENERATED_SEPARATOR, Locator.CLONE_SEPARATOR, limit = 2) @@ -99,6 +135,9 @@ public data class ResourceRef internal constructor( .split(".", limit = 2) .last() + /** + * Gets the parent folder reference of this resource, or null if it has no parent. + */ override val parent: FolderRef? get() { val list = pathList.dropLast(1) @@ -106,36 +145,67 @@ public data class ResourceRef internal constructor( return folderRef(root, list.joinToString("/")) } + /** + * Converts this resource reference to an identifier. + * @return The `Identifier` representing this resource reference. + */ public fun toIdentifier(): Identifier { return id(plugin, path) } - + /** + * Converts this resource reference to a string representation. + * @return The string representation of the resource reference. + */ override fun toString(): String { return path(withExtension = true, withRootAlias = false, withNamespace = true) } - } +/** + * Converts an `Identifier` to a `ResourceRef`. + * @return The `ResourceRef` corresponding to the identifier. + */ public fun Identifier.toResourceRef(): ResourceRef { if (plugin == null) error("Identifier doesn't have an associated plugin.") return ref(plugin!!, key) } - +/** + * Normalizes a resource path by replacing backslashes with forward slashes + * and removing leading/trailing slashes. + * @param path The path to normalize. + * @return The normalized path. + */ public fun normalizeRefPath(path: String): String { return path.replace("\\", "/").removeSuffix("/").removePrefix("/") } +/** + * Creates a `ResourceRef` from a root and a path. + * @param root The resource root. + * @param path The resource path. + * @return The `ResourceRef` for the specified root and path. + */ public fun ref(root: ResourceRoot, path: String): ResourceRef { return ResourceRef(root, normalizeRefPath(path).split("/").filterNot { it.isEmpty() }) } +/** + * Creates a `ResourceRef` from a folder and a path. + * @param folder The folder reference. + * @param path The resource path relative to the folder. + * @return The `ResourceRef` for the specified folder and path. + */ public fun ref(folder: FolderRef, path: String): ResourceRef { return folder.child(path) } - +/** + * Creates a `ResourceRef` from a string path in the format `namespace://path`. + * @param stringPath The string path. + * @return The `ResourceRef` for the specified string path. + */ public fun ref(stringPath: String): ResourceRef { require("://" in stringPath) { "String ResourceRef $stringPath does not follow namespace://path format." } val (start, path) = stringPath.split("://", limit = 2) @@ -146,18 +216,33 @@ public fun ref(stringPath: String): ResourceRef { return ref(plugin, path) } - +/** + * Serializer for `ResourceRef` objects. + */ public object ResourceRefSerializer : KSerializer> { + /** + * The descriptor for the `ResourceRef` serializer. + */ override val descriptor: SerialDescriptor get() = PrimitiveSerialDescriptor(this::class.qualifiedName!!, PrimitiveKind.STRING) + /** + * Deserializes a `ResourceRef` from a string. + * @param decoder The decoder to use. + * @return The deserialized `ResourceRef`. + */ override fun deserialize(decoder: Decoder): ResourceRef<*> { val text = decoder.decodeString() return ref(text) } + /** + * Serializes a `ResourceRef` to a string. + * @param encoder The encoder to use. + * @param value The `ResourceRef` to serialize. + */ override fun serialize(encoder: Encoder, value: ResourceRef<*>) { encoder.encodeString(value.toString()) } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRoot.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRoot.kt index 4b4280e..cd9963c 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRoot.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/ResourceRoot.kt @@ -3,25 +3,56 @@ package xyz.mastriel.cutapi.resources import xyz.mastriel.cutapi.* import java.io.* +/** + * Represents the root of a resource hierarchy. + * A `ResourceRoot` is associated with a plugin and provides access to the folder + * where resources are stored. + */ public interface ResourceRoot { + + /** + * The plugin associated with this resource root. + */ public val cutPlugin: CuTPlugin - public val namespace: String get() = cutPlugin.descriptor.namespace /** - * The folder in the file system where resources are stored. + * The namespace of the resource root, derived from the plugin's descriptor. + */ + public val namespace: String + get() = cutPlugin.descriptor.namespace + + /** + * Retrieves the folder in the file system where resources are stored. + * @return The folder containing the resources. */ public fun getResourcesFolder(): File } +/** + * A file system-based implementation of `ResourceRoot`. + * This implementation associates a resource root with a specific folder on the file system. + * + * @property cutPlugin The plugin associated with this resource root. + * @property alias An alias for the resource root, used to distinguish it from others. + * @property folder The folder in the file system where resources are stored. + */ public data class FSResourceRoot( override val cutPlugin: CuTPlugin, val alias: String, val folder: File ) : ResourceRoot { + /** + * The namespace of the resource root, which combines the plugin's namespace + * with the alias, separated by the root separator. + */ override val namespace: String get() = cutPlugin.descriptor.namespace + Locator.ROOT_SEPARATOR + alias - + + /** + * Retrieves the folder in the file system where resources are stored. + * @return The folder containing the resources. + */ override fun getResourcesFolder(): File { return folder } diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt index 4e927a2..86303e9 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/VanillaRef.kt @@ -2,6 +2,12 @@ package xyz.mastriel.cutapi.resources import kotlinx.serialization.* +/** + * Represents a reference to a vanilla resource. + * This is a lightweight wrapper around a string that holds the reference. + * + * @property ref The string representation of the vanilla resource reference. + */ @JvmInline @Serializable public value class VanillaRef(public val ref: String) { diff --git a/src/main/kotlin/xyz/mastriel/cutapi/utils/SimpleEventHandler.kt b/src/main/kotlin/xyz/mastriel/cutapi/utils/SimpleEventHandler.kt index 730c7a7..b4efeaa 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/utils/SimpleEventHandler.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/utils/SimpleEventHandler.kt @@ -19,6 +19,8 @@ public class EventHandlerList { public fun trigger(event: TInput): Unit = events.forEach { with(it) { event.trigger() } } - public operator fun invoke(event: TInput): Unit = trigger(event) + public operator fun invoke(event: SimpleEventHandler) { + events += event + } } From c7fcd8e12114663d7e80202703442b1d78fc182d Mon Sep 17 00:00:00 2001 From: Zac Date: Sat, 26 Jul 2025 22:29:08 -0600 Subject: [PATCH 05/10] improve documentation --- .../builtin/CustomModelDataAllocated.kt | 16 ++++++++++++ .../resources/builtin/FolderApplyResource.kt | 12 ++++++++- .../cutapi/resources/builtin/JsonResource.kt | 9 +++++++ .../resources/builtin/TemplateMetadata.kt | 13 ++++++++-- .../resources/data/minecraft/ItemModelData.kt | 25 ++++++------------- .../generator/PackGenerationException.kt | 6 +++++ .../generator/PackVersion46Generator.kt | 5 ++++ .../generator/ResourcePackGenerator.kt | 5 ++++ .../minecraft/MinecraftAssetLoader.kt | 5 ++++ .../resources/minecraft/MinecraftAssets.kt | 5 ++++ .../cutapi/resources/pack/PackConfig.kt | 5 ++++ .../cutapi/resources/pack/PackInfo.kt | 6 +++++ 12 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/CustomModelDataAllocated.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/CustomModelDataAllocated.kt index 331ca4d..35b6cef 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/CustomModelDataAllocated.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/CustomModelDataAllocated.kt @@ -1,14 +1,30 @@ package xyz.mastriel.cutapi.resources.builtin +/** + * Deprecated: Use ItemModel instead of CustomModelData. + * + * Provides allocation and interface for custom model data values. + */ @Deprecated("Up for removal due to changing from CustomModelData to ItemModel") internal var customModelDataCounter = 32120 +/** + * Deprecated: Use ItemModel instead of CustomModelData. + * + * Allocates a new custom model data value. + * @return The allocated custom model data integer. + */ @Deprecated("Up for removal due to changing from CustomModelData to ItemModel") public fun allocateCustomModelData(): Int { customModelDataCounter += 1 return customModelDataCounter - 1 } +/** + * Deprecated: Use ItemModel instead of CustomModelData. + * + * Interface for objects that have custom model data. + */ @Deprecated("Up for removal due to changing from CustomModelData to ItemModel") public interface CustomModelDataAllocated { public val customModelData: Int? diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/FolderApplyResource.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/FolderApplyResource.kt index 2b553fb..8ca1ab2 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/FolderApplyResource.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/FolderApplyResource.kt @@ -8,11 +8,19 @@ import xyz.mastriel.cutapi.registry.* import xyz.mastriel.cutapi.resources.* import xyz.mastriel.cutapi.resources.data.* +/** + * A resource representing a folder-level metadata application. + * + * @constructor Creates a FolderApplyResource with the given reference and metadata. + */ public class FolderApplyResource( ref: ResourceRef, metadata: Metadata ) : MetadataResource(ref, metadata) { + /** + * Metadata for FolderApplyResource, containing TOML table to apply. + */ @Serializable public data class Metadata( @SerialName("apply") @@ -20,7 +28,9 @@ public class FolderApplyResource( ) : CuTMeta() } - +/** + * Loader for FolderApplyResource. + */ public val FolderApplyResourceLoader: ResourceFileLoader> = metadataResourceLoader( listOf("meta.folder"), diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/JsonResource.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/JsonResource.kt index f968116..b03b3b3 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/JsonResource.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/JsonResource.kt @@ -6,6 +6,12 @@ import xyz.mastriel.cutapi.* import xyz.mastriel.cutapi.registry.* import xyz.mastriel.cutapi.resources.* +/** + * Represents a resource loaded from a JSON file. + * + * @property ref The reference to this JSON resource. + * @property data The parsed JSON object data. + */ public open class JsonResource( override val ref: ResourceRef, public val data: JsonObject @@ -19,6 +25,9 @@ public open class JsonResource( public operator fun component1(): JsonObject = data } +/** + * Loader for JsonResource, parses JSON files into JsonResource objects. + */ public val JsonResourceLoader: ResourceFileLoader = resourceLoader( extensions = listOf("json"), resourceTypeId = id(Plugin, "json"), diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt index 6f7fa8a..27e04be 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/builtin/TemplateMetadata.kt @@ -8,6 +8,11 @@ import xyz.mastriel.cutapi.resources.* import xyz.mastriel.cutapi.resources.data.* +/** + * A resource representing a template with metadata and string data for patching. + * + * @constructor Creates a TemplateResource with the given reference, metadata, and string data. + */ public class TemplateResource( ref: ResourceRef, metadata: Metadata, @@ -28,6 +33,9 @@ public class TemplateResource( } +/** + * Loader for TemplateResource. + */ public val TemplateResourceLoader: ResourceFileLoader = resourceLoader( listOf("template"), @@ -38,6 +46,7 @@ public val TemplateResourceLoader: ResourceFileLoader = } +/** + * Type alias for a serializable reference to a TemplateResource. + */ public typealias SerializableTemplateRef = ResourceRef<@Contextual TemplateResource> - - diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt index d0c7e81..a03942d 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/data/minecraft/ItemModelData.kt @@ -5,6 +5,13 @@ import xyz.mastriel.cutapi.resources.* import xyz.mastriel.cutapi.resources.builtin.* +/** + * Data class representing item model data for Minecraft resources. + * + * @property parent The parent model string. + * @property textures The map of texture keys to texture paths. + * @property overrides List of item model overrides. + */ @Serializable public data class ItemModelData( @SerialName("parent") @@ -14,10 +21,6 @@ public data class ItemModelData( @EncodeDefault(EncodeDefault.Mode.NEVER) @SerialName("textures") private val _textures: Map = mapOf(), - - @OptIn(ExperimentalSerializationApi::class) - @EncodeDefault(EncodeDefault.Mode.NEVER) - val overrides: List = listOf() ) { @Transient public val textures: Map = _textures.toMutableMap().also { @@ -27,16 +30,4 @@ public data class ItemModelData( } } } -} - -@Serializable -public data class ItemOverrides( - val predicate: ItemPredicates, - val model: String -) - -@Serializable -public data class ItemPredicates( - @SerialName("custom_model_data") - val customModelData: Int -) \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackGenerationException.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackGenerationException.kt index cc2465c..7619981 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackGenerationException.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackGenerationException.kt @@ -1,4 +1,10 @@ package xyz.mastriel.cutapi.resources.generator +/** + * Exception thrown when resource pack generation fails. + * + * @param reason The reason for the failure. + * @param causedBy The underlying cause of the failure, if any. + */ public class PackGenerationException(reason: String, causedBy: Throwable? = null) : Exception(reason, causedBy) { } \ No newline at end of file diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackVersion46Generator.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackVersion46Generator.kt index bbbac9a..3474973 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackVersion46Generator.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/PackVersion46Generator.kt @@ -1,5 +1,10 @@ package xyz.mastriel.cutapi.resources.generator +/** + * Resource pack generator for Minecraft pack format version 46. + * + * Implements the generation steps for version 46 resource packs. + */ public class PackVersion46Generator : ResourcePackGenerator() { override val packVersion: Int = 46 override val generationSteps: Int = 2 diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/ResourcePackGenerator.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/ResourcePackGenerator.kt index 7a81256..d2ebe25 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/ResourcePackGenerator.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/generator/ResourcePackGenerator.kt @@ -10,6 +10,11 @@ import xyz.mastriel.cutapi.utils.* import java.io.* import kotlin.time.* +/** + * Abstract base class for resource pack generators. + * + * Implementations should define the pack version, number of steps, and the generation logic. + */ public abstract class ResourcePackGenerator { /** diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssetLoader.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssetLoader.kt index 3dfc137..a736e2a 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssetLoader.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssetLoader.kt @@ -6,6 +6,11 @@ import xyz.mastriel.cutapi.resources.builtin.* import xyz.mastriel.cutapi.utils.* import java.io.* +/** + * Loader for Minecraft assets from the file system. + * + * Loads textures and models from the specified asset folders and registers them as resources. + */ public class MinecraftAssetLoader { private val resourceManager get() = CuTAPI.resourceManager diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssets.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssets.kt index 92a9f01..6897a66 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssets.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/minecraft/MinecraftAssets.kt @@ -6,6 +6,11 @@ import xyz.mastriel.cutapi.resources.* import xyz.mastriel.cutapi.utils.* import java.io.* +/** + * Singleton object representing the Minecraft assets resource root. + * + * Provides access to the Minecraft textures and models folders as FolderRefs. + */ public data object MinecraftAssets : CuTPlugin, ResourceRoot { override val namespace: String = "minecraft" override val plugin: Plugin = Plugin diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackConfig.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackConfig.kt index 5b951d9..ce8883a 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackConfig.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackConfig.kt @@ -2,6 +2,11 @@ package xyz.mastriel.cutapi.resources.pack import xyz.mastriel.cutapi.utils.* +/** + * Configuration values for resource pack generation. + * + * Provides access to the pack PNG, description, and name from configuration. + */ public object PackConfig { public val PackPng: String by cutConfigValue("pack-png", "pack.png") public val PackDescription: String by cutConfigValue("pack-description", "CuTAPI Generated Resource Pack") diff --git a/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackInfo.kt b/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackInfo.kt index 3c65e5e..a6976dd 100644 --- a/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackInfo.kt +++ b/src/main/kotlin/xyz/mastriel/cutapi/resources/pack/PackInfo.kt @@ -1,3 +1,9 @@ package xyz.mastriel.cutapi.resources.pack +/** + * Data class containing information about a generated resource pack. + * + * @property packUrl The URL where the pack can be downloaded. + * @property packHash The hash of the pack for validation. + */ public data class PackInfo(val packUrl: String, val packHash: String) \ No newline at end of file From e6b6ef381a3acbdc5897564d485e19d389c40dcc Mon Sep 17 00:00:00 2001 From: Zac Date: Tue, 23 Dec 2025 13:49:03 -0700 Subject: [PATCH 06/10] update to 1.21.11 --- .idea/copilotDiffState.xml | 18 -- .idea/modules/CuTAPI.test.iml | 1 - .idea/modules/xyz.mastriel.CuTAPI.main.iml | 1 - .idea/runConfigurations/Build_CuTAPI.xml | 2 +- build.gradle.kts | 7 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- src/main/kotlin/xyz/mastriel/cutapi/CuTAPI.kt | 4 +- .../xyz/mastriel/cutapi/CuTAPIPlugin.kt | 14 +- .../mastriel/cutapi/block/BlockDescriptor.kt | 1 - .../xyz/mastriel/cutapi/block/BlockModel.kt | 7 +- .../mastriel/cutapi/block/CuTPlacedTile.kt | 3 - .../block/breaklogic/BlockBreakManager.kt | 107 ++++++++-- .../block/breaklogic/BlockBreakVisuals.kt | 11 - .../block/breaklogic/PlayerBlockBreaker.kt | 201 +++++++++++++++++- .../xyz/mastriel/cutapi/item/ItemBuilders.kt | 13 +- .../mastriel/cutapi/item/PacketItemHandler.kt | 29 ++- .../cutapi/item/behaviors/VanillaTool.kt | 19 +- .../xyz/mastriel/cutapi/nms/NMSExtensions.kt | 33 ++- .../cutapi/registry/DeferredRegistry.kt | 40 ++-- .../mastriel/cutapi/registry/Identifiable.kt | 67 +++++- .../cutapi/registry/IdentifierRegistry.kt | 8 +- .../cutapi/registry/SealedObjectReigstry.kt | 10 +- .../cutapi/resources/ResourceManager.kt | 8 +- .../mastriel/cutapi/resources/VanillaRef.kt | 2 +- 25 files changed, 492 insertions(+), 118 deletions(-) delete mode 100644 .idea/copilotDiffState.xml delete mode 100644 src/main/kotlin/xyz/mastriel/cutapi/block/breaklogic/BlockBreakVisuals.kt diff --git a/.idea/copilotDiffState.xml b/.idea/copilotDiffState.xml deleted file mode 100644 index 1e290a1..0000000 --- a/.idea/copilotDiffState.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules/CuTAPI.test.iml b/.idea/modules/CuTAPI.test.iml index fc1a45f..a884b65 100644 --- a/.idea/modules/CuTAPI.test.iml +++ b/.idea/modules/CuTAPI.test.iml @@ -4,7 +4,6 @@ - PAPER MCP ADVENTURE diff --git a/.idea/modules/xyz.mastriel.CuTAPI.main.iml b/.idea/modules/xyz.mastriel.CuTAPI.main.iml index fc1a45f..a884b65 100644 --- a/.idea/modules/xyz.mastriel.CuTAPI.main.iml +++ b/.idea/modules/xyz.mastriel.CuTAPI.main.iml @@ -4,7 +4,6 @@ - PAPER MCP ADVENTURE diff --git a/.idea/runConfigurations/Build_CuTAPI.xml b/.idea/runConfigurations/Build_CuTAPI.xml index 04dec39..4b42eb2 100644 --- a/.idea/runConfigurations/Build_CuTAPI.xml +++ b/.idea/runConfigurations/Build_CuTAPI.xml @@ -10,7 +10,7 @@