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").
  • Loading branch information
mattco98 committed Dec 27, 2023
1 parent b52fde5 commit 063602a
Show file tree
Hide file tree
Showing 11 changed files with 492 additions and 538 deletions.
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 chatLineId the chat line id of the message to be deleted
*/
@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 063602a

Please sign in to comment.