diff --git a/geary-papermc-core/src/main/kotlin/com/mineinabyss/geary/papermc/GearyPaperConfig.kt b/geary-papermc-core/src/main/kotlin/com/mineinabyss/geary/papermc/GearyPaperConfig.kt index 33f3a6d..b542d15 100644 --- a/geary-papermc-core/src/main/kotlin/com/mineinabyss/geary/papermc/GearyPaperConfig.kt +++ b/geary-papermc-core/src/main/kotlin/com/mineinabyss/geary/papermc/GearyPaperConfig.kt @@ -20,7 +20,8 @@ class GearyPaperConfig( @YamlComment("List of mob types to remove if they are not entities with Geary prefabs (i.e. vanilla entities)") val removeVanillaMobTypes: Set = emptySet(), val logLevel: Severity = Severity.Info, - val integrations: Integrations = Integrations() + val integrations: Integrations = Integrations(), + val resourcePack: ResourcePack = ResourcePack(), ) @Serializable @@ -71,3 +72,10 @@ data class Integrations( @YamlComment("Allow binding to MythicMobs entities.") val mythicMobs: Boolean = true, ) + +@Serializable +data class ResourcePack( + val generate: Boolean = true, + @YamlComment("The path to generate the pack to from `plugins/Geary`", "Adding .zip to path will export as a zip instead of directory") + val outputPath: String = "resourcepack.zip" +) diff --git a/geary-papermc-features/build.gradle.kts b/geary-papermc-features/build.gradle.kts index c2f1e9c..e56a9c1 100644 --- a/geary-papermc-features/build.gradle.kts +++ b/geary-papermc-features/build.gradle.kts @@ -19,4 +19,6 @@ dependencies { compileOnly(idofrontLibs.kotlinx.coroutines) compileOnly(idofrontLibs.minecraft.mccoroutine) compileOnly(idofrontLibs.idofront.nms) + compileOnly(idofrontLibs.creative.api) + compileOnly(idofrontLibs.creative.serializer.minecraft) } diff --git a/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/GearyPaperMCFeatures.kt b/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/GearyPaperMCFeatures.kt index c00c52a..4c430b5 100644 --- a/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/GearyPaperMCFeatures.kt +++ b/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/GearyPaperMCFeatures.kt @@ -10,6 +10,7 @@ import com.mineinabyss.geary.papermc.features.common.event_bridge.items.ItemInte import com.mineinabyss.geary.papermc.features.common.event_bridge.items.ItemRemovedBridge import com.mineinabyss.geary.papermc.features.common.cooldowns.clearOldCooldownsSystem import com.mineinabyss.geary.papermc.features.common.cooldowns.cooldownDisplaySystem +import com.mineinabyss.geary.papermc.features.items.resourcepacks.ResourcePackGenerator import com.mineinabyss.geary.papermc.gearyPaper import com.mineinabyss.idofront.plugin.listeners @@ -32,6 +33,10 @@ open class GearyPaperMCFeatures { ItemRemovedBridge(), ) } + + geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) { + ResourcePackGenerator().generateResourcePack() + } } override fun default() = GearyPaperMCFeatures() diff --git a/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/items/resourcepacks/ResourcePackContent.kt b/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/items/resourcepacks/ResourcePackContent.kt new file mode 100644 index 0000000..a3ce397 --- /dev/null +++ b/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/items/resourcepacks/ResourcePackContent.kt @@ -0,0 +1,117 @@ +package com.mineinabyss.geary.papermc.features.items.resourcepacks + +import com.mineinabyss.idofront.serialization.* +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import net.kyori.adventure.key.Key +import org.bukkit.Material +import team.unnamed.creative.model.ItemOverride +import team.unnamed.creative.model.ItemPredicate + +@Serializable +@SerialName("geary:resourcepack") +data class ResourcePackContent( + val baseMaterial: @Serializable(MaterialByNameSerializer::class) Material, + val model: @Serializable(KeySerializer::class) Key? = null, + val parentModel: @Serializable(KeySerializer::class) Key = Key.key("minecraft:item/generated"), + val textures: @Serializable(ModelTexturesSerializer::class) ModelTexturesSurrogate = ModelTexturesSurrogate(), + val itemPredicates: ItemPredicates +) { + + init { + require(model != null || textures.layers.isNotEmpty() || textures.variables.isNotEmpty()) { "ResourcePackContent must contain atleast a model or texture reference" } + } + + fun itemOverrides(modelKey: Key): List { + val overrides = mutableListOf() + + // Shields + (itemPredicates.blockingModel ?: itemPredicates.blockingTexture?.let { modelKey.plus("_blocking") })?.let { + overrides.add(ItemOverride.of(it, itemPredicates.customModelData(), ItemPredicate.blocking())) + } + // Elytras + (itemPredicates.brokenModel ?: itemPredicates.brokenTexture?.let { modelKey.plus("_broken") })?.let { + overrides.add(ItemOverride.of(it, itemPredicates.customModelData(), ItemPredicate.broken())) + } + // Fishing Rods + overrides.add(ItemOverride.of(modelKey, itemPredicates.customModelData())) + (itemPredicates.castModel ?: itemPredicates.castTexture?.let { modelKey.plus("_cast") })?.let { + overrides.add(ItemOverride.of(it, itemPredicates.customModelData(), ItemPredicate.cast())) + } + // Charged Crossbow + (itemPredicates.chargedModel ?: itemPredicates.chargedTexture?.let { modelKey.plus("_charged") })?.let { + overrides.add(ItemOverride.of(it, itemPredicates.customModelData(), ItemPredicate.charged())) + } + // Charged Crossbow with Firework + (itemPredicates.fireworkModel ?: itemPredicates.fireworkTexture?.let { modelKey.plus("_firework") })?.let { + overrides.add(ItemOverride.of(it, itemPredicates.customModelData(), ItemPredicate.firework())) + } + // Lefthanded-players + (itemPredicates.lefthandedModel ?: itemPredicates.lefthandedTexture?.let { modelKey.plus("_lefthanded") })?.let { + overrides.add(ItemOverride.of(it, itemPredicates.customModelData(), ItemPredicate.lefthanded())) + } + // Tridents + (itemPredicates.throwingModel ?: itemPredicates.throwingTexture?.let { modelKey.plus("_throwing") })?.let { + overrides.add(ItemOverride.of(it, itemPredicates.customModelData(), ItemPredicate.throwing())) + } + + fun Map.predicateModel(suffix: String, action: (Map.Entry) -> ItemOverride) = this.toList().mapIndexed { index, (_, damage) -> modelKey.plus("_${suffix}_$index") to damage.coerceIn(0f..1f) }.toMap().map(action).forEach(overrides::add) + // Compasses + itemPredicates.angleModels.takeUnless { it.isEmpty() } ?: itemPredicates.angleTextures.predicateModel("angle") { (key, angle) -> + ItemOverride.of(key, itemPredicates.customModelData(), ItemPredicate.angle(angle)) + } + // Cooldown remaining on an item + itemPredicates.cooldownModels.takeUnless { it.isEmpty() } ?: itemPredicates.cooldownTextures.predicateModel("cooldown") { (key, damage) -> + ItemOverride.of(key, itemPredicates.customModelData(), ItemPredicate.damaged(), ItemPredicate.cooldown(damage)) + } + // Durability of an item + itemPredicates.damageModels.takeUnless { it.isEmpty() } ?: itemPredicates.damageTextures.predicateModel("damage") { (key, damage) -> + ItemOverride.of(key, itemPredicates.customModelData(), ItemPredicate.damaged(), ItemPredicate.damage(damage)) + } + // Bows & Crossbows + itemPredicates.pullingModels.takeUnless { it.isEmpty() } ?: itemPredicates.pullingTextures.predicateModel("pulling") { (key, pulling) -> + ItemOverride.of(key, itemPredicates.customModelData(), ItemPredicate.pulling(), ItemPredicate.pull(pulling)) + } + // Clocks + itemPredicates.timeModels.takeUnless { it.isEmpty() } ?: itemPredicates.timeTextures.predicateModel("time") { (key, time) -> + ItemOverride.of(key, itemPredicates.customModelData(), ItemPredicate.time(time)) + } + + return overrides + } + + private fun Key.plus(suffix: String) = Key.key(namespace(), value().plus(suffix)) + + @Serializable + data class ItemPredicates( + val customModelData: Int, + @EncodeDefault(EncodeDefault.Mode.NEVER) val blockingModel: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val blockingTexture: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val brokenModel: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val brokenTexture: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val castModel: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val castTexture: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val chargedModel: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val chargedTexture: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val fireworkModel: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val fireworkTexture: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val lefthandedModel: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val lefthandedTexture: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val throwingModel: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(EncodeDefault.Mode.NEVER) val throwingTexture: @Serializable(KeySerializer::class) Key? = null, + + @EncodeDefault(EncodeDefault.Mode.NEVER) val angleModels: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val angleTextures: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val cooldownModels: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val cooldownTextures: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val damageModels: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val damageTextures: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val pullingModels: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val pullingTextures: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val timeModels: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + @EncodeDefault(EncodeDefault.Mode.NEVER) val timeTextures: Map<@Serializable(KeySerializer::class) Key, Float> = emptyMap(), + ) { + fun customModelData(): ItemPredicate = ItemPredicate.customModelData(customModelData) + } +} diff --git a/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/items/resourcepacks/ResourcePackGenerator.kt b/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/items/resourcepacks/ResourcePackGenerator.kt new file mode 100644 index 0000000..cb81a61 --- /dev/null +++ b/geary-papermc-features/src/main/kotlin/com/mineinabyss/geary/papermc/features/items/resourcepacks/ResourcePackGenerator.kt @@ -0,0 +1,94 @@ +package com.mineinabyss.geary.papermc.features.items.resourcepacks + +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.papermc.gearyPaper +import com.mineinabyss.geary.papermc.tracking.items.components.SetItem +import com.mineinabyss.geary.prefabs.PrefabKey +import com.mineinabyss.geary.prefabs.configuration.components.Prefab +import com.mineinabyss.geary.systems.builders.cache +import com.mineinabyss.geary.systems.query.GearyQuery +import com.mineinabyss.idofront.resourcepacks.ResourcePacks +import net.kyori.adventure.key.Key +import team.unnamed.creative.ResourcePack +import team.unnamed.creative.model.Model +import team.unnamed.creative.model.ModelTexture +import team.unnamed.creative.model.ModelTextures + +class ResourcePackGenerator { + + private val resourcePack: ResourcePack = ResourcePack.resourcePack() + private val resourcePackQuery = geary.cache(ResourcePackQuery()) + + fun generateResourcePack() { + if (!gearyPaper.config.resourcePack.generate) return + val resourcePackFile = gearyPaper.plugin.dataFolder.resolve(gearyPaper.config.resourcePack.outputPath) + resourcePackFile.deleteRecursively() + + resourcePackQuery.forEach { (prefabKey, resourcePackContent) -> + val defaultVanillaModel = Key.key("item/${resourcePackContent.baseMaterial.key().value()}").let { resourcePack.model(it)?.toBuilder() ?: Model.model().key(it) } + + // Generates any missing models for predicates if only textures are provided + generatePredicateModels(resourcePack, resourcePackContent, prefabKey) + + // If a model is defined we assume it exists in the resourcepack already, and just add the override to the vanilla model + if (resourcePackContent.model != null) { + resourcePackContent.itemOverrides(resourcePackContent.model.key()).forEach(defaultVanillaModel::addOverride) + } else { // If it only has textures we need to generate the model ourselves and add it + val model = Model.model() + .key(Key.key(prefabKey.namespace, prefabKey.key)) + .parent(resourcePackContent.parentModel.key()) + .textures(resourcePackContent.textures.modelTextures).build() + resourcePackContent.itemOverrides(model.key()).forEach(defaultVanillaModel::addOverride) + model.addTo(resourcePack) + } + + defaultVanillaModel.build().addTo(resourcePack) + } + + when { + resourcePackFile.extension == "zip" -> ResourcePacks.resourcePackWriter.writeToZipFile(resourcePackFile, resourcePack) + resourcePackFile.isDirectory -> ResourcePacks.resourcePackWriter.writeToDirectory(resourcePackFile, resourcePack) + else -> { + gearyPaper.logger.w("Failed to generate resourcepack in ${resourcePackFile.path}") + gearyPaper.logger.w("Outputting to default plugins/Geary/resourcepack directory") + ResourcePacks.resourcePackWriter.writeToDirectory(gearyPaper.plugin.dataFolder.resolve("resourcepack"), resourcePack) + } + } + } + + private fun generatePredicateModels(resourcePack: ResourcePack, resourcePackContent: ResourcePackContent, prefabKey: PrefabKey) { + fun predicateModel(modelKey: Key, suffix: String) { + Model.model().key(Key.key(prefabKey.namespace, prefabKey.key.plus(suffix))) + .parent(resourcePackContent.parentModel) + .textures(ModelTextures.of(listOf(ModelTexture.ofKey(modelKey)), null, emptyMap())) + .build().addTo(resourcePack) + } + resourcePackContent.itemPredicates.blockingTexture?.let { predicateModel(it, "_blocking") } + resourcePackContent.itemPredicates.brokenTexture?.let { predicateModel(it, "_broken") } + resourcePackContent.itemPredicates.castTexture?.let { predicateModel(it, "_cast") } + resourcePackContent.itemPredicates.chargedTexture?.let { predicateModel(it, "_charged") } + resourcePackContent.itemPredicates.fireworkTexture?.let { predicateModel(it, "_firework") } + resourcePackContent.itemPredicates.lefthandedTexture?.let { predicateModel(it, "_lefthanded") } + resourcePackContent.itemPredicates.throwingTexture?.let { predicateModel(it, "_throwing") } + resourcePackContent.itemPredicates.angleTextures.onEachIndexed { i, (key, _) -> predicateModel(key, "_angle_$i") } + resourcePackContent.itemPredicates.cooldownTextures.onEachIndexed { i, (key, _) -> predicateModel(key, "_cooldown_$i") } + resourcePackContent.itemPredicates.damageTextures.onEachIndexed { i, (key, _) -> predicateModel(key, "_damage_$i") } + resourcePackContent.itemPredicates.pullingTextures.onEachIndexed { i, (key, _) -> predicateModel(key, "_pulling_$i") } + resourcePackContent.itemPredicates.timeTextures.onEachIndexed { i, (key, _) -> predicateModel(key, "_time_$i") } + + } + + companion object { + class ResourcePackQuery : GearyQuery() { + private val prefabKey by get() + private val resourcePackContent by get() + + override fun ensure() = this { + has() + } + + operator fun component1() = prefabKey + operator fun component2() = resourcePackContent + } + } +} \ No newline at end of file diff --git a/geary-papermc-plugin/src/main/kotlin/com/mineinabyss/geary/papermc/plugin/commands/GearyReloadCommand.kt b/geary-papermc-plugin/src/main/kotlin/com/mineinabyss/geary/papermc/plugin/commands/GearyReloadCommand.kt index 68422c9..8c46a9e 100644 --- a/geary-papermc-plugin/src/main/kotlin/com/mineinabyss/geary/papermc/plugin/commands/GearyReloadCommand.kt +++ b/geary-papermc-plugin/src/main/kotlin/com/mineinabyss/geary/papermc/plugin/commands/GearyReloadCommand.kt @@ -1,5 +1,6 @@ package com.mineinabyss.geary.papermc.plugin.commands +import com.mineinabyss.geary.papermc.features.items.resourcepacks.ResourcePackGenerator import com.mineinabyss.geary.papermc.gearyPaper import com.mineinabyss.geary.prefabs.prefabs import com.mineinabyss.idofront.commands.Command @@ -11,6 +12,7 @@ fun Command.reload() { action { gearyPaper.configHolder.reload() prefabLoader.loadOrUpdatePrefabs() + ResourcePackGenerator().generateResourcePack() } } } diff --git a/gradle.properties b/gradle.properties index 4ff7e09..a86dc07 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ kotlin.code.style=official group=com.mineinabyss version=0.30 -idofrontVersion=0.24.14 +idofrontVersion=0.24.17