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"
|
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"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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") {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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.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)
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
"devenv.DisableCommonPacketWarnings"
|
"devenv.DisableCommonPacketWarnings"
|
||||||
],
|
],
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"devenv.DisableInvalidFishingHook"
|
"MixinClientPacketHandler",
|
||||||
|
"devenv.DisableInvalidFishingHook",
|
||||||
|
"devenv.MixinScoreboard"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"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