diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index 99977ea81..758a17444 100644 --- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -18,6 +18,7 @@ package com.lambda.mixin.network; import com.lambda.event.EventFlow; +import com.lambda.event.events.ChatEvent; import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.WorldEvent; import com.lambda.interaction.managers.inventory.InventoryManager; @@ -118,4 +119,12 @@ private void wrapOnScreenHandlerSlotUpdate(ScreenHandlerSlotUpdateS2CPacket pack private void wrapOnInventory(InventoryS2CPacket packet, Operation original) { InventoryManager.onInventoryUpdate(packet, original); } + + @WrapMethod(method = "sendChatMessage(Ljava/lang/String;)V") + void onSendMessage(String content, Operation original) { + var event = new ChatEvent.Send(content); + + if (!EventFlow.post(event).isCanceled()) + original.call(event.getMessage()); + } } \ No newline at end of file diff --git a/src/main/java/com/lambda/mixin/render/ChatHudMixin.java b/src/main/java/com/lambda/mixin/render/ChatHudMixin.java index 890624a8d..2b6790df0 100644 --- a/src/main/java/com/lambda/mixin/render/ChatHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/ChatHudMixin.java @@ -44,7 +44,7 @@ public class ChatHudMixin { @WrapMethod(method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V") void wrapAddMessage(Text message, MessageSignatureData signatureData, MessageIndicator indicator, Operation original) { - var event = new ChatEvent.Message(message, signatureData, indicator); + var event = new ChatEvent.Receive(message, signatureData, indicator); if (!EventFlow.post(event).isCanceled()) original.call(event.getMessage(), event.getSignature(), event.getIndicator()); diff --git a/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt b/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt index 295c4db21..1d53a3d72 100644 --- a/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt @@ -23,22 +23,22 @@ import com.lambda.util.NamedEnum class FormatterSettings( c: Configurable, - baseGroup: NamedEnum, + vararg baseGroup: NamedEnum, ) : FormatterConfig, SettingGroup(c) { - val localeEnum by c.setting("Locale", FormatterConfig.Locales.US, "The regional formatting used for numbers").group(baseGroup).index() + val localeEnum by c.setting("Locale", FormatterConfig.Locales.US, "The regional formatting used for numbers").group(*baseGroup).index() override val locale get() = localeEnum.locale - val sep by c.setting("Separator", FormatterConfig.TupleSeparator.Comma, "Separator for string serialization of tuple data structures").group(baseGroup).index() - val customSep by c.setting("Custom Separator", "") { sep == FormatterConfig.TupleSeparator.Custom }.group(baseGroup).index() + val sep by c.setting("Separator", FormatterConfig.TupleSeparator.Comma, "Separator for string serialization of tuple data structures").group(*baseGroup).index() + val customSep by c.setting("Custom Separator", "") { sep == FormatterConfig.TupleSeparator.Custom }.group(*baseGroup).index() override val separator get() = if (sep == FormatterConfig.TupleSeparator.Custom) customSep else sep.separator - val group by c.setting("Tuple Prefix", FormatterConfig.TupleGrouping.Parentheses).group(baseGroup).index() + val group by c.setting("Tuple Prefix", FormatterConfig.TupleGrouping.Parentheses).group(*baseGroup).index() override val prefix get() = group.prefix override val postfix get() = group.postfix - val floatingPrecision by c.setting("Floating Precision", 3, 0..6, 1, "Precision for floating point numbers").group(baseGroup).index() + val floatingPrecision by c.setting("Floating Precision", 3, 0..6, 1, "Precision for floating point numbers").group(*baseGroup).index() override val precision get() = floatingPrecision - val timeFormat by c.setting("Time Format", FormatterConfig.Time.IsoDateTime).group(baseGroup).index() + val timeFormat by c.setting("Time Format", FormatterConfig.Time.IsoDateTime).group(*baseGroup).index() override val format get() = timeFormat.format } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/event/events/ChatEvent.kt b/src/main/kotlin/com/lambda/event/events/ChatEvent.kt index ed40475b7..aed5d57fe 100644 --- a/src/main/kotlin/com/lambda/event/events/ChatEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/ChatEvent.kt @@ -24,7 +24,9 @@ import net.minecraft.network.message.MessageSignatureData import net.minecraft.text.Text sealed class ChatEvent { - class Message( + class Send(var message: String) : Event, Cancellable() + + class Receive( var message: Text, var signature: MessageSignatureData?, var indicator: MessageIndicator?, diff --git a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt index 7a9a9756f..d01d101eb 100644 --- a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt +++ b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt @@ -35,7 +35,7 @@ import com.lambda.util.ChatUtils.slurs import com.lambda.util.ChatUtils.swears import com.lambda.util.ChatUtils.toAscii import com.lambda.util.NamedEnum -import com.lambda.util.text.MessageDirection +import com.lambda.util.text.DirectMessage import com.lambda.util.text.MessageParser import com.lambda.util.text.MessageType import net.minecraft.text.Text @@ -79,13 +79,13 @@ object AntiSpam : Module( } init { - listen { event -> + listen { event -> var raw = event.message.string val author = MessageParser.playerName(raw) if ( - ignoreSystem && !MessageType.Both.matches(raw) && !MessageDirection.Both.matches(raw) || - ignoreDms && MessageDirection.Receive.matches(raw) + ignoreSystem && !MessageType.Both.matches(raw) && !DirectMessage.Both.matches(raw) || + ignoreDms && DirectMessage.Receive.matches(raw) ) return@listen val slurMatches = slurs.takeIf { detectSlurs.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } @@ -102,8 +102,8 @@ object AntiSpam : Module( fun doMatch(replace: ReplaceConfig, matches: Sequence) { if ( cancelled || - filterSystem && !MessageType.Both.matches(raw) && !MessageDirection.Both.matches(raw) || - filterDms && MessageDirection.Receive.matches(raw) || + filterSystem && !MessageType.Both.matches(raw) && !DirectMessage.Both.matches(raw) || + filterDms && DirectMessage.Receive.matches(raw) || filterFriends && author?.let { FriendManager.isFriend(it) } == true || filterSelf && MessageType.Self.matches(raw) ) return @@ -127,9 +127,9 @@ object AntiSpam : Module( doMatch(detectColors, colorMatches) if (cancelled) return@listen event.cancel() - if (!hasMatches) return@listen - event.message = Text.of(if (fancyChats) raw.toAscii else raw) + if (hasMatches) + event.message = Text.of(if (fancyChats) raw.toAscii else raw) } } diff --git a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt new file mode 100644 index 000000000..d28caeaa0 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.lambda.config.applyEdits +import com.lambda.config.groups.FormatterConfig +import com.lambda.config.groups.FormatterSettings +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.Formatting.format +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import com.lambda.util.text.styled +import com.lambda.util.text.text +import net.minecraft.util.Formatting +import java.awt.Color +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit + +object ChatTimestamp : Module( + name = "ChatTimestamp", + description = "Displays the time a message was sent next to it", + tag = ModuleTag.CHAT, +) { + var color: Formatting by setting("Color", Formatting.GRAY) + .onValueChange { from, to -> if (to.colorIndex !in 0..15) color = from } + + val javaColor: Color get() = Color(color.colorValue!! and 16777215) + + val formatter = FormatterSettings(this).apply { applyEdits { hide(::localeEnum, ::sep, ::customSep, ::group, ::floatingPrecision); editTyped(::timeFormat) { defaultValue(FormatterConfig.Time.IsoLocalTime) } } } + + private val currentTime get() = + ZonedDateTime.of(LocalDateTime.now(), ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.SECONDS) + + init { + listen { + it.message = buildText { + text(it.message) + literal(" ") + styled(javaColor, italic = true) { literal(currentTime.format(formatter)) } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt new file mode 100644 index 000000000..af1b9edf0 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.google.common.collect.Comparators.min +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.NamedEnum + +object CustomChat : Module( + name = "CustomChat", + description = "Adds a custom ending to your message", + tag = ModuleTag.CHAT, +) { + private val decoration by setting("Decoration", Decoration.Separator) + private val text by setting("Text", Text.Lambda) + + private val customText by setting("Custom Text", "") { text == Text.Custom } + + init { + listen { + val message = "${it.message} ${decoration.block(text.block())}" + it.message = message.take(min(256, message.length)) + } + } + + enum class Decoration(val block: (String) -> String) { + Separator({ "| $it" }), + Classic({ "\u00ab $it \u00bb" }), + None({ it }) + } + + private enum class Text(override val displayName: String, val block: () -> String) : NamedEnum { + Lambda("Lambda", { "ʟᴀᴍʙᴅᴀ" }), + LambdaOnTop("Lambda On Top", { "ʟᴀᴍʙᴅᴀ ᴏɴ ᴛᴏᴘ" }), + KamiBlue("Kami Blue", { "ᴋᴀᴍɪ ʙʟᴜᴇ" }), + LambdaWebsite("Lambda Website", { "lambda-client.org" }), + Custom("Custom", { customText }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt new file mode 100644 index 000000000..f748993f2 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.ChatUtils.toBlue +import com.lambda.util.ChatUtils.toGreen +import com.lambda.util.ChatUtils.toLeet +import com.lambda.util.ChatUtils.toUwu + +object FancyChat : Module( + name = "FancyChat", + description = "Makes messages you send - fancy", + tag = ModuleTag.CHAT, +) { + private val uwu by setting("uwu", true) + private val leet by setting("1337", false) + private val green by setting(">", false) + private val blue by setting("`", false) + + init { + listen { + if (uwu) it.message = it.message.toUwu + if (leet) it.message = it.message.toLeet + if (green) it.message = it.message.toGreen + if (blue) it.message = it.message.toBlue + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt new file mode 100644 index 000000000..0f257abf8 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.friend.FriendManager +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.sound.SoundManager.playSound +import com.lambda.util.Communication.logError +import com.lambda.util.text.MessageType +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import com.lambda.util.text.styled +import net.minecraft.sound.SoundEvents +import net.minecraft.util.Formatting +import java.awt.Color + +object FriendHighlight : Module( + name = "FriendHighlight", + description = "Highlights your friends names in chat", + tag = ModuleTag.CHAT, +) { + var color: Formatting by setting("Color", Formatting.GREEN) + .onValueChange { from, to -> if (to.colorIndex !in 0..15) color = from } + + val javaColor: Color get() = Color(color.colorValue!! and 16777215) + + val bold by setting("Bold", true) + val italic by setting("Italic", false) + val underlined by setting("Underlined", false) + val strikethrough by setting("Strikethrough", false) + + val ping by setting("Ping On Message", true) + + init { + onEnable { + if (FriendManager.friends.isEmpty()) + logError("You don't have any friends added, silly! Go add some friends before using the module") + } + + listen { + val raw = it.message.string + val author = MessageType.Others.playerName(raw) ?: return@listen + val content = MessageType.Others.removedOrNull(raw) ?: return@listen + + if (!FriendManager.isFriend(author)) return@listen + + if (ping) playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP) + + it.message = buildText { + literal("<") + styled(javaColor, bold, italic, underlined, strikethrough) { literal(author) } + literal(">") + + literal(content.toString()) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/text/Detections.kt b/src/main/kotlin/com/lambda/util/text/Detections.kt index 4ddf02d7e..4d9bab24f 100644 --- a/src/main/kotlin/com/lambda/util/text/Detections.kt +++ b/src/main/kotlin/com/lambda/util/text/Detections.kt @@ -17,12 +17,7 @@ package com.lambda.util.text -import baritone.api.BaritoneAPI import com.lambda.Lambda.mc -import com.lambda.command.CommandRegistry -import com.lambda.command.commands.PrefixCommand -import kotlin.math.max -import kotlin.text.substring val playerRegex = "^<(.+)>".toRegex() @@ -72,28 +67,28 @@ enum class MessageType : Detector, PlayerDetector, RemovableDetector { override fun matches(input: CharSequence) = playerName(input) != null override fun playerName(input: CharSequence) = - playerRegex.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() && it != name }?.drop(1)?.dropLast(1) + playerRegex.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() && it != name } }, Both { override fun matches(input: CharSequence) = input.contains(playerRegex) override fun playerName(input: CharSequence) = - playerRegex.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() }?.drop(1)?.dropLast(1) + playerRegex.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() } }; override fun removedOrNull(input: CharSequence) = playerName(input)?.let { input.removePrefix("<$it>") } } -enum class MessageDirection(override vararg val regexes: Regex) : RegexDetector, PlayerDetector { - Sent("^To (.+?): ".toRegex(RegexOption.IGNORE_CASE)), +enum class DirectMessage(override vararg val regexes: Regex) : RegexDetector, PlayerDetector { + Send("^To (.+?): ".toRegex(RegexOption.IGNORE_CASE)), Receive( "^(.+?) whispers( to you)?: ".toRegex(), "^\\[?(.+?)( )?->( )?.+?]?( )?:? ".toRegex(), "^From (.+?): ".toRegex(RegexOption.IGNORE_CASE), "^. (.+?) » .w+? » ".toRegex() ), - Both(*Sent.regexes, *Receive.regexes); + Both(*Send.regexes, *Receive.regexes); override fun playerName(input: CharSequence) = result(input)?.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() }