Move android research type to codecs

This commit is contained in:
DBotThePony 2023-06-20 00:32:59 +07:00
parent e39bb8e1ab
commit 3117349757
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 143 additions and 164 deletions

View File

@ -1,11 +1,14 @@
package ru.dbotthepony.mc.otm.android
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.data.CachedOutput
import net.minecraft.data.DataProvider
import net.minecraft.data.PackOutput
import net.minecraft.resources.ResourceLocation
import net.minecraftforge.data.event.GatherDataEvent
import ru.dbotthepony.mc.otm.core.toJson
import ru.dbotthepony.mc.otm.core.toJsonStrict
import ru.dbotthepony.mc.otm.core.util.WriteOnce
import java.util.Collections
import java.util.LinkedList
@ -56,7 +59,7 @@ open class AndroidResearchDataProvider() : DataProvider {
addEverything {
if (set.add(it.id)) {
futures.add(DataProvider.saveStable(output, it.toJson(), pathProvider.json(it.id)))
futures.add(DataProvider.saveStable(output, AndroidResearchType.CODEC.toJsonStrict(it).also { (it as JsonObject).remove("id") }, pathProvider.json(it.id)))
AndroidResearchManager.put(it)
added.add(it)
} else {

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableMap
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import net.minecraft.client.server.IntegratedServer
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@ -22,6 +23,13 @@ import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.fromJsonStrict
import ru.dbotthepony.mc.otm.core.fromNetwork
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.toJsonStrict
import ru.dbotthepony.mc.otm.core.toNetwork
import ru.dbotthepony.mc.otm.core.util.readJson
import ru.dbotthepony.mc.otm.matter.MatterManager
import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel
import ru.dbotthepony.mc.otm.network.enqueueWork
@ -67,7 +75,13 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
continue
}
builder.put(k, AndroidResearchType.fromJson(v, k))
v["id"] = k.toString()
try {
builder.put(k, AndroidResearchType.CODEC.fromJsonStrict(v))
} catch(err: RuntimeException) {
throw JsonSyntaxException("Caught an exception while decoding android research $k", err)
}
}
researchMap = builder.build()
@ -101,7 +115,8 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
class SyncPacket(val collection: Collection<AndroidResearchType>) : MatteryPacket {
override fun write(buff: FriendlyByteBuf) {
buff.writeCollection(collection) { a, b -> b.toNetwork(a) }
buff.writeCollection(collection) { a, b -> AndroidResearchType.CODEC.toNetwork(a, b) }
LOGGER.debug("Constructed android research registry packet, ${buff.writerIndex()} bytes in size")
}
override fun play(context: Supplier<NetworkEvent.Context>) {
@ -130,6 +145,7 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s
}
fun readSyncPacket(buff: FriendlyByteBuf): SyncPacket {
return SyncPacket(buff.readCollection({ LinkedList() }, AndroidResearchType.Companion::fromNetwork))
LOGGER.info("Received android research registry packet, ${buff.readableBytes()} bytes in size")
return SyncPacket(buff.readCollection({ LinkedList() }, { AndroidResearchType.CODEC.fromNetwork(it) }))
}
}

View File

@ -7,6 +7,12 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException
import com.google.gson.internal.bind.TypeAdapters
import com.mojang.datafixers.util.Either
import com.mojang.serialization.Codec
import com.mojang.serialization.JsonOps
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PairCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
@ -33,11 +39,18 @@ import ru.dbotthepony.mc.otm.core.fromJsonStrict
import ru.dbotthepony.mc.otm.core.toJsonStrict
import ru.dbotthepony.mc.otm.core.util.readJson
import ru.dbotthepony.mc.otm.core.util.writeJson
import ru.dbotthepony.mc.otm.data.ComponentCodec
import ru.dbotthepony.mc.otm.data.IngredientCodec
import ru.dbotthepony.mc.otm.data.JsonElementCodec
import ru.dbotthepony.mc.otm.data.simpleCodec
import ru.dbotthepony.mc.otm.isClient
import java.util.LinkedList
import java.util.Optional
import java.util.function.Function
import java.util.stream.Stream
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
import kotlin.jvm.optionals.getOrNull
private fun findPrerequisites(
initial: Collection<AndroidResearchType>,
@ -97,6 +110,25 @@ class AndroidResearchType(
val itemIcon: Item? = null,
iconText: Component? = null,
) {
private constructor(
id: ResourceLocation,
prerequisites: Collection<Reference>,
blockedBy: Collection<Reference>,
items: Collection<Pair<Ingredient, Int>>,
results: Collection<AndroidResearchResult>,
description: Collection<AndroidResearchDescription>,
experienceLevels: Int,
customName: Optional<Component>,
// ok
skinIcon: Optional<JsonObject>,
itemIcon: Optional<Item>,
iconText: Optional<Component>,
) : this(id, prerequisites, blockedBy, items.stream().map { Pair(it.first, it.second) }.toList(), results, description, experienceLevels, customName.getOrNull(), skinIcon.getOrNull(), itemIcon.getOrNull(), iconText.getOrNull())
private val iconTextValue = iconText?.copy()
val iconText get() = iconTextValue?.copy()
@ -110,39 +142,22 @@ class AndroidResearchType(
data class Reference(
val id: ResourceLocation,
val isRigid: Boolean
val optional: Boolean = false
) {
fun toJson(): JsonObject {
return JsonObject().also {
it["id"] = JsonPrimitive(id.toString())
it["is_rigid"] = JsonPrimitive(isRigid)
}
}
fun toNetwork(buff: FriendlyByteBuf) {
buff.writeUtf(id.toString())
buff.writeBoolean(isRigid)
}
companion object {
fun fromNetwork(buff: FriendlyByteBuf): Reference {
return Reference(
ResourceLocation(buff.readUtf()),
buff.readBoolean()
val CODEC: Codec<Reference> by lazy {
Codec
.either(ResourceLocation.CODEC, RecordCodecBuilder.create<Reference> {
it.group(
ResourceLocation.CODEC.fieldOf("id").forGetter(Reference::id),
Codec.BOOL.optionalFieldOf("optional", false).forGetter(Reference::optional)
).apply(it, ::Reference)
})
.xmap(
{ c -> c.map(::Reference, Function.identity()) },
{ c -> if (c.optional) Either.right(c) else Either.left(c.id) }
)
}
fun fromJson(value: JsonElement): Reference {
if (value is JsonPrimitive) {
return Reference(ResourceLocation(value.asString), true)
} else if (value is JsonObject) {
return Reference(
ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString),
(value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true)
} else {
throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}")
}
}
}
}
@ -174,11 +189,11 @@ class AndroidResearchType(
val blockedBy: List<Reference> = ImmutableList.copyOf(blockedBy)
val resolvedPrerequisites: List<AndroidResearchType> by lazy {
ImmutableList.copyOf(this.prerequisites.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.isRigid) throw NoSuchElementException("Unable to find research ${it.id}") } })
ImmutableList.copyOf(this.prerequisites.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.optional) throw NoSuchElementException("Unable to find research ${it.id}") } })
}
val resolvedBlockedBy: List<AndroidResearchType> by lazy {
ImmutableList.copyOf(this.blockedBy.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.isRigid) throw NoSuchElementException("Unable to find research ${it.id}") } })
ImmutableList.copyOf(this.blockedBy.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.optional) throw NoSuchElementException("Unable to find research ${it.id}") } })
}
private val definedItems: List<Pair<Ingredient, Int>> = ImmutableList.copyOf(items)
@ -380,141 +395,35 @@ class AndroidResearchType(
return customName?.copy() ?: MutableComponent.create(displayContents)
}
fun toJson(): JsonElement {
return JsonObject().also {
it["prerequisites"] = JsonArray().also { for (value in prerequisites) it.add(value.toJson()) }
it["blocked_by"] = JsonArray().also { for (value in blockedBy) it.add(value.toJson()) }
it["required_items"] = JsonArray().also { for (item in definedItems) it.add(JsonObject().also { it["count"] = JsonPrimitive(item.second); it["ingredient"] = item.first.toJson() }) }
it["results"] = JsonArray().also { for (result in results) it.add(AndroidResearchResult.CODEC.toJsonStrict(result)) }
it["description"] = JsonArray().also { for (line in description) it.add(AndroidResearchDescription.CODEC.toJsonStrict(line)) }
it["experience"] = JsonPrimitive(experienceLevels)
if (skinIcon != null) {
it["skin_icon"] = skinIcon
}
if (itemIcon != null) {
it["item_icon"] = JsonPrimitive(itemIcon.registryName!!.toString())
}
if (iconTextValue != null) {
it["icon_text"] = Component.Serializer.toJsonTree(iconTextValue)
}
if (customName != null) {
it["custom_name"] = Component.Serializer.toJsonTree(customName)
}
}
}
fun validate() {
resolvedBlockedBy
resolvedPrerequisites
}
fun toNetwork(buff: FriendlyByteBuf) {
buff.writeUtf(id.toString())
buff.writeCollection(prerequisites) { a, b -> b.toNetwork(a) }
buff.writeCollection(blockedBy) { a, b -> b.toNetwork(a) }
buff.writeCollection(definedItems) { a, b -> b.first.toNetwork(a); a.writeVarInt(b.second) }
buff.writeCollection(results) { a, b -> a.writeJson(AndroidResearchResult.CODEC.toJsonStrict(b)) }
buff.writeCollection(description) { a, b -> a.writeJson(AndroidResearchDescription.CODEC.toJsonStrict(b)) }
buff.writeVarInt(experienceLevels)
buff.writeBoolean(customName != null)
if (customName != null) buff.writeComponent(customName)
buff.writeBoolean(iconTextValue != null)
if (iconTextValue != null) buff.writeComponent(iconTextValue)
buff.writeBoolean(skinIcon != null)
if (skinIcon != null) buff.writeUtf(skinIcon.toString())
buff.writeBoolean(itemIcon != null)
if (itemIcon != null) buff.writeUtf(itemIcon.registryName!!.toString())
}
companion object {
fun fromNetwork(buff: FriendlyByteBuf): AndroidResearchType {
val id = ResourceLocation(buff.readUtf())
val prerequisites = buff.readCollection({ LinkedList() }, Reference::fromNetwork)
val blockedBy = buff.readCollection({ LinkedList() }, Reference::fromNetwork)
val items = buff.readCollection({ LinkedList() }, { Ingredient.fromNetwork(it) to it.readVarInt() })
val results = buff.readCollection({ LinkedList() }, { AndroidResearchResult.CODEC.fromJsonStrict(it.readJson()) })
val description = buff.readCollection({ LinkedList() }, { AndroidResearchDescription.CODEC.fromJsonStrict(it.readJson()) })
val experienceLevels = buff.readVarInt()
val customName = if (buff.readBoolean()) {
buff.readComponent()
} else {
null
val CODEC: Codec<AndroidResearchType> by lazy {
RecordCodecBuilder.create {
it.group(
ResourceLocation.CODEC.fieldOf("id").forGetter(AndroidResearchType::id),
ListCodec(Reference.CODEC).fieldOf("prerequisites").forGetter(AndroidResearchType::prerequisites),
ListCodec(Reference.CODEC).fieldOf("blockedBy").forGetter(AndroidResearchType::blockedBy),
ListCodec(
RecordCodecBuilder.create<Pair<Ingredient, Int>> {
it.group(
IngredientCodec.fieldOf("item").forGetter { it.first },
Codec.intRange(1, Int.MAX_VALUE).optionalFieldOf("count", 1).forGetter { it.second }
).apply(it, ::Pair)
}
val iconTextValue = if (buff.readBoolean()) {
buff.readComponent()
} else {
null
).fieldOf("items").forGetter { it.definedItems },
ListCodec(AndroidResearchResult.CODEC).fieldOf("results").forGetter(AndroidResearchType::results),
ListCodec(AndroidResearchDescription.CODEC).fieldOf("description").forGetter(AndroidResearchType::description),
Codec.intRange(0, Int.MAX_VALUE).fieldOf("experienceLevels").forGetter(AndroidResearchType::experienceLevels),
ComponentCodec.optionalFieldOf("customName").forGetter { Optional.ofNullable(it.customName) },
JsonElementCodec.xmap({ it as? JsonObject ?: throw JsonSyntaxException("Not a json object: $it") }, { it }).optionalFieldOf("skinIcon").forGetter { Optional.ofNullable(it.skinIcon) },
ForgeRegistries.ITEMS.codec.optionalFieldOf("itemIcon").forGetter { Optional.ofNullable(it.itemIcon) },
ComponentCodec.optionalFieldOf("iconTextValue").forGetter { Optional.ofNullable(it.iconTextValue) },
).apply(it, ::AndroidResearchType)
}
val skinIcon = if (buff.readBoolean()) {
TypeAdapters.JSON_ELEMENT.fromJson(buff.readUtf()) as JsonObject
} else {
null
}
val itemIcon = if (buff.readBoolean()) {
ForgeRegistries.ITEMS.getValue(ResourceLocation(buff.readUtf()))?.let { if (it == Items.AIR) null else it }
} else {
null
}
return AndroidResearchType(
id = id,
prerequisites = prerequisites,
blockedBy = blockedBy,
items = items,
results = results,
description = description,
experienceLevels = experienceLevels,
customName = customName,
iconText = iconTextValue,
skinIcon = skinIcon,
itemIcon = itemIcon,
)
}
fun fromJson(value: JsonElement, id: ResourceLocation): AndroidResearchType {
if (value !is JsonObject) {
throw JsonSyntaxException("Android research type must be of Json Object")
}
val prerequisites = value["prerequisites"] as JsonArray? ?: JsonArray()
val blocked_by = value["blocked_by"] as JsonArray? ?: JsonArray()
val items = value["required_items"] as JsonArray? ?: JsonArray()
val features = value["feature_result"] as JsonArray? ?: JsonArray()
val description = value["description"] as JsonArray? ?: JsonArray()
val experience = value["experience"]?.asInt ?: 0
val customName = value["custom_name"]?.let(Component.Serializer::fromJson)
val iconText = value["icon_text"]?.let(Component.Serializer::fromJson)
val skinIcon = value["skin_icon"] as JsonObject?
val itemIcon = value["item_icon"]?.let { ForgeRegistries.ITEMS.getValue(ResourceLocation(it.asString)).let { if (it == Items.AIR) null else it } }
return AndroidResearchType(
id = id,
prerequisites = prerequisites.stream().map { Reference.fromJson(it) }.toList(),
blockedBy = blocked_by.stream().map { Reference.fromJson(it) }.toList(),
results = features.stream().map { AndroidResearchResult.CODEC.fromJsonStrict(it) }.toList(),
items = items.stream()
.map { it as? JsonObject ?: throw JsonSyntaxException("One of items is not an JsonObject") }
.map { Ingredient.fromJson(it["ingredient"] ?: throw JsonSyntaxException("Missing ingredient key")) to (it["count"]?.asInt ?: throw JsonSyntaxException("Missing count key")) }
.toList(),
description = description.stream().map { AndroidResearchDescription.CODEC.fromJsonStrict(it) }.toList(),
experienceLevels = experience,
customName = customName,
iconText = iconText,
skinIcon = skinIcon,
itemIcon = itemIcon,
)
}
}

View File

@ -26,6 +26,8 @@ import net.minecraft.world.level.material.Fluid
import net.minecraft.world.phys.Vec3
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.IForgeRegistry
import ru.dbotthepony.mc.otm.core.util.readJson
import ru.dbotthepony.mc.otm.core.util.writeJson
// because doing it inline is ugly
fun <V : Any> Codec<V>.fromJson(value: JsonElement): V? {
@ -60,6 +62,14 @@ fun <V : Any> Codec<V>.toNbtStrict(value: V, prefix: Tag = NbtOps.INSTANCE.empty
return encode(value, NbtOps.INSTANCE, prefix).get().map({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") })
}
fun <V : Any> Codec<V>.toNetwork(buff: FriendlyByteBuf, value: V) {
buff.writeJson(toJsonStrict(value))
}
fun <V : Any> Codec<V>.fromNetwork(buff: FriendlyByteBuf): V {
return fromJsonStrict(buff.readJson())
}
// 1.19 being 1.19
fun TranslatableComponent(key: String, vararg values: Any): MutableComponent = MutableComponent.create(TranslatableContents(key, null, values))
fun TextComponent(value: String): MutableComponent = MutableComponent.create(LiteralContents(value))

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.mc.otm.data
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.world.item.crafting.Ingredient
object IngredientCodec : Codec<Ingredient> {
override fun <T : Any> encode(input: Ingredient, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.toJson()))
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<Ingredient, T>> {
try {
return DataResult.success(Pair(Ingredient.fromJson(ops.convertTo(JsonOps.INSTANCE, input)), ops.empty()))
} catch (err: JsonSyntaxException) {
return DataResult.error { "Error decoding Ingredient: ${err.message}" }
}
}
}

View File

@ -0,0 +1,18 @@
package ru.dbotthepony.mc.otm.data
import com.google.gson.JsonElement
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
object JsonElementCodec : Codec<JsonElement> {
override fun <T : Any> encode(input: JsonElement, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input))
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<JsonElement, T>> {
return DataResult.success(Pair(ops.convertTo(JsonOps.INSTANCE, input), ops.empty()))
}
}