Add Nearby Burrow Highlighter

This commit is contained in:
Linnea Gräf
2024-01-18 20:00:47 +01:00
parent d7902e06cd
commit 608fec9cd0
11 changed files with 246 additions and 205 deletions

View File

@@ -13,6 +13,7 @@ import moe.nea.firmament.events.ParticleSpawnEvent;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
import net.minecraft.util.math.Vec3d;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -33,9 +34,10 @@ public abstract class IncomingPacketListenerPatches {
var event = new ParticleSpawnEvent(
packet.getParameters(),
new Vec3d(packet.getX(), packet.getY(), packet.getZ()),
new Vec3d(packet.getOffsetX(), packet.getOffsetY(), packet.getOffsetZ()),
new Vector3f(packet.getOffsetX(), packet.getOffsetY(), packet.getOffsetZ()),
packet.isLongDistance(),
packet.getCount()
packet.getCount(),
packet.getSpeed()
);
ParticleSpawnEvent.Companion.publish(event);
if (event.getCancelled())

View File

@@ -45,7 +45,7 @@ import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.ItemTooltipEvent
import moe.nea.firmament.events.ScreenRenderPostEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.registration.registerFirmamentChatEvents
import moe.nea.firmament.events.registration.registerFirmamentEvents
import moe.nea.firmament.features.FeatureManager
import moe.nea.firmament.repo.HypixelStaticData
import moe.nea.firmament.repo.RepoManager
@@ -139,7 +139,7 @@ object Firmament {
globalJob.cancel()
}
})
registerFirmamentChatEvents()
registerFirmamentEvents()
ItemTooltipCallback.EVENT.register { a, b, c ->
ItemTooltipEvent.publish(ItemTooltipEvent(a, b, c))
}

View File

@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.events
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.Hand
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
data class AttackBlockEvent(
val player: PlayerEntity,
val world: World,
val hand: Hand,
val blockPos: BlockPos,
val direction: Direction
) : FirmamentEvent.Cancellable() {
companion object : FirmamentEventBus<AttackBlockEvent>()
}

View File

@@ -6,15 +6,17 @@
package moe.nea.firmament.events
import org.joml.Vector3f
import net.minecraft.particle.ParticleEffect
import net.minecraft.util.math.Vec3d
data class ParticleSpawnEvent(
val particleEffect: ParticleEffect,
val position: Vec3d,
val offset: Vec3d,
val offset: Vector3f,
val longDistance: Boolean,
val count: Int,
val speed: Float,
) : FirmamentEvent.Cancellable() {
companion object : FirmamentEventBus<ParticleSpawnEvent>()
}

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.events
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.Hand
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.world.World
data class UseBlockEvent(val player: PlayerEntity, val world: World, val hand: Hand, val hitResult: BlockHitResult) : FirmamentEvent.Cancellable() {
companion object : FirmamentEventBus<UseBlockEvent>()
}

View File

@@ -6,24 +6,29 @@
package moe.nea.firmament.events.registration
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents
import net.fabricmc.fabric.api.event.player.AttackBlockCallback
import net.fabricmc.fabric.api.event.player.UseBlockCallback
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import moe.nea.firmament.events.AllowChatEvent
import moe.nea.firmament.events.AttackBlockEvent
import moe.nea.firmament.events.ModifyChatEvent
import moe.nea.firmament.events.ProcessChatEvent
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents
import net.minecraft.text.Text
import moe.nea.firmament.events.UseBlockEvent
private var lastReceivedMessage: Text? = null
fun registerFirmamentChatEvents() {
fun registerFirmamentEvents() {
ClientReceiveMessageEvents.ALLOW_CHAT.register(ClientReceiveMessageEvents.AllowChat { message, signedMessage, sender, params, receptionTimestamp ->
lastReceivedMessage = message
!ProcessChatEvent.publish(ProcessChatEvent(message, false)).cancelled
&& !AllowChatEvent.publish(AllowChatEvent(message)).cancelled
&& !AllowChatEvent.publish(AllowChatEvent(message)).cancelled
})
ClientReceiveMessageEvents.ALLOW_GAME.register(ClientReceiveMessageEvents.AllowGame { message, overlay ->
lastReceivedMessage = message
overlay || (!ProcessChatEvent.publish(ProcessChatEvent(message, false)).cancelled &&
!AllowChatEvent.publish(AllowChatEvent(message)).cancelled)
!AllowChatEvent.publish(AllowChatEvent(message)).cancelled)
})
ClientReceiveMessageEvents.MODIFY_GAME.register(ClientReceiveMessageEvents.ModifyGame { message, overlay ->
if (overlay) message
@@ -39,4 +44,15 @@ fun registerFirmamentChatEvents() {
ProcessChatEvent.publish(ProcessChatEvent(message, true))
}
})
AttackBlockCallback.EVENT.register(AttackBlockCallback { player, world, hand, pos, direction ->
if (AttackBlockEvent.publish(AttackBlockEvent(player, world, hand, pos, direction)).cancelled)
ActionResult.CONSUME
else ActionResult.PASS
})
UseBlockCallback.EVENT.register(UseBlockCallback { player, world, hand, hitResult ->
if (UseBlockEvent.publish(UseBlockEvent(player, world, hand, hitResult)).cancelled)
ActionResult.CONSUME
else ActionResult.PASS
})
}

View File

@@ -13,6 +13,7 @@ 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.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.render.RenderInWorldContext
@@ -28,6 +29,8 @@ object AncestralSpadeSolver {
fun onParticleSpawn(event: ParticleSpawnEvent) {
if (!DianaWaypoints.TConfig.ancestralSpadeSolver) return
if (event.particleEffect != ParticleTypes.DRIPPING_LAVA) return
if (event.offset.x != 0.0F || event.offset.y != 0F || event.offset.z != 0F)
return
particlePositions.add(event.position)
if (particlePositions.size > 20) {
particlePositions.removeFirst()
@@ -90,4 +93,11 @@ object AncestralSpadeSolver {
}
}
fun onSwapWorld(event: WorldReadyEvent) {
nextGuess = null
particlePositions.clear()
pitches.clear()
lastDing = TimeMark.farPast()
}
}

View File

@@ -6,26 +6,41 @@
package moe.nea.firmament.features.diana
import moe.nea.firmament.events.AttackBlockEvent
import moe.nea.firmament.events.ParticleSpawnEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.SoundReceiveEvent
import moe.nea.firmament.events.UseBlockEvent
import moe.nea.firmament.events.WorldReadyEvent
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
override val identifier get() = "diana-waypoints"
override val config get() = TConfig
object TConfig : ManagedConfig(identifier) {
val ancestralSpadeSolver by toggle("ancestral-spade") { false }
}
override fun onLoad() {
ParticleSpawnEvent.subscribe(NearbyBurrowsSolver::onParticles)
WorldReadyEvent.subscribe(NearbyBurrowsSolver::onSwapWorld)
WorldRenderLastEvent.subscribe(NearbyBurrowsSolver::onRender)
UseBlockEvent.subscribe {
NearbyBurrowsSolver.onBlockClick(it.hitResult.blockPos)
}
AttackBlockEvent.subscribe {
NearbyBurrowsSolver.onBlockClick(it.blockPos)
}
ProcessChatEvent.subscribe(NearbyBurrowsSolver::onChatEvent)
ParticleSpawnEvent.subscribe(AncestralSpadeSolver::onParticleSpawn)
SoundReceiveEvent.subscribe(AncestralSpadeSolver::onPlaySound)
WorldRenderLastEvent.subscribe(AncestralSpadeSolver::onWorldRender)
WorldReadyEvent.subscribe(AncestralSpadeSolver::onSwapWorld)
}
}

View File

@@ -0,0 +1,135 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.diana
import kotlin.time.Duration.Companion.seconds
import net.minecraft.particle.ParticleTypes
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.Position
import moe.nea.firmament.events.ParticleSpawnEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.mutableMapWithMaxSize
import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld
object NearbyBurrowsSolver {
private val recentlyDugBurrows: MutableMap<BlockPos, TimeMark> = mutableMapWithMaxSize(20)
private val recentEnchantParticles: MutableMap<BlockPos, TimeMark> = mutableMapWithMaxSize(500)
private var lastBlockClick: BlockPos? = null
enum class BurrowType {
START, MOB, TREASURE
}
val burrows = mutableMapOf<BlockPos, BurrowType>()
fun onChatEvent(event: ProcessChatEvent) {
val lastClickedBurrow = lastBlockClick ?: return
if (event.unformattedString.startsWith("You dug out a Griffin Burrow!") ||
event.unformattedString.startsWith(" ☠ You were killed by") ||
event.unformattedString.startsWith("You finished the Griffin burrow chain!")
) {
markAsDug(lastClickedBurrow)
burrows.remove(lastClickedBurrow)
}
}
fun wasRecentlyDug(blockPos: BlockPos): Boolean {
val lastDigTime = recentlyDugBurrows[blockPos] ?: TimeMark.farPast()
return lastDigTime.passedTime() < 10.seconds
}
fun markAsDug(blockPos: BlockPos) {
recentlyDugBurrows[blockPos] = TimeMark.now()
}
fun wasRecentlyEnchanted(blockPos: BlockPos): Boolean {
val lastEnchantTime = recentEnchantParticles[blockPos] ?: TimeMark.farPast()
return lastEnchantTime.passedTime() < 4.seconds
}
fun markAsEnchanted(blockPos: BlockPos) {
recentEnchantParticles[blockPos] = TimeMark.now()
}
fun onParticles(event: ParticleSpawnEvent) {
val position: BlockPos = event.position.toBlockPos().down()
if (wasRecentlyDug(position)) return
val isEven50Spread = (event.offset.x == 0.5f && event.offset.z == 0.5f)
if (event.particleEffect.type == ParticleTypes.ENCHANT) {
if (event.count == 5 && event.speed == 0.05F && event.offset.y == 0.4F && isEven50Spread) {
markAsEnchanted(position)
}
return
}
if (!wasRecentlyEnchanted(position)) return
if (event.particleEffect.type == ParticleTypes.ENCHANTED_HIT
&& event.count == 4
&& event.speed == 0.01F
&& event.offset.y == 0.1f
&& isEven50Spread
) {
burrows[position] = BurrowType.START
}
if (event.particleEffect.type == ParticleTypes.CRIT
&& event.count == 3
&& event.speed == 0.01F
&& event.offset.y == 0.1F
&& isEven50Spread
) {
burrows[position] = BurrowType.MOB
}
if (event.particleEffect.type == ParticleTypes.DRIPPING_LAVA
&& event.count == 2
&& event.speed == 0.01F
&& event.offset.y == 0.1F
&& event.offset.x == 0.35F && event.offset.z == 0.35f
) {
burrows[position] = BurrowType.TREASURE
}
}
fun onRender(event: WorldRenderLastEvent) {
renderInWorld(event) {
for ((location, burrow) in burrows) {
when (burrow) {
BurrowType.START -> color(.2f, .8f, .2f, 0.4f)
BurrowType.MOB -> color(0.3f, 0.4f, 0.9f, 0.4f)
BurrowType.TREASURE -> color(1f, 0.7f, 0.2f, 0.4f)
}
block(location)
}
}
}
fun onSwapWorld(worldReadyEvent: WorldReadyEvent) {
burrows.clear()
recentEnchantParticles.clear()
recentlyDugBurrows.clear()
lastBlockClick = null
}
fun onBlockClick(blockPos: BlockPos) {
lastBlockClick = blockPos
}
}
fun Position.toBlockPos(): BlockPos {
return BlockPos(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z))
}

View File

@@ -1,191 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.fishing
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.time.Duration.Companion.seconds
import net.minecraft.entity.projectile.FishingBobberEntity
import net.minecraft.particle.ParticleTypes
import net.minecraft.util.math.Vec3d
import moe.nea.firmament.Firmament
import moe.nea.firmament.events.ParticleSpawnEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.features.debug.DebugView
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld
object FishingWarning : FirmamentFeature {
override val identifier: String
get() = "fishing-warning"
object TConfig : ManagedConfig("fishing-warning") {
// Display a warning when you are about to hook a fish
val displayWarning by toggle("display-warning") { false }
val highlightWakeChain by toggle("highlight-wake-chain") { false }
}
override val config: ManagedConfig get() = TConfig
data class WakeChain(
val delta: Vec3d,
val momentum: Vec3d,
val lastContinued: TimeMark,
)
val chains = mutableListOf<WakeChain>()
private fun areAnglesClose(a: Double, b: Double, tolerance: Double): Boolean {
var dist = (a - b).absoluteValue
if (180 < dist) dist = 360 - dist;
return dist <= tolerance
}
private fun calculateAngleFromOffsets(xOffset: Double, zOffset: Double): Double {
// See also: Vanilla 1.8.9 Fishing particle code.
var angleX = Math.toDegrees(Math.acos(xOffset / 0.04))
var angleZ = Math.toDegrees(Math.asin(zOffset / 0.04))
if (xOffset < 0) {
// Old: angleZ = 180 - angleZ;
angleZ = 180 - angleZ
}
if (zOffset < 0) {
angleX = 360 - angleX
}
angleX %= 360.0
angleZ %= 360.0
if (angleX < 0) angleX += 360.0
if (angleZ < 0) angleZ += 360.0
var dist = angleX - angleZ
if (dist < -180) dist += 360.0
if (dist > 180) dist -= 360.0
return Math.toDegrees(Math.atan2(xOffset, zOffset))
return angleZ + dist / 2 + 180
}
val π = Math.PI
val τ = Math.PI * 2
private fun toDegrees(d: Double) = Math.toDegrees(d).mod(360.0)
private fun toRadians(d: Double) = Math.toRadians(d).mod(τ)
fun isHookPossible(hook: FishingBobberEntity, particlePos: Vec3d, angle1: Double, angle2: Double): Boolean {
val dx = particlePos.x - hook.trackedPosition.withDelta(0, 0, 0).x
val dz = particlePos.z - hook.trackedPosition.withDelta(0, 0, 0).z
val dist = sqrt(dx * dx + dz * dz)
if (dist < 0.2) return true
val tolerance = toDegrees(atan2(0.03125, dist)) * 1.5
val angleToHook = toDegrees(atan2(dz, dx))
return areAnglesClose(angle1, angleToHook, tolerance) || areAnglesClose(angle2, angleToHook, tolerance)
}
val recentParticles = mutableListOf<Pair<Vec3d, TimeMark>>()
data class Candidate(
val angle1: Double,
val angle2: Double,
val hookOrigin: Vec3d,
val position: Vec3d,
val timeMark: TimeMark = TimeMark.now()
)
val recentCandidates = mutableListOf<Candidate>()
private fun onParticleSpawn(event: ParticleSpawnEvent) {
if (event.particleEffect.type != ParticleTypes.FISHING) return
if (!(abs(event.offset.y - 0.01f) < 0.001f)) return
val hook = MC.player?.fishHook ?: return
val actualOffset = event.offset
val candidate1 = calculateAngleFromOffsets(-actualOffset.x, (-actualOffset.z))
val candidate2 = calculateAngleFromOffsets(actualOffset.x, actualOffset.z)
recentCandidates.add(Candidate(candidate1, candidate2, hook.trackedPosition.withDelta(0, 0, 0), event.position))
if (isHookPossible(hook, event.position, candidate1, candidate2)) {
recentParticles.add(Pair(event.position, TimeMark.now()))
}
}
override fun onLoad() {
ParticleSpawnEvent.subscribe(::onParticleSpawn)
WorldReadyEvent.subscribe {
recentParticles.clear()
}
WorldRenderLastEvent.subscribe {
recentParticles.removeIf { it.second.passedTime() > 5.seconds }
recentCandidates.removeIf { it.timeMark.passedTime() > 5.seconds }
renderInWorld(it) {
color(0f, 0f, 1f, 1f)
recentParticles.forEach {
tinyBlock(it.first, 0.1F)
}
if (Firmament.DEBUG) {
recentCandidates.forEach {
color(1f, 1f, 0f, 1f)
line(it.hookOrigin, it.position)
color(1f, 0f, 0f, 1f)
fun P(yaw: Double) = Vec3d(cos(yaw), 0.0, sin(yaw))
line(
it.position,
P(π - toRadians(it.angle1)).multiply(5.0).add(it.position)
)
color(0f, 1f, 0f, 1f)
line(
it.position,
P(π - toRadians(it.angle2)).multiply(5.0).add(it.position)
)
val tolerance = (atan2(0.03125, it.position.distanceTo(it.hookOrigin))).absoluteValue * 1.5
val diff = it.hookOrigin.subtract(it.position)
val rd = atan2(diff.z, diff.x).mod(τ)
color(0.8f, 0f, 0.8f, 1f)
DebugView.showVariable("tolerance", tolerance)
DebugView.showVariable("angle1Rad", toRadians(180 - it.angle1))
DebugView.showVariable("angle1Diff", (toRadians(it.angle1) - rd).mod(τ))
DebugView.showVariable("angle1Deg", it.angle1.mod(360.0))
DebugView.showVariable("angle2Rad", toRadians(180 - it.angle2))
DebugView.showVariable("angle2Deg", it.angle2.mod(360.0))
DebugView.showVariable("angle2Diff", (toRadians(it.angle2) - rd).mod(τ))
DebugView.showVariable("rd", rd)
DebugView.showVariable("minT", (rd + tolerance).mod(τ))
DebugView.showVariable("maxT", (rd - tolerance).mod(τ))
DebugView.showVariable(
"passes",
if (min(
(rd - toRadians(180 - it.angle2)).mod(τ),
(rd - toRadians(180 - it.angle1)).mod(τ)
) < tolerance
) {
"§aPasses"
} else {
"§cNo Pass"
}
)
line(it.position, P(rd + tolerance).add(it.position))
line(it.position, P(rd - tolerance).add(it.position))
}
color(0.8F, 0.8F, 0.8f, 1f)
val fishHook = MC.player?.fishHook
if (fishHook != null)
tinyBlock(fishHook.trackedPosition.withDelta(0, 0, 0), 0.2f)
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.util
fun <K, V> mutableMapWithMaxSize(maxSize: Int): MutableMap<K, V> = object : LinkedHashMap<K, V>() {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>): Boolean {
return size > maxSize
}
}