Fabric Message API v1 (experimental) ()

* Initial skeleton

* Fabric Chat API v1 (experimental)

* Run checkstyle

* Fix checkstyle (testmod)

* Support 1.19-pre1

* Fix spotless

* Fixes

* Remove caching of messages
This is hard to maintain and has several edge cases. Mods should cache themselves when needed.

* Make constructor private

* Add ServerChatEvents

* Update mappings

* Prepare for the rename

* Include chat decorator in exception

* Add support for blocking messages

* Rename to fabric-message-api-v1

* Update to 1.19-pre2

* Use fabric namespace

* Rename ChatDecoratorEvent

* Rename events and add more javadoc

* Rename ServerChatEvents

* Rename ChatDecorator

* Only block bad respawn point death in testmod

* Fix wrong word in javadoc

* Improve javadoc

Co-authored-by: modmuss50 <modmuss50@gmail.com>
This commit is contained in:
apple502j 2022-06-03 00:00:15 +09:00 committed by GitHub
parent e62f51a37f
commit 513f4a5977
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 617 additions and 0 deletions
fabric-message-api-v1
build.gradle
src
main
testmod
java/net/fabricmc/fabric/test/message
resources
gradle.propertiessettings.gradle

View file

@ -0,0 +1,6 @@
archivesBaseName = "fabric-message-api-v1"
version = getSubprojectVersion(project)
moduleDependencies(project, [
'fabric-api-base'
])

View file

@ -0,0 +1,110 @@
/*
* 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.message.v1;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.message.MessageDecorator;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* A class for registering a {@link MessageDecorator}. Check the message decorator documentation
* for how message decorators work. Unlike other events, this uses a functional interface that is
* provided by the vanilla game.
*
* <p>This event uses phases to provide better mod compatibilities between mods that add custom
* content and styling. Message decorators with the styling phase will always apply after the ones
* with the content phase. When registering the message decorator, it is recommended to choose one
* of the phases from this interface and pass that to the {@link Event#register(Identifier, Object)}
* function. If not given, the message decorator will run in the default phase, which is between
* the content phase and the styling phase.
*
* <p>When implementing a message decorator, it is <strong>very important that the decorator be
* pure; i.e. return the same text when called multiple times for the same arguments (message and
* sender)</strong> - otherwise the server detects a mismatch between the preview and the actual message,
* and discards the message because it was improperly signed.
*
* <p>Example of registering a content phase message decorator:
*
* <pre><code>
* ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> {
* // Add smiley face. Has to copy() to get a MutableText with siblings and styles.
* return message.copy().append(" :)");
* });
* </code></pre>
*
* <p>Example of registering a styling phase message decorator:
*
* <pre><code>
* ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.STYLING_PHASE, (sender, message) -> {
* // Apply orange color to messages sent by server operators
* if (sender != null && sender.server.getPlayerManager().isOperator(sender.getGameProfile())) {
* return CompletableFuture.completedFuture(
* message.copy().styled(style -> style.withColor(0xFFA500)));
* }
* return CompletableFuture.completedFuture(message);
* });
* </code></pre>
*/
public final class ServerMessageDecoratorEvent {
private ServerMessageDecoratorEvent() {
}
/**
* The content phase of the event, passed when registering a message decorator. Use this when
* the decorator modifies the text content of the message.
*/
public static final Identifier CONTENT_PHASE = new Identifier("fabric", "content");
/**
* The styling phase of the event, passed when registering a message decorator. Use this when
* the decorator only modifies the styling of the message with the text intact.
*/
public static final Identifier STYLING_PHASE = new Identifier("fabric", "styling");
public static final Event<MessageDecorator> EVENT = EventFactory.createWithPhases(MessageDecorator.class, decorators -> (sender, message) -> {
CompletableFuture<Text> future = null;
for (MessageDecorator decorator : decorators) {
if (future == null) {
future = decorator.decorate(sender, message).handle((decorated, throwable) -> handle(decorated, throwable, decorator));
} else {
future = future.thenCompose((decorated) -> decorator.decorate(sender, decorated).handle((newlyDecorated, throwable) -> handle(newlyDecorated, throwable, decorator)));
}
}
return future == null ? CompletableFuture.completedFuture(message) : future;
}, CONTENT_PHASE, Event.DEFAULT_PHASE, STYLING_PHASE);
private static <T extends Text> T handle(T decorated, @Nullable Throwable throwable, MessageDecorator decorator) {
String decoratorName = decorator.getClass().getName();
if (throwable != null) {
if (throwable instanceof CompletionException) throwable = throwable.getCause();
throw new CompletionException("message decorator %s failed".formatted(decoratorName), throwable);
}
return Objects.requireNonNull(decorated, "message decorator %s returned null".formatted(decoratorName));
}
}

View file

@ -0,0 +1,235 @@
/*
* 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.message.v1;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.filter.FilteredMessage;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.registry.RegistryKey;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public final class ServerMessageEvents {
/**
* An event triggered when the server broadcasts a chat message sent by a player,
* typically from a client GUI or a player-executed command. Mods can use this to block
* the message.
*
* <p>If a listener returned {@code false}, the message will not be broadcast,
* the remaining listeners will not be called (if any), and {@link #CHAT_MESSAGE}
* event will not be triggered.
*
* <p>If the message is from a player-executed command, this will be called
* only if {@link #ALLOW_COMMAND_MESSAGE} event did not block the message,
* and after triggering {@link #COMMAND_MESSAGE} event.
*/
public static final Event<AllowChatMessage> ALLOW_CHAT_MESSAGE = EventFactory.createArrayBacked(AllowChatMessage.class, handlers -> (message, sender, typeKey) -> {
for (AllowChatMessage handler : handlers) {
if (!handler.allowChatMessage(message, sender, typeKey)) return false;
}
return true;
});
/**
* An event triggered when the server broadcasts a game message to all players. Game
* messages include death messages, join/leave messages, and advancement messages.
* Mods can use this to block the message.
*
* <p>If a listener returned {@code false}, the message will not be broadcast,
* the remaining listeners will not be called (if any), and {@link #GAME_MESSAGE}
* event will not be triggered.
*/
public static final Event<AllowGameMessage> ALLOW_GAME_MESSAGE = EventFactory.createArrayBacked(AllowGameMessage.class, handlers -> (message, typeKey) -> {
for (AllowGameMessage handler : handlers) {
if (!handler.allowGameMessage(message, typeKey)) return false;
}
return true;
});
/**
* An event triggered when the server broadcasts a command message to all players, such as one
* from {@code /me}, {@code /msg}, {@code /say}, and {@code /tellraw}. Mods can use this
* to block the message.
*
* <p>If a listener returned {@code false}, the message will not be broadcast,
* the remaining listeners will not be called (if any), and {@link #COMMAND_MESSAGE}
* event will not be triggered.
*
* <p>If the command is executed by a player and the message is not blocked,
* {@link #ALLOW_CHAT_MESSAGE} and {@link #CHAT_MESSAGE} events will also be
* triggered after triggering {@link #COMMAND_MESSAGE}.
*/
public static final Event<AllowCommandMessage> ALLOW_COMMAND_MESSAGE = EventFactory.createArrayBacked(AllowCommandMessage.class, handlers -> (message, source, typeKey) -> {
for (AllowCommandMessage handler : handlers) {
if (!handler.allowCommandMessage(message, source, typeKey)) return false;
}
return true;
});
/**
* An event triggered when the server broadcasts a chat message sent by a player, typically
* from a client GUI or a player-executed command. Is not called when {@linkplain
* #ALLOW_CHAT_MESSAGE chat messages are blocked}.
*
* <p>If the message is from a player-executed command, this will be called
* only if {@link #ALLOW_COMMAND_MESSAGE} event did not block the message,
* and after triggering {@link #COMMAND_MESSAGE} event.
*/
public static final Event<ChatMessage> CHAT_MESSAGE = EventFactory.createArrayBacked(ChatMessage.class, handlers -> (message, sender, typeKey) -> {
for (ChatMessage handler : handlers) {
handler.onChatMessage(message, sender, typeKey);
}
});
/**
* An event triggered when the server broadcasts a game message to all players. Game messages
* include death messages, join/leave messages, and advancement messages. Is not called
* when {@linkplain #ALLOW_GAME_MESSAGE game messages are blocked}.
*/
public static final Event<GameMessage> GAME_MESSAGE = EventFactory.createArrayBacked(GameMessage.class, handlers -> (message, typeKey) -> {
for (GameMessage handler : handlers) {
handler.onGameMessage(message, typeKey);
}
});
/**
* An event triggered when the server broadcasts a command message to all players, such as one
* from {@code /me}, {@code /msg}, {@code /say}, and {@code /tellraw}. Is not called
* when {@linkplain #ALLOW_COMMAND_MESSAGE command messages are blocked}.
*
* <p>If the command is executed by a player, {@link #ALLOW_CHAT_MESSAGE} and
* {@link #CHAT_MESSAGE} events will also be triggered after this event.
*/
public static final Event<CommandMessage> COMMAND_MESSAGE = EventFactory.createArrayBacked(CommandMessage.class, handlers -> (message, source, typeKey) -> {
for (CommandMessage handler : handlers) {
handler.onCommandMessage(message, source, typeKey);
}
});
private ServerMessageEvents() {
}
@FunctionalInterface
public interface AllowChatMessage {
/**
* Called when the server broadcasts a chat message sent by a player, typically
* from a client GUI or a player-executed command. Returning {@code false}
* prevents the message from being broadcast and the {@link #CHAT_MESSAGE} event
* from triggering.
*
* <p>If the message is from a player-executed command, this will be called
* only if {@link #ALLOW_COMMAND_MESSAGE} event did not block the message,
* and after triggering {@link #COMMAND_MESSAGE} event.
*
* @param message the broadcast message with message decorators applied; use {@code message.raw().getContent()} to get the text
* @param sender the player that sent the message
* @param typeKey the message type
* @return {@code true} if the message should be broadcast, otherwise {@code false}
*/
boolean allowChatMessage(FilteredMessage<SignedMessage> message, ServerPlayerEntity sender, RegistryKey<MessageType> typeKey);
}
@FunctionalInterface
public interface AllowGameMessage {
/**
* Called when the server broadcasts a game message to all players. Game messages
* include death messages, join/leave messages, and advancement messages. Returning {@code false}
* prevents the message from being broadcast and the {@link #GAME_MESSAGE} event
* from triggering.
*
* @param message the broadcast message; use {@code message.raw().getContent()} to get the text
* @param typeKey the message type
* @return {@code true} if the message should be broadcast, otherwise {@code false}
*/
boolean allowGameMessage(Text message, RegistryKey<MessageType> typeKey);
}
@FunctionalInterface
public interface AllowCommandMessage {
/**
* Called when the server broadcasts a command message to all players, such as one
* from {@code /me}, {@code /msg}, {@code /say}, and {@code /tellraw}. Returning {@code false}
* prevents the message from being broadcast and the {@link #COMMAND_MESSAGE} event
* from triggering.
*
* <p>If the command is executed by a player and the message is not blocked,
* {@link #ALLOW_CHAT_MESSAGE} and {@link #CHAT_MESSAGE} events will also be
* triggered after triggering {@link #COMMAND_MESSAGE}.
*
* @param message the broadcast message with message decorators applied if applicable; use {@code message.raw().getContent()} to get the text
* @param source the command source that sent the message
* @param typeKey the message type
* @return {@code true} if the message should be broadcast, otherwise {@code false}
*/
boolean allowCommandMessage(FilteredMessage<SignedMessage> message, ServerCommandSource source, RegistryKey<MessageType> typeKey);
}
@FunctionalInterface
public interface ChatMessage {
/**
* Called when the server broadcasts a chat message sent by a player, typically
* from a client GUI or a player-executed command. Is not called when {@linkplain
* #ALLOW_CHAT_MESSAGE chat messages are blocked}.
*
* <p>If the message is from a player-executed command, this will be called
* only if {@link #ALLOW_COMMAND_MESSAGE} event did not block the message,
* and after triggering {@link #COMMAND_MESSAGE} event.
*
* @param message the broadcast message with message decorators applied; use {@code message.raw().getContent()} to get the text
* @param sender the player that sent the message
* @param typeKey the message type
*/
void onChatMessage(FilteredMessage<SignedMessage> message, ServerPlayerEntity sender, RegistryKey<MessageType> typeKey);
}
@FunctionalInterface
public interface GameMessage {
/**
* Called when the server broadcasts a game message to all players. Game messages
* include death messages, join/leave messages, and advancement messages. Is not called
* when {@linkplain #ALLOW_GAME_MESSAGE game messages are blocked}.
*
* @param message the broadcast message; use {@code message.raw().getContent()} to get the text
* @param typeKey the message type
*/
void onGameMessage(Text message, RegistryKey<MessageType> typeKey);
}
@FunctionalInterface
public interface CommandMessage {
/**
* Called when the server broadcasts a command message to all players, such as one
* from {@code /me}, {@code /msg}, {@code /say}, and {@code /tellraw}. Is not called
* when {@linkplain #ALLOW_COMMAND_MESSAGE command messages are blocked}.
*
* <p>If the command is executed by a player, {@link #ALLOW_CHAT_MESSAGE} and
* {@link #CHAT_MESSAGE} events will also be triggered after this event.
*
* @param message the broadcast message with message decorators applied if applicable; use {@code message.raw().getContent()} to get the text
* @param source the command source that sent the message
* @param typeKey the message type
*/
void onCommandMessage(FilteredMessage<SignedMessage> message, ServerCommandSource source, RegistryKey<MessageType> typeKey);
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.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.callback.CallbackInfoReturnable;
import net.minecraft.network.message.MessageDecorator;
import net.minecraft.server.MinecraftServer;
import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {
@Inject(method = "getMessageDecorator", at = @At("RETURN"), cancellable = true)
private void onGetChatDecorator(CallbackInfoReturnable<MessageDecorator> cir) {
MessageDecorator originalDecorator = cir.getReturnValue();
cir.setReturnValue((sender, message) -> originalDecorator.decorate(sender, message).thenCompose((decorated) -> ServerMessageDecoratorEvent.EVENT.invoker().decorate(sender, decorated)));
}
}

View file

@ -0,0 +1,68 @@
/*
* 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.message;
import java.util.function.Function;
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.callback.CallbackInfo;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.filter.FilteredMessage;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.registry.RegistryKey;
import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
@Mixin(PlayerManager.class)
public class PlayerManagerMixin {
@Inject(method = "broadcast(Lnet/minecraft/server/filter/FilteredMessage;Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/util/registry/RegistryKey;)V", at = @At("HEAD"), cancellable = true)
private void onSendChatMessage(FilteredMessage<SignedMessage> message, ServerPlayerEntity sender, RegistryKey<MessageType> typeKey, CallbackInfo ci) {
if (!ServerMessageEvents.ALLOW_CHAT_MESSAGE.invoker().allowChatMessage(message, sender, typeKey)) {
ci.cancel();
return;
}
ServerMessageEvents.CHAT_MESSAGE.invoker().onChatMessage(message, sender, typeKey);
}
@Inject(method = "broadcast(Lnet/minecraft/text/Text;Ljava/util/function/Function;Lnet/minecraft/util/registry/RegistryKey;)V", at = @At("HEAD"), cancellable = true)
private void onSendGameMessage(Text message, Function<ServerPlayerEntity, Text> playerMessageFactory, RegistryKey<MessageType> typeKey, CallbackInfo ci) {
if (!ServerMessageEvents.ALLOW_GAME_MESSAGE.invoker().allowGameMessage(message, typeKey)) {
ci.cancel();
return;
}
ServerMessageEvents.GAME_MESSAGE.invoker().onGameMessage(message, typeKey);
}
@Inject(method = "broadcast(Lnet/minecraft/server/filter/FilteredMessage;Lnet/minecraft/server/command/ServerCommandSource;Lnet/minecraft/util/registry/RegistryKey;)V", at = @At("HEAD"), cancellable = true)
private void onSendCommandMessage(FilteredMessage<SignedMessage> message, ServerCommandSource source, RegistryKey<MessageType> typeKey, CallbackInfo ci) {
if (!ServerMessageEvents.ALLOW_COMMAND_MESSAGE.invoker().allowCommandMessage(message, source, typeKey)) {
ci.cancel();
return;
}
ServerMessageEvents.COMMAND_MESSAGE.invoker().onCommandMessage(message, source, typeKey);
}
}

Binary file not shown.

After

(image error) Size: 1.5 KiB

View file

@ -0,0 +1,14 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.message",
"compatibilityLevel": "JAVA_16",
"mixins": [
"MinecraftServerMixin",
"PlayerManagerMixin"
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,29 @@
{
"schemaVersion": 1,
"id": "fabric-message-api-v1",
"name": "Fabric Message API (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-message-api-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.10.5",
"fabric-api-base": "*"
},
"description": "Adds message-related hooks.",
"mixins": [
"fabric-message-api-v1.mixins.json"
],
"custom": {
"fabric-api:module-lifecycle": "experimental"
}
}

View file

@ -0,0 +1,101 @@
/*
* 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 java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.text.TranslatableTextContent;
import net.minecraft.util.Util;
import net.minecraft.util.math.random.Random;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
public class ChatTest implements ModInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(ChatTest.class);
@Override
public void onInitialize() {
Executor ioWorkerExecutor = Util.getIoWorkerExecutor();
// Basic content phase testing
ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> {
if (message.getString().contains("tater")) {
return CompletableFuture.completedFuture(message.copy().append(" :tiny_potato:"));
}
return CompletableFuture.completedFuture(message);
});
// Basic styling phase testing
ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.STYLING_PHASE, (sender, message) -> {
if (sender != null && sender.getAbilities().creativeMode) {
return CompletableFuture.completedFuture(message.copy().styled(style -> style.withColor(0xFFA500)));
}
return CompletableFuture.completedFuture(message);
});
// Async testing
ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> {
if (message.getString().contains("wait")) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(Random.create().nextBetween(500, 2000));
} catch (InterruptedException ignored) {
// Ignore interruption
}
return message;
}, ioWorkerExecutor);
}
return CompletableFuture.completedFuture(message);
});
// ServerMessageEvents
ServerMessageEvents.CHAT_MESSAGE.register(
(message, sender, typeKey) -> LOGGER.info("ChatTest: {} sent \"{}\"", sender, message)
);
ServerMessageEvents.GAME_MESSAGE.register(
(message, typeKey) -> LOGGER.info("ChatTest: server sent \"{}\"", message)
);
ServerMessageEvents.COMMAND_MESSAGE.register(
(message, source, typeKey) -> LOGGER.info("ChatTest: command sent \"{}\"", message)
);
// ServerMessageEvents blocking
ServerMessageEvents.ALLOW_CHAT_MESSAGE.register(
(message, sender, typeKey) -> !message.raw().getContent().getString().contains("sadtater")
);
ServerMessageEvents.ALLOW_GAME_MESSAGE.register((message, typeKey) -> {
if (message.getContent() instanceof TranslatableTextContent translatable) {
return !translatable.getKey().startsWith("death.attack.badRespawnPoint.");
}
return true;
});
ServerMessageEvents.ALLOW_COMMAND_MESSAGE.register(
(message, source, typeKey) -> !message.raw().getContent().getString().contains("sadtater")
);
}
}

View file

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"id": "fabric-message-api-v1-testmod",
"name": "Fabric Message API (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-message-api-v1": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.message.ChatTest"
]
}
}

View file

@ -32,6 +32,7 @@ fabric-keybindings-v0-version=0.2.15
fabric-lifecycle-events-v1-version=2.0.8
fabric-loot-api-v2-version=1.0.0
fabric-loot-tables-v1-version=1.1.0
fabric-message-api-v1-version=1.0.0
fabric-mining-level-api-v1-version=2.1.6
fabric-models-v0-version=0.3.14
fabric-networking-api-v1-version=1.0.25

View file

@ -30,6 +30,7 @@ include 'fabric-item-groups-v0'
include 'fabric-key-binding-api-v1'
include 'fabric-lifecycle-events-v1'
include 'fabric-loot-api-v2'
include 'fabric-message-api-v1'
include 'fabric-mining-level-api-v1'
include 'fabric-models-v0'
include 'fabric-networking-api-v1'