Codec2RecipeSerializer, move recipe registry to kotlin

This commit is contained in:
DBotThePony 2023-07-26 16:23:58 +07:00
parent 7a2ce84e5f
commit c0faf97bb8
Signed by: DBot
GPG Key ID: DCC23B5715498507
5 changed files with 157 additions and 115 deletions

View File

@ -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<T extends Recipe<?>> implements RecipeType<T> {
public final ResourceLocation name;
private MatteryRecipeType(ResourceLocation name) {
this.name = name;
}
@Override
public String toString() {
return name.toString();
}
}
public static final MatteryRecipeType<PlatePressRecipe> PLATE_PRESS = new MatteryRecipeType<>(OverdriveThatMatters.loc(MNames.PLATE_PRESS));
public static final MatteryRecipeType<PlatePressRecipe> ENERGY_CONTAINER = new MatteryRecipeType<>(OverdriveThatMatters.loc("energy_container"));
public static final MatteryRecipeType<PlatePressRecipe> UPGRADE = new MatteryRecipeType<>(OverdriveThatMatters.loc("upgrade"));
public static final MatteryRecipeType<PlatePressRecipe> HAMMER_PRIMING = new MatteryRecipeType<>(OverdriveThatMatters.loc("hammer_priming"));
private static final DeferredRegister<RecipeSerializer<?>> serializerRegistry = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, OverdriveThatMatters.MOD_ID);
private static final DeferredRegister<RecipeType<?>> 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);
}
}

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.core.util
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.JsonOps import com.mojang.serialization.JsonOps
import io.netty.buffer.ByteBufInputStream import io.netty.buffer.ByteBufInputStream
import io.netty.buffer.ByteBufOutputStream import io.netty.buffer.ByteBufOutputStream
@ -29,6 +30,10 @@ fun <S> FriendlyByteBuf.readBinaryJsonWithCodec(codec: Codec<S>, sizeLimit: NbtA
.get().map({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") }) .get().map({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") })
} }
fun <S> FriendlyByteBuf.readBinaryJsonWithCodecIndirect(codec: Codec<S>, sizeLimit: NbtAccounter = NbtAccounter(1L shl 18)): DataResult<S> {
return codec.decode(JsonOps.INSTANCE, readBinaryJson(sizeLimit)).map { it.first }
}
fun FriendlyByteBuf.readBinaryComponent(): Component { fun FriendlyByteBuf.readBinaryComponent(): Component {
return Component.Serializer.fromJson(readBinaryJson()) ?: throw NullPointerException("Received null component") return Component.Serializer.fromJson(readBinaryJson()) ?: throw NullPointerException("Received null component")
} }

View File

@ -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<S : Recipe<*>>(val empty: S?, val codec: Codec<S>) : Codec<S> by codec, RecipeSerializer<S> {
constructor(supplier: (() -> ResourceLocation) -> Codec<S>) : this(null, supplier.invoke(::context))
constructor(empty: S, supplier: (() -> ResourceLocation) -> Codec<S>) : 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<ResourceLocation> {
private val deck = ThreadLocal<LinkedList<ResourceLocation>>()
private fun context(): ResourceLocation {
val deck = deck.getOrSet(::LinkedList)
if (deck.isEmpty()) {
throw NoSuchElementException("Context stack is empty")
} else {
return deck.last
}
}
override fun <T : Any> encode(input: ResourceLocation, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return DataResult.success(ops.empty())
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<ResourceLocation, T>> {
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()
}
}

View File

@ -1,10 +1,9 @@
package ru.dbotthepony.mc.otm.recipe package ru.dbotthepony.mc.otm.recipe
import com.google.gson.JsonObject import com.mojang.serialization.Codec
import com.google.gson.JsonPrimitive import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.NonNullList import net.minecraft.core.NonNullList
import net.minecraft.core.RegistryAccess import net.minecraft.core.RegistryAccess
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.util.valueproviders.ConstantFloat import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.FloatProvider 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.RecipeSerializer
import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.container.get 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.core.isActuallyEmpty
import ru.dbotthepony.mc.otm.registry.MRecipes import ru.dbotthepony.mc.otm.registry.MRecipes
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.toJsonStrict import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
import ru.dbotthepony.mc.otm.core.util.readBinaryJson import ru.dbotthepony.mc.otm.data.IngredientCodec
import ru.dbotthepony.mc.otm.core.util.writeBinaryJson import ru.dbotthepony.mc.otm.data.minRange
class PlatePressRecipe( class PlatePressRecipe(
private val id: ResourceLocation, private val id: ResourceLocation,
@ -35,21 +32,21 @@ class PlatePressRecipe(
val experience: FloatProvider = ConstantFloat.ZERO val experience: FloatProvider = ConstantFloat.ZERO
) : Recipe<Container> { ) : Recipe<Container> {
override fun matches(container: Container, p_44003_: Level): Boolean { override fun matches(container: Container, p_44003_: Level): Boolean {
if (output.isActuallyEmpty || input.isActuallyEmpty) if (isIncomplete)
return false return false
return input.test(container[0]) return input.test(container[0])
} }
fun matches(container: Container, slot: Int): Boolean { fun matches(container: Container, slot: Int): Boolean {
if (output.isActuallyEmpty || input.isActuallyEmpty) if (isIncomplete)
return false return false
return input.test(container[slot]) return input.test(container[slot])
} }
private val outputStack: ItemStack by lazy { private val outputStack: ItemStack by lazy {
if (output.isActuallyEmpty || input.isActuallyEmpty) { if (isIncomplete) {
ItemStack.EMPTY ItemStack.EMPTY
} else { } else {
val items = output.items val items = output.items
@ -59,7 +56,7 @@ class PlatePressRecipe(
} }
override fun getIngredients(): NonNullList<Ingredient> { override fun getIngredients(): NonNullList<Ingredient> {
if (input.isActuallyEmpty || output.isActuallyEmpty) if (isIncomplete)
return super.getIngredients() return super.getIngredients()
return NonNullList.of(Ingredient.EMPTY, input) return NonNullList.of(Ingredient.EMPTY, input)
@ -76,60 +73,22 @@ class PlatePressRecipe(
override fun getId() = id override fun getId() = id
override fun getSerializer(): RecipeSerializer<*> { override fun getSerializer(): RecipeSerializer<*> {
return Companion return SERIALIZER
} }
override fun getType(): RecipeType<PlatePressRecipe> = MRecipes.PLATE_PRESS override fun getType(): RecipeType<PlatePressRecipe> = MRecipes.PLATE_PRESS
companion object : RecipeSerializer<PlatePressRecipe> { companion object {
private val EMPTY = PlatePressRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, Ingredient.EMPTY, 1) val SERIALIZER = Codec2RecipeSerializer<PlatePressRecipe>(PlatePressRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, Ingredient.EMPTY, 1)) { context ->
private val LOGGER = LogManager.getLogger() RecordCodecBuilder.create {
it.group(
override fun fromJson(loc: ResourceLocation, obj: JsonObject): PlatePressRecipe { IngredientCodec.fieldOf("input").forGetter(PlatePressRecipe::input),
val input = try { IngredientCodec.fieldOf("output").forGetter(PlatePressRecipe::output),
Ingredient.fromJson(obj["input"] ?: throw IllegalStateException("Recipe $loc has no input field defined")) Codec.INT.minRange(1).fieldOf("count").forGetter(PlatePressRecipe::count),
} catch (err: Throwable) { Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime),
if (err.message?.lowercase()?.contains("unknown item tag") == true) { FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience)
LOGGER.warn("Ignoring recipe Input of $loc deserialization error, defaulting to empty recipe") ).apply(it) { a, b, c, d, e -> PlatePressRecipe(context.invoke(), a, b, c, d, e) }
LOGGER.warn(err)
return EMPTY
} else {
throw IllegalStateException("Input of $loc is malformed", err)
}
} }
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))
} }
} }
} }

View File

@ -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<T : Recipe<*>>(id: String) : RecipeType<T> {
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 <T : Recipe<*>> register(name: String): RegistryObject<Type<T>> {
return types.register(name) { Type(name) }
}
val PLATE_PRESS by register<PlatePressRecipe>("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 }
}
}