From 504924b65b0b1cf6bc5deaf2a46e2162504d63bf Mon Sep 17 00:00:00 2001 From: ILike2WatchMemes Date: Sun, 10 Mar 2024 20:48:46 +0100 Subject: Feature: Lane Switching Notification (#1075) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../skyhanni/features/garden/GardenAPI.kt | 2 + .../skyhanni/features/garden/GardenPlotAPI.kt | 10 +- .../garden/farming/LaneSwitchNotification.kt | 126 +++++++++++++++++++++ .../features/garden/farming/LaneSwitchUtils.kt | 104 +++++++++++++++++ 4 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchNotification.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchUtils.kt (limited to 'src/main/java/at/hannibal2/skyhanni/features/garden') diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenAPI.kt index cf9fbea6f..76e4cf872 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenAPI.kt @@ -143,6 +143,8 @@ object GardenAPI { fun inGarden() = IslandType.GARDEN.isInIsland() + fun isCurrentlyFarming() = inGarden() && GardenCropSpeed.averageBlocksPerSecond > 0.0 + fun ItemStack.getCropType(): CropType? { val internalName = getInternalName() return CropType.entries.firstOrNull { internalName.startsWith(it.toolName) } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenPlotAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenPlotAPI.kt index b6825bf43..cac76452e 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenPlotAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenPlotAPI.kt @@ -6,6 +6,7 @@ import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent import at.hannibal2.skyhanni.features.garden.pests.SprayType import at.hannibal2.skyhanni.features.misc.LockMouseLook import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.LocationUtils.isPlayerInside import at.hannibal2.skyhanni.utils.LorenzVec @@ -39,7 +40,7 @@ object GardenPlotAPI { return plots.firstOrNull { it.isPlayerInside() } } - class Plot(val id: Int, var inventorySlot: Int, val box: AxisAlignedBB, val middle: LorenzVec) + class Plot(val id: Int, var unlocked: Boolean, var inventorySlot: Int, val box: AxisAlignedBB, val middle: LorenzVec) class PlotData( @Expose @@ -133,7 +134,7 @@ object GardenPlotAPI { val b = LorenzVec(maxX, 256.0, maxY) val middle = a.interpolate(b, 0.5).copy(y = 10.0) val box = a.axisAlignedTo(b).expand(0.0001, 0.0, 0.0001) - list.add(Plot(id, slot, box, middle)) + list.add(Plot(id, false, slot, box, middle)) slot++ } slot += 4 @@ -162,8 +163,9 @@ object GardenPlotAPI { if (event.inventoryName != "Configure Plots") return for (plot in plots) { - val itemName = event.inventoryItems[plot.inventorySlot]?.name ?: continue - plotNamePattern.matchMatcher(itemName) { + val itemStack = event.inventoryItems[plot.inventorySlot] ?: continue + plot.unlocked = itemStack.getLore().all { !it.contains("§7Cost:") } + plotNamePattern.matchMatcher(itemStack.name) { plot.name = group("name") } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchNotification.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchNotification.kt new file mode 100644 index 000000000..ed1c5f074 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchNotification.kt @@ -0,0 +1,126 @@ +package at.hannibal2.skyhanni.features.garden.farming + +import at.hannibal2.skyhanni.config.features.garden.laneswitch.LaneSwitchNotificationSettings +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.features.garden.GardenAPI +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI.plots +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.LocationUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.LorenzUtils.sendTitle +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RenderUtils.renderString +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.SoundUtils +import at.hannibal2.skyhanni.utils.SoundUtils.playSound +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.math.absoluteValue +import kotlin.time.Duration.Companion.seconds + +class LaneSwitchNotification { + + private val config get() = GardenAPI.config.laneswitch + + private var bps = 0.0 // Blocks per Second + private var distancesUntilSwitch: List = listOf() + private var lastBps = 0.0 // Last blocks per Second + private var lastPosition = LorenzVec(0, 0, 0) + private var lastLaneSwitch = SimpleTimeMark.farPast() + private var lastWarning = SimpleTimeMark.farPast() + private var lastDistancesUntilSwitch: List = listOf() + private var lastDistance = 0.0 + + companion object { + private val config get() = GardenAPI.config.laneswitch + + @JvmStatic + fun playUserSound() { + SoundUtils.createSound( + config.notification.sound.notificationSound, + config.notification.sound.notificationPitch, + ).playSound() + } + } + + private fun switchPossibleInTime(from: LorenzVec, to: LorenzVec, speed: Double, time: Int): Boolean { + return from.distance(to) <= speed * time + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + val settings = config.notification.settings + val plot = GardenPlotAPI.getCurrentPlot() ?: return + if (!plot.unlocked) return + + val plotIndex = plots.indexOf(plot) + val positon = LocationUtils.playerLocation() + val farmEnd = LaneSwitchUtils.getFarmBounds(plotIndex, positon, lastPosition) ?: return + lastPosition = positon + bps = LocationUtils.distanceFromPreviousTick() + distancesUntilSwitch = farmEnd.map { end -> end.distance(positon).round(2) } + + testForLaneSwitch(settings, farmEnd, positon) + lastBps = bps + } + + private fun testForLaneSwitch( + settings: LaneSwitchNotificationSettings, + farmEnd: List, + positon: LorenzVec, + ) { + val farmLength = farmEnd[0].distance(farmEnd[1]) + // farmLength / bps to get the time needed to travel the distance, - the threshold times the farm length divided by the length of 2 plots (to give some room) + val threshold = settings.threshold + // TODO find a name for this variable + val FIND_A_NAME_FOR_ME = threshold * (farmLength / 192) + val farmTraverseTime = ((farmLength / bps) - FIND_A_NAME_FOR_ME).seconds + val bpsDifference = (bps - lastBps).absoluteValue + + if (farmEnd.isEmpty() || lastLaneSwitch.passedSince() < farmTraverseTime || bpsDifference > 20) return + if (!farmEnd.any { switchPossibleInTime(positon, it, bps, threshold) }) return + + with(settings) { + sendTitle(color.getChatColor() + text, duration.seconds) + } + playUserSound() + lastLaneSwitch = SimpleTimeMark.now() + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!config.distanceUntilSwitch || !isEnabled()) return + if (distancesUntilSwitch.isEmpty()) return + if (lastDistancesUntilSwitch.isEmpty()) { + lastDistancesUntilSwitch = distancesUntilSwitch + } + + val distances = listOf( + distancesUntilSwitch[0] - lastDistancesUntilSwitch[0], + distancesUntilSwitch[1] - lastDistancesUntilSwitch[1] + ) //Get changes in the distances + val distance = if (distances.all { it != 0.0 }) { + if (distances[0] > 0) distancesUntilSwitch[1] else distancesUntilSwitch[0] // get the direction the player is traveling and get the distance to display from that + } else { + lastDistance // display last value if no change is detected + } + + config.distanceUntilSwitchPos.renderString("Distance until Switch: $distance", posLabel = "Movement Speed") + lastDistancesUntilSwitch = distancesUntilSwitch + lastDistance = distance + } + + private fun plotsLoaded(): Boolean { + if (plots.any { it.unlocked }) return true + + if (lastWarning.passedSince() >= 30.seconds) { + ChatUtils.clickableChat("§eOpen your configure plots for lane switch detection to work.", "/desk") + lastWarning = SimpleTimeMark.now() + } + return false + } + + private fun isEnabled() = GardenAPI.isCurrentlyFarming() && config.enabled && plotsLoaded() +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchUtils.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchUtils.kt new file mode 100644 index 000000000..1bdff35de --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/LaneSwitchUtils.kt @@ -0,0 +1,104 @@ +package at.hannibal2.skyhanni.features.garden.farming + +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI.isBarn +import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.LorenzVec +import kotlin.math.absoluteValue + +object LaneSwitchUtils { + + enum class Direction { + WEST_EAST, + NORTH_SOUTH, + ; + } + + enum class Value { + MIN, + MAX, + TOP, + BOTTOM, + ; + } + + fun getFarmBounds(plotIndex: Int, current: LorenzVec, last: LorenzVec): List? { + if (GardenPlotAPI.plots[plotIndex].isBarn() || plotIndex == 12) return null + val xVelocity = current.x - last.x + val zVelocity = current.z - last.z + return if (xVelocity.absoluteValue > zVelocity.absoluteValue) { + var xValueMin = 0.0 + var xValueMax = 0.0 + + for (i in 0..4) { + if (isBoundaryPlot(plotIndex - i, Direction.WEST_EAST, Value.MIN)) { + xValueMin = GardenPlotAPI.plots[plotIndex - i].box.minX; break + } + } + for (i in 0..4) { + if (isBoundaryPlot(plotIndex + i, Direction.WEST_EAST, Value.MAX)) { + xValueMax = GardenPlotAPI.plots[plotIndex + i].box.maxX; break + } + } + + val a = LorenzVec(xValueMin, current.y, current.z) + val b = LorenzVec(xValueMax, current.y, current.z) + listOf(a, b) + } else if (xVelocity.absoluteValue < zVelocity.absoluteValue) { + // i * 5 because going vertically is always 5 plots before or after the current + var zValueTop = 0.0 + var zValueBottom = 0.0 + + for (i in 0..4) { + if (isBoundaryPlot(plotIndex - (i * 5), Direction.NORTH_SOUTH, Value.TOP)) { + zValueTop = GardenPlotAPI.plots[plotIndex - (i * 5)].box.minZ; break + } + } + for (i in 0..4) { + if (isBoundaryPlot(plotIndex + (i * 5), Direction.NORTH_SOUTH, Value.BOTTOM)) { + zValueBottom = GardenPlotAPI.plots[plotIndex + (i * 5)].box.maxZ; break + } + } + + val a = LorenzVec(current.x, current.y, zValueTop) + val b = LorenzVec(current.x, current.y, zValueBottom) + listOf(a, b) + } else null + } + + private fun isBoundaryPlot(plotIndex: Int, direction: Direction, value: Value): Boolean { + if (direction == Direction.WEST_EAST) { + val isNextNewRow: Boolean + val isNextUnlocked: Boolean + val isNextBarn: Boolean + if (value == Value.MIN) { + if (plotIndex - 1 == -1) return true // check if next plot is out of bounds + //Check if the next plot's border is 240 and therefore in the previous row + isNextNewRow = GardenPlotAPI.plots[plotIndex - 1].box.maxX.absoluteValue.round(0) == 240.0 + isNextUnlocked = GardenPlotAPI.plots[plotIndex - 1].unlocked + isNextBarn = GardenPlotAPI.plots[plotIndex - 1].isBarn() + } else { + if (plotIndex + 1 == 25) return true // check if next plot is out of bounds + isNextNewRow = (plotIndex + 1) % 5 == 0 + isNextUnlocked = GardenPlotAPI.plots[plotIndex + 1].unlocked + isNextBarn = GardenPlotAPI.plots[plotIndex + 1].isBarn() + } + return isNextNewRow || !isNextUnlocked || isNextBarn + } else if (direction == Direction.NORTH_SOUTH) { + val isNextUnlocked: Boolean + val isNextBarn: Boolean + + if (value == Value.MAX) { + if (plotIndex - 1 == -1 || (plotIndex - 5) < 0) return true // check if next plot is out of bounds + isNextUnlocked = GardenPlotAPI.plots[plotIndex - 5].unlocked + isNextBarn = GardenPlotAPI.plots[plotIndex - 5].isBarn() + } else { + if (plotIndex + 5 > 24) return true // check if next plot is out of bounds + isNextUnlocked = GardenPlotAPI.plots[plotIndex + 5].unlocked + isNextBarn = GardenPlotAPI.plots[plotIndex + 5].isBarn() + } + return !isNextUnlocked || isNextBarn + } + return false + } +} -- cgit