From 1681a7bf4aac8dcc392886ebc499ca3472ee335b Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 31 Dec 2023 20:23:45 +0700 Subject: [PATCH] Streamline trigger instances into single, more portable class --- .../datagen/advancements/AdvancementData.kt | 3 - .../advancements/AndroidAdvancementsData.kt | 4 +- .../mc/otm/core/collect/StreamyIterators.kt | 7 + .../mc/otm/data/Codec2TriggerSerializer.kt | 100 ------------- .../mc/otm/triggers/AndroidResearchTrigger.kt | 51 +++---- .../otm/triggers/AndroidTravelUnderwater.kt | 43 ++---- .../mc/otm/triggers/ExopackTriggers.kt | 28 +--- .../mc/otm/triggers/HurtTrigger.kt | 46 ++---- .../mc/otm/triggers/ItemTrigger.kt | 36 ++--- .../mc/otm/triggers/KillAsAndroidTrigger.kt | 139 ++++++------------ .../mc/otm/triggers/MCriterionTrigger.kt | 105 +++++++++++++ .../mc/otm/triggers/NailedEntityTrigger.kt | 8 - .../mc/otm/triggers/NanobotsArmorTrigger.kt | 40 ++--- .../otm/triggers/ShockwaveDamageMobTrigger.kt | 8 - .../mc/otm/triggers/SimpleTriggers.kt | 3 + .../mc/otm/triggers/SingletonTrigger.kt | 35 +---- 16 files changed, 229 insertions(+), 427 deletions(-) delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2TriggerSerializer.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NailedEntityTrigger.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveDamageMobTrigger.kt diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt index a220c1843..0cd43924e 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt @@ -1,15 +1,12 @@ package ru.dbotthepony.mc.otm.datagen.advancements -import net.minecraft.advancements.Advancement import net.minecraft.advancements.AdvancementHolder -import net.minecraft.advancements.AdvancementRequirements import net.minecraft.advancements.AdvancementRewards import net.minecraft.advancements.FrameType import net.minecraft.advancements.AdvancementRequirements.Strategy import net.minecraft.advancements.critereon.InventoryChangeTrigger import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.data.ExistingFileHelper import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.modLocation diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt index 83b58e530..2a9ed3bdf 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.datagen.advancements -import net.minecraft.advancements.Advancement import net.minecraft.advancements.AdvancementHolder import net.minecraft.advancements.AdvancementRequirements.Strategy import net.minecraft.advancements.AdvancementRewards @@ -11,7 +10,6 @@ import net.minecraft.advancements.critereon.MinMaxBounds.Doubles import net.minecraft.world.entity.EntityType import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items -import net.minecraftforge.common.data.ExistingFileHelper import ru.dbotthepony.mc.otm.datagen.DataGen import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.modLocation @@ -158,7 +156,7 @@ fun addAndroidAdvancements(serializer: Consumer, lang: Matter russian("Исследуйте что либо за андроида") }, ) - .addCriterion("research_anything", AndroidResearchTrigger.Instance(null).criterion()) + .addCriterion("research_anything", AndroidResearchTrigger.Instance(Optional.empty(), Optional.empty()).criterion()) .save(serializer, modLocation("android/research_anything")) AdvancementBuilder() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt index 5d9d0e85c..cc7aa3289 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt @@ -297,6 +297,13 @@ fun Iterator.toList(expectedSize: Int = 16): MutableList { return result } +fun Iterator.toImmutableList(expectedSize: Int = 16): List { + if (!hasNext()) + return emptyList() + + return toList(expectedSize) +} + fun Iterator.find(): Optional { if (hasNext()) { return Optional.of(next()) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2TriggerSerializer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2TriggerSerializer.kt deleted file mode 100644 index 9f4319439..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2TriggerSerializer.kt +++ /dev/null @@ -1,100 +0,0 @@ -package ru.dbotthepony.mc.otm.data - -import com.google.gson.JsonObject -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 com.mojang.serialization.MapCodec -import com.mojang.serialization.MapLike -import com.mojang.serialization.RecordBuilder -import net.minecraft.advancements.critereon.ContextAwarePredicate -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate -import ru.dbotthepony.mc.otm.core.fromJsonStrict -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.toJsonStrict -import java.util.Optional -import java.util.stream.Stream - -class Codec2TriggerSerializer(supplier: (Codec2TriggerSerializer.Context) -> Codec) : Codec { - private val codec = supplier.invoke(Context()) - - override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { - return codec.encode(input, ops, prefix) - } - - override fun decode(ops: DynamicOps, input: T): DataResult> { - return codec.decode(ops, input) - } - - fun fromJson(element: JsonObject, player: Optional, context: DeserializationContext): S { - try { - Companion.context.get().addLast(ThreadContext(player, context)) - return codec.fromJsonStrict(element) - } finally { - Companion.context.get().removeLast() - } - } - - fun toJson(value: S): JsonObject { - return codec.toJsonStrict(value) as JsonObject - } - - inner class Context { - val playerPredicate: MapCodec> - get() = CAPPredicate - - val awareContextCodec: Codec - get() = ActualCAPPredicate - } - - private data class ThreadContext(val player: Optional, val context: DeserializationContext) - - private object ActualCAPPredicate : Codec { - override fun encode(input: ContextAwarePredicate, ops: DynamicOps, prefix: T): DataResult { - return try { - DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.toJson())) - } catch (err: Exception) { - DataResult.error { "Failed to serialize ContextAwarePredicate: " + err.message } - } - } - - override fun decode(ops: DynamicOps, input: T): DataResult> { - val context = context.get().lastOrNull() ?: return DataResult.error { "Not current deserializing trigger instance" } - - return try { - DataResult.success(Pair(EntityPredicate.fromJson(JsonObject().also { it["a"] = ops.convertTo(JsonOps.INSTANCE, input) }, "a", context.context).get(), ops.empty())) - } catch (err: Exception) { - DataResult.error { "Failed to deserialize ContextAwarePredicate: " + err.message } - } - } - } - - private object CAPPredicate : MapCodec>() { - override fun keys(ops: DynamicOps): Stream { - return Stream.of(ops.createString("player")) - } - - override fun decode(ops: DynamicOps, input: MapLike): DataResult> { - return DataResult.success(context.get().lastOrNull()?.player ?: return DataResult.error { "Not currently deserializing trigger instance" }) - } - - override fun encode(input: Optional, ops: DynamicOps, prefix: RecordBuilder): RecordBuilder { - if (input.isPresent) { - return prefix.add("player", JsonOps.INSTANCE.convertTo(ops, input.get().toJson())) - } else { - return prefix - } - } - } - - companion object { - private val context = object : ThreadLocal>() { - override fun initialValue(): ArrayDeque { - return ArrayDeque() - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt index 33b43ba4c..800d22894 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt @@ -1,50 +1,33 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import net.minecraft.advancements.Criterion -import net.minecraft.advancements.critereon.* +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.advancements.critereon.ContextAwarePredicate import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidResearchType -import ru.dbotthepony.mc.otm.core.set -import java.util.Optional +import java.util.* import java.util.function.Predicate -object AndroidResearchTrigger : SimpleCriterionTrigger() { - val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_research") - - override fun createInstance( - p_66248_: JsonObject, - p_286603_: Optional, - p_66250_: DeserializationContext - ): Instance { - return Instance( - p_66248_["research"]?.asString?.let(::ResourceLocation) - ) - } - +object AndroidResearchTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_research")) { fun trigger(player: ServerPlayer, research: AndroidResearchType) { - trigger(player) { - it.test(research) - } + trigger(player) { it.test(research) } } - class Instance(val research: ResourceLocation?) : AbstractCriterionTriggerInstance(Optional.empty()), Predicate { - constructor(research: AndroidResearchType) : this(research.id) + override val codec: Codec = RecordCodecBuilder.create { + it.group( + ResourceLocation.CODEC.optionalFieldOf("research").forGetter(Instance::research), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) + } + + class Instance(val research: Optional, player: Optional = Optional.empty()) : AbstractInstance(player), Predicate { + constructor(research: ResourceLocation) : this(Optional.of(research)) + constructor(research: AndroidResearchType) : this(Optional.of(research.id)) override fun test(t: AndroidResearchType): Boolean { - return research == null || t.id == research + return research.map { it == t.id }.orElse(true) } - - override fun serializeToJson(): JsonObject { - return super.serializeToJson().also { - if (research != null) - it["research"] = JsonPrimitive(research.toString()) - } - } - - fun criterion() = Criterion(AndroidResearchTrigger, this) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt index e6dd07ac4..94d02e89d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt @@ -1,48 +1,25 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder -import net.minecraft.advancements.Criterion -import net.minecraft.advancements.critereon.* +import net.minecraft.advancements.critereon.ContextAwarePredicate import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.toJsonStrict -import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer import ru.dbotthepony.mc.otm.data.minRange -import java.util.Optional - -object AndroidTravelUnderwater : SimpleCriterionTrigger() { - val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_walk_underwater") - - override fun createInstance(pJson: JsonObject, pPlayer: Optional, pContext: DeserializationContext): Instance { - return codec.fromJson(pJson, pPlayer, pContext) - } +import java.util.* +object AndroidTravelUnderwater : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_walk_underwater")) { fun trigger(player: ServerPlayer, travelled: Double) { - trigger(player) { - it.distanceToTravel <= travelled - } + trigger(player) { it.distanceToTravel <= travelled } } - val codec = Codec2TriggerSerializer { p -> - RecordCodecBuilder.create { - it.group( - Codec.DOUBLE.minRange(0.0).fieldOf("distanceToTravel").forGetter(Instance::distanceToTravel), - p.playerPredicate.forGetter { it.playerPredicate() } - ).apply(it, ::Instance) - } + override val codec: Codec = RecordCodecBuilder.create { + it.group( + Codec.DOUBLE.minRange(0.0).fieldOf("distanceToTravel").forGetter(Instance::distanceToTravel), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) } - class Instance(val distanceToTravel: Double, playerPredicate: Optional = Optional.empty()) : AbstractCriterionTriggerInstance(playerPredicate) { - override fun serializeToJson(): JsonObject { - return codec.toJson(this) - } - - fun criterion() = Criterion(AndroidTravelUnderwater, this) - } + class Instance(val distanceToTravel: Double, playerPredicate: Optional = Optional.empty()) : AbstractInstance(playerPredicate) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt index cd174710e..1f8523669 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt @@ -1,19 +1,12 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder -import net.minecraft.advancements.Criterion -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance import net.minecraft.advancements.critereon.ContextAwarePredicate -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.SimpleCriterionTrigger import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.fromJsonStrict -import ru.dbotthepony.mc.otm.core.toJsonStrict -import java.util.Optional +import java.util.* val ExopackObtainedTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_obtained")) val ExopackGainedCraftingTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_gained_crafting")) @@ -22,13 +15,12 @@ val ExopackGainedEnderAccessTrigger = SingletonTrigger(ResourceLocation(Overdriv val ExopackBatterySlotTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_battery_slot")) -object ExopackSlotsExpandedTrigger : SimpleCriterionTrigger() { - val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_expanded") - - val codec: Codec = RecordCodecBuilder.create { +object ExopackSlotsExpandedTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_expanded")) { + override val codec: Codec = RecordCodecBuilder.create { it.group( Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minGained", 0).forGetter(Instance::minGained), Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minTotal", 0).forGetter(Instance::minTotal), + playerPredicateCodec.forGetter(Instance::playerPredicate) ).apply(it, ::Instance) } @@ -36,20 +28,10 @@ object ExopackSlotsExpandedTrigger : SimpleCriterionTrigger, p_66250_: DeserializationContext): Instance { - return codec.fromJsonStrict(p_66248_) - } - - data class Instance(val minGained: Int = 0, val minTotal: Int = 0) : AbstractCriterionTriggerInstance(Optional.empty()) { + class Instance(val minGained: Int = 0, val minTotal: Int = 0, player: Optional = Optional.empty()) : AbstractInstance(player) { init { require(minGained >= 0) { "Invalid minGained $minGained" } require(minTotal >= 0) { "Invalid minTotal $minTotal" } } - - override fun serializeToJson(): JsonObject { - return codec.toJsonStrict(this) as JsonObject - } - - fun criterion() = Criterion(ExopackSlotsExpandedTrigger, this) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt index c07e5d7e5..c5be0e2e5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt @@ -1,29 +1,19 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject +import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder -import net.minecraft.advancements.Criterion -import net.minecraft.advancements.critereon.* +import net.minecraft.advancements.critereon.ContextAwarePredicate +import net.minecraft.advancements.critereon.DamagePredicate +import net.minecraft.advancements.critereon.EntityPredicate +import net.minecraft.advancements.critereon.MinMaxBounds import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.world.damagesource.DamageSource import net.minecraft.world.entity.LivingEntity -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer import ru.dbotthepony.mc.otm.data.DamagePredicateCodec -import java.util.Optional - -abstract class HurtTrigger : SimpleCriterionTrigger() { - abstract val id: ResourceLocation - - final override fun createInstance( - p_66248_: JsonObject, - p_286603_: Optional, - p_66250_: DeserializationContext - ): Instance { - return codec.fromJson(p_66248_, p_286603_, p_66250_) - } +import java.util.* +class HurtTrigger(id: ResourceLocation) : MCriterionTrigger(id) { fun trigger(player: ServerPlayer, entity: LivingEntity, damage: Float, damageSource: DamageSource) { val context = EntityPredicate.createContext(player, entity) @@ -32,14 +22,12 @@ abstract class HurtTrigger : SimpleCriterionTrigger() { } } - val codec = Codec2TriggerSerializer { p -> - RecordCodecBuilder.create { - it.group( - p.awareContextCodec.optionalFieldOf("predicate").forGetter(Instance::predicate), - DamagePredicateCodec.optionalFieldOf("damagePredicate").forGetter(Instance::damagePredicate), - p.playerPredicate.forGetter { it.playerPredicate() } - ).apply(it, ::Instance) - } + override val codec: Codec = RecordCodecBuilder.create { + it.group( + predicateCodec.optionalFieldOf("predicate").forGetter(Instance::predicate), + DamagePredicateCodec.optionalFieldOf("damagePredicate").forGetter(Instance::damagePredicate), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) } inner class Instance( @@ -52,11 +40,5 @@ abstract class HurtTrigger : SimpleCriterionTrigger() { Optional.empty() )), player: Optional = Optional.empty() - ) : AbstractCriterionTriggerInstance(player) { - override fun serializeToJson(): JsonObject { - return codec.toJson(this) - } - - fun criterion() = Criterion(this@HurtTrigger, this) - } + ) : AbstractInstance(player) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt index f319bb05d..a7cd9c923 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt @@ -1,44 +1,26 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder -import net.minecraft.advancements.Criterion -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance import net.minecraft.advancements.critereon.ContextAwarePredicate -import net.minecraft.advancements.critereon.DeserializationContext import net.minecraft.advancements.critereon.ItemPredicate -import net.minecraft.advancements.critereon.SimpleCriterionTrigger import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer -import java.util.Optional +import java.util.* -class ItemTrigger(val id: ResourceLocation) : SimpleCriterionTrigger() { +class ItemTrigger(id: ResourceLocation) : MCriterionTrigger(id) { fun trigger(player: ServerPlayer, item: ItemStack) { trigger(player) { if (it.invert) !it.predicate.matches(item) else it.predicate.matches(item) } } - val codec = Codec2TriggerSerializer { p -> - RecordCodecBuilder.create { - it.group( - ItemPredicate.CODEC.fieldOf("predicate").forGetter(Instance::predicate), - Codec.BOOL.optionalFieldOf("invert", false).forGetter(Instance::invert), - p.playerPredicate.forGetter { it.playerPredicate() } - ).apply(it, ::Instance) - } + override val codec: Codec = RecordCodecBuilder.create { + it.group( + ItemPredicate.CODEC.fieldOf("predicate").forGetter(Instance::predicate), + Codec.BOOL.optionalFieldOf("invert", false).forGetter(Instance::invert), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) } - override fun createInstance(p_66248_: JsonObject, p_286603_: Optional, p_66250_: DeserializationContext): Instance { - return codec.fromJson(p_66248_, p_286603_, p_66250_) - } - - inner class Instance(val predicate: ItemPredicate, val invert: Boolean = false, player: Optional = Optional.empty()) : AbstractCriterionTriggerInstance(player) { - override fun serializeToJson(): JsonObject { - return codec.toJson(this) - } - - fun criterion() = Criterion(this@ItemTrigger, this) - } + inner class Instance(val predicate: ItemPredicate, val invert: Boolean = false, player: Optional = Optional.empty()) : AbstractInstance(player) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt index c78c2c040..432303360 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt @@ -1,66 +1,67 @@ package ru.dbotthepony.mc.otm.triggers import com.google.common.collect.ImmutableList -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonSyntaxException -import net.minecraft.advancements.Criterion +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.advancements.critereon.* import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer +import net.minecraft.util.StringRepresentable import net.minecraft.world.entity.monster.ElderGuardian import net.minecraftforge.event.entity.living.LivingDeathEvent import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.matteryPlayer -import ru.dbotthepony.mc.otm.core.asIterable -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.data.SingletonCodec import ru.dbotthepony.mc.otm.registry.MRegistry import java.util.Optional import java.util.function.Predicate -object KillAsAndroidTrigger : SimpleCriterionTrigger() { - val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android") +object KillAsAndroidTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android")) { + val FEATURE_CODEC: Codec = StringRepresentable.fromEnum(PredicateType::values).dispatch({ it.type }, { it.codec }) - override fun createInstance(pJson: JsonObject, pPlayer: Optional, pContext: DeserializationContext): Instance { - return Instance( - predicate = EntityPredicate.fromJson(pJson, "predicate", pContext), - playerPredicate = pPlayer, - featurePredicate = (pJson["feature_predicate"] as? JsonObject)?.let(PredicateType::from) ?: throw JsonSyntaxException("Invalid 'feature_predicate': ${pJson["feature_predicate"]}") - ) + override val codec: Codec = RecordCodecBuilder.create { + it.group( + predicateCodec.optionalFieldOf("entityPredicate").forGetter(Instance::predicate), + FEATURE_CODEC.fieldOf("featurePredicate").forGetter(Instance::featurePredicate), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) } - enum class PredicateType(val factory: (JsonObject) -> FeaturePredicate) { - ALWAYS({ Always }), - HAS(::Has), - NOT(::Not), - AND(::And), - OR(::OrPredicate); - - companion object { - fun from(json: JsonObject): FeaturePredicate { - return when (val type = json["type"]?.asString?.lowercase()) { - null -> throw JsonSyntaxException("Missing feature predicate 'type'") - "has" -> HAS.factory(json) - "and" -> AND.factory(json) - "not" -> NOT.factory(json) - "or" -> OR.factory(json) - "always" -> ALWAYS.factory(json) - else -> throw JsonSyntaxException("Unknown feature predicate $type") - } + enum class PredicateType(codec: Lazy>) : StringRepresentable { + ALWAYS(lazy { + SingletonCodec(Always) + }), + HAS(lazy { + RecordCodecBuilder.create { + it.group(ResourceLocation.CODEC.fieldOf("name").forGetter(Has::name)).apply(it, ::Has) } + }), + NOT(lazy { + RecordCodecBuilder.create { + it.group(FEATURE_CODEC.fieldOf("parent").forGetter(Not::parent)).apply(it, ::Not) + } + }), + AND(lazy { + RecordCodecBuilder.create { + it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(And::children)).apply(it, ::And) + } + }), + OR(lazy { + RecordCodecBuilder.create { + it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(OrPredicate::children)).apply(it, ::OrPredicate) + } + }); + + val codec by codec + + override fun getSerializedName(): String { + return name.lowercase() } } abstract class FeaturePredicate : Predicate { abstract val type: PredicateType - - open fun toJson(): JsonObject { - return JsonObject().also { - it["type"] = type.name.lowercase() - } - } } object Always : FeaturePredicate() { @@ -72,17 +73,8 @@ object KillAsAndroidTrigger : SimpleCriterionTrigger) : FeaturePredicate() { - constructor(input: JsonObject) : this((input["children"] as? JsonArray)?.stream()?.map { PredicateType.from(it as? JsonObject ?: throw JsonSyntaxException("Invalid element in children list: $it")) }?.asIterable() ?: throw JsonSyntaxException("Invalid children list")) - val children: ImmutableList = ImmutableList.copyOf(children) override val type: PredicateType @@ -126,21 +102,9 @@ object KillAsAndroidTrigger : SimpleCriterionTrigger) : FeaturePredicate() { - constructor(input: JsonObject) : this((input["children"] as? JsonArray)?.stream()?.map { PredicateType.from(it as? JsonObject ?: throw JsonSyntaxException("Invalid element in children list: $it")) }?.asIterable() ?: throw JsonSyntaxException("Invalid children list")) - val children: ImmutableList = ImmutableList.copyOf(children) override val type: PredicateType @@ -149,32 +113,13 @@ object KillAsAndroidTrigger : SimpleCriterionTrigger = Optional.empty(), val featurePredicate: FeaturePredicate = Always, playerPredicate: Optional = Optional.empty(), - ) : AbstractCriterionTriggerInstance(playerPredicate) { - override fun serializeToJson(): JsonObject { - return super.serializeToJson().also { - predicate.ifPresent { p -> it["predicate"] = p.toJson() } - it["feature_predicate"] = featurePredicate.toJson() - } - } - - fun criterion() = Criterion(KillAsAndroidTrigger, this) - } + ) : AbstractInstance(playerPredicate) fun onKill(event: LivingDeathEvent) { if (event.entity is ElderGuardian) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt new file mode 100644 index 000000000..d1c9b2370 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt @@ -0,0 +1,105 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +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 com.mojang.serialization.MapCodec +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import net.minecraft.advancements.Criterion +import net.minecraft.advancements.CriterionTrigger +import net.minecraft.advancements.CriterionTriggerInstance +import net.minecraft.advancements.critereon.ContextAwarePredicate +import net.minecraft.advancements.critereon.DeserializationContext +import net.minecraft.advancements.critereon.EntityPredicate +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.PlayerAdvancements +import net.minecraft.server.level.ServerPlayer +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.toImmutableList +import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.toJsonStrict +import java.util.Optional +import java.util.function.Predicate + +// allows to support both 1.20.1 and 1.20.2 with ease +// and has slightly less memory footprint than vanilla SimpleCriterionTrigger +abstract class MCriterionTrigger.AbstractInstance>(val id: ResourceLocation) : CriterionTrigger { + private val listeners = Reference2ObjectOpenHashMap>>() + + override fun addPlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener) { + listeners.computeIfAbsent(advancements, Reference2ObjectFunction { ObjectOpenHashSet() }).add(listener) + } + + override fun removePlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener) { + listeners[advancements]?.remove(listener) + } + + override fun removePlayerListeners(advancements: PlayerAdvancements) { + listeners.remove(advancements) + } + + protected abstract val codec: Codec + + override fun createInstance(data: JsonObject, context: DeserializationContext): T { + return try { + deserializationContext.get().addLast(context) + codec.decode(JsonOps.INSTANCE, data).getOrThrow(false) { throw JsonParseException("Failed to decode data: $it") }.first + } finally { + deserializationContext.get().removeLast() + } + } + + fun trigger(player: ServerPlayer, predicate: Predicate = Predicate { true }) { + val advancements = player.advancements + val listeners = listeners[advancements] ?: return + + val context = EntityPredicate.createContext(player, player) + + listeners.iterator() + .filter { predicate.test(it.trigger) && it.trigger.playerPredicate.map { it.matches(context) }.orElse(true) } + .toImmutableList() + .forEach { it.run(advancements) } + } + + abstract inner class AbstractInstance(val playerPredicate: Optional) : CriterionTriggerInstance { + fun criterion() = Criterion(this@MCriterionTrigger, this as T) + + final override fun serializeToJson(): JsonObject { + return codec.toJsonStrict(this as T) as JsonObject + } + } + + companion object { + protected val deserializationContext = object : ThreadLocal>() { + override fun initialValue(): ArrayDeque { + return ArrayDeque() + } + } + + @JvmStatic + protected val predicateCodec: Codec = object : Codec { + override fun encode(input: ContextAwarePredicate, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.toJson())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val context = deserializationContext.get().lastOrNull() ?: return DataResult.error { "Not current deserializing trigger instance" } + + return try { + DataResult.success(Pair.of(EntityPredicate.fromJson(JsonObject().also { it["a"] = ops.convertTo(JsonOps.INSTANCE, input) }, "a", context).get(), ops.empty())) + } catch (err: Exception) { + DataResult.error { "Failed to deserialize ContextAwarePredicate: " + err.message } + } + } + } + + @JvmStatic + protected val playerPredicateCodec: MapCodec> = predicateCodec.optionalFieldOf("player") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NailedEntityTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NailedEntityTrigger.kt deleted file mode 100644 index 6414c8a1b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NailedEntityTrigger.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object NailedEntityTrigger : HurtTrigger() { - override val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "hammer_nail_damage") -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt index e5ef672e2..9c290969e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt @@ -1,45 +1,25 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject +import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder -import net.minecraft.advancements.Criterion -import net.minecraft.advancements.critereon.* +import net.minecraft.advancements.critereon.ContextAwarePredicate import net.minecraft.advancements.critereon.MinMaxBounds.Doubles import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer -import java.util.Optional +import java.util.* -object NanobotsArmorTrigger : SimpleCriterionTrigger() { - val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor") - - override fun createInstance( - p_66248_: JsonObject, - p_286603_: Optional, - p_66250_: DeserializationContext - ): Instance { - return codec.fromJson(p_66248_, p_286603_, p_66250_) - } - - val codec = Codec2TriggerSerializer { p -> - RecordCodecBuilder.create { - it.group( - Doubles.CODEC.fieldOf("predicate").forGetter(Instance::predicate), - p.playerPredicate.forGetter { it.playerPredicate() }, - ).apply(it, ::Instance) - } +object NanobotsArmorTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor")) { + override val codec: Codec = RecordCodecBuilder.create { + it.group( + Doubles.CODEC.fieldOf("predicate").forGetter(Instance::predicate), + playerPredicateCodec.forGetter(Instance::playerPredicate), + ).apply(it, ::Instance) } fun trigger(player: ServerPlayer, damageAbsorbed: Double) { trigger(player) { it.predicate.matches(damageAbsorbed) } } - class Instance(val predicate: Doubles, player: Optional = Optional.empty()) : AbstractCriterionTriggerInstance(player) { - override fun serializeToJson(): JsonObject { - return codec.toJson((this)) - } - - fun criterion() = Criterion(NanobotsArmorTrigger, this) - } + class Instance(val predicate: Doubles, player: Optional = Optional.empty()) : AbstractInstance(player) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveDamageMobTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveDamageMobTrigger.kt deleted file mode 100644 index 7f8727b2e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveDamageMobTrigger.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object ShockwaveDamageMobTrigger : HurtTrigger() { - override val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave_damage_mob") -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt index 174884c99..18adae924 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt @@ -15,3 +15,6 @@ val BecomeHumaneTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters val AndroidBatteryTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_battery")) val TakeItemOutOfReplicatorTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "take_item_out_of_replicator")) + +val NailedEntityTrigger = HurtTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "hammer_nail_damage")) +val ShockwaveDamageMobTrigger = HurtTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave_damage_mob")) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt index 04f447a65..6ad9afd6d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt @@ -1,42 +1,19 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject +import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.advancements.Criterion -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance import net.minecraft.advancements.critereon.ContextAwarePredicate -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.SimpleCriterionTrigger import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer -import java.util.Optional +import java.util.* -class SingletonTrigger(val id: ResourceLocation) : SimpleCriterionTrigger() { - override fun createInstance(p_66248_: JsonObject, p_286603_: Optional, p_66250_: DeserializationContext): AbstractCriterionTriggerInstance { - return codec.fromJson(p_66248_, p_286603_, p_66250_) - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - val codec = Codec2TriggerSerializer { p -> - RecordCodecBuilder.create { - it.group( - p.playerPredicate.forGetter { it.playerPredicate() } - ).apply(it, ::Instance) - } +class SingletonTrigger(id: ResourceLocation) : MCriterionTrigger(id) { + override val codec: Codec = RecordCodecBuilder.create { + it.group(playerPredicateCodec.forGetter(Instance::playerPredicate)).apply(it, ::Instance) } val empty = Instance() val criterion = Criterion(this@SingletonTrigger, empty) - inner class Instance(player: Optional = Optional.empty()) : AbstractCriterionTriggerInstance(player) { - override fun serializeToJson(): JsonObject { - return codec.toJson(this) - } - - fun criterion() = Criterion(this@SingletonTrigger, this) - } + inner class Instance(player: Optional = Optional.empty()) : AbstractInstance(player) }