Add fishing particle highlighter

Currently does not work when sneaking or when the bobber moves too much, since the position desyncs with the server
This commit is contained in:
nea
2023-05-07 12:22:36 +02:00
parent 229f724ef4
commit 942dd629ef
13 changed files with 238 additions and 6 deletions

View File

@@ -12,6 +12,7 @@ ktor = "2.3.0"
dbus_java = "4.2.1" dbus_java = "4.2.1"
architectury = "8.1.79" architectury = "8.1.79"
neurepoparser = "0.0.1" neurepoparser = "0.0.1"
qolify = "1.2.2-1.19.4"
[libraries] [libraries]
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
@@ -30,11 +31,12 @@ architectury_fabric = { module = "dev.architectury:architectury-fabric", version
rei_fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" } rei_fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
devauth = { module = "me.djtheredstoner:DevAuth-fabric", version.ref = "devauth" } devauth = { module = "me.djtheredstoner:DevAuth-fabric", version.ref = "devauth" }
modmenu = { module = "maven.modrinth:modmenu", version.ref = "modmenu" } modmenu = { module = "maven.modrinth:modmenu", version.ref = "modmenu" }
qolify = { module = "maven.modrinth:qolify", version.ref = "qolify" }
[bundles] [bundles]
dbus = ["dbus_java_core", "dbus_java_unixsocket"] dbus = ["dbus_java_core", "dbus_java_unixsocket"]
runtime_required = ["architectury_fabric", "rei_fabric"] runtime_required = ["architectury_fabric", "rei_fabric"]
runtime_optional = ["devauth", "modmenu"] runtime_optional = ["devauth", "modmenu", "qolify"]

View File

@@ -0,0 +1,23 @@
package moe.nea.notenoughupdates.mixins;
import moe.nea.notenoughupdates.events.ParticleSpawnEvent;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
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 MixinClientPacketHandler {
@Inject(method = "onParticle", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V", shift = At.Shift.AFTER))
public void onParticleSpawn(ParticleS2CPacket packet, CallbackInfo ci) {
ParticleSpawnEvent.Companion.publish(new ParticleSpawnEvent(
packet.getParameters(),
new Vec3d(packet.getX(), packet.getY(), packet.getZ()),
new Vec3d(packet.getOffsetX(), packet.getOffsetY(), packet.getOffsetZ()),
packet.isLongDistance()
));
}
}

View File

@@ -0,0 +1,16 @@
package moe.nea.notenoughupdates.mixins.devenv;
import net.minecraft.scoreboard.Scoreboard;
import org.slf4j.Logger;
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.Redirect;
@Mixin(Scoreboard.class)
public class MixinScoreboard {
@Redirect(method = "addTeam", at=@At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V"))
public void onExistingteam(Logger instance, String s, Object o) {
// Ignore creations of existing teams
}
}

View File

@@ -0,0 +1,13 @@
package moe.nea.notenoughupdates.events
import net.minecraft.particle.ParticleEffect
import net.minecraft.util.math.Vec3d
data class ParticleSpawnEvent(
val particleEffect: ParticleEffect,
val position: Vec3d,
val offset: Vec3d,
val longDistance: Boolean,
) : NEUEvent() {
companion object : NEUEventBus<ParticleSpawnEvent>()
}

View File

@@ -3,6 +3,7 @@ package moe.nea.notenoughupdates.features
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import moe.nea.notenoughupdates.NotEnoughUpdates import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.features.fishing.FishingWarning
import moe.nea.notenoughupdates.features.world.FairySouls import moe.nea.notenoughupdates.features.world.FairySouls
import moe.nea.notenoughupdates.util.data.DataHolder import moe.nea.notenoughupdates.util.data.DataHolder
@@ -24,6 +25,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
synchronized(this) { synchronized(this) {
if (hasAutoloaded) return if (hasAutoloaded) return
loadFeature(FairySouls) loadFeature(FairySouls)
loadFeature(FishingWarning)
hasAutoloaded = true hasAutoloaded = true
} }
} }

View File

@@ -0,0 +1,117 @@
package moe.nea.notenoughupdates.features.fishing
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.acos
import kotlin.math.asin
import kotlin.math.atan2
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.notenoughupdates.events.ParticleSpawnEvent
import moe.nea.notenoughupdates.events.WorldReadyEvent
import moe.nea.notenoughupdates.events.WorldRenderLastEvent
import moe.nea.notenoughupdates.features.NEUFeature
import moe.nea.notenoughupdates.util.MC
import moe.nea.notenoughupdates.util.TimeMark
import moe.nea.notenoughupdates.util.config.ManagedConfig
import moe.nea.notenoughupdates.util.render.RenderBlockContext.Companion.renderBlocks
object FishingWarning : NEUFeature {
override val name: String
get() = "Fishing Warning"
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(acos(xOffset / 0.04))
var angleZ = Math.toDegrees(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 angleZ + dist / 2
}
private fun toDegrees(d: Double) = d * 180 / Math.PI
fun isHookPossible(hook: FishingBobberEntity, particlePos: Vec3d, angle1: Double, angle2: Double): Boolean {
val dx = particlePos.x - hook.pos.x
val dz = particlePos.z - hook.pos.z
val dist = sqrt(dx * dx + dz * dz)
if (dist < 0.2) return true
val tolerance = toDegrees(atan2(0.03125, dist)) * 1.5
var angleToHook = toDegrees(atan2(dx, dz)) % 360
if (angleToHook < 0) angleToHook += 360
return areAnglesClose(angle1, angleToHook, tolerance) || areAnglesClose(angle2, angleToHook, tolerance)
}
val recentParticles = mutableListOf<Pair<Vec3d, TimeMark>>()
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)
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 }
renderBlocks(it.matrices, it.camera) {
color(0f, 0f, 1f, 1f)
recentParticles.forEach {
tinyBlock(it.first, 0.1F)
}
}
}
}
}

View File

@@ -31,7 +31,7 @@ object FairySouls : NEUFeature {
object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "found-fairysouls", ::Data) object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "found-fairysouls", ::Data)
object TConfig : ManagedConfig("fairysouls") { object TConfig : ManagedConfig("fairy-souls") {
val displaySouls by toggle("show") { false } val displaySouls by toggle("show") { false }
val resetSouls by button("reset") { val resetSouls by button("reset") {

View File

@@ -6,6 +6,7 @@ import net.minecraft.util.math.BlockPos
object MC { object MC {
inline val player get() = MinecraftClient.getInstance().player inline val player get() = MinecraftClient.getInstance().player
inline val world get() = MinecraftClient.getInstance().world
} }
val Coordinate.blockPos: BlockPos val Coordinate.blockPos: BlockPos

View File

@@ -0,0 +1,15 @@
package moe.nea.notenoughupdates.util
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource
@OptIn(ExperimentalTime::class)
class TimeMark private constructor(private val timeMark: TimeSource.Monotonic.ValueTimeMark?) {
fun passedTime() = timeMark?.elapsedNow() ?: Duration.INFINITE
companion object {
fun now() = TimeMark(TimeSource.Monotonic.markNow())
fun farPast() = TimeMark(null)
}
}

View File

@@ -11,6 +11,7 @@ import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats import net.minecraft.client.render.VertexFormats
import net.minecraft.client.util.math.MatrixStack import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
class RenderBlockContext private constructor(private val tesselator: Tessellator, private val matrixStack: MatrixStack) { class RenderBlockContext private constructor(private val tesselator: Tessellator, private val matrixStack: MatrixStack) {
private val buffer = tesselator.buffer private val buffer = tesselator.buffer
@@ -21,7 +22,16 @@ class RenderBlockContext private constructor(private val tesselator: Tessellator
fun block(blockPos: BlockPos) { fun block(blockPos: BlockPos) {
matrixStack.push() matrixStack.push()
matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
RenderSystem.setShader(GameRenderer::getPositionColorProgram) buildCube(matrixStack.peek().positionMatrix, buffer)
tesselator.draw()
matrixStack.pop()
}
fun tinyBlock(vec3d: Vec3d, size: Float) {
matrixStack.push()
matrixStack.translate(vec3d.x, vec3d.y, vec3d.z)
matrixStack.scale(size, size, size)
matrixStack.translate(-.5, -.5, -.5)
buildCube(matrixStack.peek().positionMatrix, buffer) buildCube(matrixStack.peek().positionMatrix, buffer)
tesselator.draw() tesselator.draw()
matrixStack.pop() matrixStack.pop()
@@ -74,6 +84,7 @@ class RenderBlockContext private constructor(private val tesselator: Tessellator
RenderSystem.disableDepthTest() RenderSystem.disableDepthTest()
RenderSystem.enableBlend() RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc() RenderSystem.defaultBlendFunc()
RenderSystem.setShader(GameRenderer::getPositionColorProgram)
matrices.push() matrices.push()
matrices.translate(-camera.pos.x, -camera.pos.y, -camera.pos.z) matrices.translate(-camera.pos.x, -camera.pos.y, -camera.pos.z)

View File

@@ -19,6 +19,8 @@
"notenoughupdates.sbinfo.gametype": "Locraw Gametype: %s", "notenoughupdates.sbinfo.gametype": "Locraw Gametype: %s",
"notenoughupdates.sbinfo.mode": "Locraw Mode: %s", "notenoughupdates.sbinfo.mode": "Locraw Mode: %s",
"notenoughupdates.sbinfo.map": "Locraw Map: %s", "notenoughupdates.sbinfo.map": "Locraw Map: %s",
"neu.config.fairysouls.show": "Show Fairy Soul Waypoints", "neu.config.fairy-souls.show": "Show Fairy Soul Waypoints",
"neu.config.fairysouls.reset": "Reset Collected Fairy Souls" "neu.config.fairy-souls.reset": "Reset Collected Fairy Souls",
"neu.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish",
"neu.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles"
} }

View File

@@ -12,7 +12,9 @@
"devenv.DisableCommonPacketWarnings" "devenv.DisableCommonPacketWarnings"
], ],
"mixins": [ "mixins": [
"devenv.DisableInvalidFishingHook" "MixinClientPacketHandler",
"devenv.DisableInvalidFishingHook",
"devenv.MixinScoreboard"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1

View File

@@ -0,0 +1,28 @@
import kotlin.math.atan
private fun calculateAngleFromOffsets(xOffset: Double, zOffset: Double): Double {
var angleX = Math.toDegrees(Math.acos(xOffset / 0.04f))
var angleZ = Math.toDegrees(Math.asin(zOffset / 0.04f))
if (xOffset < 0) {
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 angleZ + dist / 2
}
fun main() {
for(i in 0..10) {
for (j in 0..10) {
println("${calculateAngleFromOffsets(i.toDouble(),j.toDouble())} ${atan(i.toDouble() / j.toDouble())}")
}
}
}