From d7902e06cd7285c72cd4ea2be6f18ead56a8775e Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Thu, 18 Jan 2024 00:10:25 +0100 Subject: Add Ancestral Spade solver --- .../firmament/mixins/SoundReceiveEventPatch.java | 35 ++++++++ .../moe/nea/firmament/events/SoundReceiveEvent.kt | 23 ++++++ .../moe/nea/firmament/features/FeatureManager.kt | 2 + .../features/diana/AncestralSpadeSolver.kt | 93 ++++++++++++++++++++++ .../nea/firmament/features/diana/DianaWaypoints.kt | 32 ++++++++ .../firmament/util/render/RenderInWorldContext.kt | 8 +- 6 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java create mode 100644 src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt (limited to 'src') diff --git a/src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java b/src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java new file mode 100644 index 0000000..64f8765 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import moe.nea.firmament.events.SoundReceiveEvent; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; +import net.minecraft.util.math.Vec3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayNetworkHandler.class) +public class SoundReceiveEventPatch { + @Inject(method = "onPlaySound", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientWorld;playSound(Lnet/minecraft/entity/player/PlayerEntity;DDDLnet/minecraft/registry/entry/RegistryEntry;Lnet/minecraft/sound/SoundCategory;FFJ)V"), cancellable = true) + private void postEventWhenSoundIsPlayed(PlaySoundS2CPacket packet, CallbackInfo ci) { + var event = new SoundReceiveEvent( + packet.getSound(), + packet.getCategory(), + new Vec3d(packet.getX(), packet.getY(), packet.getZ()), + packet.getPitch(), + packet.getVolume(), + packet.getSeed() + ); + SoundReceiveEvent.Companion.publish(event); + if (event.getCancelled()) { + ci.cancel(); + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt b/src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt new file mode 100644 index 0000000..45b3982 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/SoundReceiveEvent.kt @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import net.minecraft.registry.entry.RegistryEntry +import net.minecraft.sound.SoundCategory +import net.minecraft.sound.SoundEvent +import net.minecraft.util.math.Vec3d + +data class SoundReceiveEvent( + val sound: RegistryEntry, + val category: SoundCategory, + val position: Vec3d, + val pitch: Float, + val volume: Float, + val seed: Long +) : FirmamentEvent.Cancellable() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index 3c5ac62..99f84e6 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -16,6 +16,7 @@ import moe.nea.firmament.features.debug.DebugView import moe.nea.firmament.features.debug.DeveloperFeatures import moe.nea.firmament.features.debug.MinorTrolling import moe.nea.firmament.features.debug.PowerUserTools +import moe.nea.firmament.features.diana.DianaWaypoints import moe.nea.firmament.features.fixes.CompatibliltyFeatures import moe.nea.firmament.features.fixes.Fixes import moe.nea.firmament.features.inventory.CraftingOverlay @@ -68,6 +69,7 @@ object FeatureManager : DataHolder(serializer(), "feature loadFeature(CustomSkyBlockTextures) loadFeature(PriceData) loadFeature(Fixes) + loadFeature(DianaWaypoints) loadFeature(ItemRarityCosmetics) if (Firmament.DEBUG) { loadFeature(DeveloperFeatures) diff --git a/src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt b/src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt new file mode 100644 index 0000000..c34e68e --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/diana/AncestralSpadeSolver.kt @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.diana + +import org.joml.Vector3f +import kotlin.time.Duration.Companion.seconds +import net.minecraft.particle.ParticleTypes +import net.minecraft.sound.SoundEvents +import net.minecraft.util.math.Vec3d +import moe.nea.firmament.events.ParticleSpawnEvent +import moe.nea.firmament.events.SoundReceiveEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.render.RenderInWorldContext + +object AncestralSpadeSolver { + var lastDing = TimeMark.farPast() + private set + private val pitches = mutableListOf() + val particlePositions = mutableListOf() + var nextGuess: Vec3d? = null + private set + + fun onParticleSpawn(event: ParticleSpawnEvent) { + if (!DianaWaypoints.TConfig.ancestralSpadeSolver) return + if (event.particleEffect != ParticleTypes.DRIPPING_LAVA) return + particlePositions.add(event.position) + if (particlePositions.size > 20) { + particlePositions.removeFirst() + } + } + + fun onPlaySound(event: SoundReceiveEvent) { + if (!DianaWaypoints.TConfig.ancestralSpadeSolver) return + if (!SoundEvents.BLOCK_NOTE_BLOCK_HARP.matchesId(event.sound.value().id)) return + + if (lastDing.passedTime() > 1.seconds) { + particlePositions.clear() + pitches.clear() + } + lastDing = TimeMark.now() + + pitches.add(event.pitch) + if (pitches.size > 20) { + pitches.removeFirst() + } + + if (particlePositions.size < 3) { + return + } + + val averagePitchDelta = + if (pitches.isEmpty()) 0.0 + else pitches + .zipWithNext { a, b -> b - a } + .average() + + val soundDistanceEstimate = (Math.E / averagePitchDelta) - particlePositions.first().distanceTo(event.position) + + if (soundDistanceEstimate > 1000) { + return + } + + val lastParticleDirection = particlePositions + .takeLast(3) + .let { (a, _, b) -> b.subtract(a) } + .normalize() + + nextGuess = event.position.add(lastParticleDirection.multiply(soundDistanceEstimate)) + } + + fun onWorldRender(event: WorldRenderLastEvent) { + if (!DianaWaypoints.TConfig.ancestralSpadeSolver) return + RenderInWorldContext.renderInWorld(event) { + nextGuess?.let { + color(1f, 1f, 0f, 0.5f) + tinyBlock(it, 1f) + color(1f, 1f, 0f, 1f) + val cameraForward = Vector3f(0f, 0f, 1f).rotate(event.camera.rotation) + line(event.camera.pos.add(Vec3d(cameraForward)), it, lineWidth = 3f) + } + if (particlePositions.size > 2 && lastDing.passedTime() < 10.seconds) { + color(0f, 1f, 0f, 0.7f) + line(*particlePositions.toTypedArray()) + } + } + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt b/src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt new file mode 100644 index 0000000..eb20852 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/diana/DianaWaypoints.kt @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.diana + +import moe.nea.firmament.events.ParticleSpawnEvent +import moe.nea.firmament.events.SoundReceiveEvent +import moe.nea.firmament.events.WorldRenderLastEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig + +object DianaWaypoints : FirmamentFeature { + override val identifier: String + get() = "diana-waypoints" + override val config: ManagedConfig? + get() = TConfig + + object TConfig : ManagedConfig(identifier) { + val ancestralSpadeSolver by toggle("ancestral-spade") { false } + } + + override fun onLoad() { + ParticleSpawnEvent.subscribe(AncestralSpadeSolver::onParticleSpawn) + SoundReceiveEvent.subscribe(AncestralSpadeSolver::onPlaySound) + WorldRenderLastEvent.subscribe(AncestralSpadeSolver::onWorldRender) + } +} + + diff --git a/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt b/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt index aa54979..20f9de8 100644 --- a/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt +++ b/src/main/kotlin/moe/nea/firmament/util/render/RenderInWorldContext.kt @@ -147,12 +147,16 @@ class RenderInWorldContext private constructor( } fun line(vararg points: Vec3d, lineWidth: Float = 10F) { + line(points.toList(), lineWidth) + } + + fun line(points: List, lineWidth: Float = 10F) { RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram) RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(points.first()), 0.25).toFloat()) buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) buffer.fixedColor(255, 255, 255, 255) - points.toList().zipWithNext().forEach { (a, b) -> + points.zipWithNext().forEach { (a, b) -> doLine(matrixStack.peek(), buffer, a.x, a.y, a.z, b.x, b.y, b.z) } buffer.unfixColor() @@ -173,7 +177,7 @@ class RenderInWorldContext private constructor( ) { val normal = Vector3f(x.toFloat(), y.toFloat(), z.toFloat()) .sub(i.toFloat(), j.toFloat(), k.toFloat()) - .mul(-1F) + .normalize() buf.vertex(matrix.positionMatrix, i.toFloat(), j.toFloat(), k.toFloat()) .normal(matrix.normalMatrix, normal.x, normal.y, normal.z).next() buf.vertex(matrix.positionMatrix, x.toFloat(), y.toFloat(), z.toFloat()) -- cgit