From 1ee8be400a8b42f9cafde14a50e3c30d0d3ef4c7 Mon Sep 17 00:00:00 2001 From: Kevin <92656833+kevinthegreat1@users.noreply.github.com> Date: Thu, 23 Feb 2023 05:13:47 -0500 Subject: [PATCH] Added Client Message Events (#2646) * Added Client Message Events * Applied suggestions and fixed checkstyle * Inject before fabric-command-api and updated Javadocs * Updated Javadocs regarding client commands * Update fabric-message-api-v1/src/client/resources/fabric-message-api-v1.client.mixins.json Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> * Updated Javadocs regarding commands * Fixed duplicated package names * Updated ClientMessageEvents.java Javadoc Co-authored-by: Sideroo <109681866+Sideroo@users.noreply.github.com> * Removed duplicated client commands Javadoc * Added cancelled sending and receiving events * Seperated send and receive events and changed event names * Fixed checkstyle * Added support for modifying messages * Added client command test * Added narration and message indicator support for modifying received messages * Added tests for modifying messages * Updated ClientReceiveMessageEvents#CHAT Javadocs * Small Javadoc fixes * Added Modify to names * Always narrate original message * Removed modifying receive chat message * Split notify and modify events * Fixed checkstyle --------- Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> Co-authored-by: Sideroo <109681866+Sideroo@users.noreply.github.com> (cherry picked from commit c85585f8702861e559f9da6928a4f60e9a2dbbc8) --- fabric-message-api-v1/build.gradle | 4 + .../v1/ClientReceiveMessageEvents.java | 259 ++++++++++++++++++ .../message/v1/ClientSendMessageEvents.java | 251 +++++++++++++++++ .../ClientPlayNetworkHandlerMixin.java | 64 +++++ .../client/message/MessageHandlerMixin.java | 83 ++++++ .../fabric-message-api-v1.client.mixins.json | 12 + .../src/main/resources/fabric.mod.json | 6 +- .../fabric/test/message/ChatTestClient.java | 108 ++++++++ .../src/testmod/resources/fabric.mod.json | 6 +- 9 files changed, 791 insertions(+), 2 deletions(-) create mode 100644 fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientReceiveMessageEvents.java create mode 100644 fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientSendMessageEvents.java create mode 100644 fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java create mode 100644 fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/MessageHandlerMixin.java create mode 100644 fabric-message-api-v1/src/client/resources/fabric-message-api-v1.client.mixins.json create mode 100644 fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTestClient.java diff --git a/fabric-message-api-v1/build.gradle b/fabric-message-api-v1/build.gradle index ebfd97c13..ae3fd953c 100644 --- a/fabric-message-api-v1/build.gradle +++ b/fabric-message-api-v1/build.gradle @@ -4,3 +4,7 @@ version = getSubprojectVersion(project) moduleDependencies(project, [ 'fabric-api-base' ]) + +testDependencies(project, [ + 'fabric-command-api-v2' +]) diff --git a/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientReceiveMessageEvents.java b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientReceiveMessageEvents.java new file mode 100644 index 000000000..3abd8180c --- /dev/null +++ b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientReceiveMessageEvents.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.message.v1; + +import java.time.Instant; + +import com.mojang.authlib.GameProfile; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.gui.hud.ChatHud; +import net.minecraft.network.message.MessageType; +import net.minecraft.network.message.SignedMessage; +import net.minecraft.text.Text; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Contains client-side events triggered when receiving messages. + */ +public final class ClientReceiveMessageEvents { + private ClientReceiveMessageEvents() { + } + + /** + * An event triggered when the client receives a chat message, + * which is any message sent by a player. Mods can use this to block the message. + * + * <p>If a listener returned {@code false}, the message will not be displayed, + * the remaining listeners will not be called (if any), and + * {@link #CHAT_CANCELED} will be triggered instead of {@link #CHAT}. + */ + public static final Event<AllowChat> ALLOW_CHAT = EventFactory.createArrayBacked(AllowChat.class, listeners -> (message, signedMessage, sender, params, receptionTimestamp) -> { + for (AllowChat listener : listeners) { + if (!listener.allowReceiveChatMessage(message, signedMessage, sender, params, receptionTimestamp)) { + return false; + } + } + + return true; + }); + + /** + * An event triggered when the client receives a game message, + * which is any message sent by the server. + * Mods can use this to block the message or toggle overlay. + * + * <p>If a listener returned {@code false}, the message will not be displayed, + * the remaining listeners will not be called (if any), and + * {@link #GAME_CANCELED} will be triggered instead of {@link #MODIFY_GAME}. + * + * <p>Overlay is whether the message will be displayed in the action bar. + * To toggle overlay, return false and call + * {@link net.minecraft.client.network.ClientPlayerEntity#sendMessage(Text, boolean) ClientPlayerEntity.sendMessage(message, overlay)}. + */ + public static final Event<AllowGame> ALLOW_GAME = EventFactory.createArrayBacked(AllowGame.class, listeners -> (message, overlay) -> { + for (AllowGame listener : listeners) { + if (!listener.allowReceiveGameMessage(message, overlay)) { + return false; + } + } + + return true; + }); + + /** + * An event triggered when the client receives a game message, + * which is any message sent by the server. Is not called when + * {@linkplain #ALLOW_GAME game messages are blocked}. + * Mods can use this to modify the message. + * Use {@link #GAME} if not modifying the message. + * + * <p>Overlay is whether the message will be displayed in the action bar. + * Use {@link #ALLOW_GAME to toggle overlay}. + */ + public static final Event<ModifyGame> MODIFY_GAME = EventFactory.createArrayBacked(ModifyGame.class, listeners -> (message, overlay) -> { + for (ModifyGame listener : listeners) { + message = listener.modifyReceivedGameMessage(message, overlay); + } + + return message; + }); + + /** + * An event triggered when the client receives a chat message, + * which is any message sent by a player. Is not called when + * {@linkplain #ALLOW_CHAT chat messages are blocked}. + * Mods can use this to listen to the message. + * + * <p>If mods want to modify the message, they should use {@link #ALLOW_CHAT} + * and manually add the new message to the chat hud using {@link ChatHud#addMessage(Text)} + */ + public static final Event<Chat> CHAT = EventFactory.createArrayBacked(Chat.class, listeners -> (message, signedMessage, sender, params, receptionTimestamp) -> { + for (Chat listener : listeners) { + listener.onReceiveChatMessage(message, signedMessage, sender, params, receptionTimestamp); + } + }); + + /** + * An event triggered when the client receives a game message, + * which is any message sent by the server. Is not called when + * {@linkplain #ALLOW_GAME game messages are blocked}. + * Mods can use this to listen to the message. + * + * <p>Overlay is whether the message will be displayed in the action bar. + * Use {@link #ALLOW_GAME to toggle overlay}. + */ + public static final Event<Game> GAME = EventFactory.createArrayBacked(Game.class, listeners -> (message, overlay) -> { + for (Game listener : listeners) { + listener.onReceiveGameMessage(message, overlay); + } + }); + + /** + * An event triggered when receiving a chat message is canceled with {@link #ALLOW_CHAT}. + */ + public static final Event<ChatCanceled> CHAT_CANCELED = EventFactory.createArrayBacked(ChatCanceled.class, listeners -> (message, signedMessage, sender, params, receptionTimestamp) -> { + for (ChatCanceled listener : listeners) { + listener.onReceiveChatMessageCanceled(message, signedMessage, sender, params, receptionTimestamp); + } + }); + + /** + * An event triggered when receiving a game message is canceled with {@link #ALLOW_GAME}. + * + * <p>Overlay is whether the message would have been displayed in the action bar. + */ + public static final Event<GameCanceled> GAME_CANCELED = EventFactory.createArrayBacked(GameCanceled.class, listeners -> (message, overlay) -> { + for (GameCanceled listener : listeners) { + listener.onReceiveGameMessageCanceled(message, overlay); + } + }); + + @FunctionalInterface + public interface AllowChat { + /** + * Called when the client receives a chat message, + * which is any message sent by a player. + * Returning {@code false} prevents the message from being displayed, and + * {@link #CHAT_CANCELED} will be triggered instead of {@link #CHAT}. + * + * @param message the message received from the server + * @param signedMessage the signed message received from the server (nullable) + * @param sender the sender of the message (nullable) + * @param params the parameters of the message + * @param receptionTimestamp the timestamp when the message was received + * @return {@code true} if the message should be displayed, otherwise {@code false} + */ + boolean allowReceiveChatMessage(Text message, @Nullable SignedMessage signedMessage, @Nullable GameProfile sender, MessageType.Parameters params, Instant receptionTimestamp); + } + + @FunctionalInterface + public interface AllowGame { + /** + * Called when the client receives a game message, + * which is any message sent by the server. Returning {@code false} + * prevents the message from being displayed, and + * {@link #GAME_CANCELED} will be triggered instead of {@link #MODIFY_GAME}. + * + * <p>Overlay is whether the message will be displayed in the action bar. + * To toggle overlay, return false and call + * {@link net.minecraft.client.network.ClientPlayerEntity#sendMessage(Text, boolean) ClientPlayerEntity.sendMessage(message, overlay)}. + * + * @param message the message received from the server + * @param overlay whether the message will be displayed in the action bar + * @return {@code true} if the message should be displayed, otherwise {@code false} + */ + boolean allowReceiveGameMessage(Text message, boolean overlay); + } + + @FunctionalInterface + public interface ModifyGame { + /** + * Called when the client receives a game message, + * which is any message sent by the server. Is not called when + * {@linkplain #ALLOW_GAME game messages are blocked}. + * Use {@link #GAME} if not modifying the message. + * + * <p>Overlay is whether the message will be displayed in the action bar. + * Use {@link #ALLOW_GAME} to toggle overlay. + * + * @param message the message received from the server + * @param overlay whether the message will be displayed in the action bar + * @return the modified message to display or the original {@code message} if the message is not modified + */ + Text modifyReceivedGameMessage(Text message, boolean overlay); + } + + @FunctionalInterface + public interface Chat { + /** + * Called when the client receives a chat message, + * which is any message sent by a player. Is not called when + * {@linkplain #ALLOW_CHAT chat messages are blocked}. + * + * @param message the message received from the server + * @param signedMessage the signed message received from the server (nullable) + * @param sender the sender of the message (nullable) + * @param params the parameters of the message + * @param receptionTimestamp the timestamp when the message was received + */ + void onReceiveChatMessage(Text message, @Nullable SignedMessage signedMessage, @Nullable GameProfile sender, MessageType.Parameters params, Instant receptionTimestamp); + } + + @FunctionalInterface + public interface Game { + /** + * Called when the client receives a game message, + * which is any message sent by the server. Is not called when + * {@linkplain #ALLOW_GAME game messages are blocked}. + * + * <p>Overlay is whether the message will be displayed in the action bar. + * Use {@link #ALLOW_GAME} to toggle overlay. + * + * @param message the message received from the server + * @param overlay whether the message will be displayed in the action bar + */ + void onReceiveGameMessage(Text message, boolean overlay); + } + + @FunctionalInterface + public interface ChatCanceled { + /** + * Called when receiving a chat message is canceled with {@link #ALLOW_CHAT}. + * + * @param message the message received from the server + * @param signedMessage the signed message received from the server (nullable) + * @param sender the sender of the message (nullable) + * @param params the parameters of the message + * @param receptionTimestamp the timestamp when the message was received + */ + void onReceiveChatMessageCanceled(Text message, @Nullable SignedMessage signedMessage, @Nullable GameProfile sender, MessageType.Parameters params, Instant receptionTimestamp); + } + + @FunctionalInterface + public interface GameCanceled { + /** + * Called when receiving a game message is canceled with {@link #ALLOW_GAME}. + * + * @param message the message received from the server + * @param overlay whether the message would have been displayed in the action bar + */ + void onReceiveGameMessageCanceled(Text message, boolean overlay); + } +} diff --git a/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientSendMessageEvents.java b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientSendMessageEvents.java new file mode 100644 index 000000000..e16904169 --- /dev/null +++ b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/api/client/message/v1/ClientSendMessageEvents.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.message.v1; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Contains client-side events triggered when sending messages. + */ +public final class ClientSendMessageEvents { + private ClientSendMessageEvents() { + } + + /** + * An event triggered when the client is about to send a chat message, + * typically from a client GUI. Mods can use this to block the message. + * + * <p>If a listener returned {@code false}, the message will not be sent, + * the remaining listeners will not be called (if any), and + * {@link #CHAT_CANCELED} will be triggered instead of {@link #MODIFY_CHAT}. + */ + public static final Event<AllowChat> ALLOW_CHAT = EventFactory.createArrayBacked(AllowChat.class, listeners -> (message) -> { + for (AllowChat listener : listeners) { + if (!listener.allowSendChatMessage(message)) { + return false; + } + } + + return true; + }); + + /** + * An event triggered when the client is about to send a command, + * which is whenever the player executes a command + * including client commands registered with {@code fabric-command-api}. + * Mods can use this to block the message. + * The command string does not include a slash at the beginning. + * + * <p>If a listener returned {@code false}, the command will not be sent, + * the remaining listeners will not be called (if any), and + * {@link #COMMAND_CANCELED} will be triggered instead of {@link #MODIFY_COMMAND}. + */ + public static final Event<AllowCommand> ALLOW_COMMAND = EventFactory.createArrayBacked(AllowCommand.class, listeners -> (command) -> { + for (AllowCommand listener : listeners) { + if (!listener.allowSendCommandMessage(command)) { + return false; + } + } + + return true; + }); + + /** + * An event triggered when the client sends a chat message, + * typically from a client GUI. Is not called when {@linkplain + * #ALLOW_CHAT chat messages are blocked}. + * Mods can use this to modify the message. + * Use {@link #CHAT} if not modifying the message. + */ + public static final Event<ModifyChat> MODIFY_CHAT = EventFactory.createArrayBacked(ModifyChat.class, listeners -> (message) -> { + for (ModifyChat listener : listeners) { + message = listener.modifySendChatMessage(message); + } + + return message; + }); + + /** + * An event triggered when the client sends a command, + * which is whenever the player executes a command + * including client commands registered with {@code fabric-command-api}. + * Is not called when {@linkplain #ALLOW_COMMAND command messages are blocked}. + * The command string does not include a slash at the beginning. + * Mods can use this to modify the command. + * Use {@link #COMMAND} if not modifying the command. + */ + public static final Event<ModifyCommand> MODIFY_COMMAND = EventFactory.createArrayBacked(ModifyCommand.class, listeners -> (command) -> { + for (ModifyCommand listener : listeners) { + command = listener.modifySendCommandMessage(command); + } + + return command; + }); + + /** + * An event triggered when the client sends a chat message, + * typically from a client GUI. Is not called when {@linkplain + * #ALLOW_CHAT chat messages are blocked}. + * Mods can use this to listen to the message. + */ + public static final Event<Chat> CHAT = EventFactory.createArrayBacked(Chat.class, listeners -> (message) -> { + for (Chat listener : listeners) { + listener.onSendChatMessage(message); + } + }); + + /** + * An event triggered when the client sends a command, + * which is whenever the player executes a command + * including client commands registered with {@code fabric-command-api}. + * Is not called when {@linkplain #ALLOW_COMMAND command messages are blocked}. + * The command string does not include a slash at the beginning. + * Mods can use this to listen to the command. + */ + public static final Event<Command> COMMAND = EventFactory.createArrayBacked(Command.class, listeners -> (command) -> { + for (Command listener : listeners) { + listener.onSendCommandMessage(command); + } + }); + + /** + * An event triggered when sending a chat message is canceled with {@link #ALLOW_CHAT}. + */ + public static final Event<ChatCanceled> CHAT_CANCELED = EventFactory.createArrayBacked(ChatCanceled.class, listeners -> (message) -> { + for (ChatCanceled listener : listeners) { + listener.onSendChatMessageCanceled(message); + } + }); + + /** + * An event triggered when sending a command is canceled with {@link #ALLOW_COMMAND}. + * The command string does not include a slash at the beginning. + */ + public static final Event<CommandCanceled> COMMAND_CANCELED = EventFactory.createArrayBacked(CommandCanceled.class, listeners -> (command) -> { + for (CommandCanceled listener : listeners) { + listener.onSendCommandMessageCanceled(command); + } + }); + + @FunctionalInterface + public interface AllowChat { + /** + * Called when the client is about to send a chat message, + * typically from a client GUI. Returning {@code false} + * prevents the message from being sent, and + * {@link #CHAT_CANCELED} will be triggered instead of {@link #MODIFY_CHAT}. + * + * @param message the message that will be sent to the server + * @return {@code true} if the message should be sent, otherwise {@code false} + */ + boolean allowSendChatMessage(String message); + } + + @FunctionalInterface + public interface AllowCommand { + /** + * Called when the client is about to send a command, + * which is whenever the player executes a command + * including client commands registered with {@code fabric-command-api}. + * Returning {@code false} prevents the command from being sent, and + * {@link #COMMAND_CANCELED} will be triggered instead of {@link #MODIFY_COMMAND}. + * The command string does not include a slash at the beginning. + * + * @param command the command that will be sent to the server, without a slash at the beginning. + * @return {@code true} if the command should be sent, otherwise {@code false} + */ + boolean allowSendCommandMessage(String command); + } + + @FunctionalInterface + public interface ModifyChat { + /** + * Called when the client sends a chat message, + * typically from a client GUI. Is not called when {@linkplain + * #ALLOW_CHAT chat messages are blocked}. + * Use {@link #CHAT} if not modifying the message. + * + * @param message the message that will be sent to the server + * @return the modified message that will be sent to the server + */ + String modifySendChatMessage(String message); + } + + @FunctionalInterface + public interface ModifyCommand { + /** + * Called when the client sends a command, + * which is whenever the player executes a command + * including client commands registered with {@code fabric-command-api}. + * Is not called when {@linkplain #ALLOW_COMMAND command messages are blocked}. + * The command string does not include a slash at the beginning. + * Use {@link #COMMAND} if not modifying the command. + * + * @param command the command that will be sent to the server, without a slash at the beginning. + * @return the modified command that will be sent to the server, without a slash at the beginning. + */ + String modifySendCommandMessage(String command); + } + + @FunctionalInterface + public interface Chat { + /** + * Called when the client sends a chat message, + * typically from a client GUI. Is not called when {@linkplain + * #ALLOW_CHAT chat messages are blocked}. + * + * @param message the message that will be sent to the server + */ + void onSendChatMessage(String message); + } + + @FunctionalInterface + public interface Command { + /** + * Called when the client sends a command, + * which is whenever the player executes a command + * including client commands registered with {@code fabric-command-api}. + * Is not called when {@linkplain #ALLOW_COMMAND command messages are blocked}. + * The command string does not include a slash at the beginning. + * + * @param command the command that will be sent to the server, without a slash at the beginning. + */ + void onSendCommandMessage(String command); + } + + @FunctionalInterface + public interface ChatCanceled { + /** + * Called when sending a chat message is canceled with {@link #ALLOW_CHAT}. + * + * @param message the message that is canceled from being sent to the server + */ + void onSendChatMessageCanceled(String message); + } + + @FunctionalInterface + public interface CommandCanceled { + /** + * Called when sending a command is canceled with {@link #ALLOW_COMMAND}. + * The command string does not include a slash at the beginning. + * + * @param command the command that is being sent to the server, without a slash at the beginning. + */ + void onSendCommandMessageCanceled(String command); + } +} diff --git a/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 000000000..864075004 --- /dev/null +++ b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.message; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.network.ClientPlayNetworkHandler; + +import net.fabricmc.fabric.api.client.message.v1.ClientSendMessageEvents; + +/** + * Mixin to {@link ClientPlayNetworkHandler} to listen for sending messages and commands. + * Priority set to 800 to inject before {@code fabric-command-api} so that this api will be called first. + */ +@Mixin(value = ClientPlayNetworkHandler.class, priority = 800) +public abstract class ClientPlayNetworkHandlerMixin { + @Inject(method = "sendChatMessage", at = @At("HEAD"), cancellable = true) + private void fabric_allowSendChatMessage(String content, CallbackInfo ci) { + if (!ClientSendMessageEvents.ALLOW_CHAT.invoker().allowSendChatMessage(content)) { + ClientSendMessageEvents.CHAT_CANCELED.invoker().onSendChatMessageCanceled(content); + ci.cancel(); + } + } + + @ModifyVariable(method = "sendChatMessage", at = @At(value = "LOAD", ordinal = 0), ordinal = 0, argsOnly = true) + private String fabric_modifySendChatMessage(String content) { + content = ClientSendMessageEvents.MODIFY_CHAT.invoker().modifySendChatMessage(content); + ClientSendMessageEvents.CHAT.invoker().onSendChatMessage(content); + return content; + } + + @Inject(method = "sendChatCommand", at = @At("HEAD"), cancellable = true) + private void fabric_allowSendCommandMessage(String command, CallbackInfo ci) { + if (!ClientSendMessageEvents.ALLOW_COMMAND.invoker().allowSendCommandMessage(command)) { + ClientSendMessageEvents.COMMAND_CANCELED.invoker().onSendCommandMessageCanceled(command); + ci.cancel(); + } + } + + @ModifyVariable(method = "sendChatCommand", at = @At(value = "LOAD", ordinal = 0), ordinal = 0, argsOnly = true) + private String fabric_modifySendCommandMessage(String command) { + command = ClientSendMessageEvents.MODIFY_COMMAND.invoker().modifySendCommandMessage(command); + ClientSendMessageEvents.COMMAND.invoker().onSendCommandMessage(command); + return command; + } +} diff --git a/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/MessageHandlerMixin.java b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/MessageHandlerMixin.java new file mode 100644 index 000000000..c4a5d8b32 --- /dev/null +++ b/fabric-message-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/message/MessageHandlerMixin.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.message; + +import java.time.Instant; + +import com.mojang.authlib.GameProfile; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.client.network.message.MessageHandler; +import net.minecraft.network.message.MessageType; +import net.minecraft.network.message.SignedMessage; +import net.minecraft.text.Text; + +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; + +@Mixin(MessageHandler.class) +public abstract class MessageHandlerMixin { + @Inject(method = "processChatMessageInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;getChatHud()Lnet/minecraft/client/gui/hud/ChatHud;", ordinal = 0), cancellable = true) + private void fabric_onSignedChatMessage(MessageType.Parameters params, SignedMessage message, Text decorated, GameProfile sender, boolean onlyShowSecureChat, Instant receptionTimestamp, CallbackInfoReturnable<Boolean> cir) { + fabric_onChatMessage(decorated, message, sender, params, receptionTimestamp, cir); + } + + @Inject(method = "processChatMessageInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;getChatHud()Lnet/minecraft/client/gui/hud/ChatHud;", ordinal = 1), cancellable = true) + private void fabric_onFilteredSignedChatMessage(MessageType.Parameters params, SignedMessage message, Text decorated, GameProfile sender, boolean onlyShowSecureChat, Instant receptionTimestamp, CallbackInfoReturnable<Boolean> cir) { + Text filtered = message.filterMask().getFilteredText(message.getSignedContent()); + + if (filtered != null) { + fabric_onChatMessage(params.applyChatDecoration(filtered), message, sender, params, receptionTimestamp, cir); + } + } + + @Inject(method = "method_45745", at = @At("HEAD"), cancellable = true) + private void fabric_onProfilelessChatMessage(MessageType.Parameters params, Text content, Instant receptionTimestamp, CallbackInfoReturnable<Boolean> cir) { + fabric_onChatMessage(params.applyChatDecoration(content), null, null, params, receptionTimestamp, cir); + } + + @Unique + private void fabric_onChatMessage(Text message, @Nullable SignedMessage signedMessage, @Nullable GameProfile sender, MessageType.Parameters params, Instant receptionTimestamp, CallbackInfoReturnable<Boolean> cir) { + if (ClientReceiveMessageEvents.ALLOW_CHAT.invoker().allowReceiveChatMessage(message, signedMessage, sender, params, receptionTimestamp)) { + ClientReceiveMessageEvents.CHAT.invoker().onReceiveChatMessage(message, signedMessage, sender, params, receptionTimestamp); + } else { + ClientReceiveMessageEvents.CHAT_CANCELED.invoker().onReceiveChatMessageCanceled(message, signedMessage, sender, params, receptionTimestamp); + cir.setReturnValue(false); + } + } + + @Inject(method = "onGameMessage", at = @At("HEAD"), cancellable = true) + private void fabric_allowGameMessage(Text message, boolean overlay, CallbackInfo ci) { + if (!ClientReceiveMessageEvents.ALLOW_GAME.invoker().allowReceiveGameMessage(message, overlay)) { + ClientReceiveMessageEvents.GAME_CANCELED.invoker().onReceiveGameMessageCanceled(message, overlay); + ci.cancel(); + } + } + + @ModifyVariable(method = "onGameMessage", at = @At(value = "LOAD", ordinal = 0), ordinal = 0, argsOnly = true) + private Text fabric_modifyGameMessage(Text message, Text message1, boolean overlay) { + message = ClientReceiveMessageEvents.MODIFY_GAME.invoker().modifyReceivedGameMessage(message, overlay); + ClientReceiveMessageEvents.GAME.invoker().onReceiveGameMessage(message, overlay); + return message; + } +} diff --git a/fabric-message-api-v1/src/client/resources/fabric-message-api-v1.client.mixins.json b/fabric-message-api-v1/src/client/resources/fabric-message-api-v1.client.mixins.json new file mode 100644 index 000000000..00088d171 --- /dev/null +++ b/fabric-message-api-v1/src/client/resources/fabric-message-api-v1.client.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.client.message", + "compatibilityLevel": "JAVA_17", + "client": [ + "ClientPlayNetworkHandlerMixin", + "MessageHandlerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-message-api-v1/src/main/resources/fabric.mod.json b/fabric-message-api-v1/src/main/resources/fabric.mod.json index 2ac9e4d52..7a0e078cb 100644 --- a/fabric-message-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-message-api-v1/src/main/resources/fabric.mod.json @@ -21,7 +21,11 @@ }, "description": "Adds message-related hooks.", "mixins": [ - "fabric-message-api-v1.mixins.json" + "fabric-message-api-v1.mixins.json", + { + "config": "fabric-message-api-v1.client.mixins.json", + "environment": "client" + } ], "custom": { "fabric-api:module-lifecycle": "experimental" diff --git a/fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTestClient.java b/fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTestClient.java new file mode 100644 index 000000000..6fb9d9e8a --- /dev/null +++ b/fabric-message-api-v1/src/testmod/java/net/fabricmc/fabric/test/message/ChatTestClient.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.message; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.text.Text; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.fabricmc.fabric.api.client.message.v1.ClientSendMessageEvents; + +public class ChatTestClient implements ClientModInitializer { + private static final Logger LOGGER = LoggerFactory.getLogger(ChatTestClient.class); + + @Override + public void onInitializeClient() { + //Register test client commands + ClientCommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(ClientCommandManager.literal("block").then(ClientCommandManager.literal("send").executes(context -> { + throw new AssertionError("This client command should be blocked!"); + })))); + //Test client send message events + ClientSendMessageEvents.ALLOW_CHAT.register((message) -> { + if (message.contains("block send")) { + LOGGER.info("Blocked chat message: " + message); + return false; + } + + return true; + }); + ClientSendMessageEvents.MODIFY_CHAT.register((message) -> { + if (message.contains("modify send")) { + LOGGER.info("Modifying chat message: " + message); + return "sending modified chat message"; + } + + return message; + }); + ClientSendMessageEvents.CHAT.register((message -> LOGGER.info("Sent chat message: " + message))); + ClientSendMessageEvents.CHAT_CANCELED.register((message) -> LOGGER.info("Canceled sending chat message: " + message)); + //Test client send command events + ClientSendMessageEvents.ALLOW_COMMAND.register((command) -> { + if (command.contains("block send")) { + LOGGER.info("Blocked command message: " + command); + return false; + } + + return true; + }); + ClientSendMessageEvents.MODIFY_COMMAND.register((command) -> { + if (command.contains("modify send")) { + LOGGER.info("Modifying command message: " + command); + return "sending modified command message"; + } + + return command; + }); + ClientSendMessageEvents.COMMAND.register((command -> LOGGER.info("Sent command message: " + command))); + ClientSendMessageEvents.COMMAND_CANCELED.register((command) -> LOGGER.info("Canceled sending command message: " + command)); + //Test client receive message events + ClientReceiveMessageEvents.ALLOW_CHAT.register((message, signedMessage, sender, params, receptionTimestamp) -> { + if (message.getString().contains("block receive")) { + LOGGER.info("Blocked receiving chat message: " + message.getString()); + return false; + } + + return true; + }); + ClientReceiveMessageEvents.CHAT.register((message, signedMessage, sender, params, receptionTimestamp) -> LOGGER.info("Received chat message sent by {} at time {}: {}", sender == null ? "null" : sender.getName(), receptionTimestamp.toEpochMilli(), message.getString())); + ClientReceiveMessageEvents.CHAT_CANCELED.register((message, signedMessage, sender, params, receptionTimestamp) -> LOGGER.info("Cancelled receiving chat message sent by {} at time {}: {}", sender == null ? "null" : sender.getName(), receptionTimestamp.toEpochMilli(), message.getString())); + //Test client receive game message events + ClientReceiveMessageEvents.ALLOW_GAME.register((message, overlay) -> { + if (message.getString().contains("block receive")) { + LOGGER.info("Blocked receiving game message: " + message.getString()); + return false; + } + + return true; + }); + ClientReceiveMessageEvents.MODIFY_GAME.register((message, overlay) -> { + if (message.getString().contains("modify receive")) { + LOGGER.info("Modifying received game message: " + message.getString()); + return Text.of("modified receiving game message"); + } + + return message; + }); + ClientReceiveMessageEvents.GAME.register((message, overlay) -> LOGGER.info("Received game message with overlay {}: {}", overlay, message.getString())); + ClientReceiveMessageEvents.GAME_CANCELED.register((message, overlay) -> LOGGER.info("Cancelled receiving game message with overlay {}: {}", overlay, message.getString())); + } +} diff --git a/fabric-message-api-v1/src/testmod/resources/fabric.mod.json b/fabric-message-api-v1/src/testmod/resources/fabric.mod.json index 4a5a8e645..06bbd4d47 100644 --- a/fabric-message-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-message-api-v1/src/testmod/resources/fabric.mod.json @@ -6,11 +6,15 @@ "environment": "*", "license": "Apache-2.0", "depends": { - "fabric-message-api-v1": "*" + "fabric-message-api-v1": "*", + "fabric-command-api-v2": "*" }, "entrypoints": { "main": [ "net.fabricmc.fabric.test.message.ChatTest" + ], + "client": [ + "net.fabricmc.fabric.test.message.ChatTestClient" ] } }