Streamline trigger instances into single, more portable class

This commit is contained in:
DBotThePony 2023-12-31 20:23:45 +07:00
parent 242876d533
commit 1681a7bf4a
Signed by: DBot
GPG Key ID: DCC23B5715498507
16 changed files with 229 additions and 427 deletions

View File

@ -1,15 +1,12 @@
package ru.dbotthepony.mc.otm.datagen.advancements package ru.dbotthepony.mc.otm.datagen.advancements
import net.minecraft.advancements.Advancement
import net.minecraft.advancements.AdvancementHolder import net.minecraft.advancements.AdvancementHolder
import net.minecraft.advancements.AdvancementRequirements
import net.minecraft.advancements.AdvancementRewards import net.minecraft.advancements.AdvancementRewards
import net.minecraft.advancements.FrameType import net.minecraft.advancements.FrameType
import net.minecraft.advancements.AdvancementRequirements.Strategy import net.minecraft.advancements.AdvancementRequirements.Strategy
import net.minecraft.advancements.critereon.InventoryChangeTrigger import net.minecraft.advancements.critereon.InventoryChangeTrigger
import net.minecraft.world.item.DyeColor import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.data.ExistingFileHelper
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider
import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.datagen.modLocation

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.mc.otm.datagen.advancements package ru.dbotthepony.mc.otm.datagen.advancements
import net.minecraft.advancements.Advancement
import net.minecraft.advancements.AdvancementHolder import net.minecraft.advancements.AdvancementHolder
import net.minecraft.advancements.AdvancementRequirements.Strategy import net.minecraft.advancements.AdvancementRequirements.Strategy
import net.minecraft.advancements.AdvancementRewards 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.entity.EntityType
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items 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.DataGen
import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider
import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.datagen.modLocation
@ -158,7 +156,7 @@ fun addAndroidAdvancements(serializer: Consumer<AdvancementHolder>, lang: Matter
russian("Исследуйте что либо за андроида") russian("Исследуйте что либо за андроида")
}, },
) )
.addCriterion("research_anything", AndroidResearchTrigger.Instance(null).criterion()) .addCriterion("research_anything", AndroidResearchTrigger.Instance(Optional.empty(), Optional.empty()).criterion())
.save(serializer, modLocation("android/research_anything")) .save(serializer, modLocation("android/research_anything"))
AdvancementBuilder() AdvancementBuilder()

View File

@ -297,6 +297,13 @@ fun <T> Iterator<T>.toList(expectedSize: Int = 16): MutableList<T> {
return result return result
} }
fun <T> Iterator<T>.toImmutableList(expectedSize: Int = 16): List<T> {
if (!hasNext())
return emptyList()
return toList(expectedSize)
}
fun <T : Any> Iterator<T>.find(): Optional<T> { fun <T : Any> Iterator<T>.find(): Optional<T> {
if (hasNext()) { if (hasNext()) {
return Optional.of(next()) return Optional.of(next())

View File

@ -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<S : Any>(supplier: (Codec2TriggerSerializer<S>.Context) -> Codec<S>) : Codec<S> {
private val codec = supplier.invoke(Context())
override fun <T : Any> encode(input: S, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return codec.encode(input, ops, prefix)
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<S, T>> {
return codec.decode(ops, input)
}
fun fromJson(element: JsonObject, player: Optional<ContextAwarePredicate>, 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<Optional<ContextAwarePredicate>>
get() = CAPPredicate
val awareContextCodec: Codec<ContextAwarePredicate>
get() = ActualCAPPredicate
}
private data class ThreadContext(val player: Optional<ContextAwarePredicate>, val context: DeserializationContext)
private object ActualCAPPredicate : Codec<ContextAwarePredicate> {
override fun <T : Any> encode(input: ContextAwarePredicate, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return try {
DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.toJson()))
} catch (err: Exception) {
DataResult.error { "Failed to serialize ContextAwarePredicate: " + err.message }
}
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<ContextAwarePredicate, T>> {
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<Optional<ContextAwarePredicate>>() {
override fun <T : Any> keys(ops: DynamicOps<T>): Stream<T> {
return Stream.of(ops.createString("player"))
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: MapLike<T>): DataResult<Optional<ContextAwarePredicate>> {
return DataResult.success(context.get().lastOrNull()?.player ?: return DataResult.error { "Not currently deserializing trigger instance" })
}
override fun <T : Any> encode(input: Optional<ContextAwarePredicate>, ops: DynamicOps<T>, prefix: RecordBuilder<T>): RecordBuilder<T> {
if (input.isPresent) {
return prefix.add("player", JsonOps.INSTANCE.convertTo(ops, input.get().toJson()))
} else {
return prefix
}
}
}
companion object {
private val context = object : ThreadLocal<ArrayDeque<ThreadContext>>() {
override fun initialValue(): ArrayDeque<ThreadContext> {
return ArrayDeque()
}
}
}
}

View File

@ -1,50 +1,33 @@
package ru.dbotthepony.mc.otm.triggers package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject import com.mojang.serialization.Codec
import com.google.gson.JsonPrimitive import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.Criterion import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.*
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
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.core.set import java.util.*
import java.util.Optional
import java.util.function.Predicate import java.util.function.Predicate
object AndroidResearchTrigger : SimpleCriterionTrigger<AndroidResearchTrigger.Instance>() { object AndroidResearchTrigger : MCriterionTrigger<AndroidResearchTrigger.Instance>(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_research")) {
val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_research")
override fun createInstance(
p_66248_: JsonObject,
p_286603_: Optional<ContextAwarePredicate>,
p_66250_: DeserializationContext
): Instance {
return Instance(
p_66248_["research"]?.asString?.let(::ResourceLocation)
)
}
fun trigger(player: ServerPlayer, research: AndroidResearchType) { fun trigger(player: ServerPlayer, research: AndroidResearchType) {
trigger(player) { trigger(player) { it.test(research) }
it.test(research)
}
} }
class Instance(val research: ResourceLocation?) : AbstractCriterionTriggerInstance(Optional.empty()), Predicate<AndroidResearchType> { override val codec: Codec<Instance> = RecordCodecBuilder.create {
constructor(research: AndroidResearchType) : this(research.id) it.group(
ResourceLocation.CODEC.optionalFieldOf("research").forGetter(Instance::research),
playerPredicateCodec.forGetter(Instance::playerPredicate)
).apply(it, ::Instance)
}
class Instance(val research: Optional<ResourceLocation>, player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractInstance(player), Predicate<AndroidResearchType> {
constructor(research: ResourceLocation) : this(Optional.of(research))
constructor(research: AndroidResearchType) : this(Optional.of(research.id))
override fun test(t: AndroidResearchType): Boolean { 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)
} }
} }

View File

@ -1,48 +1,25 @@
package ru.dbotthepony.mc.otm.triggers 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.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.Criterion import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.*
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import ru.dbotthepony.mc.otm.OverdriveThatMatters 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 ru.dbotthepony.mc.otm.data.minRange
import java.util.Optional import java.util.*
object AndroidTravelUnderwater : SimpleCriterionTrigger<AndroidTravelUnderwater.Instance>() {
val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_walk_underwater")
override fun createInstance(pJson: JsonObject, pPlayer: Optional<ContextAwarePredicate>, pContext: DeserializationContext): Instance {
return codec.fromJson(pJson, pPlayer, pContext)
}
object AndroidTravelUnderwater : MCriterionTrigger<AndroidTravelUnderwater.Instance>(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_walk_underwater")) {
fun trigger(player: ServerPlayer, travelled: Double) { fun trigger(player: ServerPlayer, travelled: Double) {
trigger(player) { trigger(player) { it.distanceToTravel <= travelled }
it.distanceToTravel <= travelled
}
} }
val codec = Codec2TriggerSerializer<Instance> { p -> override val codec: Codec<Instance> = RecordCodecBuilder.create {
RecordCodecBuilder.create { it.group(
it.group( Codec.DOUBLE.minRange(0.0).fieldOf("distanceToTravel").forGetter(Instance::distanceToTravel),
Codec.DOUBLE.minRange(0.0).fieldOf("distanceToTravel").forGetter(Instance::distanceToTravel), playerPredicateCodec.forGetter(Instance::playerPredicate)
p.playerPredicate.forGetter { it.playerPredicate() } ).apply(it, ::Instance)
).apply(it, ::Instance)
}
} }
class Instance(val distanceToTravel: Double, playerPredicate: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractCriterionTriggerInstance(playerPredicate) { class Instance(val distanceToTravel: Double, playerPredicate: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractInstance(playerPredicate)
override fun serializeToJson(): JsonObject {
return codec.toJson(this)
}
fun criterion() = Criterion(AndroidTravelUnderwater, this)
}
} }

View File

@ -1,19 +1,12 @@
package ru.dbotthepony.mc.otm.triggers package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder 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.ContextAwarePredicate
import net.minecraft.advancements.critereon.DeserializationContext
import net.minecraft.advancements.critereon.SimpleCriterionTrigger
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.fromJsonStrict import java.util.*
import ru.dbotthepony.mc.otm.core.toJsonStrict
import java.util.Optional
val ExopackObtainedTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_obtained")) val ExopackObtainedTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_obtained"))
val ExopackGainedCraftingTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_gained_crafting")) 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")) val ExopackBatterySlotTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_battery_slot"))
object ExopackSlotsExpandedTrigger : SimpleCriterionTrigger<ExopackSlotsExpandedTrigger.Instance>() { object ExopackSlotsExpandedTrigger : MCriterionTrigger<ExopackSlotsExpandedTrigger.Instance>(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_expanded")) {
val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_expanded") override val codec: Codec<Instance> = RecordCodecBuilder.create {
val codec: Codec<Instance> = RecordCodecBuilder.create {
it.group( it.group(
Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minGained", 0).forGetter(Instance::minGained), Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minGained", 0).forGetter(Instance::minGained),
Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minTotal", 0).forGetter(Instance::minTotal), Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minTotal", 0).forGetter(Instance::minTotal),
playerPredicateCodec.forGetter(Instance::playerPredicate)
).apply(it, ::Instance) ).apply(it, ::Instance)
} }
@ -36,20 +28,10 @@ object ExopackSlotsExpandedTrigger : SimpleCriterionTrigger<ExopackSlotsExpanded
trigger(player) { it.minGained <= gained && it.minTotal <= total } trigger(player) { it.minGained <= gained && it.minTotal <= total }
} }
override fun createInstance(p_66248_: JsonObject, p_286603_: Optional<ContextAwarePredicate>, p_66250_: DeserializationContext): Instance { class Instance(val minGained: Int = 0, val minTotal: Int = 0, player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractInstance(player) {
return codec.fromJsonStrict(p_66248_)
}
data class Instance(val minGained: Int = 0, val minTotal: Int = 0) : AbstractCriterionTriggerInstance(Optional.empty()) {
init { init {
require(minGained >= 0) { "Invalid minGained $minGained" } require(minGained >= 0) { "Invalid minGained $minGained" }
require(minTotal >= 0) { "Invalid minTotal $minTotal" } require(minTotal >= 0) { "Invalid minTotal $minTotal" }
} }
override fun serializeToJson(): JsonObject {
return codec.toJsonStrict(this) as JsonObject
}
fun criterion() = Criterion(ExopackSlotsExpandedTrigger, this)
} }
} }

View File

@ -1,29 +1,19 @@
package ru.dbotthepony.mc.otm.triggers package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.Criterion import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.* 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.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.damagesource.DamageSource import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity 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 ru.dbotthepony.mc.otm.data.DamagePredicateCodec
import java.util.Optional import java.util.*
abstract class HurtTrigger : SimpleCriterionTrigger<HurtTrigger.Instance>() {
abstract val id: ResourceLocation
final override fun createInstance(
p_66248_: JsonObject,
p_286603_: Optional<ContextAwarePredicate>,
p_66250_: DeserializationContext
): Instance {
return codec.fromJson(p_66248_, p_286603_, p_66250_)
}
class HurtTrigger(id: ResourceLocation) : MCriterionTrigger<HurtTrigger.Instance>(id) {
fun trigger(player: ServerPlayer, entity: LivingEntity, damage: Float, damageSource: DamageSource) { fun trigger(player: ServerPlayer, entity: LivingEntity, damage: Float, damageSource: DamageSource) {
val context = EntityPredicate.createContext(player, entity) val context = EntityPredicate.createContext(player, entity)
@ -32,14 +22,12 @@ abstract class HurtTrigger : SimpleCriterionTrigger<HurtTrigger.Instance>() {
} }
} }
val codec = Codec2TriggerSerializer<Instance> { p -> override val codec: Codec<Instance> = RecordCodecBuilder.create {
RecordCodecBuilder.create { it.group(
it.group( predicateCodec.optionalFieldOf("predicate").forGetter(Instance::predicate),
p.awareContextCodec.optionalFieldOf("predicate").forGetter(Instance::predicate), DamagePredicateCodec.optionalFieldOf("damagePredicate").forGetter(Instance::damagePredicate),
DamagePredicateCodec.optionalFieldOf("damagePredicate").forGetter(Instance::damagePredicate), playerPredicateCodec.forGetter(Instance::playerPredicate)
p.playerPredicate.forGetter { it.playerPredicate() } ).apply(it, ::Instance)
).apply(it, ::Instance)
}
} }
inner class Instance( inner class Instance(
@ -52,11 +40,5 @@ abstract class HurtTrigger : SimpleCriterionTrigger<HurtTrigger.Instance>() {
Optional.empty() Optional.empty()
)), )),
player: Optional<ContextAwarePredicate> = Optional.empty() player: Optional<ContextAwarePredicate> = Optional.empty()
) : AbstractCriterionTriggerInstance(player) { ) : AbstractInstance(player)
override fun serializeToJson(): JsonObject {
return codec.toJson(this)
}
fun criterion() = Criterion(this@HurtTrigger, this)
}
} }

View File

@ -1,44 +1,26 @@
package ru.dbotthepony.mc.otm.triggers package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder 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.ContextAwarePredicate
import net.minecraft.advancements.critereon.DeserializationContext
import net.minecraft.advancements.critereon.ItemPredicate import net.minecraft.advancements.critereon.ItemPredicate
import net.minecraft.advancements.critereon.SimpleCriterionTrigger
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer import java.util.*
import java.util.Optional
class ItemTrigger(val id: ResourceLocation) : SimpleCriterionTrigger<ItemTrigger.Instance>() { class ItemTrigger(id: ResourceLocation) : MCriterionTrigger<ItemTrigger.Instance>(id) {
fun trigger(player: ServerPlayer, item: ItemStack) { fun trigger(player: ServerPlayer, item: ItemStack) {
trigger(player) { if (it.invert) !it.predicate.matches(item) else it.predicate.matches(item) } trigger(player) { if (it.invert) !it.predicate.matches(item) else it.predicate.matches(item) }
} }
val codec = Codec2TriggerSerializer<Instance> { p -> override val codec: Codec<Instance> = RecordCodecBuilder.create {
RecordCodecBuilder.create { it.group(
it.group( ItemPredicate.CODEC.fieldOf("predicate").forGetter(Instance::predicate),
ItemPredicate.CODEC.fieldOf("predicate").forGetter(Instance::predicate), Codec.BOOL.optionalFieldOf("invert", false).forGetter(Instance::invert),
Codec.BOOL.optionalFieldOf("invert", false).forGetter(Instance::invert), playerPredicateCodec.forGetter(Instance::playerPredicate)
p.playerPredicate.forGetter { it.playerPredicate() } ).apply(it, ::Instance)
).apply(it, ::Instance)
}
} }
override fun createInstance(p_66248_: JsonObject, p_286603_: Optional<ContextAwarePredicate>, p_66250_: DeserializationContext): Instance { inner class Instance(val predicate: ItemPredicate, val invert: Boolean = false, player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractInstance(player)
return codec.fromJson(p_66248_, p_286603_, p_66250_)
}
inner class Instance(val predicate: ItemPredicate, val invert: Boolean = false, player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractCriterionTriggerInstance(player) {
override fun serializeToJson(): JsonObject {
return codec.toJson(this)
}
fun criterion() = Criterion(this@ItemTrigger, this)
}
} }

View File

@ -1,66 +1,67 @@
package ru.dbotthepony.mc.otm.triggers package ru.dbotthepony.mc.otm.triggers
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.gson.JsonArray import com.mojang.serialization.Codec
import com.google.gson.JsonObject import com.mojang.serialization.codecs.RecordCodecBuilder
import com.google.gson.JsonSyntaxException
import net.minecraft.advancements.Criterion
import net.minecraft.advancements.critereon.* import net.minecraft.advancements.critereon.*
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.StringRepresentable
import net.minecraft.world.entity.monster.ElderGuardian import net.minecraft.world.entity.monster.ElderGuardian
import net.minecraftforge.event.entity.living.LivingDeathEvent import net.minecraftforge.event.entity.living.LivingDeathEvent
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.core.asIterable import ru.dbotthepony.mc.otm.data.SingletonCodec
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.stream
import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.registry.MRegistry
import java.util.Optional import java.util.Optional
import java.util.function.Predicate import java.util.function.Predicate
object KillAsAndroidTrigger : SimpleCriterionTrigger<KillAsAndroidTrigger.Instance>() { object KillAsAndroidTrigger : MCriterionTrigger<KillAsAndroidTrigger.Instance>(ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android")) {
val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android") val FEATURE_CODEC: Codec<FeaturePredicate> = StringRepresentable.fromEnum(PredicateType::values).dispatch({ it.type }, { it.codec })
override fun createInstance(pJson: JsonObject, pPlayer: Optional<ContextAwarePredicate>, pContext: DeserializationContext): Instance { override val codec: Codec<Instance> = RecordCodecBuilder.create {
return Instance( it.group(
predicate = EntityPredicate.fromJson(pJson, "predicate", pContext), predicateCodec.optionalFieldOf("entityPredicate").forGetter(Instance::predicate),
playerPredicate = pPlayer, FEATURE_CODEC.fieldOf("featurePredicate").forGetter(Instance::featurePredicate),
featurePredicate = (pJson["feature_predicate"] as? JsonObject)?.let(PredicateType::from) ?: throw JsonSyntaxException("Invalid 'feature_predicate': ${pJson["feature_predicate"]}") playerPredicateCodec.forGetter(Instance::playerPredicate)
) ).apply(it, ::Instance)
} }
enum class PredicateType(val factory: (JsonObject) -> FeaturePredicate) { enum class PredicateType(codec: Lazy<Codec<out FeaturePredicate>>) : StringRepresentable {
ALWAYS({ Always }), ALWAYS(lazy {
HAS(::Has), SingletonCodec(Always)
NOT(::Not), }),
AND(::And), HAS(lazy {
OR(::OrPredicate); RecordCodecBuilder.create<Has> {
it.group(ResourceLocation.CODEC.fieldOf("name").forGetter(Has::name)).apply(it, ::Has)
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")
}
} }
}),
NOT(lazy {
RecordCodecBuilder.create<Not> {
it.group(FEATURE_CODEC.fieldOf("parent").forGetter(Not::parent)).apply(it, ::Not)
}
}),
AND(lazy {
RecordCodecBuilder.create<And> {
it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(And::children)).apply(it, ::And)
}
}),
OR(lazy {
RecordCodecBuilder.create<OrPredicate> {
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<MatteryPlayerCapability> { abstract class FeaturePredicate : Predicate<MatteryPlayerCapability> {
abstract val type: PredicateType abstract val type: PredicateType
open fun toJson(): JsonObject {
return JsonObject().also {
it["type"] = type.name.lowercase()
}
}
} }
object Always : FeaturePredicate() { object Always : FeaturePredicate() {
@ -72,17 +73,8 @@ object KillAsAndroidTrigger : SimpleCriterionTrigger<KillAsAndroidTrigger.Instan
} }
} }
class Has : FeaturePredicate { class Has(val name: ResourceLocation) : FeaturePredicate() {
private val resolved by lazy { MRegistry.ANDROID_FEATURES.getValue(name) } private val resolved by lazy { MRegistry.ANDROID_FEATURES.getValue(name) }
val name: ResourceLocation
constructor(name: ResourceLocation) {
this.name = name
}
constructor(value: JsonObject) {
this.name = ResourceLocation.tryParse(value["name"]?.asString ?: throw JsonSyntaxException("Invalid android feature name: ${value["name"]}")) ?: throw JsonSyntaxException("Invalid android feature name ${value["name"]}")
}
override val type: PredicateType override val type: PredicateType
get() = PredicateType.HAS get() = PredicateType.HAS
@ -90,34 +82,18 @@ object KillAsAndroidTrigger : SimpleCriterionTrigger<KillAsAndroidTrigger.Instan
override fun test(t: MatteryPlayerCapability): Boolean { override fun test(t: MatteryPlayerCapability): Boolean {
return t.hasFeature(resolved ?: return false) return t.hasFeature(resolved ?: return false)
} }
override fun toJson(): JsonObject {
return super.toJson().also {
it["name"] = name.toString()
}
}
} }
class Not(val parent: FeaturePredicate) : FeaturePredicate() { class Not(val parent: FeaturePredicate) : FeaturePredicate() {
constructor(input: JsonObject) : this(PredicateType.from(input["parent"] as? JsonObject ?: throw JsonSyntaxException("Invalid parent")))
override val type: PredicateType override val type: PredicateType
get() = PredicateType.NOT get() = PredicateType.NOT
override fun test(t: MatteryPlayerCapability): Boolean { override fun test(t: MatteryPlayerCapability): Boolean {
return !parent.test(t) return !parent.test(t)
} }
override fun toJson(): JsonObject {
return super.toJson().also {
it["parent"] = parent.toJson()
}
}
} }
class And(children: Iterable<FeaturePredicate>) : FeaturePredicate() { class And(children: Iterable<FeaturePredicate>) : 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<FeaturePredicate> = ImmutableList.copyOf(children) val children: ImmutableList<FeaturePredicate> = ImmutableList.copyOf(children)
override val type: PredicateType override val type: PredicateType
@ -126,21 +102,9 @@ object KillAsAndroidTrigger : SimpleCriterionTrigger<KillAsAndroidTrigger.Instan
override fun test(t: MatteryPlayerCapability): Boolean { override fun test(t: MatteryPlayerCapability): Boolean {
return children.all { it.test(t) } return children.all { it.test(t) }
} }
override fun toJson(): JsonObject {
return super.toJson().also {
it["children"] = JsonArray().also {
for (child in children) {
it.add(child.toJson())
}
}
}
}
} }
class OrPredicate(children: Iterable<FeaturePredicate>) : FeaturePredicate() { class OrPredicate(children: Iterable<FeaturePredicate>) : 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<FeaturePredicate> = ImmutableList.copyOf(children) val children: ImmutableList<FeaturePredicate> = ImmutableList.copyOf(children)
override val type: PredicateType override val type: PredicateType
@ -149,32 +113,13 @@ object KillAsAndroidTrigger : SimpleCriterionTrigger<KillAsAndroidTrigger.Instan
override fun test(t: MatteryPlayerCapability): Boolean { override fun test(t: MatteryPlayerCapability): Boolean {
return children.any { it.test(t) } return children.any { it.test(t) }
} }
override fun toJson(): JsonObject {
return super.toJson().also {
it["children"] = JsonArray().also {
for (child in children) {
it.add(child.toJson())
}
}
}
}
} }
class Instance( class Instance(
val predicate: Optional<ContextAwarePredicate> = Optional.empty(), val predicate: Optional<ContextAwarePredicate> = Optional.empty(),
val featurePredicate: FeaturePredicate = Always, val featurePredicate: FeaturePredicate = Always,
playerPredicate: Optional<ContextAwarePredicate> = Optional.empty(), playerPredicate: Optional<ContextAwarePredicate> = Optional.empty(),
) : AbstractCriterionTriggerInstance(playerPredicate) { ) : AbstractInstance(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)
}
fun onKill(event: LivingDeathEvent) { fun onKill(event: LivingDeathEvent) {
if (event.entity is ElderGuardian) { if (event.entity is ElderGuardian) {

View File

@ -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<T : MCriterionTrigger<T>.AbstractInstance>(val id: ResourceLocation) : CriterionTrigger<T> {
private val listeners = Reference2ObjectOpenHashMap<PlayerAdvancements, ObjectOpenHashSet<CriterionTrigger.Listener<T>>>()
override fun addPlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener<T>) {
listeners.computeIfAbsent(advancements, Reference2ObjectFunction { ObjectOpenHashSet() }).add(listener)
}
override fun removePlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener<T>) {
listeners[advancements]?.remove(listener)
}
override fun removePlayerListeners(advancements: PlayerAdvancements) {
listeners.remove(advancements)
}
protected abstract val codec: Codec<T>
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<T> = 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<ContextAwarePredicate>) : 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<ArrayDeque<DeserializationContext>>() {
override fun initialValue(): ArrayDeque<DeserializationContext> {
return ArrayDeque()
}
}
@JvmStatic
protected val predicateCodec: Codec<ContextAwarePredicate> = object : Codec<ContextAwarePredicate> {
override fun <T : Any?> encode(input: ContextAwarePredicate, 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<ContextAwarePredicate, T>> {
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<Optional<ContextAwarePredicate>> = predicateCodec.optionalFieldOf("player")
}
}

View File

@ -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")
}

View File

@ -1,45 +1,25 @@
package ru.dbotthepony.mc.otm.triggers package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.Criterion import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.*
import net.minecraft.advancements.critereon.MinMaxBounds.Doubles import net.minecraft.advancements.critereon.MinMaxBounds.Doubles
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer import java.util.*
import java.util.Optional
object NanobotsArmorTrigger : SimpleCriterionTrigger<NanobotsArmorTrigger.Instance>() { object NanobotsArmorTrigger : MCriterionTrigger<NanobotsArmorTrigger.Instance>(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor")) {
val id = ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor") override val codec: Codec<Instance> = RecordCodecBuilder.create {
it.group(
override fun createInstance( Doubles.CODEC.fieldOf("predicate").forGetter(Instance::predicate),
p_66248_: JsonObject, playerPredicateCodec.forGetter(Instance::playerPredicate),
p_286603_: Optional<ContextAwarePredicate>, ).apply(it, ::Instance)
p_66250_: DeserializationContext
): Instance {
return codec.fromJson(p_66248_, p_286603_, p_66250_)
}
val codec = Codec2TriggerSerializer<Instance> { p ->
RecordCodecBuilder.create {
it.group(
Doubles.CODEC.fieldOf("predicate").forGetter(Instance::predicate),
p.playerPredicate.forGetter { it.playerPredicate() },
).apply(it, ::Instance)
}
} }
fun trigger(player: ServerPlayer, damageAbsorbed: Double) { fun trigger(player: ServerPlayer, damageAbsorbed: Double) {
trigger(player) { it.predicate.matches(damageAbsorbed) } trigger(player) { it.predicate.matches(damageAbsorbed) }
} }
class Instance(val predicate: Doubles, player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractCriterionTriggerInstance(player) { class Instance(val predicate: Doubles, player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractInstance(player)
override fun serializeToJson(): JsonObject {
return codec.toJson((this))
}
fun criterion() = Criterion(NanobotsArmorTrigger, this)
}
} }

View File

@ -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")
}

View File

@ -15,3 +15,6 @@ val BecomeHumaneTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters
val AndroidBatteryTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_battery")) val AndroidBatteryTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_battery"))
val TakeItemOutOfReplicatorTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "take_item_out_of_replicator")) 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"))

View File

@ -1,42 +1,19 @@
package ru.dbotthepony.mc.otm.triggers package ru.dbotthepony.mc.otm.triggers
import com.google.gson.JsonObject import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.advancements.Criterion import net.minecraft.advancements.Criterion
import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance
import net.minecraft.advancements.critereon.ContextAwarePredicate 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.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import java.util.*
import ru.dbotthepony.mc.otm.data.Codec2TriggerSerializer
import java.util.Optional
class SingletonTrigger(val id: ResourceLocation) : SimpleCriterionTrigger<AbstractCriterionTriggerInstance>() { class SingletonTrigger(id: ResourceLocation) : MCriterionTrigger<SingletonTrigger.Instance>(id) {
override fun createInstance(p_66248_: JsonObject, p_286603_: Optional<ContextAwarePredicate>, p_66250_: DeserializationContext): AbstractCriterionTriggerInstance { override val codec: Codec<Instance> = RecordCodecBuilder.create {
return codec.fromJson(p_66248_, p_286603_, p_66250_) it.group(playerPredicateCodec.forGetter(Instance::playerPredicate)).apply(it, ::Instance)
}
fun trigger(player: ServerPlayer) {
trigger(player) { true }
}
val codec = Codec2TriggerSerializer<Instance> { p ->
RecordCodecBuilder.create {
it.group(
p.playerPredicate.forGetter { it.playerPredicate() }
).apply(it, ::Instance)
}
} }
val empty = Instance() val empty = Instance()
val criterion = Criterion(this@SingletonTrigger, empty) val criterion = Criterion(this@SingletonTrigger, empty)
inner class Instance(player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractCriterionTriggerInstance(player) { inner class Instance(player: Optional<ContextAwarePredicate> = Optional.empty()) : AbstractInstance(player)
override fun serializeToJson(): JsonObject {
return codec.toJson(this)
}
fun criterion() = Criterion(this@SingletonTrigger, this)
}
} }