diff options
13 files changed, 238 insertions, 6 deletions
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 934f853..3c87247 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"] diff --git a/src/main/java/moe/nea/notenoughupdates/mixins/MixinClientPacketHandler.java b/src/main/java/moe/nea/notenoughupdates/mixins/MixinClientPacketHandler.java new file mode 100644 index 0000000..dc57113 --- /dev/null +++ b/src/main/java/moe/nea/notenoughupdates/mixins/MixinClientPacketHandler.java @@ -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() + )); + } +} diff --git a/src/main/java/moe/nea/notenoughupdates/mixins/devenv/MixinScoreboard.java b/src/main/java/moe/nea/notenoughupdates/mixins/devenv/MixinScoreboard.java new file mode 100644 index 0000000..306b900 --- /dev/null +++ b/src/main/java/moe/nea/notenoughupdates/mixins/devenv/MixinScoreboard.java @@ -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 + } +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/events/ParticleSpawnEvent.kt b/src/main/kotlin/moe/nea/notenoughupdates/events/ParticleSpawnEvent.kt new file mode 100644 index 0000000..23c67a0 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/events/ParticleSpawnEvent.kt @@ -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>() +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt index bc9a916..2512992 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt @@ -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 } } diff --git a/src/main/kotlin/moe/nea/notenoughupdates/features/fishing/FishingWarning.kt b/src/main/kotlin/moe/nea/notenoughupdates/features/fishing/FishingWarning.kt new file mode 100644 index 0000000..c44201c --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/features/fishing/FishingWarning.kt @@ -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) + } + } + } + } +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt b/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt index 2a5219a..ae045c0 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt @@ -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") { diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/MC.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/MC.kt index 36aa726..956a330 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/util/MC.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/MC.kt @@ -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 diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/TimeMark.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/TimeMark.kt new file mode 100644 index 0000000..5196606 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/TimeMark.kt @@ -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) + } +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/render/block.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/render/block.kt index 5fc70ef..0d6c63b 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/util/render/block.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/render/block.kt @@ -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) diff --git a/src/main/resources/assets/notenoughupdates/lang/en_us.json b/src/main/resources/assets/notenoughupdates/lang/en_us.json index e9d8ea5..e688eb7 100644 --- a/src/main/resources/assets/notenoughupdates/lang/en_us.json +++ b/src/main/resources/assets/notenoughupdates/lang/en_us.json @@ -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" } diff --git a/src/main/resources/notenoughupdates.mixins.json b/src/main/resources/notenoughupdates.mixins.json index ce2317a..2b3a4e2 100644 --- a/src/main/resources/notenoughupdates.mixins.json +++ b/src/main/resources/notenoughupdates.mixins.json @@ -12,7 +12,9 @@ "devenv.DisableCommonPacketWarnings" ], "mixins": [ - "devenv.DisableInvalidFishingHook" + "MixinClientPacketHandler", + "devenv.DisableInvalidFishingHook", + "devenv.MixinScoreboard" ], "injectors": { "defaultRequire": 1 diff --git a/src/test/kotlin/AngleTest.kt b/src/test/kotlin/AngleTest.kt new file mode 100644 index 0000000..4be6086 --- /dev/null +++ b/src/test/kotlin/AngleTest.kt @@ -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())}") + } + } +} |