Serializable curried functions
This marks final step in migration of research to JSON Fixes #98
This commit is contained in:
parent
c619bf4787
commit
5c8ddc505d
@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.datagen
|
|||||||
|
|
||||||
import ru.dbotthepony.mc.otm.OverdriveThatMatters
|
import ru.dbotthepony.mc.otm.OverdriveThatMatters
|
||||||
import ru.dbotthepony.mc.otm.android.AndroidResearchType
|
import ru.dbotthepony.mc.otm.android.AndroidResearchType
|
||||||
|
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
|
||||||
import ru.dbotthepony.mc.otm.client.render.ResearchIcons
|
import ru.dbotthepony.mc.otm.client.render.ResearchIcons
|
||||||
import ru.dbotthepony.mc.otm.core.TextComponent
|
import ru.dbotthepony.mc.otm.core.TextComponent
|
||||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||||
@ -176,7 +177,10 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
|
|||||||
(i + 1) * 6
|
(i + 1) * 6
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0) // TODO
|
.addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0,
|
||||||
|
transformersUp = listOf(NanobotsArmorFeature.STRENGTH_TRANSFORMER_UP.bind(level)),
|
||||||
|
transformersDown = listOf(NanobotsArmorFeature.STRENGTH_TRANSFORMER_DOWN.bind(level)),
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -199,7 +203,10 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
|
|||||||
(i + 1) * 6
|
(i + 1) * 6
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0) // TODO
|
.addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0,
|
||||||
|
transformersUp = listOf(NanobotsArmorFeature.SPEED_TRANSFORMER_UP.bind(level)),
|
||||||
|
transformersDown = listOf(NanobotsArmorFeature.SPEED_TRANSFORMER_DOWN.bind(level)),
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
|
import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
|
||||||
|
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature;
|
||||||
import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue;
|
import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue;
|
||||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability;
|
import ru.dbotthepony.mc.otm.capability.MatteryCapability;
|
||||||
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
|
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
|
||||||
@ -99,6 +100,8 @@ public final class OverdriveThatMatters {
|
|||||||
|
|
||||||
ClientConfig.INSTANCE.register();
|
ClientConfig.INSTANCE.register();
|
||||||
ServerConfig.INSTANCE.register();
|
ServerConfig.INSTANCE.register();
|
||||||
|
|
||||||
|
NanobotsArmorFeature.Companion.register();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setup(final FMLCommonSetupEvent event) {
|
private void setup(final FMLCommonSetupEvent event) {
|
||||||
|
@ -63,6 +63,10 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
|
|||||||
capability.removeFeature(feature.feature)
|
capability.removeFeature(feature.feature)
|
||||||
} else {
|
} else {
|
||||||
get.level = level.level
|
get.level = level.level
|
||||||
|
|
||||||
|
for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersDown) {
|
||||||
|
transformer.accept(this to get)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,6 +92,10 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
|
|||||||
get.level = feature.level
|
get.level = feature.level
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersUp) {
|
||||||
|
transformer.accept(this to get)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch(err: Throwable) {
|
} catch(err: Throwable) {
|
||||||
oldResearchLevel.clear()
|
oldResearchLevel.clear()
|
||||||
|
@ -17,9 +17,11 @@ import net.minecraftforge.network.PacketDistributor
|
|||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
|
import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
|
||||||
import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
|
import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
|
||||||
|
import ru.dbotthepony.mc.otm.OverdriveThatMatters
|
||||||
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
|
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
|
||||||
import ru.dbotthepony.mc.otm.capability.matteryPlayer
|
import ru.dbotthepony.mc.otm.capability.matteryPlayer
|
||||||
import ru.dbotthepony.mc.otm.client.minecraft
|
import ru.dbotthepony.mc.otm.client.minecraft
|
||||||
|
import ru.dbotthepony.mc.otm.data.SerializedFunctionRegistry
|
||||||
import ru.dbotthepony.mc.otm.network.MatteryPacket
|
import ru.dbotthepony.mc.otm.network.MatteryPacket
|
||||||
import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel
|
import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel
|
||||||
import ru.dbotthepony.mc.otm.network.enqueueWork
|
import ru.dbotthepony.mc.otm.network.enqueueWork
|
||||||
@ -29,6 +31,14 @@ import java.util.LinkedList
|
|||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
|
|
||||||
object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> {
|
object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> {
|
||||||
|
/**
|
||||||
|
* Feel free to register functions inside this thing from anywhere in your code
|
||||||
|
* (registration and querying is completely thread safe).
|
||||||
|
*
|
||||||
|
* Just make sure client and server has the same set of functions defined.
|
||||||
|
*/
|
||||||
|
val featureResultTransformers = SerializedFunctionRegistry<Pair<AndroidResearch, AndroidFeature>>()
|
||||||
|
|
||||||
const val DIRECTORY = "otm_android_research"
|
const val DIRECTORY = "otm_android_research"
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ import ru.dbotthepony.mc.otm.core.set
|
|||||||
import ru.dbotthepony.mc.otm.core.stream
|
import ru.dbotthepony.mc.otm.core.stream
|
||||||
import ru.dbotthepony.mc.otm.core.toImmutableList
|
import ru.dbotthepony.mc.otm.core.toImmutableList
|
||||||
import ru.dbotthepony.mc.otm.data.ItemStackCodec
|
import ru.dbotthepony.mc.otm.data.ItemStackCodec
|
||||||
|
import ru.dbotthepony.mc.otm.data.SerializedFunctionRegistry
|
||||||
|
import ru.dbotthepony.mc.otm.data.stream
|
||||||
import ru.dbotthepony.mc.otm.registry.MRegistry
|
import ru.dbotthepony.mc.otm.registry.MRegistry
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
@ -134,13 +136,26 @@ class AndroidResearchType(
|
|||||||
data class FeatureReference(
|
data class FeatureReference(
|
||||||
val id: ResourceLocation,
|
val id: ResourceLocation,
|
||||||
val level: Int = 0,
|
val level: Int = 0,
|
||||||
val isRigid: Boolean
|
val isRigid: Boolean,
|
||||||
|
val transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
|
val transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
) {
|
) {
|
||||||
fun toJson(): JsonObject {
|
fun toJson(): JsonObject {
|
||||||
return JsonObject().also {
|
return JsonObject().also {
|
||||||
it["id"] = JsonPrimitive(id.toString())
|
it["id"] = JsonPrimitive(id.toString())
|
||||||
it["level"] = JsonPrimitive(level)
|
it["level"] = JsonPrimitive(level)
|
||||||
it["is_rigid"] = JsonPrimitive(isRigid)
|
it["is_rigid"] = JsonPrimitive(isRigid)
|
||||||
|
it["functions_up"] = JsonArray().also {
|
||||||
|
for (transformer in transformersUp) {
|
||||||
|
it.add(transformer.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it["functions_down"] = JsonArray().also {
|
||||||
|
for (transformer in transformersDown) {
|
||||||
|
it.add(transformer.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +163,8 @@ class AndroidResearchType(
|
|||||||
buff.writeUtf(id.toString())
|
buff.writeUtf(id.toString())
|
||||||
buff.writeVarInt(level)
|
buff.writeVarInt(level)
|
||||||
buff.writeBoolean(isRigid)
|
buff.writeBoolean(isRigid)
|
||||||
|
buff.writeCollection(transformersUp) { a, b -> b.toNetwork(a) }
|
||||||
|
buff.writeCollection(transformersDown) { a, b -> b.toNetwork(a) }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -155,7 +172,9 @@ class AndroidResearchType(
|
|||||||
return FeatureReference(
|
return FeatureReference(
|
||||||
ResourceLocation(buff.readUtf()),
|
ResourceLocation(buff.readUtf()),
|
||||||
buff.readVarInt(),
|
buff.readVarInt(),
|
||||||
buff.readBoolean()
|
buff.readBoolean(),
|
||||||
|
buff.readCollection({ LinkedList() }, AndroidResearchManager.featureResultTransformers::fromNetwork).filterNotNull().toImmutableList(),
|
||||||
|
buff.readCollection({ LinkedList() }, AndroidResearchManager.featureResultTransformers::fromNetwork).filterNotNull().toImmutableList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +184,11 @@ class AndroidResearchType(
|
|||||||
} else if (value is JsonObject) {
|
} else if (value is JsonObject) {
|
||||||
return FeatureReference(
|
return FeatureReference(
|
||||||
ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString),
|
ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString),
|
||||||
(value["level"] as JsonPrimitive?)?.asInt ?: 0, (value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true)
|
(value["level"] as JsonPrimitive?)?.asInt ?: 0,
|
||||||
|
(value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true,
|
||||||
|
(value["functions_up"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>>()),
|
||||||
|
(value["functions_down"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>>()),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}")
|
throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}")
|
||||||
}
|
}
|
||||||
@ -659,19 +682,41 @@ class AndroidResearchType(
|
|||||||
fun addBlocker(type: AndroidResearchType, rigid: Boolean = true) = addBlocker(type.id, rigid)
|
fun addBlocker(type: AndroidResearchType, rigid: Boolean = true) = addBlocker(type.id, rigid)
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addFeatureResult(id: ResourceLocation, level: Int = 0, rigid: Boolean = false): Builder {
|
fun addFeatureResult(
|
||||||
features.add(FeatureReference(id, level, rigid))
|
id: ResourceLocation,
|
||||||
|
level: Int = 0,
|
||||||
|
rigid: Boolean = false,
|
||||||
|
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
|
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
|
): Builder {
|
||||||
|
features.add(FeatureReference(id, level, rigid, transformersUp, transformersDown))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addFeatureResult(feature: AndroidFeatureType<*>, level: Int = 0, rigid: Boolean = true): Builder {
|
fun addFeatureResult(
|
||||||
features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid))
|
feature: AndroidFeatureType<*>,
|
||||||
|
level: Int = 0,
|
||||||
|
rigid: Boolean = true,
|
||||||
|
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
|
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
|
): Builder {
|
||||||
|
features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid, transformersUp, transformersDown))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFeatureResult(id: ResourceLocation, rigid: Boolean = false): Builder {
|
fun addFeatureResult(
|
||||||
features.add(FeatureReference(id, 0, rigid))
|
id: ResourceLocation,
|
||||||
|
rigid: Boolean = false,
|
||||||
|
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
|
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
|
||||||
|
): Builder {
|
||||||
|
features.add(FeatureReference(id, 0, rigid, transformersUp, transformersDown))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFeatureResult(ref: FeatureReference): Builder {
|
||||||
|
features.add(ref)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package ru.dbotthepony.mc.otm.android.feature
|
package ru.dbotthepony.mc.otm.android.feature
|
||||||
|
|
||||||
import net.minecraft.nbt.CompoundTag
|
import net.minecraft.nbt.CompoundTag
|
||||||
|
import net.minecraft.resources.ResourceLocation
|
||||||
import net.minecraft.server.level.ServerPlayer
|
import net.minecraft.server.level.ServerPlayer
|
||||||
import net.minecraftforge.event.entity.living.LivingHurtEvent
|
import net.minecraftforge.event.entity.living.LivingHurtEvent
|
||||||
|
import ru.dbotthepony.mc.otm.OverdriveThatMatters
|
||||||
import ru.dbotthepony.mc.otm.android.AndroidFeature
|
import ru.dbotthepony.mc.otm.android.AndroidFeature
|
||||||
|
import ru.dbotthepony.mc.otm.android.AndroidResearchManager
|
||||||
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
|
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
|
||||||
import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact
|
import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact
|
||||||
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
|
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
|
||||||
@ -14,10 +17,10 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) {
|
class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) {
|
||||||
var strength: Int = 0
|
var strength: Int = 0
|
||||||
set(value) { field = value.coerceIn(0 .. 2) }
|
set(value) { field = value.coerceIn(0 .. 3) }
|
||||||
|
|
||||||
var speed: Int = 0
|
var speed: Int = 0
|
||||||
set(value) { field = value.coerceIn(0 .. 2) }
|
set(value) { field = value.coerceIn(0 .. 3) }
|
||||||
|
|
||||||
private var ticksPassed = 0
|
private var ticksPassed = 0
|
||||||
private var layers = 0
|
private var layers = 0
|
||||||
@ -86,5 +89,40 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An
|
|||||||
0.3f,
|
0.3f,
|
||||||
0.4f,
|
0.4f,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val STRENGTH_TRANSFORMER_UP = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_up"))
|
||||||
|
{ level: Int ->
|
||||||
|
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).strength == level - 1) {
|
||||||
|
(second as NanobotsArmorFeature).strength = level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val STRENGTH_TRANSFORMER_DOWN = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_down"))
|
||||||
|
{ level: Int ->
|
||||||
|
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).strength == level) {
|
||||||
|
(second as NanobotsArmorFeature).strength = level - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val SPEED_TRANSFORMER_UP = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_up"))
|
||||||
|
{ level: Int ->
|
||||||
|
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).speed == level - 1) {
|
||||||
|
(second as NanobotsArmorFeature).speed = level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val SPEED_TRANSFORMER_DOWN = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_down"))
|
||||||
|
{ level: Int ->
|
||||||
|
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).speed == level) {
|
||||||
|
(second as NanobotsArmorFeature).speed = level - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy method just for static initializer to execute
|
||||||
|
*/
|
||||||
|
fun register() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,11 +188,11 @@ class JEIPlugin : IModPlugin {
|
|||||||
val combined = combine.toImmutableList()
|
val combined = combine.toImmutableList()
|
||||||
|
|
||||||
val newView = object : IRecipeSlotsView {
|
val newView = object : IRecipeSlotsView {
|
||||||
override fun getSlotViews(): MutableList<IRecipeSlotView> {
|
override fun getSlotViews(): List<IRecipeSlotView> {
|
||||||
return combined
|
return combined
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSlotViews(role: RecipeIngredientRole): MutableList<IRecipeSlotView> {
|
override fun getSlotViews(role: RecipeIngredientRole): List<IRecipeSlotView> {
|
||||||
return when (role) {
|
return when (role) {
|
||||||
RecipeIngredientRole.INPUT -> filteredInputs
|
RecipeIngredientRole.INPUT -> filteredInputs
|
||||||
RecipeIngredientRole.OUTPUT -> outputs
|
RecipeIngredientRole.OUTPUT -> outputs
|
||||||
|
@ -349,7 +349,7 @@ operator fun <T : Comparable<T>> StateHolder<*, *>.get(value: Property<T>): T {
|
|||||||
return getValue(value)
|
return getValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> List<T>.toImmutableList(): ImmutableList<T> {
|
fun <T> List<T>.toImmutableList(): List<T> {
|
||||||
return ImmutableList.copyOf(this)
|
return ImmutableList.copyOf(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.mc.otm.core
|
package ru.dbotthepony.mc.otm.core
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
import io.netty.handler.codec.EncoderException
|
import io.netty.handler.codec.EncoderException
|
||||||
import net.minecraft.nbt.CompoundTag
|
import net.minecraft.nbt.CompoundTag
|
||||||
import net.minecraft.nbt.NbtAccounter
|
import net.minecraft.nbt.NbtAccounter
|
||||||
@ -8,9 +9,24 @@ import net.minecraft.world.item.Item
|
|||||||
import net.minecraft.world.item.ItemStack
|
import net.minecraft.world.item.ItemStack
|
||||||
import net.minecraftforge.registries.ForgeRegistries
|
import net.minecraftforge.registries.ForgeRegistries
|
||||||
import net.minecraftforge.registries.ForgeRegistry
|
import net.minecraftforge.registries.ForgeRegistry
|
||||||
|
import ru.dbotthepony.mc.otm.network.BigDecimalValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.BooleanValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.ByteValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.DoubleValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.FloatValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.ImpreciseFractionValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.IntValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.ItemStackValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.LongValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.NetworkValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.NullValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.ShortValueCodec
|
||||||
|
import ru.dbotthepony.mc.otm.network.UUIDValueCodec
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.function.Predicate
|
||||||
|
|
||||||
// But seriously, Mojang, why would you need to derive from ByteBuf directly, when you can implement
|
// But seriously, Mojang, why would you need to derive from ByteBuf directly, when you can implement
|
||||||
// your own InputStream and OutputStream, since ByteBuf is meant to be operated on most time like a stream anyway?
|
// your own InputStream and OutputStream, since ByteBuf is meant to be operated on most time like a stream anyway?
|
||||||
@ -181,3 +197,71 @@ fun InputStream.readBigDecimal(): BigDecimal {
|
|||||||
read(bytes)
|
read(bytes)
|
||||||
return BigDecimal(BigInteger(bytes), scale)
|
return BigDecimal(BigInteger(bytes), scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class StreamCodec<T>(
|
||||||
|
val condition: Predicate<Any?>,
|
||||||
|
val id: Int,
|
||||||
|
val codec: NetworkValueCodec<T>
|
||||||
|
) {
|
||||||
|
fun read(stream: DataInputStream): T {
|
||||||
|
return codec.read(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(stream: DataOutputStream, value: Any?) {
|
||||||
|
codec.write(stream, value as T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> StreamCodec(id: Int, codec: NetworkValueCodec<T>): StreamCodec<T> {
|
||||||
|
return StreamCodec({it is T}, id, codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val codecs: List<StreamCodec<*>> = ImmutableList.builder<StreamCodec<*>>().let {
|
||||||
|
var codecID = 0
|
||||||
|
|
||||||
|
it.add(StreamCodec({ it == null }, codecID++, NullValueCodec))
|
||||||
|
|
||||||
|
it.add(StreamCodec(codecID++, BooleanValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, ByteValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, ShortValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, IntValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, LongValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, FloatValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, DoubleValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, ItemStackValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, ImpreciseFractionValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, BigDecimalValueCodec))
|
||||||
|
it.add(StreamCodec(codecID++, UUIDValueCodec))
|
||||||
|
|
||||||
|
it.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotSerializableValueException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write arbitrary data to this stream, in exploit-free way
|
||||||
|
*/
|
||||||
|
fun DataOutputStream.writeType(value: Any?) {
|
||||||
|
for (codec in codecs) {
|
||||||
|
if (codec.condition.test(value)) {
|
||||||
|
write(codec.id)
|
||||||
|
codec.write(this, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw NotSerializableValueException("Value $value <${value?.let { it::class.qualifiedName }}> can not be serialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read arbitrary data from this stream, in exploit-free way
|
||||||
|
*/
|
||||||
|
fun DataInputStream.readType(): Any? {
|
||||||
|
val id = read()
|
||||||
|
|
||||||
|
if (id >= codecs.size) {
|
||||||
|
throw IndexOutOfBoundsException("No codec for network type $id")
|
||||||
|
}
|
||||||
|
|
||||||
|
return codecs[id].read(this)
|
||||||
|
}
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
package ru.dbotthepony.mc.otm.data
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonDeserializationContext
|
||||||
|
import com.google.gson.JsonDeserializer
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSerializationContext
|
||||||
|
import com.google.gson.JsonSerializer
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.internal.bind.TypeAdapters
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import io.netty.buffer.ByteBufInputStream
|
||||||
|
import io.netty.buffer.ByteBufOutputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
|
import net.minecraft.network.FriendlyByteBuf
|
||||||
|
import net.minecraft.resources.ResourceLocation
|
||||||
|
import ru.dbotthepony.mc.otm.core.readType
|
||||||
|
import ru.dbotthepony.mc.otm.core.readVarIntLE
|
||||||
|
import ru.dbotthepony.mc.otm.core.set
|
||||||
|
import ru.dbotthepony.mc.otm.core.writeType
|
||||||
|
import ru.dbotthepony.mc.otm.core.writeVarIntLE
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.util.Base64
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<SerializedFunctionRegistry.BoundFunction<R>>, JsonDeserializer<SerializedFunctionRegistry.BoundFunction<R>>, TypeAdapter<SerializedFunctionRegistry.BoundFunction<R>>() {
|
||||||
|
fun interface AnonymousFunction<R> {
|
||||||
|
fun invoke(receiver: R, arguments: List<Any?>)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BoundFunction<R>(
|
||||||
|
val function: Function<R>,
|
||||||
|
val arguments: List<Any?>
|
||||||
|
) : Consumer<R> {
|
||||||
|
fun toJson(): JsonElement {
|
||||||
|
return JsonObject().also {
|
||||||
|
it["function"] = function.toJson()
|
||||||
|
val stream = FastByteArrayOutputStream()
|
||||||
|
val dataStream = DataOutputStream(stream)
|
||||||
|
|
||||||
|
stream.writeVarIntLE(arguments.size)
|
||||||
|
|
||||||
|
for (argument in arguments) {
|
||||||
|
dataStream.writeType(argument)
|
||||||
|
}
|
||||||
|
|
||||||
|
it["arguments"] = JsonPrimitive(Base64.getEncoder().encode(stream.array.copyOfRange(0, stream.length)).toString(Charsets.UTF_8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toNetwork(buff: FriendlyByteBuf) {
|
||||||
|
function.toNetwork(buff)
|
||||||
|
|
||||||
|
val stream = DataOutputStream(ByteBufOutputStream(buff))
|
||||||
|
stream.writeVarIntLE(arguments.size)
|
||||||
|
for (argument in arguments) {
|
||||||
|
stream.writeType(argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(t: R) {
|
||||||
|
function.body.invoke(t, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Function<R>(
|
||||||
|
val id: ResourceLocation,
|
||||||
|
val body: AnonymousFunction<R>,
|
||||||
|
val registry: SerializedFunctionRegistry<R>
|
||||||
|
) {
|
||||||
|
fun bind(vararg arguments: Any?): BoundFunction<R> {
|
||||||
|
validate(arguments.iterator().withIndex())
|
||||||
|
return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(arguments: List<Any?>): BoundFunction<R> {
|
||||||
|
validate(arguments.iterator().withIndex())
|
||||||
|
return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJson(): JsonElement {
|
||||||
|
return JsonPrimitive(id.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toNetwork(buff: FriendlyByteBuf) {
|
||||||
|
buff.writeUtf(id.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun validate(iterator: Iterator<IndexedValue<Any?>>) {
|
||||||
|
try {
|
||||||
|
val stream = DataOutputStream(FastByteArrayOutputStream())
|
||||||
|
|
||||||
|
for ((i, argument) in iterator) {
|
||||||
|
try {
|
||||||
|
stream.writeType(argument)
|
||||||
|
} catch(err: Exception) {
|
||||||
|
throw IllegalArgumentException("Argument at $i can not be serialized", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(err: Exception) {
|
||||||
|
throw IllegalArgumentException("Argument list validation failed", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val map = HashMap<ResourceLocation, Function<R>>()
|
||||||
|
|
||||||
|
fun register(id: ResourceLocation, function: AnonymousFunction<R>): Function<R> {
|
||||||
|
synchronized(map) {
|
||||||
|
return map.computeIfAbsent(id) { Function(id, function, this) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified A : Any> register(id: ResourceLocation, noinline function: R.(A) -> Unit): Function<R> {
|
||||||
|
return register(id, AnonymousFunction { r, args ->
|
||||||
|
check(args.size == 1) { "Invalid amount of arguments. 1 is required" }
|
||||||
|
function.invoke(
|
||||||
|
r,
|
||||||
|
args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified A : Any, reified B : Any> register(id: ResourceLocation, noinline function: R.(A, B) -> Unit): Function<R> {
|
||||||
|
return register(id, AnonymousFunction { r, args ->
|
||||||
|
check(args.size == 2) { "Invalid amount of arguments. 2 is required" }
|
||||||
|
|
||||||
|
function.invoke(
|
||||||
|
r,
|
||||||
|
args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given"),
|
||||||
|
args[1] as? B ?: throw ClassCastException("Argument at 1 is supposed to be ${B::class.qualifiedName}, ${args[1]?.let { it::class.qualifiedName }} given"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified A : Any, reified B : Any, reified C : Any> register(id: ResourceLocation, noinline function: R.(A, B, C) -> Unit): Function<R> {
|
||||||
|
return register(id, AnonymousFunction { r, args ->
|
||||||
|
check(args.size == 3) { "Invalid amount of arguments. 3 is required" }
|
||||||
|
|
||||||
|
function.invoke(
|
||||||
|
r,
|
||||||
|
args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given"),
|
||||||
|
args[1] as? B ?: throw ClassCastException("Argument at 1 is supposed to be ${B::class.qualifiedName}, ${args[1]?.let { it::class.qualifiedName }} given"),
|
||||||
|
args[2] as? C ?: throw ClassCastException("Argument at 2 is supposed to be ${B::class.qualifiedName}, ${args[2]?.let { it::class.qualifiedName }} given"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(id: ResourceLocation): Function<R>? {
|
||||||
|
synchronized(map) {
|
||||||
|
return map[id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromNetwork(buff: FriendlyByteBuf): BoundFunction<R>? {
|
||||||
|
val id = ResourceLocation(buff.readUtf())
|
||||||
|
val stream = DataInputStream(ByteBufInputStream(buff))
|
||||||
|
|
||||||
|
val arguments = LinkedList<Any?>()
|
||||||
|
|
||||||
|
for (i in 0 until stream.readVarIntLE()) {
|
||||||
|
arguments.add(stream.readType())
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[id]?.bind(arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromJson(value: JsonElement): BoundFunction<R>? {
|
||||||
|
if (value !is JsonObject) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val id = value["function"]?.asString ?: return null
|
||||||
|
val argumentString = value["arguments"]?.asString ?: return null
|
||||||
|
val stream = DataInputStream(FastByteArrayInputStream(Base64.getDecoder().decode(argumentString)))
|
||||||
|
val arguments = LinkedList<Any?>()
|
||||||
|
|
||||||
|
for (i in 0 until stream.readVarIntLE()) {
|
||||||
|
arguments.add(stream.readType())
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[ResourceLocation(id)]?.bind(arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(src: BoundFunction<R>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||||
|
return src.toJson()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(
|
||||||
|
json: JsonElement,
|
||||||
|
typeOfT: Type,
|
||||||
|
context: JsonDeserializationContext
|
||||||
|
): BoundFunction<R> {
|
||||||
|
return fromJson(json) ?: throw JsonSyntaxException("Function is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: BoundFunction<R>) {
|
||||||
|
TypeAdapters.JSON_ELEMENT.write(out, value.toJson())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): BoundFunction<R> {
|
||||||
|
return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`)) ?: throw JsonSyntaxException("Function is invalid")
|
||||||
|
}
|
||||||
|
}
|
@ -59,6 +59,8 @@ class NetworkValueCodec<V>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val NullValueCodec = NetworkValueCodec({ null }, { _, _ -> })
|
||||||
|
|
||||||
val BooleanValueCodec = NetworkValueCodec(DataInputStream::readBoolean, DataOutputStream::writeBoolean)
|
val BooleanValueCodec = NetworkValueCodec(DataInputStream::readBoolean, DataOutputStream::writeBoolean)
|
||||||
val ByteValueCodec = NetworkValueCodec(DataInputStream::readByte, { s, v -> s.writeByte(v.toInt()) })
|
val ByteValueCodec = NetworkValueCodec(DataInputStream::readByte, { s, v -> s.writeByte(v.toInt()) })
|
||||||
val ShortValueCodec = NetworkValueCodec(DataInputStream::readShort, { s, v -> s.writeShort(v.toInt()) })
|
val ShortValueCodec = NetworkValueCodec(DataInputStream::readShort, { s, v -> s.writeShort(v.toInt()) })
|
||||||
|
Loading…
Reference in New Issue
Block a user