Skip to content

Commit 2c48a93

Browse files
committed
Added AutoDisconnect
1 parent f9b51ad commit 2c48a93

File tree

2 files changed

+176
-4
lines changed

2 files changed

+176
-4
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.module.modules.combat
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.event.events.TickEvent
22+
import com.lambda.event.listener.SafeListener.Companion.listen
23+
import com.lambda.friend.FriendManager
24+
import com.lambda.module.Module
25+
import com.lambda.module.tag.ModuleTag
26+
import com.lambda.sound.SoundManager.playSound
27+
import com.lambda.util.Communication
28+
import com.lambda.util.Communication.prefix
29+
import com.lambda.util.Formatting.string
30+
import com.lambda.util.combat.Explosion.explosionDamage
31+
import com.lambda.util.player.SlotUtils.combined
32+
import com.lambda.util.text.*
33+
import com.lambda.util.world.fastEntitySearch
34+
import net.minecraft.entity.decoration.EndCrystalEntity
35+
import net.minecraft.entity.mob.CreeperEntity
36+
import net.minecraft.entity.player.PlayerEntity
37+
import net.minecraft.item.Items
38+
import net.minecraft.sound.SoundEvents
39+
import net.minecraft.text.Text
40+
import java.awt.Color
41+
42+
object AutoDisconnect : Module(
43+
name = "AutoDisconnect",
44+
description = "Automatically disconnects when in danger or on low health",
45+
defaultTags = setOf(ModuleTag.COMBAT)
46+
) {
47+
private val health by setting("Health", true, "Disconnect from the server when health is below the set limit.")
48+
private val minimumHealth by setting("Min Health", 10, 6..36, 1, description = "Set the minimum health threshold for disconnection.", unit = " hearts") { health }
49+
private val crystals by setting("Crystals", false, "Disconnect if an End Crystal explosion would be lethal.")
50+
private val creeper by setting("Creepers", true, "Disconnect when an ignited Creeper is nearby.")
51+
private val totem by setting("Totem", false, "Disconnect if the number of Totems of Undying is below the required amount.")
52+
private val minTotems by setting("Min Totems", 2, 1..10, 1, "Set the minimum number of Totems of Undying required to prevent disconnection.") { totem }
53+
private val players by setting("Players", false, "Disconnect if a nearby player is detected within the set distance.")
54+
private val minPlayerDistance by setting("Player Distance", 64, 32..128, 4, "Set the distance to detect players for disconnection.") { players }
55+
private val friends by setting("Friends", false, "Exclude friends from triggering player-based disconnections.") { players }
56+
57+
init {
58+
listen<TickEvent.Pre>(-1000) {
59+
Reason.entries.filter {
60+
it.check()
61+
}.forEach { reason ->
62+
reason.generateReason(this)?.let { reasonText ->
63+
disconnect(reason, reasonText)
64+
return@listen
65+
}
66+
}
67+
}
68+
}
69+
70+
private fun SafeContext.disconnect(reason: Reason, reasonText: Text) {
71+
connection.connection.disconnect(generateInfo(reasonText))
72+
playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP)
73+
if (reason == Reason.HEALTH || reason == Reason.TOTEM) disable()
74+
}
75+
76+
private fun SafeContext.generateInfo(text: Text) = buildText {
77+
text(prefix(Communication.LogLevel.WARN.logoColor))
78+
text(text)
79+
literal("\n\n")
80+
literal("Disconnected at ")
81+
highlighted(player.pos.string)
82+
literal(" on ")
83+
highlighted(Communication.currentTime())
84+
literal(" with ")
85+
highlighted("%.2f".format(player.health))
86+
literal(" health.")
87+
if (player.isOnFire) {
88+
literal("\n")
89+
literal("Burning for ")
90+
highlighted("${player.fireTicks}")
91+
literal(" ticks.")
92+
}
93+
94+
color(Color.YELLOW) {
95+
literal("\n\n")
96+
literal("AutoDisconnect disabled.")
97+
}
98+
}
99+
100+
enum class Reason(val check: () -> Boolean, val generateReason: SafeContext.() -> Text?) {
101+
HEALTH({ health }, {
102+
if (player.health < minimumHealth) {
103+
buildText {
104+
literal("Health ")
105+
highlighted("%.2f".format(player.health))
106+
literal(" below minimum of ")
107+
highlighted("$minimumHealth")
108+
literal("!")
109+
}
110+
} else null
111+
}),
112+
TOTEM({ totem }, {
113+
val totemCount = player.combined.count { it.item == Items.TOTEM_OF_UNDYING }
114+
if (totemCount < minTotems) {
115+
buildText {
116+
literal("Only ")
117+
highlighted("$totemCount")
118+
literal(" totems left, required minimum: ")
119+
highlighted("$minTotems")
120+
literal("!")
121+
}
122+
} else null
123+
}),
124+
CREEPER({ creeper }, {
125+
fastEntitySearch<CreeperEntity>(15.0).find {
126+
it.getClientFuseTime(mc.tickDelta) > 0.0
127+
&& it.pos.distanceTo(player.pos) <= 5.0
128+
}?.let { creeper ->
129+
buildText {
130+
literal("An ignited creeper was ")
131+
highlighted("%.2f".format(creeper.pos.distanceTo(player.pos)))
132+
literal(" blocks away!")
133+
}
134+
}
135+
}),
136+
PLAYER({ players }, {
137+
fastEntitySearch<PlayerEntity>(minPlayerDistance.toDouble()).find { otherPlayer ->
138+
otherPlayer != player
139+
&& player.distanceTo(otherPlayer) <= minPlayerDistance
140+
&& (!friends || !FriendManager.isFriend(otherPlayer.uuid))
141+
}?.let { otherPlayer ->
142+
buildText {
143+
literal("A player (${otherPlayer.name}) was ")
144+
highlighted("${"%.2f".format(otherPlayer.distanceTo(player))} blocks away")
145+
literal("!")
146+
}
147+
}
148+
}),
149+
END_CRYSTAL({ crystals }, {
150+
fastEntitySearch<EndCrystalEntity>(10.2).find {
151+
player.health - explosionDamage(it.pos, player, 6.0) <= 1.0
152+
}?.let { crystal ->
153+
val damage = explosionDamage(crystal.pos, player, 6.0)
154+
buildText {
155+
literal("An end crystal at ")
156+
highlighted(crystal.pos.string)
157+
literal(" could give you ")
158+
highlighted("$damage")
159+
literal(" damage what would kill you!")
160+
}
161+
}
162+
});
163+
}
164+
}

common/src/main/kotlin/com/lambda/util/Communication.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ import com.lambda.util.text.*
3434
import net.minecraft.client.toast.SystemToast
3535
import net.minecraft.text.Text
3636
import java.awt.Color
37+
import java.time.LocalDateTime
38+
import java.time.ZoneId
39+
import java.time.format.DateTimeFormatter
40+
import java.time.format.FormatStyle
3741

3842
object Communication {
3943
val ascii = """
@@ -48,6 +52,10 @@ object Communication {
4852
4953
""".trimIndent()
5054

55+
fun currentTime(): String = LocalDateTime.now()
56+
.atZone(ZoneId.systemDefault())
57+
.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG))
58+
5159
fun Any.debug(message: String, source: String = "") = log(LogLevel.DEBUG.text(message), LogLevel.DEBUG, source)
5260
fun Any.debug(message: Text, source: Text = Text.empty()) = log(message, LogLevel.DEBUG, textSource = source)
5361
fun Any.info(message: String, source: String = "") = log(LogLevel.INFO.text(message), LogLevel.INFO, source)
@@ -103,7 +111,7 @@ object Communication {
103111
textSource: Text = Text.empty(),
104112
color: Color = Color.GRAY,
105113
) = buildText {
106-
text(logLevel.prefix())
114+
text(prefix(logLevel.logoColor))
107115

108116
// ToDo: HUD elements
109117

@@ -196,11 +204,11 @@ object Communication {
196204
}
197205
}
198206

199-
private fun LogLevel.prefix() =
207+
fun prefix(color: Color) =
200208
buildText {
201209
hoverEvent(HoverEvents.showText(buildText {
202210
literal("Lambda ")
203-
color(logoColor) {
211+
color(color) {
204212
literal(Lambda.SYMBOL)
205213
}
206214
literal(" v${Lambda.VERSION}\n")
@@ -219,7 +227,7 @@ object Communication {
219227
literal("Concurrent listeners: ${EventFlow.concurrentListeners.size}")
220228

221229
})) {
222-
styled(logoColor) {
230+
styled(color) {
223231
literal(Lambda.SYMBOL)
224232
}
225233
literal(" ")

0 commit comments

Comments
 (0)