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:
@@ -12,6 +12,7 @@ ktor = "2.3.0"
|
||||
dbus_java = "4.2.1"
|
||||
architectury = "8.1.79"
|
||||
neurepoparser = "0.0.1"
|
||||
qolify = "1.2.2-1.19.4"
|
||||
|
||||
[libraries]
|
||||
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" }
|
||||
devauth = { module = "me.djtheredstoner:DevAuth-fabric", version.ref = "devauth" }
|
||||
modmenu = { module = "maven.modrinth:modmenu", version.ref = "modmenu" }
|
||||
qolify = { module = "maven.modrinth:qolify", version.ref = "qolify" }
|
||||
|
||||
[bundles]
|
||||
dbus = ["dbus_java_core", "dbus_java_unixsocket"]
|
||||
runtime_required = ["architectury_fabric", "rei_fabric"]
|
||||
runtime_optional = ["devauth", "modmenu"]
|
||||
runtime_optional = ["devauth", "modmenu", "qolify"]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>()
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package moe.nea.notenoughupdates.features
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.serializer
|
||||
import moe.nea.notenoughupdates.NotEnoughUpdates
|
||||
import moe.nea.notenoughupdates.features.fishing.FishingWarning
|
||||
import moe.nea.notenoughupdates.features.world.FairySouls
|
||||
import moe.nea.notenoughupdates.util.data.DataHolder
|
||||
|
||||
@@ -24,6 +25,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
|
||||
synchronized(this) {
|
||||
if (hasAutoloaded) return
|
||||
loadFeature(FairySouls)
|
||||
loadFeature(FishingWarning)
|
||||
hasAutoloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ object FairySouls : NEUFeature {
|
||||
object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "found-fairysouls", ::Data)
|
||||
|
||||
|
||||
object TConfig : ManagedConfig("fairysouls") {
|
||||
object TConfig : ManagedConfig("fairy-souls") {
|
||||
|
||||
val displaySouls by toggle("show") { false }
|
||||
val resetSouls by button("reset") {
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.minecraft.util.math.BlockPos
|
||||
|
||||
object MC {
|
||||
inline val player get() = MinecraftClient.getInstance().player
|
||||
inline val world get() = MinecraftClient.getInstance().world
|
||||
}
|
||||
|
||||
val Coordinate.blockPos: BlockPos
|
||||
|
||||
15
src/main/kotlin/moe/nea/notenoughupdates/util/TimeMark.kt
Normal file
15
src/main/kotlin/moe/nea/notenoughupdates/util/TimeMark.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import net.minecraft.client.render.VertexFormat
|
||||
import net.minecraft.client.render.VertexFormats
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Vec3d
|
||||
|
||||
class RenderBlockContext private constructor(private val tesselator: Tessellator, private val matrixStack: MatrixStack) {
|
||||
private val buffer = tesselator.buffer
|
||||
@@ -21,7 +22,16 @@ class RenderBlockContext private constructor(private val tesselator: Tessellator
|
||||
fun block(blockPos: BlockPos) {
|
||||
matrixStack.push()
|
||||
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)
|
||||
tesselator.draw()
|
||||
matrixStack.pop()
|
||||
@@ -74,6 +84,7 @@ class RenderBlockContext private constructor(private val tesselator: Tessellator
|
||||
RenderSystem.disableDepthTest()
|
||||
RenderSystem.enableBlend()
|
||||
RenderSystem.defaultBlendFunc()
|
||||
RenderSystem.setShader(GameRenderer::getPositionColorProgram)
|
||||
|
||||
matrices.push()
|
||||
matrices.translate(-camera.pos.x, -camera.pos.y, -camera.pos.z)
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
"notenoughupdates.sbinfo.gametype": "Locraw Gametype: %s",
|
||||
"notenoughupdates.sbinfo.mode": "Locraw Mode: %s",
|
||||
"notenoughupdates.sbinfo.map": "Locraw Map: %s",
|
||||
"neu.config.fairysouls.show": "Show Fairy Soul Waypoints",
|
||||
"neu.config.fairysouls.reset": "Reset Collected Fairy Souls"
|
||||
"neu.config.fairy-souls.show": "Show Fairy Soul Waypoints",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
"devenv.DisableCommonPacketWarnings"
|
||||
],
|
||||
"mixins": [
|
||||
"devenv.DisableInvalidFishingHook"
|
||||
"MixinClientPacketHandler",
|
||||
"devenv.DisableInvalidFishingHook",
|
||||
"devenv.MixinScoreboard"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
28
src/test/kotlin/AngleTest.kt
Normal file
28
src/test/kotlin/AngleTest.kt
Normal 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())}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user