From c0faf97bb85ab52ae44c7f64f9aa470babe05b83 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 26 Jul 2023 16:23:58 +0700 Subject: [PATCH] Codec2RecipeSerializer, move recipe registry to kotlin --- .../dbotthepony/mc/otm/registry/MRecipes.java | 54 ------------ .../mc/otm/core/util/ByteBufExtensions.kt | 5 ++ .../mc/otm/data/Codec2RecipeSerializer.kt | 86 +++++++++++++++++++ .../mc/otm/recipe/PlatePressRecipe.kt | 81 +++++------------ .../dbotthepony/mc/otm/registry/MRecipes.kt | 46 ++++++++++ 5 files changed, 157 insertions(+), 115 deletions(-) delete mode 100644 src/main/java/ru/dbotthepony/mc/otm/registry/MRecipes.java create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt diff --git a/src/main/java/ru/dbotthepony/mc/otm/registry/MRecipes.java b/src/main/java/ru/dbotthepony/mc/otm/registry/MRecipes.java deleted file mode 100644 index 304ce47e0..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/registry/MRecipes.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.dbotthepony.mc.otm.registry; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.registries.DeferredRegister; -import net.minecraftforge.registries.ForgeRegistries; -import ru.dbotthepony.mc.otm.OverdriveThatMatters; -import ru.dbotthepony.mc.otm.recipe.EnergyContainerRecipe; -import ru.dbotthepony.mc.otm.recipe.ExplosiveHammerPrimingRecipe; -import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe; -import ru.dbotthepony.mc.otm.recipe.UpgradeRecipe; - -public class MRecipes { - public static class MatteryRecipeType> implements RecipeType { - public final ResourceLocation name; - - private MatteryRecipeType(ResourceLocation name) { - this.name = name; - } - - @Override - public String toString() { - return name.toString(); - } - } - - public static final MatteryRecipeType PLATE_PRESS = new MatteryRecipeType<>(OverdriveThatMatters.loc(MNames.PLATE_PRESS)); - public static final MatteryRecipeType ENERGY_CONTAINER = new MatteryRecipeType<>(OverdriveThatMatters.loc("energy_container")); - public static final MatteryRecipeType UPGRADE = new MatteryRecipeType<>(OverdriveThatMatters.loc("upgrade")); - public static final MatteryRecipeType HAMMER_PRIMING = new MatteryRecipeType<>(OverdriveThatMatters.loc("hammer_priming")); - - private static final DeferredRegister> serializerRegistry = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, OverdriveThatMatters.MOD_ID); - private static final DeferredRegister> typeRegistry = DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, OverdriveThatMatters.MOD_ID); - - static { - serializerRegistry.register(MNames.PLATE_PRESS, () -> PlatePressRecipe.Companion); - serializerRegistry.register(ENERGY_CONTAINER.name.getPath(), () -> EnergyContainerRecipe.Companion); - serializerRegistry.register(UPGRADE.name.getPath(), () -> UpgradeRecipe.Companion); - serializerRegistry.register(HAMMER_PRIMING.name.getPath(), () -> ExplosiveHammerPrimingRecipe.Companion); - - typeRegistry.register(MNames.PLATE_PRESS, () -> PLATE_PRESS); - typeRegistry.register(ENERGY_CONTAINER.name.getPath(), () -> ENERGY_CONTAINER); - typeRegistry.register(UPGRADE.name.getPath(), () -> UPGRADE); - typeRegistry.register(HAMMER_PRIMING.name.getPath(), () -> HAMMER_PRIMING); - } - - public static void register(IEventBus bus) { - serializerRegistry.register(bus); - typeRegistry.register(bus); - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt index 5de89cfe1..eaff47fd6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.core.util import com.google.gson.JsonElement import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult import com.mojang.serialization.JsonOps import io.netty.buffer.ByteBufInputStream import io.netty.buffer.ByteBufOutputStream @@ -29,6 +30,10 @@ fun FriendlyByteBuf.readBinaryJsonWithCodec(codec: Codec, sizeLimit: NbtA .get().map({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") }) } +fun FriendlyByteBuf.readBinaryJsonWithCodecIndirect(codec: Codec, sizeLimit: NbtAccounter = NbtAccounter(1L shl 18)): DataResult { + return codec.decode(JsonOps.INSTANCE, readBinaryJson(sizeLimit)).map { it.first } +} + fun FriendlyByteBuf.readBinaryComponent(): Component { return Component.Serializer.fromJson(readBinaryJson()) ?: throw NullPointerException("Received null component") } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt new file mode 100644 index 000000000..f3543dbb0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt @@ -0,0 +1,86 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonObject +import com.google.gson.JsonSyntaxException +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeSerializer +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodecIndirect +import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec +import java.util.* +import kotlin.NoSuchElementException +import kotlin.concurrent.getOrSet + +class Codec2RecipeSerializer>(val empty: S?, val codec: Codec) : Codec by codec, RecipeSerializer { + constructor(supplier: (() -> ResourceLocation) -> Codec) : this(null, supplier.invoke(::context)) + constructor(empty: S, supplier: (() -> ResourceLocation) -> Codec) : this(empty, supplier.invoke(::context)) + + override fun fromJson(p_44103_: ResourceLocation, p_44104_: JsonObject): S { + try { + deck.getOrSet(::LinkedList).addLast(p_44103_) + + return codec.decode(JsonOps.INSTANCE, p_44104_).get().map( + { + it.first + }, + { + empty ?: throw JsonSyntaxException("Failed to deserialize recipe from JSON: ${it.message()}") + } + ) + } finally { + deck.get().removeLast() + } + } + + override fun fromNetwork(p_44105_: ResourceLocation, p_44106_: FriendlyByteBuf): S? { + try { + deck.getOrSet(::LinkedList).addLast(p_44105_) + + return p_44106_.readBinaryJsonWithCodecIndirect(codec) + .resultOrPartial { LOGGER.error("Failed to read recipe $p_44105_ from network: $it") }.orElse(null) + } finally { + deck.get().removeLast() + } + } + + override fun toNetwork(p_44101_: FriendlyByteBuf, p_44102_: S) { + p_44101_.writeBinaryJsonWithCodec(codec, p_44102_) + } + + companion object : Codec { + private val deck = ThreadLocal>() + + private fun context(): ResourceLocation { + val deck = deck.getOrSet(::LinkedList) + + if (deck.isEmpty()) { + throw NoSuchElementException("Context stack is empty") + } else { + return deck.last + } + } + + override fun encode(input: ResourceLocation, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(ops.empty()) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val deck = deck.getOrSet(::LinkedList) + + if (deck.isEmpty()) { + return DataResult.error { "Attempt to use recipe serializer codec ResourceLocation' hack outside Codec2RecipeSerializer" } + } else { + return DataResult.success(Pair(deck.last, ops.empty())) + } + } + + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt index 783ee71d5..e4d1b4b5f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt @@ -1,10 +1,9 @@ package ru.dbotthepony.mc.otm.recipe -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.NonNullList import net.minecraft.core.RegistryAccess -import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation import net.minecraft.util.valueproviders.ConstantFloat import net.minecraft.util.valueproviders.FloatProvider @@ -15,16 +14,14 @@ import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.RecipeSerializer import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.level.Level -import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.container.get -import ru.dbotthepony.mc.otm.core.fromJsonStrict import ru.dbotthepony.mc.otm.core.isActuallyEmpty import ru.dbotthepony.mc.otm.registry.MRecipes import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.toJsonStrict -import ru.dbotthepony.mc.otm.core.util.readBinaryJson -import ru.dbotthepony.mc.otm.core.util.writeBinaryJson +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer +import ru.dbotthepony.mc.otm.data.IngredientCodec +import ru.dbotthepony.mc.otm.data.minRange class PlatePressRecipe( private val id: ResourceLocation, @@ -35,21 +32,21 @@ class PlatePressRecipe( val experience: FloatProvider = ConstantFloat.ZERO ) : Recipe { override fun matches(container: Container, p_44003_: Level): Boolean { - if (output.isActuallyEmpty || input.isActuallyEmpty) + if (isIncomplete) return false return input.test(container[0]) } fun matches(container: Container, slot: Int): Boolean { - if (output.isActuallyEmpty || input.isActuallyEmpty) + if (isIncomplete) return false return input.test(container[slot]) } private val outputStack: ItemStack by lazy { - if (output.isActuallyEmpty || input.isActuallyEmpty) { + if (isIncomplete) { ItemStack.EMPTY } else { val items = output.items @@ -59,7 +56,7 @@ class PlatePressRecipe( } override fun getIngredients(): NonNullList { - if (input.isActuallyEmpty || output.isActuallyEmpty) + if (isIncomplete) return super.getIngredients() return NonNullList.of(Ingredient.EMPTY, input) @@ -76,60 +73,22 @@ class PlatePressRecipe( override fun getId() = id override fun getSerializer(): RecipeSerializer<*> { - return Companion + return SERIALIZER } override fun getType(): RecipeType = MRecipes.PLATE_PRESS - companion object : RecipeSerializer { - private val EMPTY = PlatePressRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, Ingredient.EMPTY, 1) - private val LOGGER = LogManager.getLogger() - - override fun fromJson(loc: ResourceLocation, obj: JsonObject): PlatePressRecipe { - val input = try { - Ingredient.fromJson(obj["input"] ?: throw IllegalStateException("Recipe $loc has no input field defined")) - } catch (err: Throwable) { - if (err.message?.lowercase()?.contains("unknown item tag") == true) { - LOGGER.warn("Ignoring recipe Input of $loc deserialization error, defaulting to empty recipe") - LOGGER.warn(err) - return EMPTY - } else { - throw IllegalStateException("Input of $loc is malformed", err) - } + companion object { + val SERIALIZER = Codec2RecipeSerializer(PlatePressRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, Ingredient.EMPTY, 1)) { context -> + RecordCodecBuilder.create { + it.group( + IngredientCodec.fieldOf("input").forGetter(PlatePressRecipe::input), + IngredientCodec.fieldOf("output").forGetter(PlatePressRecipe::output), + Codec.INT.minRange(1).fieldOf("count").forGetter(PlatePressRecipe::count), + Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime), + FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience) + ).apply(it) { a, b, c, d, e -> PlatePressRecipe(context.invoke(), a, b, c, d, e) } } - - val result = try { - Ingredient.fromJson(obj["result"] ?: throw IllegalStateException("Recipe $loc has no result field defined")) - } catch (err: Throwable) { - if (err.message?.lowercase()?.contains("unknown item tag") == true) { - LOGGER.warn("Ignoring recipe Output of $loc deserialization error, defaulting to empty recipe") - LOGGER.warn(err) - return EMPTY - } else { - throw IllegalStateException("Result of $loc is malformed", err) - } - } - - val workTime = (obj["work_time"] as? JsonPrimitive)?.let { return@let try {it.asInt} catch(err: Throwable) {throw IllegalStateException("Invalid work_time")} } ?: 200 - - check(workTime >= 0) { "work_time of $loc does not make any sense" } - - val count = ((obj["result"] as JsonObject)["count"] as? JsonPrimitive)?.let { return@let try {it.asInt} catch(err: Throwable) {throw IllegalStateException("Invalid result.count")} } ?: 1 - - val experience = obj["experience"]?.let { FloatProvider.CODEC.fromJsonStrict(it) } ?: ConstantFloat.ZERO - return PlatePressRecipe(loc, input, result, count, workTime, experience) - } - - override fun fromNetwork(loc: ResourceLocation, buff: FriendlyByteBuf): PlatePressRecipe { - return PlatePressRecipe(loc, Ingredient.fromNetwork(buff), Ingredient.fromNetwork(buff), buff.readInt(), buff.readInt(), FloatProvider.CODEC.fromJsonStrict(buff.readBinaryJson())) - } - - override fun toNetwork(buff: FriendlyByteBuf, recipe: PlatePressRecipe) { - recipe.input.toNetwork(buff) - recipe.output.toNetwork(buff) - buff.writeInt(recipe.count) - buff.writeInt(recipe.workTime) - buff.writeBinaryJson(FloatProvider.CODEC.toJsonStrict(recipe.experience)) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt new file mode 100644 index 000000000..a1f67f3ab --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.mc.otm.registry + +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeType +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.registries.RegistryObject +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.recipe.EnergyContainerRecipe +import ru.dbotthepony.mc.otm.recipe.ExplosiveHammerPrimingRecipe +import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe +import ru.dbotthepony.mc.otm.recipe.UpgradeRecipe + +@Suppress("SameParameterValue") +object MRecipes { + class Type>(id: String) : RecipeType { + val id = ResourceLocation(OverdriveThatMatters.MOD_ID, id) + + override fun toString(): String { + return id.toString() + } + } + + private val types = DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, OverdriveThatMatters.MOD_ID) + private val serializers = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, OverdriveThatMatters.MOD_ID) + + internal fun register(bus: IEventBus) { + types.register(bus) + serializers.register(bus) + } + + private fun > register(name: String): RegistryObject> { + return types.register(name) { Type(name) } + } + + val PLATE_PRESS by register("plate_press") + + init { + serializers.register("plate_press") { PlatePressRecipe.SERIALIZER } + serializers.register("energy_container") { EnergyContainerRecipe.Companion } + serializers.register("upgrade") { UpgradeRecipe.Companion } + serializers.register("hammer_priming") { ExplosiveHammerPrimingRecipe.Companion } + } +}