wip: refactor chatting, signed chat: chat formatting

This commit is contained in:
Bixilon 2022-10-19 19:49:23 +02:00
parent e99f47b845
commit f4bb0f2c52
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
32 changed files with 553 additions and 170 deletions

View File

@ -0,0 +1,36 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat
import de.bixilon.minosoft.data.chat.sender.*
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.text.formatting.color.ChatColors
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import java.util.*
object ChatUtil {
val DEFAULT_CHAT_COLOR = ChatColors.WHITE
fun PlayConnection.getMessageSender(uuid: UUID): MessageSender {
val entity = this.world.entities[uuid]
if (entity == null) {
val tab = tabList.tabListItemsByUUID[uuid] ?: return UnknownMessageSender(uuid)
return TabMessageSender(uuid, tab)
}
if (entity !is PlayerEntity) {
return InvalidSender(uuid)
}
return PlayerEntityMessageSender(uuid, entity.name, entity)
}
}

View File

@ -13,8 +13,10 @@
package de.bixilon.minosoft.data.chat.message
import de.bixilon.minosoft.data.registries.chat.ChatMessageType
import de.bixilon.minosoft.data.text.ChatComponent
interface ChatMessage {
val text: ChatComponent
val type: ChatMessageType
}

View File

@ -0,0 +1,37 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.message
import de.bixilon.minosoft.data.chat.ChatTextPositions
import de.bixilon.minosoft.data.chat.ChatUtil
import de.bixilon.minosoft.data.registries.chat.ChatMessageType
import de.bixilon.minosoft.data.registries.chat.ChatParameter
import de.bixilon.minosoft.data.registries.chat.TypeProperties
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.util.KUtil.minosoft
class InternalChatMessage(
override val text: ChatComponent,
) : ChatMessage {
override val type: ChatMessageType get() = TYPE
init {
text.setFallbackColor(ChatUtil.DEFAULT_CHAT_COLOR)
}
companion object {
val TYPE = ChatMessageType(minosoft("internal"), TypeProperties("%s", listOf(ChatParameter.CONTENT), mapOf()), narration = null, position = ChatTextPositions.CHAT)
}
}

View File

@ -13,12 +13,12 @@
package de.bixilon.minosoft.data.chat.message
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
import de.bixilon.minosoft.data.chat.sender.MessageSender
import de.bixilon.minosoft.data.registries.chat.ChatMessageType
import de.bixilon.minosoft.data.text.ChatComponent
import java.util.*
open class PlayerChatMessage(
text: ChatComponent,
type: DefaultMessageTypes,
val sender: UUID,
) : SimpleChatMessage(text, type)
type: ChatMessageType,
override val sender: MessageSender,
) : SimpleChatMessage(text, type), PlayerSentMessage

View File

@ -0,0 +1,20 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.message
import de.bixilon.minosoft.data.chat.sender.MessageSender
interface PlayerSentMessage {
val sender: MessageSender
}

View File

@ -0,0 +1,50 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.message
import de.bixilon.minosoft.data.chat.ChatUtil
import de.bixilon.minosoft.data.chat.filter.Filter
import de.bixilon.minosoft.data.chat.sender.MessageSender
import de.bixilon.minosoft.data.language.lang.Language
import de.bixilon.minosoft.data.registries.chat.ChatMessageType
import de.bixilon.minosoft.data.registries.chat.ChatParameter
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import java.time.Instant
class SignedChatMessage(
private val connection: PlayConnection,
val message: String,
override val type: ChatMessageType,
override val sender: MessageSender,
val parameters: Map<ChatParameter, ChatComponent>,
val filter: Filter?,
val error: Exception?,
val sent: Instant,
val received: Instant,
) : ChatMessage, PlayerSentMessage {
override val text: ChatComponent
init {
// ToDo: parent (formatting)
val data = type.chat.formatParameters(parameters)
text = if (connection.language.canTranslate(type.chat.translationKey.toResourceLocation())) {
connection.language.translate(type.chat.translationKey.toResourceLocation(), data = data)
} else {
Language.translate(type.chat.translationKey, data = data)
}
text.setFallbackColor(ChatUtil.DEFAULT_CHAT_COLOR)
}
}

View File

@ -13,10 +13,15 @@
package de.bixilon.minosoft.data.chat.message
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
import de.bixilon.minosoft.data.chat.ChatUtil
import de.bixilon.minosoft.data.registries.chat.ChatMessageType
import de.bixilon.minosoft.data.text.ChatComponent
open class SimpleChatMessage(
override val text: ChatComponent,
val type: DefaultMessageTypes,
) : ChatMessage
override val type: ChatMessageType,
) : ChatMessage {
init {
text.setFallbackColor(ChatUtil.DEFAULT_CHAT_COLOR)
}
}

View File

@ -0,0 +1,20 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.sender
import java.util.*
class InvalidSender(
override val uuid: UUID,
) : MessageSender

View File

@ -0,0 +1,20 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.sender
import java.util.*
interface MessageSender {
val uuid: UUID
}

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.sender
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import java.util.*
class PlayerEntityMessageSender(
uuid: UUID,
name: String,
val player: PlayerEntity,
) : PlayerMessageSender(uuid, name)

View File

@ -0,0 +1,21 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.sender
import java.util.*
abstract class PlayerMessageSender(
override val uuid: UUID,
val name: String,
) : MessageSender

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.sender
import de.bixilon.minosoft.data.entities.entities.player.tab.TabListItem
import java.util.*
@Deprecated("Bad name")
class TabMessageSender(
uuid: UUID,
val tab: TabListItem,
) : PlayerMessageSender(uuid, tab.name)

View File

@ -0,0 +1,18 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.sender
import java.util.*
class UnknownMessageSender(override val uuid: UUID) : MessageSender

View File

@ -0,0 +1,18 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.signature
object ChatSignatureProperties {
const val MESSAGE_TTL = 2 * 60 * 1000 // 2 Minutes
}

View File

@ -0,0 +1,16 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.signature.errors
class InvalidSignatureError : Exception("Signature does not match")

View File

@ -0,0 +1,21 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.chat.signature.errors
import java.time.Instant
class MessageExpiredError(
val sent: Instant,
val received: Instant,
) : Exception("Message expired: Sent at $sent, but received to late at $received")

View File

@ -25,52 +25,12 @@ class Language(
) : Translator {
override fun canTranslate(key: ResourceLocation?): Boolean {
return data.containsKey(key?.namespace)
return data.containsKey(key?.path)
}
override fun translate(key: ResourceLocation?, parent: TextComponent?, vararg data: Any?): ChatComponent {
val placeholder = this.data[key?.path] ?: return LanguageUtil.getFallbackTranslation(key, parent, data)
val ret = BaseComponent()
val arguments: MutableList<Any?> = mutableListOf()
var splitPlaceholder: List<String> = emptyList()
// Bring arguments in correct oder
FORMATTER_ORDER_REGEX.findAll(placeholder).toList().let {
if (it.isEmpty()) {
// this is not the correct formatter
return@let
}
splitPlaceholder = placeholder.split(FORMATTER_ORDER_REGEX)
for (matchResult in it) {
// 2 groups: Full, index. We don't care about the full value, just skip it
val dataIndex = matchResult.groupValues[1].toInt() - 1
if (dataIndex < 0 || dataIndex > data.size) {
arguments += null
continue
}
arguments += data[dataIndex]
}
}
// check if other splitter already did the job for us
if (splitPlaceholder.isEmpty()) {
placeholder.split(FORMATTER_SPLIT_REGEX).let {
splitPlaceholder = it
arguments.addAll(data.toList())
}
}
// create base component
for ((index, part) in splitPlaceholder.withIndex()) {
ret += ChatComponent.of(part, this, parent)
if (index < data.size) {
ret += ChatComponent.of(arguments[index], this, parent)
}
}
return ret
return Companion.translate(placeholder, parent, this, *data)
}
override fun toString(): String {
@ -80,5 +40,50 @@ class Language(
companion object {
private val FORMATTER_ORDER_REGEX = "%(\\w+)\\\$[sd]".toRegex() // %1$s fell from a high place
private val FORMATTER_SPLIT_REGEX = "%[ds]".toRegex() // %s fell from a high place
fun translate(placeholder: String, parent: TextComponent? = null, translator: Translator? = null, vararg data: Any?): ChatComponent {
val ret = BaseComponent()
val arguments: MutableList<Any?> = mutableListOf()
var splitPlaceholder: List<String> = emptyList()
// Bring arguments in correct oder
FORMATTER_ORDER_REGEX.findAll(placeholder).toList().let {
if (it.isEmpty()) {
// this is not the correct formatter
return@let
}
splitPlaceholder = placeholder.split(FORMATTER_ORDER_REGEX)
for (matchResult in it) {
// 2 groups: Full, index. We don't care about the full value, just skip it
val dataIndex = matchResult.groupValues[1].toInt() - 1
if (dataIndex < 0 || dataIndex > data.size) {
arguments += null
continue
}
arguments += data[dataIndex]
}
}
// check if other splitter already did the job for us
if (splitPlaceholder.isEmpty()) {
placeholder.split(FORMATTER_SPLIT_REGEX).let {
splitPlaceholder = it
arguments.addAll(data.toList())
}
}
// create base component
for ((index, part) in splitPlaceholder.withIndex()) {
ret += ChatComponent.of(part, translator, parent)
if (index < data.size) {
ret += ChatComponent.of(arguments[index], translator, parent)
}
}
return ret
}
}
}

View File

@ -28,6 +28,10 @@ class ChatMessageType(
val position: ChatTextPositions,
) : RegistryItem() {
override fun toString(): String {
return resourceLocation.toString()
}
companion object : ResourceLocationCodec<ChatMessageType> {
override fun deserialize(registries: Registries?, resourceLocation: ResourceLocation, data: JsonObject): ChatMessageType {

View File

@ -16,6 +16,7 @@ package de.bixilon.minosoft.data.registries.chat
import de.bixilon.kutil.json.JsonObject
import de.bixilon.kutil.json.JsonUtil.asJsonList
import de.bixilon.kutil.json.JsonUtil.asJsonObject
import de.bixilon.minosoft.data.text.ChatComponent
class TypeProperties(
val translationKey: String,
@ -23,6 +24,16 @@ class TypeProperties(
val style: Map<String, Any>,
) {
fun formatParameters(parameters: Map<ChatParameter, ChatComponent>): Array<ChatComponent> {
val output: MutableList<ChatComponent> = mutableListOf()
for (parameter in this.parameters) {
output += parameters[parameter] ?: continue
}
return output.toTypedArray()
}
companion object {
fun deserialize(data: JsonObject): TypeProperties {

View File

@ -33,8 +33,8 @@ import de.bixilon.minosoft.gui.rendering.gui.hud.elements.HUDBuilder
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
import de.bixilon.minosoft.modding.event.events.InternalMessageReceiveEvent
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageReceiveEvent
import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker
import de.bixilon.minosoft.util.KUtil.toResourceLocation
@ -82,16 +82,16 @@ class ChatElement(guiRenderer: GUIRenderer) : AbstractChatElement(guiRenderer),
override fun init() {
connection.registerEvent(CallbackEventInvoker.of<ChatMessageReceiveEvent> {
if (it.type.position == ChatTextPositions.HOTBAR) {
if (it.message.type.position == ChatTextPositions.HOTBAR) {
return@of
}
DefaultThreadPool += { messages += it.message }
DefaultThreadPool += { messages += it.message.text }
})
connection.registerEvent(CallbackEventInvoker.of<InternalMessageReceiveEvent> {
if (!profile.chat.internal.hidden) {
return@of
}
DefaultThreadPool += { messages += it.message }
DefaultThreadPool += { messages += it.message.text }
})
renderWindow.inputHandler.registerKeyCallback(

View File

@ -51,7 +51,7 @@ class InternalChatElement(guiRenderer: GUIRenderer) : AbstractChatElement(guiRen
if (profile.chat.internal.hidden) {
return@of
}
DefaultThreadPool += { messages += it.message }
DefaultThreadPool += { messages += it.message.text }
})
}

View File

@ -35,9 +35,9 @@ import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4iUtil.left
import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4iUtil.right
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
import de.bixilon.minosoft.modding.event.events.ExperienceChangeEvent
import de.bixilon.minosoft.modding.event.events.SelectHotbarSlotEvent
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageReceiveEvent
import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import java.lang.Integer.max
@ -179,7 +179,7 @@ class HotbarElement(guiRenderer: GUIRenderer) : Element(guiRenderer), LayoutedEl
connection.registerEvent(CallbackEventInvoker.of<SelectHotbarSlotEvent> { core.base.apply() })
connection.registerEvent(CallbackEventInvoker.of<ChatMessageReceiveEvent> {
if (it.type.position != ChatTextPositions.HOTBAR) {
if (it.message.type.position != ChatTextPositions.HOTBAR) {
return@of
}
hoverText.text = it.message

View File

@ -1,40 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.modding.event.events
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
import de.bixilon.minosoft.data.registries.chat.ChatMessageType
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.connection.play.PlayConnectionEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.s2c.play.chat.ChatMessageS2CP
import de.bixilon.minosoft.protocol.packets.s2c.play.chat.SignedChatMessageS2CP
import de.bixilon.minosoft.protocol.packets.s2c.play.title.HotbarTextS2CP
import java.util.*
class ChatMessageReceiveEvent(
connection: PlayConnection,
initiator: EventInitiators,
val message: ChatComponent,
val type: ChatMessageType,
val sender: UUID?,
) : PlayConnectionEvent(connection, initiator), CancelableEvent {
constructor(connection: PlayConnection, packet: ChatMessageS2CP) : this(connection, EventInitiators.SERVER, packet.message, packet.type, packet.sender)
constructor(connection: PlayConnection, packet: HotbarTextS2CP) : this(connection, EventInitiators.SERVER, packet.text, connection.registries.messageTypeRegistry[DefaultMessageTypes.GAME]!!, null)
constructor(connection: PlayConnection, packet: SignedChatMessageS2CP) : this(connection, EventInitiators.SERVER, packet.message.body.text, connection.registries.messageTypeRegistry[DefaultMessageTypes.CHAT]!!, packet.message.header.sender)
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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.
*
@ -12,12 +12,12 @@
*/
package de.bixilon.minosoft.modding.event.events
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.chat.message.InternalChatMessage
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.connection.play.PlayConnectionEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
class InternalMessageReceiveEvent(
connection: PlayConnection,
val message: ChatComponent,
val message: InternalChatMessage,
) : PlayConnectionEvent(connection, EventInitiators.CLIENT), CancelableEvent

View File

@ -0,0 +1,25 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.modding.event.events.chat
import de.bixilon.minosoft.data.chat.message.ChatMessage
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.CancelableEvent
import de.bixilon.minosoft.modding.event.events.connection.play.PlayConnectionEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
class ChatMessageReceiveEvent(
connection: PlayConnection,
initiator: EventInitiators,
val message: ChatMessage,
) : PlayConnectionEvent(connection, initiator), CancelableEvent

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
* Copyright (C) 2020-2022 Moritz Zwerger
*
* 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.
*
@ -10,9 +10,10 @@
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.modding.event.events
package de.bixilon.minosoft.modding.event.events.chat
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.CancelableEvent
import de.bixilon.minosoft.modding.event.events.connection.play.PlayConnectionEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection

View File

@ -16,6 +16,7 @@ package de.bixilon.minosoft.protocol.network.connection.play
import de.bixilon.kotlinglm.vec3.Vec3d
import de.bixilon.kutil.string.WhitespaceUtil.trimWhitespaces
import de.bixilon.minosoft.commands.stack.CommandStack
import de.bixilon.minosoft.data.chat.message.InternalChatMessage
import de.bixilon.minosoft.data.chat.signature.Acknowledgement
import de.bixilon.minosoft.data.chat.signature.MessageChain
import de.bixilon.minosoft.data.text.BaseComponent
@ -23,8 +24,8 @@ import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.text.formatting.color.ChatColors
import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.EMPTY
import de.bixilon.minosoft.modding.event.events.ChatMessageSendEvent
import de.bixilon.minosoft.modding.event.events.InternalMessageReceiveEvent
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageSendEvent
import de.bixilon.minosoft.modding.event.events.container.ContainerCloseEvent
import de.bixilon.minosoft.protocol.ProtocolUtil.encodeNetwork
import de.bixilon.minosoft.protocol.packets.c2s.play.chat.ChatMessageC2SP
@ -46,14 +47,14 @@ class ConnectionUtil(
fun sendDebugMessage(message: Any) {
val component = BaseComponent(RenderConstants.DEBUG_MESSAGES_PREFIX, ChatComponent.of(message).apply { this.setFallbackColor(ChatColors.BLUE) })
connection.fireEvent(InternalMessageReceiveEvent(connection, component))
connection.fireEvent(InternalMessageReceiveEvent(connection, InternalChatMessage(component)))
Log.log(LogMessageType.CHAT_IN, LogLevels.INFO) { component }
}
fun sendInternal(message: Any) {
val component = ChatComponent.of(message)
val prefixed = BaseComponent(RenderConstants.INTERNAL_MESSAGES_PREFIX, component)
connection.fireEvent(InternalMessageReceiveEvent(connection, if (connection.profiles.gui.chat.internal.hidden) prefixed else component))
connection.fireEvent(InternalMessageReceiveEvent(connection, InternalChatMessage(if (connection.profiles.gui.chat.internal.hidden) prefixed else component)))
Log.log(LogMessageType.CHAT_IN, LogLevels.INFO) { prefixed }
}

View File

@ -45,7 +45,7 @@ import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.gui.eros.dialog.ErosErrorReport.Companion.report
import de.bixilon.minosoft.gui.rendering.Rendering
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageReceiveEvent
import de.bixilon.minosoft.modding.event.events.loading.RegistriesLoadEvent
import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker
import de.bixilon.minosoft.protocol.network.connection.Connection
@ -151,12 +151,12 @@ class PlayConnection(
}
registerEvent(CallbackEventInvoker.of<ChatMessageReceiveEvent> {
val additionalPrefix = when (it.type.position) {
val additionalPrefix = when (it.message.type.position) {
ChatTextPositions.SYSTEM -> "[SYSTEM] "
ChatTextPositions.HOTBAR -> "[HOTBAR] "
else -> ""
}
Log.log(LogMessageType.CHAT_IN, additionalPrefix = ChatComponent.of(additionalPrefix)) { it.message }
Log.log(LogMessageType.CHAT_IN, additionalPrefix = ChatComponent.of(additionalPrefix)) { it.message.text }
})
}
}

View File

@ -12,10 +12,15 @@
*/
package de.bixilon.minosoft.protocol.packets.s2c.play.chat
import de.bixilon.minosoft.data.chat.ChatUtil.getMessageSender
import de.bixilon.minosoft.data.chat.message.ChatMessage
import de.bixilon.minosoft.data.chat.message.PlayerChatMessage
import de.bixilon.minosoft.data.chat.message.SimpleChatMessage
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
import de.bixilon.minosoft.data.registries.chat.ChatMessageType
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageReceiveEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.factory.LoadPacket
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
@ -29,7 +34,7 @@ import java.util.*
@LoadPacket(threadSafe = false)
class ChatMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
val message: ChatComponent = buffer.readChatComponent()
val text: ChatComponent = buffer.readChatComponent()
var type: ChatMessageType = buffer.connection.registries.messageTypeRegistry[DefaultMessageTypes.CHAT]!!
private set
var sender: UUID? = null
@ -48,17 +53,21 @@ class ChatMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
}
}
}
message.setFallbackColor(ProtocolDefinition.DEFAULT_COLOR)
text.setFallbackColor(ProtocolDefinition.DEFAULT_COLOR)
}
override fun handle(connection: PlayConnection) {
val event = ChatMessageReceiveEvent(connection, this)
if (connection.fireEvent(event)) {
return
val type = if (overlay) connection.registries.messageTypeRegistry[DefaultMessageTypes.GAME]!! else type
val sender = sender
val message: ChatMessage = if (sender == null) {
SimpleChatMessage(text, type)
} else {
PlayerChatMessage(text, type, connection.getMessageSender(sender))
}
connection.fireEvent(ChatMessageReceiveEvent(connection, EventInitiators.SERVER, message))
}
override fun log(reducedLog: Boolean) {
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Chat message (message=\"$message\")" }
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Chat message (test=\"$text\", sender=$sender, overlay=$overlay)" }
}
}

View File

@ -12,24 +12,105 @@
*/
package de.bixilon.minosoft.protocol.packets.s2c.play.chat
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
import de.bixilon.minosoft.data.chat.ChatUtil.getMessageSender
import de.bixilon.minosoft.data.chat.filter.ChatFilter
import de.bixilon.minosoft.data.chat.filter.Filter
import de.bixilon.minosoft.data.chat.message.SignedChatMessage
import de.bixilon.minosoft.data.chat.signature.ChatSignatureProperties
import de.bixilon.minosoft.data.chat.signature.LastSeenMessage
import de.bixilon.minosoft.data.chat.signature.errors.MessageExpiredError
import de.bixilon.minosoft.data.registries.chat.ChatParameter
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageReceiveEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.factory.LoadPacket
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import java.time.Instant
@LoadPacket(threadSafe = false)
class SignedChatMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
val message = buffer.readSignedMessage()
override fun handle(connection: PlayConnection) {
val event = ChatMessageReceiveEvent(connection, this)
if (connection.fireEvent(event)) {
return
fun PlayInByteBuffer.readLastSeenMessage(): LastSeenMessage {
return LastSeenMessage(readUUID(), readByteArray())
}
private fun PlayInByteBuffer.readLegacySignedMessage(): SignedChatMessage {
val message = readChatComponent()
val unsignedContent = if (versionId >= ProtocolVersions.V_22W19A) readOptional { readChatComponent() } else null
var type = readRegistryItem(connection.registries.messageTypeRegistry)
val sender = readChatMessageSender()
val sendingTime = readInstant()
val salt = readLong()
val signatureData = readSignatureData()
TODO("return message, refactor")
}
fun PlayInByteBuffer.readSignedMessage(): SignedChatMessage {
if (versionId < ProtocolVersions.V_1_19_1_PRE4) {
return readLegacySignedMessage()
}
val parameters: MutableMap<ChatParameter, ChatComponent> = mutableMapOf()
val header = readMessageHeader()
val signature = readByteArray()
val message = readChatComponent().message
if (versionId >= ProtocolVersions.V_1_19_1_PRE5) {
readOptional { readChatComponent() } // formatted text
}
val sent = readInstant()
val salt = readLong()
val lastSeen = readArray { readLastSeenMessage() }
parameters[ChatParameter.CONTENT] = TextComponent(message)
val unsigned = readOptional { readChatComponent() }
var filter: Filter? = null
if (versionId >= ProtocolVersions.V_1_19_1_RC3) {
filter = ChatFilter[readVarInt()].reader.invoke(this)
}
val type = readRegistryItem(connection.registries.messageTypeRegistry)
parameters[ChatParameter.SENDER] = readChatComponent()
readOptional { readChatComponent() }?.let { parameters[ChatParameter.TARGET] = it }
val sender = connection.getMessageSender(header.sender)
val received = Instant.now()
var error: Exception? = null
if (received.toEpochMilli() - sent.toEpochMilli() > ChatSignatureProperties.MESSAGE_TTL) {
// expired
error = MessageExpiredError(sent, received)
} else {
// ToDo: check signature
}
return SignedChatMessage(
connection = connection,
message = message,
type = type,
sender = sender,
parameters = parameters,
filter = filter,
error = error,
sent = sent,
received = received
)
}
override fun handle(connection: PlayConnection) {
connection.fireEvent(ChatMessageReceiveEvent(connection, EventInitiators.SERVER, message))
}
override fun log(reducedLog: Boolean) {

View File

@ -13,7 +13,10 @@
package de.bixilon.minosoft.protocol.packets.s2c.play.title
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
import de.bixilon.minosoft.data.chat.message.SimpleChatMessage
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
import de.bixilon.minosoft.modding.event.EventInitiators
import de.bixilon.minosoft.modding.event.events.chat.ChatMessageReceiveEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.factory.LoadPacket
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
@ -31,6 +34,7 @@ class HotbarTextS2CP(buffer: PlayInByteBuffer) : TitleS2CP {
override fun handle(connection: PlayConnection) {
Log.log(LogMessageType.CHAT_IN) { "[HOTBAR] $text" }
connection.fireEvent(ChatMessageReceiveEvent(connection, this))
val message = SimpleChatMessage(text, connection.registries.messageTypeRegistry[DefaultMessageTypes.GAME]!!)
connection.fireEvent(ChatMessageReceiveEvent(connection, EventInitiators.SERVER, message))
}
}

View File

@ -22,10 +22,7 @@ import de.bixilon.minosoft.commands.nodes.builder.CommandNodeBuilder
import de.bixilon.minosoft.commands.parser.factory.ArgumentParserFactories
import de.bixilon.minosoft.commands.parser.minosoft.dummy.DummyParser
import de.bixilon.minosoft.commands.suggestion.factory.SuggestionFactories
import de.bixilon.minosoft.data.chat.filter.ChatFilter
import de.bixilon.minosoft.data.chat.filter.Filter
import de.bixilon.minosoft.data.chat.signature.*
import de.bixilon.minosoft.data.chat.type.MessageType
import de.bixilon.minosoft.data.container.ItemStackUtil
import de.bixilon.minosoft.data.container.stack.ItemStack
import de.bixilon.minosoft.data.entities.entities.player.properties.PlayerProperties
@ -364,49 +361,4 @@ class PlayInByteBuffer : InByteBuffer {
fun readMessageHeader(): MessageHeader {
return MessageHeader(readOptional { readByteArray() }, readUUID())
}
fun readLastSeenMessage(): LastSeenMessage {
return LastSeenMessage(readUUID(), readByteArray())
}
fun readMessageBody(): MessageBody {
val text: ChatComponent = readChatComponent()
if (versionId >= ProtocolVersions.V_1_19_1_PRE5) {
readOptional { readChatComponent() } // decorated content
}
return MessageBody(
text = text,
time = readInstant(),
salt = readLong(),
lastSeen = readArray { readLastSeenMessage() }
)
}
fun readMessageType(): MessageType {
return MessageType(readRegistryItem(connection.registries.messageTypeRegistry), readChatComponent(), readOptional { readChatComponent() })
}
fun readSignedMessage(): SignedMessage {
if (versionId < ProtocolVersions.V_1_19_1_PRE4) {
val message = readChatComponent()
val unsignedContent = if (versionId >= ProtocolVersions.V_22W19A) readOptional { readChatComponent() } else null
var type = readRegistryItem(connection.registries.messageTypeRegistry)
val sender = readChatMessageSender()
val sendingTime = readInstant()
val salt = readLong()
val signatureData = readSignatureData()
TODO("return message, refactor")
}
val header = readMessageHeader()
val signature = readByteArray()
val body = readMessageBody()
val unsigned = readOptional { readChatComponent() }
var filter: Filter? = null
if (versionId >= ProtocolVersions.V_1_19_1_RC3) {
filter = ChatFilter[readVarInt()].reader.invoke(this)
}
val type = readMessageType()
return SignedMessage(header, signature, body, unsigned, type, filter)
}
}