aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at
diff options
context:
space:
mode:
authorThunderblade73 <85900443+Thunderblade73@users.noreply.github.com>2024-05-07 22:43:17 +0200
committerGitHub <noreply@github.com>2024-05-07 22:43:17 +0200
commitd0345f39230e4f07f061b6c82a381c863e9787d8 (patch)
treebeaf9d162d1ee0b891fefefeb1fb4815d9adb89b /src/main/java/at
parent912c8d8a8c9ad412b2f94827e09a9cf262e5b69a (diff)
downloadskyhanni-d0345f39230e4f07f061b6c82a381c863e9787d8.tar.gz
skyhanni-d0345f39230e4f07f061b6c82a381c863e9787d8.tar.bz2
skyhanni-d0345f39230e4f07f061b6c82a381c863e9787d8.zip
Feature: Tunnels maps (#1546)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src/main/java/at')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/mining/TunnelMapsConfig.java80
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/EntityMovementData.kt23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt180
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/LorenzWarpEvent.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/RepositoryReloadEvent.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt451
-rw-r--r--src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt16
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt24
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt1
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt204
15 files changed, 970 insertions, 40 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
index b615238db..5caf50566 100644
--- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
+++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt
@@ -298,6 +298,7 @@ import at.hannibal2.skyhanni.features.mining.HighlightMiningCommissionMobs
import at.hannibal2.skyhanni.features.mining.KingTalismanHelper
import at.hannibal2.skyhanni.features.mining.MiningCommissionsBlocksColor
import at.hannibal2.skyhanni.features.mining.MiningNotifications
+import at.hannibal2.skyhanni.features.mining.TunnelsMaps
import at.hannibal2.skyhanni.features.mining.crystalhollows.CrystalHollowsNamesInCore
import at.hannibal2.skyhanni.features.mining.crystalhollows.CrystalHollowsWalls
import at.hannibal2.skyhanni.features.mining.eventtracker.MiningEventDisplay
@@ -610,6 +611,7 @@ class SkyHanniMod {
loadModule(DungeonMilestonesDisplay)
loadModule(DungeonDeathCounter())
loadModule(DungeonCleanEnd())
+ loadModule(TunnelsMaps())
loadModule(DungeonBossMessages())
loadModule(DungeonBossHideDamageSplash())
loadModule(UniqueGiftingOpportunitiesFeatures)
diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt
index c3b011d0e..5c85c97ec 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt
@@ -159,6 +159,17 @@ class ConfigManager {
.create()
var configDirectory = File("config/skyhanni")
+
+ inline fun <reified T> GsonBuilder.registerTypeAdapter(
+ crossinline write: (JsonWriter, T) -> Unit,
+ crossinline read: (JsonReader) -> T,
+ ): GsonBuilder {
+ this.registerTypeAdapter(T::class.java, object : TypeAdapter<T>() {
+ override fun write(out: JsonWriter, value: T) = write(out, value)
+ override fun read(reader: JsonReader) = read(reader)
+ }.nullSafe())
+ return this
+ }
}
val features get() = jsonHolder[ConfigFileType.FEATURES] as Features
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java
index b529a7c91..6d8bd06ab 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java
@@ -48,6 +48,9 @@ public class MiningConfig {
public MiningNotificationsConfig notifications = new MiningNotificationsConfig();
@Expose
+ @Category(name = "Tunnel Maps", desc = "Settings for the Tunnel Maps")
+ public TunnelMapsConfig tunnelMaps = new TunnelMapsConfig();
+ @Expose
@ConfigOption(name = "Commissions Blocks Color", desc = "")
@Accordion
public CommissionsBlocksColorConfig commissionsBlocksColor = new CommissionsBlocksColorConfig();
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/TunnelMapsConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/TunnelMapsConfig.java
new file mode 100644
index 000000000..99f36a429
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/TunnelMapsConfig.java
@@ -0,0 +1,80 @@
+package at.hannibal2.skyhanni.config.features.mining;
+
+import at.hannibal2.skyhanni.config.FeatureToggle;
+import at.hannibal2.skyhanni.config.core.config.Position;
+import com.google.gson.annotations.Expose;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorColour;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorKeybind;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigLink;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
+import io.github.notenoughupdates.moulconfig.observer.Property;
+import org.lwjgl.input.Keyboard;
+
+public class TunnelMapsConfig {
+
+ @Expose
+ @ConfigOption(name = "Enable", desc = "Enables the tunnel maps, which give you a path to any location you want. Open the Inventory to select a destination.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean enable = true;
+
+ @ConfigLink(owner = TunnelMapsConfig.class, field = "enable")
+ public Position position = new Position(20, 20);
+
+ @Expose
+ @ConfigOption(name = "Auto Commission", desc = "Takes the first collector commission as target when opening the commissions inventory, also works when completing commissions.")
+ @ConfigEditorBoolean
+ public boolean autoCommission = false;
+
+ @Expose
+ @ConfigOption(name = "Campfire Hotkey", desc = "Hotkey to warp to the campfire, if the travel scroll is not unlocked shows a path to the campfire.")
+ @ConfigEditorKeybind(defaultKey = Keyboard.KEY_NONE)
+ public int campfireKey = Keyboard.KEY_NONE;
+
+ @Expose
+ @ConfigOption(name = "Travel Scroll", desc = "Lets the mod know that you have unlocked the travel scroll to basecamp.")
+ @ConfigEditorBoolean
+ public boolean travelScroll = false;
+
+ @Expose
+ @ConfigOption(name = "Next Spot Hotkey", desc = "Hotkey to select the next spot.")
+ @ConfigEditorKeybind(defaultKey = Keyboard.KEY_NONE)
+ public int nextSpotHotkey = Keyboard.KEY_NONE;
+
+ @Expose
+ @ConfigOption(name = "Dynamic Path Colour", desc = "Instead of the selected color use the color of the target as line colour.")
+ @ConfigEditorBoolean
+ public boolean dynamicPathColour = true;
+
+ @Expose
+ @ConfigOption(name = "Path Colour", desc = "The colour for the paths, if the dynamic colour option is turned off.")
+ @ConfigEditorColour
+ public String pathColour = "0:255:0:255:0";
+
+ @Expose
+ @ConfigOption(name = "Text Size", desc = "Size of the waypoint texts.")
+ @ConfigEditorSlider(minValue = 0.5f, maxValue = 2.5f, minStep = 0.1f)
+ public float textSize = 1.0f;
+
+ @Expose
+ @ConfigOption(name = "Path width", desc = "Size of the path lines.")
+ @ConfigEditorSlider(minValue = 1f, maxValue = 15f, minStep = 1f)
+ public float pathWidth = 4.0f;
+
+ @Expose
+ @ConfigOption(name = "Distance at First", desc = "Shows the distance at the first edge instead of the end.")
+ @ConfigEditorBoolean
+ public boolean distanceFirst = false;
+
+ @Expose
+ @ConfigOption(name = "Compact Gemstone", desc = "Only shows the icon for gemstones in the selection list.")
+ @ConfigEditorBoolean
+ public Property<Boolean> compactGemstone = Property.of(false);
+
+ @Expose
+ @ConfigOption(name = "Exclude Fairy", desc = "Excludes the fairy soul spots from the selection list.")
+ @ConfigEditorBoolean
+ public Property<Boolean> excludeFairy = Property.of(false);
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/data/EntityMovementData.kt b/src/main/java/at/hannibal2/skyhanni/data/EntityMovementData.kt
index ae5a03878..97039022a 100644
--- a/src/main/java/at/hannibal2/skyhanni/data/EntityMovementData.kt
+++ b/src/main/java/at/hannibal2/skyhanni/data/EntityMovementData.kt
@@ -1,11 +1,16 @@
package at.hannibal2.skyhanni.data
import at.hannibal2.skyhanni.events.EntityMoveEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.events.LorenzWarpEvent
import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent
+import at.hannibal2.skyhanni.utils.DelayedRun
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.LorenzVec
+import at.hannibal2.skyhanni.utils.StringUtils.matches
import at.hannibal2.skyhanni.utils.getLorenzVec
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
import net.minecraft.client.Minecraft
import net.minecraft.entity.Entity
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
@@ -41,6 +46,24 @@ class EntityMovementData {
}
}
+ private val warpingPattern by RepoPattern.pattern(
+ "warping",
+ "§7Warping...|" +
+ "§7Warping you to your SkyBlock island...|" +
+ "§7Warping using transfer token...|" +
+ "§7Finding player...|" +
+ "§7Sending a visit request..."
+ )
+
+ @SubscribeEvent
+ fun onChat(event: LorenzChatEvent) {
+ if (!LorenzUtils.inSkyBlock) return
+ if (!warpingPattern.matches(event.message)) return
+ DelayedRun.runNextTick {
+ LorenzWarpEvent().postAndCatch()
+ }
+ }
+
@SubscribeEvent
fun onWorldChange(event: LorenzWorldChangeEvent) {
entityLocation.clear()
diff --git a/src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt b/src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt
new file mode 100644
index 000000000..b6e6f7b8e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt
@@ -0,0 +1,180 @@
+package at.hannibal2.skyhanni.data.model
+
+import at.hannibal2.skyhanni.config.ConfigManager.Companion.registerTypeAdapter
+import at.hannibal2.skyhanni.utils.LorenzVec
+import at.hannibal2.skyhanni.utils.fromJson
+import com.google.gson.GsonBuilder
+import com.google.gson.JsonElement
+import com.google.gson.annotations.Expose
+import java.util.PriorityQueue
+
+@JvmInline
+value class Graph(
+ @Expose
+ val graph: List<GraphNode>,
+) : List<GraphNode> {
+ override val size
+ get() = graph.size
+
+ override fun contains(element: GraphNode) = graph.contains(element)
+
+ override fun containsAll(elements: Collection<GraphNode>) = graph.containsAll(elements)
+
+ override fun get(index: Int) = graph.get(index)
+
+ override fun isEmpty() = graph.isEmpty()
+
+ override fun indexOf(element: GraphNode) = graph.indexOf(element)
+
+ override fun iterator(): Iterator<GraphNode> = graph.iterator()
+ override fun listIterator() = graph.listIterator()
+
+ override fun listIterator(index: Int) = graph.listIterator(index)
+
+ override fun subList(fromIndex: Int, toIndex: Int) = graph.subList(fromIndex, toIndex)
+
+ override fun lastIndexOf(element: GraphNode) = graph.lastIndexOf(element)
+
+ companion object {
+ val gson = GsonBuilder().setPrettyPrinting()
+ /* ConfigManager.createBaseGsonBuilder() */.registerTypeAdapter<Graph>({ out, value ->
+ out.beginObject()
+ value.forEach {
+ out.name(it.id.toString()).beginObject()
+ out.name("Position").value(with(it.position) { "$x:$y:$z" })
+ if (it.name != null) {
+ out.name("Name").value(it.name)
+ }
+ out.name("Neighbours")
+ out.beginObject()
+ it.neighbours.forEach { (node, weight) ->
+ val id = node.id.toString()
+ out.name(id).value(weight)
+ }
+ out.endObject()
+ out.endObject()
+ }
+ out.endObject()
+ }, { reader ->
+ reader.beginObject()
+ val list = mutableListOf<GraphNode>()
+ val neigbourMap = mutableMapOf<GraphNode, List<Pair<Int, Double>>>()
+ while (reader.hasNext()) {
+ val id = reader.nextName().toInt()
+ reader.beginObject()
+ var position: LorenzVec? = null
+ var name: String? = null
+ var neighbors = mutableListOf<Pair<Int, Double>>()
+ while (reader.hasNext()) {
+ when (reader.nextName()) {
+ "Position" -> {
+ position = reader.nextString().split(":").let { parts ->
+ LorenzVec(parts[0].toDouble(), parts[1].toDouble(), parts[2].toDouble())
+ }
+ }
+
+ "Neighbours" -> {
+ reader.beginObject()
+ while (reader.hasNext()) {
+ val nId = reader.nextName().toInt()
+ val distance = reader.nextDouble()
+ neighbors.add(nId to distance)
+ }
+ reader.endObject()
+ }
+
+ "Name" -> {
+ name = reader.nextString()
+ }
+
+ }
+ }
+ val node = GraphNode(id, position!!, name)
+ list.add(node)
+ neigbourMap[node] = neighbors
+ reader.endObject()
+ }
+ neigbourMap.forEach { (node, edge) ->
+ node.neighbours = edge.associate { (id, distance) ->
+ list.first { it.id == id } to distance
+ }
+ }
+ reader.endObject()
+ Graph(list)
+ }).create()
+
+ fun fromJson(json: String): Graph = gson.fromJson<Graph>(json)
+ fun fromJson(json: JsonElement): Graph = gson.fromJson<Graph>(json)
+ }
+}
+
+class GraphNode(val id: Int, val position: LorenzVec, val name: String? = null) {
+
+ /** Keys are the neighbours and value the edge weight (e.g. Distance) */
+ lateinit var neighbours: Map<GraphNode, Double>
+
+ override fun hashCode(): Int {
+ return id
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as GraphNode
+
+ if (id != other.id) return false
+
+ return true
+ }
+}
+
+fun Graph.findShortestPathAsGraph(start: GraphNode, end: GraphNode): Graph =
+ this.findShortestPathAsGraphWithDistance(start, end).first
+
+fun Graph.findShortestPathAsGraphWithDistance(start: GraphNode, end: GraphNode): Pair<Graph, Double> {
+ val distances = mutableMapOf<GraphNode, Double>()
+ val previous = mutableMapOf<GraphNode, GraphNode>()
+ val visited = mutableSetOf<GraphNode>()
+ val queue = PriorityQueue<GraphNode>(compareBy { distances.getOrDefault(it, Double.MAX_VALUE) })
+
+ distances[start] = 0.0
+ queue.add(start)
+
+ while (queue.isNotEmpty()) {
+ val current = queue.poll()
+ if (current == end) break
+
+ visited.add(current)
+
+ current.neighbours.forEach { (neighbour, weight) ->
+ if (neighbour !in visited) {
+ val newDistance = distances.getValue(current) + weight
+ if (newDistance < distances.getOrDefault(neighbour, Double.MAX_VALUE)) {
+ distances[neighbour] = newDistance
+ previous[neighbour] = current
+ queue.add(neighbour)
+ }
+ }
+ }
+ }
+
+ return Graph(buildList {
+ var current = end
+ while (current != start) {
+ add(current)
+ current = previous[current] ?: return Graph(emptyList()) to 0.0
+ }
+ add(start)
+ }.reversed()) to distances[end]!!
+}
+
+fun Graph.findShortestPath(start: GraphNode, end: GraphNode): List<LorenzVec> =
+ this.findShortestPathAsGraph(start, end).toPositionsList()
+
+fun Graph.findShortestDistance(start: GraphNode, end: GraphNode): Double =
+ this.findShortestPathAsGraphWithDistance(start, end).second
+
+fun Graph.toPositionsList() = this.map { it.position }
+
+fun Graph.toJson(): String = Graph.gson.toJson(this)
diff --git a/src/main/java/at/hannibal2/skyhanni/events/LorenzWarpEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/LorenzWarpEvent.kt
new file mode 100644
index 000000000..170dcabe9
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/events/LorenzWarpEvent.kt
@@ -0,0 +1,3 @@
+package at.hannibal2.skyhanni.events
+
+class LorenzWarpEvent : LorenzEvent()
diff --git a/src/main/java/at/hannibal2/skyhanni/events/RepositoryReloadEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/RepositoryReloadEvent.kt
index 3b2b426fe..fc006b049 100644
--- a/src/main/java/at/hannibal2/skyhanni/events/RepositoryReloadEvent.kt
+++ b/src/main/java/at/hannibal2/skyhanni/events/RepositoryReloadEvent.kt
@@ -9,7 +9,7 @@ import java.lang.reflect.Type
class RepositoryReloadEvent(val repoLocation: File, val gson: Gson) : LorenzEvent() {
- inline fun <reified T : Any> getConstant(constant: String, type: Type? = null): T = try {
+ inline fun <reified T : Any> getConstant(constant: String, type: Type? = null, gson: Gson = this.gson): T = try {
RepoManager.setlastConstant(constant)
if (!repoLocation.exists()) throw RepoError("Repo folder does not exist!")
RepoUtils.getConstant(repoLocation, constant, gson, T::class.java, type)
diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt
new file mode 100644
index 000000000..32e5864af
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt
@@ -0,0 +1,451 @@
+package at.hannibal2.skyhanni.features.mining
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.IslandType
+import at.hannibal2.skyhanni.data.model.Graph
+import at.hannibal2.skyhanni.data.model.GraphNode
+import at.hannibal2.skyhanni.data.model.findShortestDistance
+import at.hannibal2.skyhanni.data.model.findShortestPathAsGraphWithDistance
+import at.hannibal2.skyhanni.events.ConfigLoadEvent
+import at.hannibal2.skyhanni.events.GuiContainerEvent
+import at.hannibal2.skyhanni.events.GuiRenderEvent
+import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
+import at.hannibal2.skyhanni.events.LorenzKeyPressEvent
+import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent
+import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.events.LorenzToolTipEvent
+import at.hannibal2.skyhanni.events.LorenzWarpEvent
+import at.hannibal2.skyhanni.events.RepositoryReloadEvent
+import at.hannibal2.skyhanni.test.command.ErrorManager
+import at.hannibal2.skyhanni.utils.ChatUtils
+import at.hannibal2.skyhanni.utils.CollectionUtils.filterNotNullKeys
+import at.hannibal2.skyhanni.utils.ColorUtils.getFirstColorCode
+import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor
+import at.hannibal2.skyhanni.utils.ConditionalUtils.onToggle
+import at.hannibal2.skyhanni.utils.DelayedRun
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.ItemUtils.itemName
+import at.hannibal2.skyhanni.utils.LocationUtils
+import at.hannibal2.skyhanni.utils.LocationUtils.distanceSqToPlayer
+import at.hannibal2.skyhanni.utils.LorenzColor
+import at.hannibal2.skyhanni.utils.LorenzColor.Companion.toLorenzColor
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland
+import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName
+import at.hannibal2.skyhanni.utils.RenderUtils
+import at.hannibal2.skyhanni.utils.RenderUtils.draw3DPathWithWaypoint
+import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText
+import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import at.hannibal2.skyhanni.utils.SimpleTimeMark.Companion.fromNow
+import at.hannibal2.skyhanni.utils.StringUtils.anyMatches
+import at.hannibal2.skyhanni.utils.StringUtils.matchFirst
+import at.hannibal2.skyhanni.utils.StringUtils.matches
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import at.hannibal2.skyhanni.utils.renderables.Renderable
+import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.awt.Color
+import kotlin.math.roundToInt
+import kotlin.time.Duration.Companion.seconds
+
+class TunnelsMaps {
+
+ private val config get() = SkyHanniMod.feature.mining.tunnelMaps
+
+ private var graph: Graph = Graph(emptyList())
+ private lateinit var campfire: GraphNode
+
+ private var goalReached = false
+ private var prevGoal: GraphNode? = null
+ private var goal: GraphNode? = null
+ set(value) {
+ prevGoal = field
+ field = value
+ }
+
+ private var closedNote: GraphNode? = null
+ private var path: Pair<Graph, Double>? = null
+
+ private var possibleLocations = mapOf<String, List<GraphNode>>()
+ private val cooldowns = mutableMapOf<GraphNode, SimpleTimeMark>()
+ private var active: String = ""
+
+ private lateinit var fairySouls: Map<String, GraphNode>
+ private lateinit var newGemstones: Map<String, List<GraphNode>>
+ private lateinit var oldGemstones: Map<String, List<GraphNode>>
+ private lateinit var normalLocations: Map<String, List<GraphNode>>
+
+ private var locationDisplay: List<Renderable> = emptyList()
+
+ private fun getNext(name: String = active): GraphNode? {
+ fairySouls[name]?.let {
+ goalReached = false
+ return it
+ }
+
+ val closed = closedNote ?: return null
+ val list = possibleLocations[name] ?: return null
+
+ val offCooldown = list.filter { cooldowns[it]?.isInPast() != false }
+ val best = offCooldown.minByOrNull { graph.findShortestDistance(closed, it) } ?: list.minBy {
+ cooldowns[it] ?: SimpleTimeMark.farPast()
+ }
+ if (cooldowns[best]?.isInPast() != false) {
+ cooldowns[best] = 5.0.seconds.fromNow()
+ }
+ goalReached = false
+ return best
+ }
+
+ private fun hasNext(name: String = active): Boolean {
+ val list = possibleLocations[name] ?: return false
+ return list.size > 1
+ }
+
+ private val oldGemstonePattern by RepoPattern.pattern(
+ "mining.tunnels.maps.gem.old", ".*(?:Ruby|Amethyst|Jade|Sapphire|Amber|Topaz).*"
+ )
+ private val newGemstonePattern by RepoPattern.pattern(
+ "mining.tunnels.maps.gem.new", ".*(?:Aquamarine|Onyx|Citrine|Peridot).*"
+ )
+ private val commissionInvPattern by RepoPattern.pattern(
+ "mining.commission.inventory", "Commissions"
+ )
+ private val glacitePattern by RepoPattern.pattern(
+ "mining.commisson.reward.glacite",
+ "§7- §b\\d+ Glacite Powder"
+ )
+ private val collectorCommissionPattern by RepoPattern.pattern(
+ "mining.commisson.collector",
+ "§9(?<what>\\w+(?: \\w+)?) Collector"
+ )
+ private val invalidGoalPattern by RepoPattern.pattern(
+ "mining.commisson.collector.invalid",
+ "Glacite|Scrap"
+ )
+ private val completedPattern by RepoPattern.pattern(
+ "mining.commisson.completed",
+ "§a§lCOMPLETED"
+ )
+
+ private val translateTable = mutableMapOf<String, String>()
+
+ /** @return Errors with an empty String */
+ private fun getGenericName(input: String): String = translateTable.getOrPut(input) {
+ possibleLocations.keys.firstOrNull() { it.uppercase().removeColor().contains(input.uppercase()) } ?: ""
+ }
+
+ private var clickTranslate = mapOf<Int, String>()
+
+ @SubscribeEvent
+ fun onInventoryFullyOpened(event: InventoryFullyOpenedEvent) {
+ if (!isEnabled()) return
+ clickTranslate = mapOf()
+ if (!commissionInvPattern.matches(event.inventoryName)) return
+ clickTranslate = event.inventoryItems.mapNotNull { (slotId, item) ->
+ val lore = item.getLore()
+ if (!glacitePattern.anyMatches(lore)) return@mapNotNull null
+ if (completedPattern.anyMatches(lore)) return@mapNotNull null
+ val type = lore.matchFirst(collectorCommissionPattern) {
+ group("what")
+ } ?: return@mapNotNull null
+ if (invalidGoalPattern.matches(type)) return@mapNotNull null
+ val mapName = getGenericName(type)
+ if (mapName.isEmpty()) {
+ ErrorManager.logErrorStateWithData(
+ "Unknown Collection Commission: $type", "$type can't be found in the graph.",
+ "type" to type,
+ "graphNames" to possibleLocations.keys,
+ "lore" to lore
+ )
+ null
+ } else {
+ slotId to getGenericName(type)
+ }
+ }.toMap()
+ if (config.autoCommission) {
+ clickTranslate.values.firstOrNull()?.let {
+ setActiveAndGoal(it)
+ }
+ }
+ }
+
+ @SubscribeEvent
+ fun onRenderItemTooltip(event: LorenzToolTipEvent) {
+ if (!isEnabled()) return
+ clickTranslate[event.slot.slotIndex]?.let {
+ event.toolTip.add("§e§lRight Click §r§eto for Tunnel Maps.")
+ }
+ }
+
+ @SubscribeEvent
+ fun onGuiContainerSlotClick(event: GuiContainerEvent.SlotClickEvent) {
+ if (!isEnabled()) return
+ if (event.clickedButton != 1) return
+ clickTranslate[event.slotId]?.let {
+ setActiveAndGoal(it)
+ }
+ }
+
+ @SubscribeEvent
+ fun onRepoReload(event: RepositoryReloadEvent) {
+ graph = event.getConstant<Graph>("TunnelsGraph", gson = Graph.gson)
+ possibleLocations = graph.groupBy { it.name }.filterNotNullKeys().mapValues { (_, value) ->
+ value
+ }
+ val fairy = mutableMapOf<String, GraphNode>()
+ val oldGemstone = mutableMapOf<String, List<GraphNode>>()
+ val newGemstone = mutableMapOf<String, List<GraphNode>>()
+ val other = mutableMapOf<String, List<GraphNode>>()
+ possibleLocations.forEach { (key, value) ->
+ when {
+ key.contains("Campfire") -> campfire = value.first()
+ key.contains("Fairy") -> fairy[key] = value.first()
+ newGemstonePattern.matches(key) -> newGemstone[key] = value
+ oldGemstonePattern.matches(key) -> oldGemstone[key] = value
+ else -> other[key] = value
+ }
+ }
+ fairySouls = fairy
+ this.newGemstones = newGemstone
+ this.oldGemstones = oldGemstone
+ normalLocations = other
+ translateTable.clear()
+ DelayedRun.runNextTick { // Needs to be delayed since the config may not be loaded
+ locationDisplay = generateLocationsDisplay()
+ }
+ }
+
+ @SubscribeEvent
+ fun onConfigLoad(event: ConfigLoadEvent) {
+ onToggle(
+ config.compactGemstone,
+ config.excludeFairy
+ ) {
+ locationDisplay = generateLocationsDisplay()
+ }
+ }
+
+ @SubscribeEvent
+ fun onRenderDisplay(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) {
+ if (!isEnabled()) return
+ val display = buildList<Renderable> {
+ if (active.isNotEmpty()) {
+ if (goal == campfire && active != campfire.name) {
+ add(Renderable.string("§6Override for ${campfire.name}"))
+ add(Renderable.clickable(Renderable.string("§eMake §f$active §eactive"), onClick = {
+ goal = getNext()
+ }))
+ } else {
+ add(
+ Renderable.clickAndHover(
+ Renderable.string("§6Active: §f$active"),
+ listOf("§eClick to disable current Waypoint"),
+ onClick = ::clearPath
+ )
+ )
+ if (hasNext()) {
+ add(Renderable.clickable(Renderable.string("§eNext Spot"), onClick = {
+ goal = getNext()
+ }))
+ } else {
+ add(Renderable.string(""))
+ }
+ }
+ } else {
+ add(Renderable.string(""))
+ add(Renderable.string(""))
+ }
+ addAll(locationDisplay)
+ }
+ config.position.renderRenderables(display, posLabel = "TunnelsMaps")
+ }
+
+ private fun generateLocationsDisplay() = buildList {
+ add(Renderable.string("§6Loactions:"))
+ add(
+ Renderable.multiClickAndHover(
+ campfire.name!!, listOf(
+ "§eLeft Click to set active", "§eRight Click for override"
+ ), click = mapOf(
+ 0 to guiSetActive(campfire.name!!), 1 to ::campfireOverride
+ )
+ )
+ )
+ if (!config.excludeFairy.get()) {
+ add(Renderable.hoverable(Renderable.horizontalContainer(listOf(Renderable.string("§dFairy Souls")) + fairySouls.map {
+ val name = it.key.removePrefix("§dFairy Soul ")
+ Renderable.clickable(Renderable.string("§d[${name}]"), onClick = guiSetActive(it.key))
+ }), Renderable.string("§dFairy Souls")))
+ }
+ if (config.compactGemstone.get()) {
+ add(
+ Renderable.table(
+ listOf(
+ newGemstones.map(::toCompactGemstoneName), oldGemstones.map(::toCompactGemstoneName)
+ )
+ )
+ )
+ } else {
+ addAll(newGemstones.map {
+ Renderable.clickable(Renderable.string(it.key), onClick = guiSetActive(it.key))
+ })
+ addAll(oldGemstones.map {
+ Renderable.clickable(Renderable.string(it.key), onClick = guiSetActive(it.key))
+ })
+ }
+ addAll(normalLocations.map {
+ Renderable.clickable(Renderable.string(it.key), onClick = guiSetActive(it.key))
+ })
+ }
+
+ private fun toCompactGemstoneName(it: Map.Entry<String, List<GraphNode>>): Renderable = Renderable.clickAndHover(
+ Renderable.string((it.key.getFirstColorCode()?.let { "§$it" } ?: "") + ("ROUGH_".plus(
+ it.key.removeColor().removeSuffix("stone")
+ ).asInternalName().itemName.takeWhile { it != ' ' }.removeColor()),
+ horizontalAlign = RenderUtils.HorizontalAlignment.CENTER
+ ),
+ tips = listOf(it.key),
+ onClick = guiSetActive(it.key),
+ )
+
+ private fun campfireOverride() {
+ goalReached = false
+ goal = campfire
+ }
+
+ private fun setActiveAndGoal(it: String) {
+ active = it
+ goal = getNext()
+ }
+
+ private fun guiSetActive(it: String): () -> Unit = {
+ setActiveAndGoal(it)
+ }
+
+ @SubscribeEvent
+ fun onTick(event: LorenzTickEvent) {
+ if (!isEnabled()) return
+ if (checkGoalReached()) return
+ val prevClosed = closedNote
+ closedNote = graph.minBy { it.position.distanceSqToPlayer() }
+ val closest = closedNote ?: return
+ val goal = goal ?: return
+ if (closest == prevClosed && goal == prevGoal) return
+ val (path, distance) = graph.findShortestPathAsGraphWithDistance(closest, goal)
+ val first = path.firstOrNull()
+ val second = path.getOrNull(1)
+
+ val playerPosition = LocationUtils.playerLocation()
+ val nodeDistance = first?.let { playerPosition.distance(it.position) } ?: 0.0
+ if (first != null && second != null) {
+ val direct = playerPosition.distance(second.position)
+ val firstPath = first.neighbours[second] ?: 0.0
+ val around = nodeDistance + firstPath
+ if (direct < around) {
+ this.path = Graph(path.drop(1)) to (distance - firstPath + direct)
+ return
+ }
+ }
+ this.path = path to (distance + nodeDistance)
+ }
+
+ private fun checkGoalReached(): Boolean {
+ if (goalReached) return true
+ val goal = goal ?: return false
+ val distance = goal.position.distanceSqToPlayer()
+ goalReached = distance < if (goal == campfire) {
+ 15.0 * 15.0
+ } else {
+ 6.0 * 6.0
+ }
+ if (goalReached) {
+ if (goal == campfire && active != campfire.name) {
+ this.goal = getNext()
+ } else {
+ cooldowns[goal] = 60.0.seconds.fromNow()
+ clearPath()
+ }
+ return true
+ }
+ return false
+ }
+
+ private fun clearPath() {
+ path = null
+ goal = null
+ }
+
+ @SubscribeEvent
+ fun onRenderWorld(event: LorenzRenderWorldEvent) {
+ if (!isEnabled()) return
+ val path = path?.takeIf { it.first.isNotEmpty() } ?: return
+ event.draw3DPathWithWaypoint(
+ path.first,
+ getPathColor(),
+ config.pathWidth.toInt(),
+ true,
+ bezierPoint = 2.0,
+ textSize = config.textSize.toDouble()
+ )
+ event.drawDynamicText(
+ if (config.distanceFirst) {
+ path.first.first()
+ } else {
+ path.first.last()
+ }.position,
+ "§e${path.second.roundToInt()}m",
+ config.textSize.toDouble(),
+ yOff = 10f
+ )
+ }
+
+ private fun getPathColor(): Color = if (config.dynamicPathColour) {
+ goal?.name?.getFirstColorCode()?.toLorenzColor()?.takeIf { it != LorenzColor.WHITE }?.toColor()
+ } else {
+ null
+ } ?: config.pathColour.toChromaColor()
+
+ @SubscribeEvent
+ fun onKeyPress(event: LorenzKeyPressEvent) {
+ if (!isEnabled()) return
+ campfireKey(event)
+ nextSpotKey(event)
+ }
+
+ private fun campfireKey(event: LorenzKeyPressEvent) {
+ if (event.keyCode != config.campfireKey) return
+ if (config.travelScroll) {
+ ChatUtils.sendMessageToServer("/warp basecamp")
+ } else {
+ campfireOverride()
+ }
+ }
+
+ @SubscribeEvent
+ fun onLorenzWarp(event: LorenzWarpEvent) {
+ if (!isEnabled()) return
+ if (goal != null) {
+ DelayedRun.runNextTick {
+ goal = getNext()
+ }
+ }
+ }
+
+ private var nextSpotDelay = SimpleTimeMark.farPast()
+
+ private fun nextSpotKey(event: LorenzKeyPressEvent) {
+ if (event.keyCode != config.nextSpotHotkey) return
+ if (!nextSpotDelay.isInPast()) return
+ nextSpotDelay = 0.5.seconds.fromNow()
+ goal = getNext()
+ }
+
+ val areas = setOf(
+ "Glacite Tunnels", "Dwarven Base Camp", "Glacite Lake", "Fossil Research Center"
+ )
+
+ private fun isEnabled() =
+ IslandType.DWARVEN_MINES.isInIsland() && config.enable && areas.contains(LorenzUtils.skyBlockArea)
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt
index b81a25599..0ae712f9d 100644
--- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt
+++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt
@@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.config.ConfigManager
import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
import at.hannibal2.skyhanni.config.core.config.Position
import at.hannibal2.skyhanni.data.HypixelData
+import at.hannibal2.skyhanni.data.model.Graph
import at.hannibal2.skyhanni.events.GuiKeyPressEvent
import at.hannibal2.skyhanni.events.GuiRenderEvent
import at.hannibal2.skyhanni.events.LorenzChatEvent
@@ -43,6 +44,7 @@ import at.hannibal2.skyhanni.utils.NEUItems.getPriceOrNull
import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
import at.hannibal2.skyhanni.utils.OSUtils
import at.hannibal2.skyhanni.utils.ReflectionUtils.makeAccessible
+import at.hannibal2.skyhanni.utils.RenderUtils.draw3DPathWithWaypoint
import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText
import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables
import at.hannibal2.skyhanni.utils.RenderUtils.renderString
@@ -52,6 +54,7 @@ import at.hannibal2.skyhanni.utils.SoundUtils
import at.hannibal2.skyhanni.utils.renderables.Renderable
import at.hannibal2.skyhanni.utils.renderables.Renderable.Companion.renderBounds
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
import net.minecraft.client.Minecraft
import net.minecraft.init.Blocks
import net.minecraft.init.Items
@@ -59,6 +62,7 @@ import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.awt.Color
import java.io.File
import kotlin.time.Duration.Companion.seconds
@@ -116,9 +120,14 @@ class SkyHanniDebugsAndTests {
ChatUtils.chat("set test waypoint")
}
+ var path: Graph = Graph(emptyList())
+
fun testCommand(args: Array<String>) {
SoundUtils.playBeepSound()
-
+ runBlocking {
+ val input = OSUtils.readFromClipboard() ?: return@runBlocking
+ path = Graph.fromJson(input)
+ }
// val a = Thread { OSUtils.copyToClipboard("123") }
// val b = Thread { OSUtils.copyToClipboard("456") }
// a.start()
@@ -425,6 +434,11 @@ class SkyHanniDebugsAndTests {
}
@SubscribeEvent
+ fun onTestGraphPath(event: LorenzRenderWorldEvent) {
+ event.draw3DPathWithWaypoint(path, Color.GREEN, 8, true)
+ }
+
+ @SubscribeEvent
fun onShowInternalName(event: LorenzToolTipEvent) {
if (!LorenzUtils.inSkyBlock) return
if (!debugConfig.showInternalName) return
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt
index 53ae224d9..915677bb6 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt
@@ -285,4 +285,28 @@ object CollectionUtils {
}
return destination
}
+
+ inline fun <T, R> Iterable<T>.zipWithNext3(transform: (a: T, b: T, c: T) -> R): List<R> {
+ val iterator = iterator()
+ if (!iterator.hasNext()) return emptyList()
+ var one = iterator.next()
+ if (!iterator.hasNext()) return emptyList()
+ var two = iterator.next()
+ val result = mutableListOf<R>()
+ while (iterator.hasNext()) {
+ val next = iterator.next()
+ result.add(transform(one, two, next))
+ one = two
+ two = next
+ }
+ return result
+ }
+
+ fun <T> Iterable<T>.zipWithNext3(): List<Triple<T, T, T>> {
+ return zipWithNext3 { a, b, c -> Triple(a, b, c) }
+ }
+
+ fun <K, V : Any> Map<K?, V>.filterNotNullKeys(): Map<K, V> {
+ return filterKeys { it != null } as Map<K, V>
+ }
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt
index 97a1342c1..e5ff47073 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt
@@ -9,6 +9,8 @@ object ColorUtils {
fun String.toChromaColor() = Color(toChromaColorInt(), true)
fun String.toChromaColorInt() = SpecialColour.specialToChromaRGB(this)
+ fun String.getFirstColorCode() = this.takeIf { it.firstOrNull() == '§' }?.getOrNull(1)
+
fun getRed(colour: Int) = colour shr 16 and 0xFF
fun getGreen(colour: Int) = colour shr 8 and 0xFF
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt
index 16fe8cd6e..08de7038a 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt
@@ -81,5 +81,13 @@ enum class LorenzColor(val chatColorCode: Char, private val color: Color, privat
EnumDyeColor.BROWN -> GOLD
EnumDyeColor.BLACK -> BLACK
}
+
+ fun Char.toLorenzColor(): LorenzColor? = entries.firstOrNull { it.chatColorCode == this } ?: run {
+ ErrorManager.logErrorWithData(
+ Exception("Unknown chat color: $this"),
+ "Unknown chat color: $this"
+ )
+ null
+ }
}
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt
index 55be8cd3e..dc143a841 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt
@@ -136,6 +136,7 @@ data class LorenzVec(
fun clone(): LorenzVec = LorenzVec(x, y, z)
fun toDoubleArray(): Array<Double> = arrayOf(x, y, z)
+ fun toFloatArray(): Array<Float> = arrayOf(x.toFloat(), y.toFloat(), z.toFloat())
fun equalsIgnoreY(other: LorenzVec) = x == other.x && z == other.z
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
index d17831fb5..fb97a8357 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
@@ -5,13 +5,19 @@ import at.hannibal2.skyhanni.data.GuiEditManager
import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsX
import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsY
import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getDummySize
+import at.hannibal2.skyhanni.data.model.Graph
+import at.hannibal2.skyhanni.data.model.toPositionsList
import at.hannibal2.skyhanni.events.GuiContainerEvent
import at.hannibal2.skyhanni.events.GuiRenderItemEvent
import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent
import at.hannibal2.skyhanni.events.RenderGuiItemOverlayEvent
import at.hannibal2.skyhanni.features.misc.RoundedRectangleOutlineShader
import at.hannibal2.skyhanni.features.misc.RoundedRectangleShader
+import at.hannibal2.skyhanni.test.GriffinUtils.drawWaypointFilled
import at.hannibal2.skyhanni.test.command.ErrorManager
+import at.hannibal2.skyhanni.utils.CollectionUtils.zipWithNext3
+import at.hannibal2.skyhanni.utils.ColorUtils.getFirstColorCode
+import at.hannibal2.skyhanni.utils.LorenzColor.Companion.toLorenzColor
import at.hannibal2.skyhanni.utils.renderables.Renderable
import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderXAligned
import at.hannibal2.skyhanni.utils.shader.ShaderManager
@@ -35,6 +41,7 @@ import net.minecraft.util.MathHelper
import net.minecraft.util.ResourceLocation
import org.lwjgl.opengl.GL11
import java.awt.Color
+import java.nio.FloatBuffer
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
@@ -66,8 +73,9 @@ object RenderUtils {
private val beaconBeam = ResourceLocation("textures/entity/beacon_beam.png")
- private val matrixBuffer = GLAllocation.createDirectFloatBuffer(16)
- private val colourBuffer = GLAllocation.createDirectFloatBuffer(16)
+ private val matrixBuffer: FloatBuffer = GLAllocation.createDirectFloatBuffer(16);
+ private val colourBuffer: FloatBuffer = GLAllocation.createDirectFloatBuffer(16)
+ private val bezier2Buffer: FloatBuffer = GLAllocation.createDirectFloatBuffer(9)
infix fun Slot.highlight(color: LorenzColor) {
highlight(color.toColor())
@@ -1017,43 +1025,10 @@ object RenderUtils {
}
}
- fun LorenzRenderWorldEvent.draw3DLine(p1: LorenzVec, p2: LorenzVec, color: Color, lineWidth: Int, depth: Boolean) {
- GlStateManager.disableCull()
-
- val render = Minecraft.getMinecraft().renderViewEntity
- val worldRenderer = Tessellator.getInstance().worldRenderer
- val realX = render.lastTickPosX + (render.posX - render.lastTickPosX) * partialTicks
- val realY = render.lastTickPosY + (render.posY - render.lastTickPosY) * partialTicks
- val realZ = render.lastTickPosZ + (render.posZ - render.lastTickPosZ) * partialTicks
- GlStateManager.pushMatrix()
- GlStateManager.translate(-realX, -realY, -realZ)
- GlStateManager.disableTexture2D()
- GlStateManager.enableBlend()
- GlStateManager.disableAlpha()
- GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0)
- GL11.glLineWidth(lineWidth.toFloat())
- if (!depth) {
- GL11.glDisable(GL11.GL_DEPTH_TEST)
- GlStateManager.depthMask(false)
- }
- GlStateManager.color(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f)
- worldRenderer.begin(GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION)
- worldRenderer.pos(p1.x, p1.y, p1.z).endVertex()
- worldRenderer.pos(p2.x, p2.y, p2.z).endVertex()
- Tessellator.getInstance().draw()
- GlStateManager.translate(realX, realY, realZ)
- if (!depth) {
- GL11.glEnable(GL11.GL_DEPTH_TEST)
- GlStateManager.depthMask(true)
+ fun LorenzRenderWorldEvent.draw3DLine(p1: LorenzVec, p2: LorenzVec, color: Color, lineWidth: Int, depth: Boolean) =
+ LineDrawer.draw3D(partialTicks) {
+ draw3DLine(p1, p2, color, lineWidth, depth)
}
- GlStateManager.disableBlend()
- GlStateManager.enableAlpha()
- GlStateManager.enableTexture2D()
- GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f)
- GlStateManager.popMatrix()
- GlStateManager.disableLighting()
- GlStateManager.enableDepth()
- }
fun LorenzRenderWorldEvent.exactLocation(entity: Entity) = exactLocation(entity, partialTicks)
@@ -1232,6 +1207,159 @@ object RenderUtils {
)
}
+ fun LorenzRenderWorldEvent.draw3DPathWithWaypoint(
+ path: Graph,
+ colorLine: Color,
+ lineWidth: Int,
+ depth: Boolean,
+ startAtEye: Boolean = true,
+ textSize: Double = 1.0,
+ waypointColor: Color =
+ (path.lastOrNull()?.name?.getFirstColorCode()?.toLorenzColor() ?: LorenzColor.WHITE).toColor(),
+ bezierPoint: Double = 1.0,
+ ) {
+ if (path.isEmpty()) return
+ val points = if (startAtEye) {
+ listOf(
+ this.exactPlayerEyeLocation()
+ + Minecraft.getMinecraft().thePlayer.getLook(this.partialTicks)
+ .toLorenzVec()/* .rotateXZ(-Math.PI / 72.0) */.times(2)
+ )
+ } else {
+ emptyList()
+ } + path.toPositionsList().map { it.add(0.5, 0.5, 0.5) }
+ LineDrawer.draw3D(partialTicks) {
+ drawPath(
+ points,
+ colorLine,
+ lineWidth,
+ depth,
+ bezierPoint
+ )
+ }
+ path.filter { it.name?.isNotEmpty() == true }.forEach {
+ this.drawDynamicText(it.position, it.name!!, textSize)
+ }
+ val last = path.last()
+ drawWaypointFilled(last.position, waypointColor, seeThroughBlocks = true)
+ }
+
+ class LineDrawer @PublishedApi internal constructor(val tessellator: Tessellator) {
+ val worldRenderer = tessellator.worldRenderer
+ fun drawPath(path: List<LorenzVec>, color: Color, lineWidth: Int, depth: Boolean, bezierPoint: Double = 1.0) {
+ if (bezierPoint < 0) {
+ path.zipWithNext().forEach {
+ draw3DLine(it.first, it.second, color, lineWidth, depth)
+ }
+ } else {
+ val pathLines = path.zipWithNext()
+ pathLines.forEachIndexed { index, it ->
+ val reduce = it.second.minus(it.first).normalize().times(bezierPoint)
+ draw3DLine(
+ if (index != 0) it.first + reduce else it.first,
+ if (index != pathLines.lastIndex) it.second - reduce else it.second,
+ color,
+ lineWidth,
+ depth
+ )
+ }
+ path.zipWithNext3().forEach {
+ val p1 = it.second.minus(it.second.minus(it.first).normalize().times(bezierPoint))
+ val p3 = it.second.minus(it.second.minus(it.third).normalize().times(bezierPoint))
+ val p2 = it.second
+ drawBezier2(p1, p2, p3, color, lineWidth, depth)
+ }
+ }
+ }
+
+ fun draw3DLine(p1: LorenzVec, p2: LorenzVec, color: Color, lineWidth: Int, depth: Boolean) {
+ GL11.glLineWidth(lineWidth.toFloat())
+ if (!depth) {
+ GL11.glDisable(GL11.GL_DEPTH_TEST)
+ GlStateManager.depthMask(false)
+ }
+ GlStateManager.color(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f)
+ worldRenderer.begin(GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION)
+ worldRenderer.pos(p1.x, p1.y, p1.z).endVertex()
+ worldRenderer.pos(p2.x, p2.y, p2.z).endVertex()
+ tessellator.draw()
+ if (!depth) {
+ GL11.glEnable(GL11.GL_DEPTH_TEST)
+ GlStateManager.depthMask(true)
+ }
+ }
+
+ fun drawBezier2(
+ p1: LorenzVec,
+ p2: LorenzVec,
+ p3: LorenzVec,
+ color: Color,
+ lineWidth: Int,
+ depth: Boolean,
+ segments: Int = 30,
+ ) {
+ GL11.glLineWidth(lineWidth.toFloat())
+ if (!depth) {
+ GL11.glDisable(GL11.GL_DEPTH_TEST)
+ GlStateManager.depthMask(false)
+ }
+ GlStateManager.color(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f)
+ val ctrlpoints = p1.toFloatArray() + p2.toFloatArray() + p3.toFloatArray()
+ bezier2Buffer.clear()
+ ctrlpoints.forEach {
+ bezier2Buffer.put(it)
+ }
+ bezier2Buffer.flip()
+ GL11.glMap1f(
+ GL11.GL_MAP1_VERTEX_3, 0.0f, 1.0f, 3, 3,
+ bezier2Buffer
+ )
+
+ GL11.glEnable(GL11.GL_MAP1_VERTEX_3)
+
+ GL11.glBegin(GL11.GL_LINE_STRIP)
+ for (i in 0..segments) {
+ GL11.glEvalCoord1f(i.toFloat() / segments.toFloat())
+ }
+ GL11.glEnd()
+ if (!depth) {
+ GL11.glEnable(GL11.GL_DEPTH_TEST)
+ GlStateManager.depthMask(true)
+ }
+ }
+
+ companion object {
+ inline fun draw3D(
+ partialTicks: Float = 0F,
+ crossinline quads: LineDrawer.() -> Unit,
+ ) {
+
+ GlStateManager.enableBlend()
+ GlStateManager.disableLighting()
+ GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0)
+ GlStateManager.disableTexture2D()
+ GlStateManager.disableCull()
+ GlStateManager.disableAlpha()
+
+ val tessellator = Tessellator.getInstance()
+
+ GlStateManager.pushMatrix()
+ RenderUtils.translate(getViewerPos(partialTicks).negated())
+ getViewerPos(partialTicks)
+
+ quads.invoke(LineDrawer(Tessellator.getInstance()))
+
+ GlStateManager.popMatrix()
+
+ GlStateManager.enableAlpha()
+ GlStateManager.enableTexture2D()
+ GlStateManager.enableCull()
+ GlStateManager.disableBlend()
+ GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f)
+ }
+ }
+ }
+
class QuadDrawer @PublishedApi internal constructor(val tessellator: Tessellator) {
val worldRenderer = tessellator.worldRenderer
inline fun draw(