Skip to content

Commit

Permalink
TextComponent: Completely rewrite + delete Message
Browse files Browse the repository at this point in the history
The separation between TextComponent and Message was vague at best, and
confusing at worst (as observed from questions in the Discord). To solve
this, they are merged together into a single TextComponent class, and
the API has been altered a bit. Specifically:

- It is now very easy to iterate over the different parts (i.e. text +
  style) of a TextComponent. It implements List<NativeObject>, and the
  objects that are iterated are in the form "{ text: '...', bold: true,
  italic: true, ... }".
- It is now immutable. To this end, there are helper methods that return
  modified version of the TextComponent, like withTextAt() that inserts
  a part at a given index.
- The 'isFormatted' flag has been removed, and TextComponents are now
  always formatted. This flag had limited utility, as formatting codes
  can be stripped with ChatLib.removeFormatting, and they can also be
  escaped anywhere where formatted strings are accepted (i.e. "\&afoo").

Closes #93
  • Loading branch information
mattco98 committed Dec 27, 2023
1 parent 39e9587 commit bd38315
Show file tree
Hide file tree
Showing 13 changed files with 552 additions and 597 deletions.
115 changes: 56 additions & 59 deletions api/ctjs.api
Original file line number Diff line number Diff line change
Expand Up @@ -1387,12 +1387,14 @@ public final class com/chattriggers/ctjs/api/message/ChatLib {
public static synthetic fun command$default (Ljava/lang/String;ZILjava/lang/Object;)V
public static final fun copyToClipboard (Ljava/lang/String;)V
public static final fun deleteChat (I)V
public static final fun deleteChat (Lcom/chattriggers/ctjs/api/message/Message;)V
public static final fun deleteChat (Lcom/chattriggers/ctjs/api/message/TextComponent;)V
public static final fun deleteChat (Ljava/lang/String;)V
public static final fun deleteChat (Lkotlin/jvm/functions/Function1;)V
public static final fun deleteChat (Lorg/mozilla/javascript/regexp/NativeRegExp;)V
public static final fun editChat (I[Ljava/lang/Object;)V
public static final fun editChat (Lcom/chattriggers/ctjs/api/message/Message;[Ljava/lang/Object;)V
public static final fun editChat (Lcom/chattriggers/ctjs/api/message/TextComponent;[Ljava/lang/Object;)V
public static final fun editChat (Ljava/lang/String;[Ljava/lang/Object;)V
public static final fun editChat (Lkotlin/jvm/functions/Function1;[Ljava/lang/Object;)V
public static final fun editChat (Lorg/mozilla/javascript/regexp/NativeRegExp;[Ljava/lang/Object;)V
public static final fun getCenteredText (Ljava/lang/String;)Ljava/lang/String;
public static final fun getChatBreak ()Ljava/lang/String;
Expand All @@ -1406,77 +1408,72 @@ public final class com/chattriggers/ctjs/api/message/ChatLib {
public static final fun simulateChat (Ljava/lang/Object;)V
}

public final class com/chattriggers/ctjs/api/message/Message {
public fun <init> (Lcom/chattriggers/ctjs/api/message/TextComponent;)V
public fun <init> ([Ljava/lang/Object;)V
public final fun actionBar ()Lcom/chattriggers/ctjs/api/message/Message;
public final fun addTextComponent (ILjava/lang/Object;)Lcom/chattriggers/ctjs/api/message/Message;
public final fun addTextComponent (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/Message;
public final fun chat ()Lcom/chattriggers/ctjs/api/message/Message;
public final fun edit ([Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/Message;
public final fun getChatLineId ()I
public final fun getChatMessage ()Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun getFormattedText ()Ljava/lang/String;
public final fun getMessageParts ()Ljava/util/List;
public final fun getUnformattedText ()Ljava/lang/String;
public final fun isFormatted ()Z
public final fun isRecursive ()Z
public final fun mutable ()Lcom/chattriggers/ctjs/api/message/Message;
public final fun setChatLineId (I)Lcom/chattriggers/ctjs/api/message/Message;
public final fun setFormatted (Z)Lcom/chattriggers/ctjs/api/message/Message;
public final fun setRecursive (Z)Lcom/chattriggers/ctjs/api/message/Message;
public final fun setTextComponent (ILjava/lang/Object;)Lcom/chattriggers/ctjs/api/message/Message;
}

public final class com/chattriggers/ctjs/api/message/TextComponent : net/minecraft/text/Text {
public final class com/chattriggers/ctjs/api/message/TextComponent : java/util/List, kotlin/jvm/internal/markers/KMappedMarker, net/minecraft/text/Text {
public static final field Companion Lcom/chattriggers/ctjs/api/message/TextComponent$Companion;
public fun <init> (Ljava/lang/String;)V
public fun <init> (Lnet/minecraft/text/MutableText;)V
public fun <init> (Lnet/minecraft/text/Text;)V
public fun <init> ()V
public fun <init> ([Ljava/lang/Object;)V
public final fun actionBar ()Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun append (Lnet/minecraft/text/Text;)Lnet/minecraft/text/MutableText;
public final fun appendSibling (Lnet/minecraft/text/Text;)Lnet/minecraft/text/MutableText;
public synthetic fun add (ILjava/lang/Object;)V
public fun add (ILorg/mozilla/javascript/NativeObject;)V
public synthetic fun add (Ljava/lang/Object;)Z
public fun add (Lorg/mozilla/javascript/NativeObject;)Z
public fun addAll (ILjava/util/Collection;)Z
public fun addAll (Ljava/util/Collection;)Z
public fun asOrderedText ()Lnet/minecraft/text/OrderedText;
public fun asTruncatedString (I)Ljava/lang/String;
public final fun chat ()Lcom/chattriggers/ctjs/api/message/TextComponent;
public fun contains (Lnet/minecraft/text/Text;)Z
public fun copy ()Lnet/minecraft/text/MutableText;
public fun copyContentOnly ()Lnet/minecraft/text/MutableText;
public final fun getClickAction ()Lnet/minecraft/text/ClickEvent$Action;
public final fun getClickValue ()Ljava/lang/String;
public final fun getComponent ()Lnet/minecraft/text/MutableText;
public fun clear ()V
public final fun contains (Ljava/lang/Object;)Z
public fun contains (Lorg/mozilla/javascript/NativeObject;)Z
public fun containsAll (Ljava/util/Collection;)Z
public final fun delete ()Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun edit (Lcom/chattriggers/ctjs/api/message/TextComponent;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun edit ([Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public synthetic fun get (I)Ljava/lang/Object;
public fun get (I)Lorg/mozilla/javascript/NativeObject;
public final fun getChatLineId ()I
public fun getContent ()Lnet/minecraft/text/TextContent;
public final fun getFormattedText ()Ljava/lang/String;
public final fun getHoverAction ()Lnet/minecraft/text/HoverEvent$Action;
public final fun getHoverValue ()Ljava/lang/Object;
public fun getSiblings ()Ljava/util/List;
public fun getSize ()I
public fun getString ()Ljava/lang/String;
public fun getStyle ()Lnet/minecraft/text/Style;
public final fun getText ()Ljava/lang/String;
public final fun getUnformattedText ()Ljava/lang/String;
public fun getWithStyle (Lnet/minecraft/text/Style;)Ljava/util/List;
public final fun isFormatted ()Z
public final fun setClick (Ljava/lang/String;Ljava/lang/String;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setClick (Lnet/minecraft/text/ClickEvent$Action;Ljava/lang/String;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setClickAction (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setClickValue (Ljava/lang/String;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setColor (III)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setColor (J)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setFormatted (Z)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setHover (Ljava/lang/String;Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setHover (Lnet/minecraft/text/HoverEvent$Action;Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setHoverAction (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setHoverValue (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun setText (Ljava/lang/String;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun indexOf (Ljava/lang/Object;)I
public fun indexOf (Lorg/mozilla/javascript/NativeObject;)I
public fun isEmpty ()Z
public final fun isRecursive ()Z
public fun iterator ()Ljava/util/Iterator;
public final fun lastIndexOf (Ljava/lang/Object;)I
public fun lastIndexOf (Lorg/mozilla/javascript/NativeObject;)I
public fun listIterator ()Ljava/util/ListIterator;
public fun listIterator (I)Ljava/util/ListIterator;
public synthetic fun remove (I)Ljava/lang/Object;
public fun remove (I)Lorg/mozilla/javascript/NativeObject;
public fun remove (Ljava/lang/Object;)Z
public fun removeAll (Ljava/util/Collection;)Z
public fun replaceAll (Ljava/util/function/UnaryOperator;)V
public fun retainAll (Ljava/util/Collection;)Z
public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object;
public fun set (ILorg/mozilla/javascript/NativeObject;)Lorg/mozilla/javascript/NativeObject;
public final fun size ()I
public fun sort (Ljava/util/Comparator;)V
public fun spliterator ()Ljava/util/Spliterator;
public fun subList (II)Ljava/util/List;
public fun toArray ()[Ljava/lang/Object;
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
public fun toString ()Ljava/lang/String;
public fun visit (Lnet/minecraft/text/StringVisitable$StyledVisitor;Lnet/minecraft/text/Style;)Ljava/util/Optional;
public fun visit (Lnet/minecraft/text/StringVisitable$Visitor;)Ljava/util/Optional;
public fun withoutStyle ()Ljava/util/List;
public final fun withChatLineId ()Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun withChatLineId (I)Lcom/chattriggers/ctjs/api/message/TextComponent;
public static synthetic fun withChatLineId$default (Lcom/chattriggers/ctjs/api/message/TextComponent;IILjava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun withRecursive ()Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun withRecursive (Z)Lcom/chattriggers/ctjs/api/message/TextComponent;
public static synthetic fun withRecursive$default (Lcom/chattriggers/ctjs/api/message/TextComponent;ZILjava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun withText (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun withTextAt (ILjava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun withoutTextAt (I)Lcom/chattriggers/ctjs/api/message/TextComponent;
}

public final class com/chattriggers/ctjs/api/message/TextComponent$Companion {
public final fun from (Ljava/lang/Object;)Lcom/chattriggers/ctjs/api/message/TextComponent;
public final fun stripFormatting (Ljava/lang/String;)Ljava/lang/String;
}

public final class com/chattriggers/ctjs/api/render/Book {
Expand Down
4 changes: 4 additions & 0 deletions docs/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Here is a list of targeted changes for various different APIs:
- `renderOverlay` no longer passes in the event, as it was unused previously
- Removed all Trigger classes from the global namespace
- Removed `CancellableEvent` from the global scope
- `Message`/`TextComponent`
- `Message` has been removed, and its primary functionality (i.e. `chat()`/`actionBar()`) has been added to `TextComponent`
- `TextComponent` has been heavily changed such that it can be easily introspected. It now implements `List<NativeObject>`, and each object is of the form `{ text: '...', bold: true, underline: true, ... }`. This form can also be used to construct and create new `TextComponent`s
- `TextComponent` is now immutable. Methods such as `withText()` can be used to return a modified `TextComponent` based on the original
- The `/ct` command
- Removed `/ct copy`. Replace this with `Client.copy(text: String)`
- Removed the following aliases:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class PlayerEntityMixin implements NameTagOverridable {
@ModifyVariable(method = "getDisplayName", at = @At(value = "STORE", ordinal = 0))
private MutableText injectGetName(MutableText original) {
if (overriddenNametagName != null)
return overriddenNametagName.getComponent();
return overriddenNametagName.toMutableText$ctjs();
return original;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,23 +853,23 @@ object DynamicCommands : CommandCollection() {
if (impl.selectors.isEmpty())
return TextComponent(text)

val component = TextComponent(text.substring(0, impl.selectors[0].start))
var component = TextComponent(text.substring(0, impl.selectors[0].start))
var i = impl.selectors[0].start

for (selector in impl.selectors) {
val entities = EntitySelectorWrapper(selector.selector).getEntities()
val nameComponent = EntitySelector.getNames(entities.map(Entity::toMC))
if (i < selector.start)
component.append(TextComponent(text.substring(i, selector.start)))
component = component.withText(text.substring(i, selector.start))

if (nameComponent != null)
component.append(nameComponent)
component = component.withText(nameComponent)

i = selector.end
}

if (i < text.length)
component.append(TextComponent(text.drop(i)))
component = component.withText(text.drop(i))

return component
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class PlayerMP(override val mcValue: PlayerEntity) : LivingEntity(mcValue) {
}

private fun getPlayerName(playerListEntry: PlayerListEntry?): TextComponent {
return playerListEntry?.displayName?.let(::TextComponent)
return playerListEntry?.displayName?.let { TextComponent(it) }
?: TextComponent(
MCTeam.decorateName(
playerListEntry?.scoreboardTeam,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class Item(override val mcValue: ItemStack) : CTWrapper<ItemStack> {
fun getLore(advanced: Boolean = false): List<TextComponent> {
mcValue.asMixin<Skippable>().ctjs_setShouldSkip(true)
val tooltip = mcValue.getTooltip(Player.toMC(), if (advanced) TooltipContext.ADVANCED else TooltipContext.BASIC)
.map(::TextComponent)
.map { TextComponent(it) }
mcValue.asMixin<Skippable>().ctjs_setShouldSkip(false)

return tooltip
Expand Down
86 changes: 50 additions & 36 deletions src/main/kotlin/com/chattriggers/ctjs/api/message/ChatLib.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.chattriggers.ctjs.api.message

import com.chattriggers.ctjs.api.client.Client
import com.chattriggers.ctjs.api.client.Player
import com.chattriggers.ctjs.api.render.Renderer
import com.chattriggers.ctjs.engine.printToConsole
import com.chattriggers.ctjs.internal.listeners.ClientListener
import com.chattriggers.ctjs.internal.mixins.ChatHudAccessor
import com.chattriggers.ctjs.internal.utils.asMixin
Expand All @@ -22,50 +20,47 @@ object ChatLib {

/**
* Prints text in the chat.
* The text can be a String, a [Message] or a [TextComponent]
* The text can be a String or a [TextComponent]
*
* @param text the text to be printed
*/
@JvmStatic
fun chat(text: Any?) {
when (text) {
is String -> Message(text).chat()
is Message -> text.chat()
is TextComponent -> text.chat()
else -> Message(text.toString()).chat()
}
is TextComponent -> text
is String -> TextComponent(text)
else -> TextComponent(text.toString())
}.chat()
}

/**
* Shows text in the action bar.
* The text can be a String, a [Message] or a [TextComponent]
* The text can be a String or a [TextComponent]
*
* @param text the text to show
*/
@JvmStatic
fun actionBar(text: Any?) {
when (text) {
is String -> Message(text).actionBar()
is Message -> text.actionBar()
is TextComponent -> text.actionBar()
else -> Message(text.toString()).actionBar()
}
is TextComponent -> text
is String -> TextComponent(text)
else -> TextComponent(text.toString())
}.actionBar()
}

/**
* Simulates a chat message to be caught by other triggers for testing.
* The text can be a String, a [Message] or a [TextComponent]
* The text can be a String or a [TextComponent]
*
* @param text The message to simulate
*/
@JvmStatic
fun simulateChat(text: Any?) {
when (text) {
is String -> Message(text).setRecursive(true).chat()
is Message -> text.setRecursive(true).chat()
is TextComponent -> Message(text).setRecursive(true).chat()
else -> Message(text.toString()).setRecursive(true).chat()
}
is TextComponent -> text
is String -> TextComponent(text)
else -> TextComponent(text.toString())
}.withRecursive().chat()
}


Expand Down Expand Up @@ -229,15 +224,15 @@ object ChatLib {
}

/**
* Edits an already sent chat message by the [Message]
* Edits an already sent chat message by the [TextComponent]
*
* @param toReplace the message to be replaced
* @param replacements the new message(s) to be put in place of the old one
*/
@JvmStatic
fun editChat(toReplace: Message, vararg replacements: Any) {
fun editChat(toReplace: TextComponent, vararg replacements: Any) {
editLines(replacements) {
toReplace.chatMessage.formattedText == TextComponent(it.content).formattedText
toReplace.formattedText == TextComponent(it.content).formattedText
}
}

Expand All @@ -254,6 +249,18 @@ object ChatLib {
}
}

/**
* Edits an already sent chat message by given a callback that receives
* [TextComponent] instances
*
* @param matcher a function that accepts a [TextComponent] and returns a boolean
* @param replacements the new message(s) to be put in place of the old one
*/
@JvmStatic
fun editChat(matcher: (TextComponent) -> Boolean, vararg replacements: Any) {
editLines(replacements) { matcher(TextComponent(it.content)) }
}

private fun editLines(replacements: Array<out Any>, matcher: (ChatHudLine) -> Boolean) {
val mc = Client.getMinecraft()
val indicator = if (mc.isConnectedToLocalServer) MessageIndicator.singlePlayer() else MessageIndicator.system()
Expand All @@ -267,11 +274,8 @@ object ChatLib {
it.remove()
chatLineIds.remove(next)
for (replacement in replacements) {
val message = if (replacement !is Message) {
TextComponent.from(replacement)?.let(::Message) ?: continue
} else replacement

val line = ChatHudLine(next.creationTick, message.chatMessage, null, indicator)
val message = replacement as? TextComponent ?: TextComponent(replacement)
val line = ChatHudLine(next.creationTick, message, null, indicator)
if (message.getChatLineId() != -1)
chatLineIds[line] = message.getChatLineId()

Expand Down Expand Up @@ -317,14 +321,14 @@ object ChatLib {
}

/**
* Deletes an already sent chat message by the [Message]
* Deletes an already sent chat message by the [TextComponent]
*
* @param toDelete the message to be deleted
*/
@JvmStatic
fun deleteChat(toDelete: Message) {
fun deleteChat(toDelete: TextComponent) {
removeLines {
toDelete.chatMessage.formattedText == TextComponent(it.content).formattedText
toDelete.formattedText == TextComponent(it.content).formattedText
}
}

Expand All @@ -338,6 +342,17 @@ object ChatLib {
removeLines { chatLineIds[it] == chatLineId }
}

/**
* Deletes an already sent chat message given a callback that receives
* [TextComponent] instances
*
* @param matcher a function that accepts a [TextComponent] and returns a boolean
*/
@JvmStatic
fun deleteChat(matcher: (TextComponent) -> Boolean) {
removeLines { matcher(TextComponent(it.content)) }
}

private fun removeLines(matcher: (ChatHudLine) -> Boolean) {
var removed = false
val it = chatHudAccessor?.messages?.listIterator() ?: return
Expand Down Expand Up @@ -383,15 +398,14 @@ object ChatLib {
}
}

internal fun sendMessageWithId(message: Message) {
internal fun sendMessageWithId(message: TextComponent) {
require(message.getChatLineId() != -1)

val chatGui = Client.getChatGui() ?: return
val chatMessage = message.chatMessage
chatGui.addMessage(chatMessage)
val newChatLine: ChatHudLine = chatHudAccessor!!.messages[0]
chatGui.addMessage(message)
val newChatLine = chatHudAccessor!!.messages[0]

check(chatMessage == newChatLine.content()) {
check(message == newChatLine.content()) {
"Expected new chat message to be at index 0"
}

Expand Down
Loading

0 comments on commit bd38315

Please sign in to comment.