More achievements!

Travel underwater without Air Bags, fixes #209
Kill Elder Guardian without Air Bags, fixes #210
Kill the Wither as Android, fixes #206
This commit is contained in:
DBotThePony 2023-02-25 18:11:54 +07:00
parent 9924711f31
commit 5b4c68b201
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 389 additions and 21 deletions

View File

@ -20,17 +20,20 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent
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
import ru.dbotthepony.mc.otm.registry.AndroidFeatures
import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItemTags
import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger
import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger 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.BecomeAndroidDeathTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger
import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger
import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger 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.NanobotsArmorTrigger
import ru.dbotthepony.mc.otm.triggers.PhantomSpawnDeniedTrigger import ru.dbotthepony.mc.otm.triggers.PhantomSpawnDeniedTrigger
import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger
@ -46,8 +49,8 @@ fun addAndroidAdvancements(serializer: Consumer<Advancement>, existingFileHelper
title = translation.add("root", "Androids and Humans") { title = translation.add("root", "Androids and Humans") {
russian("Андроиды и Люди") russian("Андроиды и Люди")
}, },
description = translation.add("root.desc", "Can you make out who is cruel machine and who care about others?") { description = translation.add("root.desc", "Can you make out who is cruel machine and who shows empathy?") {
russian("Сможете ли вы отличить бездушную машину от того, кому другие не безразличны?") russian("Сможете ли вы отличить бездушную машину от того, кто показывает сочувствие?")
}, },
showToast = false, showToast = false,
announceChat = false, announceChat = false,
@ -381,4 +384,73 @@ fun addAndroidAdvancements(serializer: Consumer<Advancement>, existingFileHelper
) )
.addCriterion("shockwave_warden", ShockwaveDamageMobTrigger.Instance(EntityPredicate.Builder.entity().of(EntityType.WARDEN).build().wrap())) .addCriterion("shockwave_warden", ShockwaveDamageMobTrigger.Instance(EntityPredicate.Builder.entity().of(EntityType.WARDEN).build().wrap()))
.save(serializer, modLocation("android/shockwave_warden"), existingFileHelper) .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)
} }

View File

@ -50,6 +50,7 @@ import ru.dbotthepony.mc.otm.matter.MatterManager;
import ru.dbotthepony.mc.otm.network.*; import ru.dbotthepony.mc.otm.network.*;
import ru.dbotthepony.mc.otm.registry.*; import ru.dbotthepony.mc.otm.registry.*;
import ru.dbotthepony.mc.otm.storage.*; import ru.dbotthepony.mc.otm.storage.*;
import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger;
import static net.minecraftforge.common.MinecraftForge.EVENT_BUS; 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.NORMAL, MatteryBlockEntity.Companion::playerDisconnected);
EVENT_BUS.addListener(EventPriority.LOWEST, MatteryBlockEntity.Companion::postLevelTick); 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.NORMAL, EnderTeleporterFeature.Companion::onEntityDeath);
EVENT_BUS.addListener(EventPriority.HIGH, ItemTritaniumArmor.Companion::onHurt); EVENT_BUS.addListener(EventPriority.HIGH, ItemTritaniumArmor.Companion::onHurt);

View File

@ -21,6 +21,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.ProjectileWeaponItem import net.minecraft.world.item.ProjectileWeaponItem
import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.ForgeHooks import net.minecraftforge.common.ForgeHooks
import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ICapabilityProvider 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.UUIDIntModifiersMap
import ru.dbotthepony.mc.otm.core.collect.nonEmpty import ru.dbotthepony.mc.otm.core.collect.nonEmpty
import ru.dbotthepony.mc.otm.core.math.Decimal 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.getCompoundList
import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.util.IntValueCodec 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.core.util.UUIDValueCodec
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.network.* import ru.dbotthepony.mc.otm.network.*
import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.AndroidFeatures
import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.registry.MRegistry
import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger 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.BecomeAndroidDeathTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger
@ -98,6 +102,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
*/ */
val synchronizer = FieldSynchronizer() 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 * For fields that need to be synchronized to everyone
* *
@ -251,6 +260,19 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
var ticksIExist = 0 var ticksIExist = 0
private set 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) * 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.batteryLevel = AndroidConfig.ANDROID_MAX_ENERGY
androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY
lastOutsideLiquid = ply.position()
wasInLiquid = false
if (ply is ServerPlayer) { if (ply is ServerPlayer) {
BecomeAndroidTrigger.trigger(ply) BecomeAndroidTrigger.trigger(ply)
} }
@ -374,6 +399,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY
dropBattery() dropBattery()
lastOutsideLiquid = ply.position()
wasInLiquid = false
if (ply is ServerPlayer) { if (ply is ServerPlayer) {
BecomeHumaneTrigger.trigger(ply) BecomeHumaneTrigger.trigger(ply)
} }
@ -574,14 +602,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
override fun serializeNBT(): CompoundTag { override fun serializeNBT(): CompoundTag {
val tag = CompoundTag() val tag = savetables.serializeNBT()
tag["ticksIExist"] = ticksIExist
// iteration // iteration
tag["iteration"] = iteration
tag["shouldSendIteration"] = shouldSendIteration
tag["deathLog"] = ListTag().also { tag["deathLog"] = ListTag().also {
for ((ticks, component) in deathLog) { for ((ticks, component) in deathLog) {
it.add(CompoundTag().also { it.add(CompoundTag().also {
@ -626,12 +649,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
override fun deserializeNBT(tag: CompoundTag) { override fun deserializeNBT(tag: CompoundTag) {
ticksIExist = tag.getInt("ticksIExist") savetables.deserializeNBT(tag)
// iterations // iterations
iteration = tag.getInt("iteration")
shouldSendIteration = tag.getBoolean("shouldSendIteration")
deathLog.clear() deathLog.clear()
for (value in tag.getCompoundList("deathLog")) { for (value in tag.getCompoundList("deathLog")) {
@ -773,18 +793,38 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
if (isAndroid) { 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() androidEnergy.tick()
if (!ply.isSpectator) { 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 val stats = ply.foodData
while (stats.foodLevel < 18 && androidEnergy.extractEnergy(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT, true) >= AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT) { while (stats.foodLevel < 18 && androidEnergy.extractEnergy(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT, true) >= AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT) {

View File

@ -298,3 +298,11 @@ fun <T : Comparable<T>> BlockState.getValueNullable(prop: Property<T>): T? {
return null return null
} }
fun <T> Stream<T>.asIterable(): Iterable<T> {
return object : Iterable<T> {
override fun iterator(): Iterator<T> {
return this@asIterable.iterator()
}
}
}

View File

@ -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.DecorativeBlock
import ru.dbotthepony.mc.otm.registry.objects.StripedColoredDecorativeBlock import ru.dbotthepony.mc.otm.registry.objects.StripedColoredDecorativeBlock
import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger 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.AndroidResearchTrigger
import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger
import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger
@ -265,6 +267,8 @@ object MRegistry {
CriteriaTriggers.register(NanobotsArmorTrigger) CriteriaTriggers.register(NanobotsArmorTrigger)
CriteriaTriggers.register(FallDampenersSaveTrigger) CriteriaTriggers.register(FallDampenersSaveTrigger)
CriteriaTriggers.register(EnderTeleporterFallDeathTrigger) CriteriaTriggers.register(EnderTeleporterFallDeathTrigger)
CriteriaTriggers.register(KillAsAndroidTrigger)
CriteriaTriggers.register(AndroidTravelUnderwater)
} }
} }

View File

@ -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<AndroidTravelUnderwater.Instance>() {
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)
}
}
}
}

View File

@ -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<KillAsAndroidTrigger.Instance>() {
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<MatteryPlayerCapability> {
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>) : 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)
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>) : 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)
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)
}
}
}
}
}
}