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 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 patternGroup = RepoPattern.group("garden.plot") private val plotNamePattern by patternGroup.pattern( "name", "§.Plot §7- §b(?.*)" ) private val plotSprayedPattern by patternGroup.pattern( "spray.target", "§a§lSPRAYONATOR! §r§7You sprayed §r§aPlot §r§7- §r§b(?.*) §r§7with §r§a(?.*)§r§7!" ) var plots = listOf() fun getCurrentPlot(): Plot? { return plots.firstOrNull { it.isPlayerInside() } } class Plot(val id: Int, var inventorySlot: Int, val box: AxisAlignedBB, val middle: LorenzVec) class PlotData( @Expose val id: Int, @Expose var name: String, @Expose 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, null, null, false) } var Plot.name: String get() = getData()?.name ?: "$id" set(value) { getData()?.name = value } var Plot.pests: Int get() = getData()?.pests ?: 0 set(value) { 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() fun Plot.sendTeleportTo() { LorenzUtils.sendCommandToServer("tptoplot $name") LockMouseLook.autoDisable() } init { val plotMap = listOf( listOf(21, 13, 9, 14, 22), listOf(15, 5, 1, 6, 16), listOf(10, 2, -1, 3, 11), listOf(17, 7, 4, 8, 18), listOf(23, 19, 12, 20, 24), ) val list = mutableListOf() var slot = 2 for ((y, rows) in plotMap.withIndex()) { for ((x, id) in rows.withIndex()) { val minX = ((x - 2) * 96 - 48).toDouble() val minY = ((y - 2) * 96 - 48).toDouble() val maxX = ((x - 2) * 96 + 48).toDouble() val maxY = ((y - 2) * 96 + 48).toDouble() val a = LorenzVec(minX, 0.0, minY) 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)) slot++ } slot += 4 } plots = list } @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 for (plot in plots) { val itemName = event.inventoryItems[plot.inventorySlot]?.name ?: continue plotNamePattern.matchMatcher(itemName) { plot.name = group("name") } } } fun getPlotByName(plotName: String) = plots.firstOrNull { it.name == plotName } fun LorenzRenderWorldEvent.renderPlot( plot: Plot, lineColor: Color, cornerColor: Color, showBuildLimit: Boolean = false, ) { // These don't refer to Minecraft chunks but rather garden plots, but I use // the word chunk as the logic closely represents how chunk borders are rendered in latter mc versions val plotSize = 96 val chunkX = floor((plot.middle.x + 48) / plotSize).toInt() val chunkZ = floor((plot.middle.z + 48) / plotSize).toInt() val chunkMinX = (chunkX * plotSize) - 48 val chunkMinZ = (chunkZ * plotSize) - 48 // Lowest point in the garden val minHeight = 66 val maxHeight = 66 + 36 // Render 4 vertical corners for (i in 0..plotSize step plotSize) { for (j in 0..plotSize step plotSize) { val start = LorenzVec(chunkMinX + i, minHeight, chunkMinZ + j) val end = LorenzVec(chunkMinX + i, maxHeight, chunkMinZ + j) tryDraw3DLine(start, end, cornerColor, 2, true) } } // Render vertical on X-Axis for (x in 4.. (minX, minZ + 96) tryDraw3DLine(start, start.add(z = plotSize), color, depth, true) // (minX, minZ + 96) -> (minX + 96, minZ + 96) tryDraw3DLine(start.add(z = plotSize), start.add(x = plotSize, z = plotSize), color, depth, true) // (minX + 96, minZ + 96) -> (minX + 96, minZ) tryDraw3DLine(start.add(x = plotSize, z = plotSize), start.add(x = plotSize), color, depth, true) // (minX + 96, minZ) -> (minX, minZ) tryDraw3DLine(start.add(x = plotSize), start, color, depth, true) } } private fun LorenzRenderWorldEvent.tryDraw3DLine( p1: LorenzVec, p2: LorenzVec, color: Color, lineWidth: Int, depth: Boolean, ) { if (isOutOfBorders(p1)) return if (isOutOfBorders(p2)) return draw3DLine(p1, p2, color, lineWidth, depth) } private fun isOutOfBorders(location: LorenzVec) = when { location.x > 240 -> true location.x < -240 -> true location.z > 240 -> true location.z < -240 -> true else -> false } }