1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
package at.hannibal2.skyhanni.features.cosmetics
import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator
import at.hannibal2.skyhanni.config.enums.OutsideSbFeature
import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent
import at.hannibal2.skyhanni.events.LorenzTickEvent
import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent
import at.hannibal2.skyhanni.utils.CollectionUtils.editCopy
import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor
import at.hannibal2.skyhanni.utils.LocationUtils
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.LorenzVec
import at.hannibal2.skyhanni.utils.RenderUtils.draw3DLine
import at.hannibal2.skyhanni.utils.RenderUtils.exactLocation
import at.hannibal2.skyhanni.utils.SimpleTimeMark
import net.minecraft.client.Minecraft
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import java.awt.Color
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class CosmeticFollowingLine {
private val config get() = SkyHanniMod.feature.gui.cosmetic.followingLine
private var locations = mapOf<LorenzVec, LocationSpot>()
private var latestLocations = mapOf<LorenzVec, LocationSpot>()
class LocationSpot(val time: SimpleTimeMark, val onGround: Boolean)
@SubscribeEvent
fun onWorldChange(event: LorenzWorldChangeEvent) {
locations = emptyMap()
}
@SubscribeEvent
fun onRenderWorld(event: LorenzRenderWorldEvent) {
if (!isEnabled()) return
updateClose(event)
val firstPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView == 0
val color = config.lineColor.toChromaColor()
renderClose(event, firstPerson, color)
renderFar(event, firstPerson, color)
}
private fun renderFar(
event: LorenzRenderWorldEvent,
firstPerson: Boolean,
color: Color,
) {
val last7 = locations.keys.toList().takeLast(7)
val last2 = locations.keys.toList().takeLast(2)
locations.keys.zipWithNext { a, b ->
val locationSpot = locations[b]!!
if (firstPerson && !locationSpot.onGround && b in last7) {
// Do not render the line in the face, keep more distance while the line is in the air
return
}
if (b in last2 && locationSpot.time.passedSince() < 400.milliseconds) {
// Do not render the line directly next to the player, prevent laggy design
return
}
event.draw3DLine(a, b, color, locationSpot.getWidth(), !config.behindBlocks)
}
}
private fun updateClose(event: LorenzRenderWorldEvent) {
val playerLocation = event.exactLocation(Minecraft.getMinecraft().thePlayer).add(y = 0.3)
latestLocations = latestLocations.editCopy {
val locationSpot = LocationSpot(SimpleTimeMark.now(), Minecraft.getMinecraft().thePlayer.onGround)
this[playerLocation] = locationSpot
values.removeIf { it.time.passedSince() > 600.milliseconds }
}
}
private fun renderClose(event: LorenzRenderWorldEvent, firstPerson: Boolean, color: Color) {
if (firstPerson && latestLocations.any { !it.value.onGround }) return
latestLocations.keys.zipWithNext { a, b ->
val locationSpot = latestLocations[b]!!
event.draw3DLine(a, b, color, locationSpot.getWidth(), !config.behindBlocks)
}
}
private fun LocationSpot.getWidth(): Int {
val millis = time.passedSince().inWholeMilliseconds
val percentage = millis.toDouble() / (config.secondsAlive * 1000.0)
val maxWidth = config.lineWidth
val lineWidth = 1 + maxWidth - percentage * maxWidth
return lineWidth.toInt().coerceAtLeast(1)
}
@SubscribeEvent
fun onTick(event: LorenzTickEvent) {
if (!isEnabled()) return
if (event.isMod(5)) {
locations = locations.editCopy { values.removeIf { it.time.passedSince() > config.secondsAlive.seconds } }
// Safety check to not cause lags
while (locations.size > 5_000) {
locations = locations.editCopy { remove(keys.first()) }
}
}
if (event.isMod(2)) {
val playerLocation = LocationUtils.playerLocation().add(y = 0.3)
locations.keys.lastOrNull()?.let {
if (it.distance(playerLocation) < 0.1) return
}
locations = locations.editCopy {
this[playerLocation] = LocationSpot(SimpleTimeMark.now(), Minecraft.getMinecraft().thePlayer.onGround)
}
}
}
private fun isEnabled() = (LorenzUtils.inSkyBlock || OutsideSbFeature.FOLLOWING_LINE.isSelected()) && config.enabled
@SubscribeEvent
fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) {
event.move(9, "misc.cosmeticConfig", "misc.cosmetic")
event.move(9, "misc.cosmeticConfig.followingLineConfig", "misc.cosmetic.followingLine")
event.move(9, "misc.cosmeticConfig.arrowTrailConfig", "misc.cosmetic.arrowTrail")
event.move(31, "misc.cosmetic", "gui.cosmetic")
}
}
|