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.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<Advancement>, 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<Advancement>, 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)
}

View File

@ -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);

View File

@ -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) {

View File

@ -298,3 +298,11 @@ fun <T : Comparable<T>> BlockState.getValueNullable(prop: Property<T>): T? {
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.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)
}
}

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