diff options
8 files changed, 186 insertions, 15 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 0a8e08ce5..7bcf31f72 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -185,6 +185,7 @@ import at.hannibal2.skyhanni.features.garden.inventory.SkyMartCopperPrice import at.hannibal2.skyhanni.features.garden.pests.PestFinder import at.hannibal2.skyhanni.features.garden.pests.PestSpawn import at.hannibal2.skyhanni.features.garden.pests.PestSpawnTimer +import at.hannibal2.skyhanni.features.garden.pests.SprayDisplay import at.hannibal2.skyhanni.features.garden.pests.SprayFeatures import at.hannibal2.skyhanni.features.garden.visitor.GardenVisitorColorNames import at.hannibal2.skyhanni.features.garden.visitor.GardenVisitorDropStatistics @@ -675,6 +676,7 @@ class SkyHanniMod { loadModule(PestSpawnTimer) loadModule(PestFinder()) loadModule(SprayFeatures()) + loadModule(SprayDisplay()) loadModule(HighlightPlaceableNpcs()) loadModule(PresentWaypoints()) loadModule(JyrreTimer()) diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/garden/pests/SprayConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/garden/pests/SprayConfig.java index da715712f..07f73ba78 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/garden/pests/SprayConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/garden/pests/SprayConfig.java @@ -25,4 +25,25 @@ public class SprayConfig { @Expose public Position position = new Position(315, -200, 2.3f); + + @Expose + @ConfigOption( + name = "Spray Display", + desc = "Show the active spray and duration for your current plot." + ) + @ConfigEditorBoolean + @FeatureToggle + public boolean displayEnabled = true; + + @Expose + @ConfigOption( + name = "Spray Expiration Notice", + desc = "Show a notification in chat when a spray runs out in any plot. Only active in Garden." + ) + @ConfigEditorBoolean + @FeatureToggle + public boolean expiryNotification = true; + + @Expose + public Position displayPosition = new Position(390, 75, false, true); } 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 f55f87c4c..7dde87771 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenPlotAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenPlotAPI.kt @@ -1,13 +1,16 @@ package at.hannibal2.skyhanni.features.garden import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent 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.ItemUtils.name import at.hannibal2.skyhanni.utils.LocationUtils.isPlayerInside import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.RenderUtils.draw3DLine +import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import com.google.gson.annotations.Expose @@ -15,11 +18,18 @@ import net.minecraft.util.AxisAlignedBB import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import java.awt.Color import kotlin.math.floor +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes object GardenPlotAPI { private val plotNamePattern by RepoPattern.pattern("garden.plot.name", "§.Plot §7- §b(?<name>.*)") + private val plotSprayedPattern by RepoPattern.pattern( + "garden.plot.spray.target", + "§a§lSPRAYONATOR! §r§7You sprayed §r§aPlot §r§7- §r§b(?<plot>.*) §r§7with §r§a(?<spray>.*)§r§7!" + ) + var plots = listOf<Plot>() fun getCurrentPlot(): Plot? { @@ -36,10 +46,24 @@ object GardenPlotAPI { var name: String, @Expose - var pests: Int + var pests: Int, + + @Expose + var sprayExpiryTime: SimpleTimeMark?, + + @Expose + var sprayType: SprayType?, + + @Expose + var sprayHasNotified: Boolean + ) + + data class SprayData( + val expiry: SimpleTimeMark, + val type: SprayType ) - private fun Plot.getData() = GardenAPI.storage?.plotData?.getOrPut(id) { PlotData(id, "$id", 0) } + private fun Plot.getData() = GardenAPI.storage?.plotData?.getOrPut(id) { PlotData(id, "$id", 0, null, null, false) } var Plot.name: String get() = getData()?.name ?: "$id" @@ -53,6 +77,31 @@ object GardenPlotAPI { getData()?.pests = value } + val Plot.currentSpray: SprayData? + get() = this.getData()?.let { plot -> + val expiry = plot.sprayExpiryTime?.takeIf { !it.isInPast() } ?: return null + val type = plot.sprayType ?: return null + return SprayData(expiry, type) + } + + val Plot.isSprayExpired: Boolean + get() = this.getData()?.let { + !it.sprayHasNotified && it.sprayExpiryTime?.isInPast() == true + } == true + + + fun Plot.markExpiredSprayAsNotified() { + getData()?.apply { sprayHasNotified = true } + } + + private fun Plot.setSpray(spray: SprayType, duration: Duration) { + getData()?.apply { + sprayType = spray + sprayExpiryTime = SimpleTimeMark.now() + duration + sprayHasNotified = false + } + } + fun Plot.isBarn() = id == -1 fun Plot.isPlayerInside() = box.isPlayerInside() @@ -91,6 +140,21 @@ object GardenPlotAPI { } @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!GardenAPI.inGarden()) return + + plotSprayedPattern.matchMatcher(event.message) { + val sprayName = group("spray") + val plotName = group("plot") + + val plot = getPlotByName(plotName) + val spray = SprayType.getByName(sprayName) ?: return + + plot?.setSpray(spray, 30.minutes) + } + } + + @SubscribeEvent fun onInventoryOpen(event: InventoryFullyOpenedEvent) { if (!GardenAPI.inGarden()) return if (event.inventoryName != "Configure Plots") return diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/pests/SprayDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/pests/SprayDisplay.kt new file mode 100644 index 000000000..cfee8face --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/pests/SprayDisplay.kt @@ -0,0 +1,66 @@ +package at.hannibal2.skyhanni.features.garden.pests + +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.IslandChangeEvent +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.currentSpray +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI.isSprayExpired +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI.markExpiredSprayAsNotified +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI.name +import at.hannibal2.skyhanni.features.garden.GardenPlotAPI.plots +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RenderUtils.renderString +import at.hannibal2.skyhanni.utils.StringUtils +import at.hannibal2.skyhanni.utils.TimeUtils.timerColor +import at.hannibal2.skyhanni.utils.TimeUtils.format +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class SprayDisplay { + private val config get() = PestAPI.config.spray + private var display: String? = null + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!GardenAPI.inGarden() || !event.isMod(5)) return + + if (config.displayEnabled) { + val plot = GardenPlotAPI.getCurrentPlot() ?: return + display = plot.currentSpray?.let { + val timer = it.expiry.timeUntil() + "§eSprayed with §a${it.type.displayName} §7- ${timer.timerColor("§b")}${timer.format()}" + } + } + + if (config.expiryNotification) { + sendExpiredPlotsToChat(false) + } + } + + @SubscribeEvent + fun onJoin(event: IslandChangeEvent) { + if (!config.expiryNotification || event.newIsland != IslandType.GARDEN) return + sendExpiredPlotsToChat(true) + } + + @SubscribeEvent + fun onRenderOverlay(ignored: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!GardenAPI.inGarden() || !config.displayEnabled) return + val display = display ?: return + config.displayPosition.renderString(display, posLabel = "Active Plot Spray Display") + } + + private fun sendExpiredPlotsToChat(wasAway: Boolean) { + val expiredPlots = plots.filter { it.isSprayExpired } + if (expiredPlots.isEmpty()) return + + expiredPlots.forEach { it.markExpiredSprayAsNotified() } + val wasAwayString = if (wasAway) "§7While you were away, your" else "§7Your" + val plotString = StringUtils.createCommaSeparatedList(expiredPlots.map { "§b${it.name}" }, "§7") + val sprayString = if (expiredPlots.size > 1) "sprays" else "spray" + val out = "$wasAwayString $sprayString on §aPlot §7- $plotString §7expired." + LorenzUtils.chat(out) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/pests/SprayFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/pests/SprayFeatures.kt index c51070f03..f3a21f6be 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/pests/SprayFeatures.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/pests/SprayFeatures.kt @@ -9,13 +9,12 @@ import at.hannibal2.skyhanni.features.garden.GardenPlotAPI.renderPlot import at.hannibal2.skyhanni.features.garden.pests.PestAPI.getPests import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.InventoryUtils -import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.RenderUtils.renderString import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.time.Duration.Companion.seconds @@ -25,8 +24,10 @@ class SprayFeatures { private var display: String? = null private var lastChangeTime = SimpleTimeMark.farPast() - // TODO repo - private val pattern = "§a§lSPRAYONATOR! §r§7Your selected material is now §r§a(?<spray>.*)§r§7!".toPattern() + private val pattern by RepoPattern.pattern( + "garden.spray.material", + "§a§lSPRAYONATOR! §r§7Your selected material is now §r§a(?<spray>.*)§r§7!" + ) @SubscribeEvent fun onChat(event: LorenzChatEvent) { diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/NonGodPotEffectDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/NonGodPotEffectDisplay.kt index d08dd888a..d9a478d4e 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/NonGodPotEffectDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/NonGodPotEffectDisplay.kt @@ -18,11 +18,11 @@ import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.TimeUnit import at.hannibal2.skyhanni.utils.TimeUtils +import at.hannibal2.skyhanni.utils.TimeUtils.timerColor import at.hannibal2.skyhanni.utils.Timer import net.minecraft.network.play.server.S47PacketPlayerListHeaderFooter import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import kotlin.time.Duration import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.minutes @@ -123,7 +123,7 @@ class NonGodPotEffectDisplay { val remaining = time.remaining.coerceAtLeast(0.seconds) val format = TimeUtils.formatDuration(remaining.inWholeMilliseconds, TimeUnit.HOUR) - val color = colorForTime(remaining) + val color = remaining.timerColor() val displayName = effect.tabListName newDisplay.add("$displayName $color$format") @@ -137,13 +137,6 @@ class NonGodPotEffectDisplay { return newDisplay } - private fun colorForTime(duration: Duration) = when (duration) { - in 0.seconds..60.seconds -> "§c" - in 60.seconds..3.minutes -> "§6" - in 3.minutes..10.minutes -> "§e" - else -> "§f" - } - @SubscribeEvent fun onTick(event: LorenzTickEvent) { if (!isEnabled()) return diff --git a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt index daa619982..a9ac0ca82 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt @@ -156,6 +156,21 @@ object StringUtils { } } + /** + * Creates a comma-separated list using natural formatting (a, b, and c). + * @param list - the list of strings to join into a string, containing 0 or more elements. + * @param delimiterColor - the color code of the delimiter, inserted before each delimiter (commas and "and"). + * @return a string representing the list joined with the Oxford comma and the word "and". + */ + fun createCommaSeparatedList(list: List<String>, delimiterColor: String = ""): String { + if (list.isEmpty()) return "" + if (list.size == 1) return list[0] + if (list.size == 2) return "${list[0]}$delimiterColor and ${list[1]}" + val lastIndex = list.size - 1 + val allButLast = list.subList(0, lastIndex).joinToString("$delimiterColor, ") + return "$allButLast$delimiterColor, and ${list[lastIndex]}" + } + fun optionalPlural(number: Int, singular: String, plural: String) = "${number.addSeparators()} " + canBePlural(number, singular, plural) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt index ecb842f63..6aa418f85 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt @@ -6,6 +6,8 @@ import io.github.moulberry.notenoughupdates.util.SkyBlockTime import java.time.LocalDate import java.time.ZoneId import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -30,6 +32,13 @@ object TimeUtils { inWholeMilliseconds - 999, biggestUnit, showMilliSeconds, longName, maxUnits ) + fun Duration.timerColor(default: String = "§f") = when (this) { + in 0.seconds..60.seconds -> "§c" + in 60.seconds..3.minutes -> "§6" + in 3.minutes..10.minutes -> "§e" + else -> default + } + fun formatDuration( millis: Long, biggestUnit: TimeUnit = TimeUnit.YEAR, |