From 5b4c68b2017e7675256bf260b182c66288fc641a Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 25 Feb 2023 18:11:54 +0700 Subject: [PATCH] More achievements! Travel underwater without Air Bags, fixes #209 Kill Elder Guardian without Air Bags, fixes #210 Kill the Wither as Android, fixes #206 --- .../advancements/AndroidAdvancementsData.kt | 76 ++++++- .../mc/otm/OverdriveThatMatters.java | 3 + .../otm/capability/MatteryPlayerCapability.kt | 78 +++++-- .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt | 8 + .../dbotthepony/mc/otm/registry/MRegistry.kt | 4 + .../otm/triggers/AndroidTravelUnderwater.kt | 40 ++++ .../mc/otm/triggers/KillAsAndroidTrigger.kt | 201 ++++++++++++++++++ 7 files changed, 389 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt 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 9d53300a1..cc035e41f 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 @@ -20,17 +20,20 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.datagen.DataGen import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.modLocation +import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger +import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger +import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger import ru.dbotthepony.mc.otm.triggers.PhantomSpawnDeniedTrigger import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger @@ -46,8 +49,8 @@ fun addAndroidAdvancements(serializer: Consumer, existingFileHelper title = translation.add("root", "Androids and Humans") { russian("Андроиды и Люди") }, - description = translation.add("root.desc", "Can you make out who is cruel machine and who care about others?") { - russian("Сможете ли вы отличить бездушную машину от того, кому другие не безразличны?") + description = translation.add("root.desc", "Can you make out who is cruel machine and who shows empathy?") { + russian("Сможете ли вы отличить бездушную машину от того, кто показывает сочувствие?") }, showToast = false, announceChat = false, @@ -381,4 +384,73 @@ fun addAndroidAdvancements(serializer: Consumer, existingFileHelper ) .addCriterion("shockwave_warden", ShockwaveDamageMobTrigger.Instance(EntityPredicate.Builder.entity().of(EntityType.WARDEN).build().wrap())) .save(serializer, modLocation("android/shockwave_warden"), existingFileHelper) + + AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(Items.WITHER_SKELETON_SKULL), + title = translation.add("wither", "Not Quite Alive, Not Quite Undead") { + russian("Ни Живой, Ни Мёртвый") + }, + description = translation.add("wither.desc", "Defeat The Wither as Android. The Wither was surely confused over kind of thing you are") { + russian("Победите Иссушителя будучи Андроидом. Наверняка Иссушитель был ошеломлён таким раскладом дел") + }, + frameType = FrameType.GOAL, + hidden = true + ) + .addCriterion("kill_wither", KillAsAndroidTrigger.Instance( + predicate = EntityPredicate.Builder.entity().of(EntityType.WITHER).build().wrap(), + )) + .save(serializer, modLocation("android/wither"), existingFileHelper) + + val underwater = AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(Items.TURTLE_SPAWN_EGG), + title = translation.add("travel_underwater", "Underwater Walk") { + russian("Подводная Прогулка") + }, + description = translation.add("travel_underwater.desc", "Travel at least 200 meters underwater as Android without Air Bags research. This reminds us of someone...") { + russian("Преодолейте как минимум 200 метров под водой будучи Андроидом без исследования Воздушных Мешков. Кого-то это нам напоминает...") + }, + frameType = FrameType.GOAL, + hidden = true + ) + .addCriterion("travel", AndroidTravelUnderwater.Instance(200.0)) + .save(serializer, modLocation("android/underwater"), existingFileHelper) + + AdvancementBuilder() + .parent(underwater) + .display( + itemStack = ItemStack(Items.TURTLE_SPAWN_EGG), + title = translation.add("travel_underwater2", "Underwater Travel") { + russian("Подводная Прогулка") + }, + description = translation.add("travel_underwater2.desc", "Travel at least 1046 meters underwater as Android without Air Bags research, like someone else did so") { + russian("Преодолейте как минимум 1046 метров под водой будучи Андроидом без исследования Воздушных Мешков, прям как тот, кто так однажды так и сделал") + }, + frameType = FrameType.CHALLENGE, + hidden = true + ) + .addCriterion("travel", AndroidTravelUnderwater.Instance(1046.0)) + .save(serializer, modLocation("android/underwater2"), existingFileHelper) + + AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(Items.PRISMARINE_CRYSTALS), + title = translation.add("elder_guardian", "Drowned, but Still Determined") { + russian("Затонувший, но Всё Ещё Целеустремлённый") + }, + description = translation.add("elder_guardian.desc", "Slay Elder Guardian as Android without Air Bags researched") { + russian("Победите Древнего Стража будучи Андроидом без исследования Воздушных Мешков") + }, + frameType = FrameType.CHALLENGE, + hidden = true + ) + .addCriterion("kill_elder_guardian", KillAsAndroidTrigger.Instance( + predicate = EntityPredicate.Builder.entity().of(EntityType.ELDER_GUARDIAN).build().wrap(), + featurePredicate = KillAsAndroidTrigger.Not(KillAsAndroidTrigger.Has(AndroidFeatures.AIR_BAGS.registryName!!)) + )) + .save(serializer, modLocation("android/elder_guardian"), existingFileHelper) } diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index e4d26df10..46bf4ab8b 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -50,6 +50,7 @@ import ru.dbotthepony.mc.otm.matter.MatterManager; import ru.dbotthepony.mc.otm.network.*; import ru.dbotthepony.mc.otm.registry.*; import ru.dbotthepony.mc.otm.storage.*; +import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger; import static net.minecraftforge.common.MinecraftForge.EVENT_BUS; @@ -177,6 +178,8 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::playerDisconnected); EVENT_BUS.addListener(EventPriority.LOWEST, MatteryBlockEntity.Companion::postLevelTick); + EVENT_BUS.addListener(EventPriority.LOWEST, KillAsAndroidTrigger.INSTANCE::onKill); + EVENT_BUS.addListener(EventPriority.NORMAL, EnderTeleporterFeature.Companion::onEntityDeath); EVENT_BUS.addListener(EventPriority.HIGH, ItemTritaniumArmor.Companion::onHurt); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index c73cc4d96..506e347a9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -21,6 +21,7 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ProjectileWeaponItem import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse +import net.minecraft.world.phys.Vec3 import net.minecraftforge.common.ForgeHooks import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ICapabilityProvider @@ -53,16 +54,19 @@ import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.collect.UUIDIntModifiersMap import ru.dbotthepony.mc.otm.core.collect.nonEmpty import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.minus import ru.dbotthepony.mc.otm.core.nbt.getCompoundList import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.IntValueCodec +import ru.dbotthepony.mc.otm.core.util.Savetables import ru.dbotthepony.mc.otm.core.util.UUIDValueCodec import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu import ru.dbotthepony.mc.otm.network.* import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger +import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger @@ -98,6 +102,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial */ val synchronizer = FieldSynchronizer() + /** + * For data to be stored and loaded from NBT automatically + */ + val savetables = Savetables() + /** * For fields that need to be synchronized to everyone * @@ -251,6 +260,19 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial var ticksIExist = 0 private set + private var lastOutsideLiquid = Vec3(0.0, 0.0, 0.0) + private var wasInLiquid = false + private var lastDimension = ResourceLocation("overworld") + + init { + savetables.int(::ticksIExist) + savetables.int(::iteration) + savetables.bool(::shouldSendIteration) + savetables.bool(::wasInLiquid) + savetables.vector(::lastOutsideLiquid) + savetables.location(::lastDimension) + } + /** * Whenever player should become an Android once transformation conditions are met (e.g. player dies or sleeps in bed) */ @@ -326,6 +348,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial androidEnergy.batteryLevel = AndroidConfig.ANDROID_MAX_ENERGY androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY + lastOutsideLiquid = ply.position() + wasInLiquid = false + if (ply is ServerPlayer) { BecomeAndroidTrigger.trigger(ply) } @@ -374,6 +399,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY dropBattery() + lastOutsideLiquid = ply.position() + wasInLiquid = false + if (ply is ServerPlayer) { BecomeHumaneTrigger.trigger(ply) } @@ -574,14 +602,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } override fun serializeNBT(): CompoundTag { - val tag = CompoundTag() - - tag["ticksIExist"] = ticksIExist + val tag = savetables.serializeNBT() // iteration - tag["iteration"] = iteration - tag["shouldSendIteration"] = shouldSendIteration - tag["deathLog"] = ListTag().also { for ((ticks, component) in deathLog) { it.add(CompoundTag().also { @@ -626,12 +649,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } override fun deserializeNBT(tag: CompoundTag) { - ticksIExist = tag.getInt("ticksIExist") + savetables.deserializeNBT(tag) // iterations - iteration = tag.getInt("iteration") - shouldSendIteration = tag.getBoolean("shouldSendIteration") - deathLog.clear() for (value in tag.getCompoundList("deathLog")) { @@ -773,18 +793,38 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } if (isAndroid) { - if (!ply.isSpectator && ply.airSupply < ply.maxAirSupply) - ply.airSupply = ply.maxAirSupply - - // TODO: Maybe passive drain? - // extractEnergyInner(BigDecimal.valueOf(new Random().nextDouble()), false); - if (!ply.isSpectator && ply.isSwimming && !hasFeature(AndroidFeatures.AIR_BAGS)) { - ply.isSwimming = false - } - androidEnergy.tick() if (!ply.isSpectator) { + if (ply.airSupply < ply.maxAirSupply) + ply.airSupply = ply.maxAirSupply + + if (ply.isSwimming && !hasFeature(AndroidFeatures.AIR_BAGS)) + ply.isSwimming = false + + if (ply is ServerPlayer) { + if (ply.level.dimension().location() != lastDimension) { + lastDimension = ply.level.dimension().location() + wasInLiquid = false + lastOutsideLiquid = ply.position + } + + if (ply.isUnderWater) { + if (!wasInLiquid) { + wasInLiquid = true + lastOutsideLiquid = ply.position + } + } else { + if (wasInLiquid) { + wasInLiquid = false + val distance = (lastOutsideLiquid - ply.position).length() + AndroidTravelUnderwater.trigger(ply, distance) + } + + lastOutsideLiquid = ply.position + } + } + val stats = ply.foodData while (stats.foodLevel < 18 && androidEnergy.extractEnergy(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT, true) >= AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index 4ca99c91e..05c455fe9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -298,3 +298,11 @@ fun > BlockState.getValueNullable(prop: Property): T? { return null } + +fun Stream.asIterable(): Iterable { + return object : Iterable { + override fun iterator(): Iterator { + return this@asIterable.iterator() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt index e7bfac5fb..ae466a53c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt @@ -34,7 +34,9 @@ import ru.dbotthepony.mc.otm.registry.objects.CrateProperties import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock import ru.dbotthepony.mc.otm.registry.objects.StripedColoredDecorativeBlock import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger +import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger +import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger @@ -265,6 +267,8 @@ object MRegistry { CriteriaTriggers.register(NanobotsArmorTrigger) CriteriaTriggers.register(FallDampenersSaveTrigger) CriteriaTriggers.register(EnderTeleporterFallDeathTrigger) + CriteriaTriggers.register(KillAsAndroidTrigger) + CriteriaTriggers.register(AndroidTravelUnderwater) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt new file mode 100644 index 000000000..4224aed90 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt @@ -0,0 +1,40 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance +import net.minecraft.advancements.critereon.DeserializationContext +import net.minecraft.advancements.critereon.EntityPredicate +import net.minecraft.advancements.critereon.SerializationContext +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.set + +object AndroidTravelUnderwater : SimpleCriterionTrigger() { + val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_walk_underwater") + + override fun getId(): ResourceLocation { + return ID + } + + override fun createInstance(pJson: JsonObject, pPlayer: EntityPredicate.Composite, pContext: DeserializationContext): Instance { + return Instance((pJson["distance_to_travel"] as? JsonPrimitive)?.asDouble ?: throw JsonParseException("Invalid 'distance_to_travel' value")) + } + + fun trigger(player: ServerPlayer, travelled: Double) { + trigger(player) { + it.distanceToTravel <= travelled + } + } + + class Instance(val distanceToTravel: Double) : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) { + override fun serializeToJson(pConditions: SerializationContext): JsonObject { + return super.serializeToJson(pConditions).also { + it["distance_to_travel"] = JsonPrimitive(distanceToTravel) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt new file mode 100644 index 000000000..2d783b159 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt @@ -0,0 +1,201 @@ +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.critereon.AbstractCriterionTriggerInstance +import net.minecraft.advancements.critereon.DeserializationContext +import net.minecraft.advancements.critereon.EntityPredicate +import net.minecraft.advancements.critereon.SerializationContext +import net.minecraft.advancements.critereon.SimpleCriterionTrigger +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +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.registry.MRegistry +import java.util.function.Predicate + +object KillAsAndroidTrigger : SimpleCriterionTrigger() { + val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android") + + override fun getId(): ResourceLocation { + return ID + } + + override fun createInstance(pJson: JsonObject, pPlayer: EntityPredicate.Composite, pContext: DeserializationContext): Instance { + return Instance( + predicate = EntityPredicate.Composite.fromJson(pJson, "predicate", pContext), + playerPredicate = pPlayer, + featurePredicate = (pJson["feature_predicate"] as? JsonObject)?.let(PredicateType::from) ?: throw JsonSyntaxException("Invalid 'feature_predicate': ${pJson["feature_predicate"]}") + ) + } + + 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") + } + } + } + } + + abstract class FeaturePredicate : Predicate { + abstract val type: PredicateType + + open fun toJson(): JsonObject { + return JsonObject().also { + it["type"] = type.name.lowercase() + } + } + } + + object Always : FeaturePredicate() { + override val type: PredicateType + get() = PredicateType.ALWAYS + + override fun test(t: MatteryPlayerCapability): Boolean { + return true + } + } + + class Has : FeaturePredicate { + 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 + get() = PredicateType.HAS + + override fun test(t: MatteryPlayerCapability): Boolean { + return t.hasFeature(resolved ?: return false) + } + + override fun toJson(): JsonObject { + return super.toJson().also { + it["name"] = name.toString() + } + } + } + + class Not(val parent: FeaturePredicate) : FeaturePredicate() { + constructor(input: JsonObject) : this(PredicateType.from(input["parent"] as? JsonObject ?: throw JsonSyntaxException("Invalid parent"))) + + override val type: PredicateType + get() = PredicateType.NOT + + override fun test(t: MatteryPlayerCapability): Boolean { + return !parent.test(t) + } + + override fun toJson(): JsonObject { + return super.toJson().also { + it["parent"] = parent.toJson() + } + } + } + + class And(children: Iterable) : 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 + get() = PredicateType.AND + + override fun test(t: MatteryPlayerCapability): Boolean { + 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() { + 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 + get() = PredicateType.OR + + override fun test(t: MatteryPlayerCapability): Boolean { + 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( + val predicate: EntityPredicate.Composite = EntityPredicate.Composite.ANY, + val featurePredicate: FeaturePredicate = Always, + playerPredicate: EntityPredicate.Composite = EntityPredicate.Composite.ANY, + ) : AbstractCriterionTriggerInstance(ID, playerPredicate) { + override fun serializeToJson(pConditions: SerializationContext): JsonObject { + return super.serializeToJson(pConditions).also { + it["predicate"] = predicate.toJson(pConditions) + it["feature_predicate"] = featurePredicate.toJson() + } + } + } + + fun onKill(event: LivingDeathEvent) { + if (event.entity is ElderGuardian) { + val killer = event.entity.combatTracker.killer + + if (killer is ServerPlayer) { + val data = killer.matteryPlayer ?: return + + if (data.isAndroid) { + val context = EntityPredicate.createContext(killer, event.entity) + + trigger(killer) { + it.predicate.matches(context) && + it.featurePredicate.test(data) + } + } + } + } + } +}