Replace /tell with custom implementation

This commit is contained in:
Alessandro Proto 2024-10-22 22:28:24 +02:00
parent 4d2c2cb36f
commit 2fe6498830
11 changed files with 272 additions and 2 deletions

View file

@ -49,6 +49,7 @@ dependencies {
include modImplementation("me.lucko:fabric-permissions-api:${project.permissions_api_version}") include modImplementation("me.lucko:fabric-permissions-api:${project.permissions_api_version}")
include modImplementation("net.kyori:adventure-platform-fabric:${project.adventure_version}") include modImplementation("net.kyori:adventure-platform-fabric:${project.adventure_version}")
modImplementation include("eu.pb4:placeholder-api:${project.placeholderapi_version}")
} }
processResources { processResources {

View file

@ -9,7 +9,7 @@ yarn_mappings=1.20.1+build.10
loader_version=0.16.5 loader_version=0.16.5
# Mod Properties # Mod Properties
mod_version=1.10.2 mod_version=1.11.0
maven_group=cc.reconnected maven_group=cc.reconnected
archives_base_name=rcc-server archives_base_name=rcc-server
@ -22,3 +22,4 @@ luckpermsapi_version=5.4
permissions_api_version=0.2-SNAPSHOT permissions_api_version=0.2-SNAPSHOT
adventure_version=5.9.1 adventure_version=5.9.1
placeholderapi_version=2.1.3+1.20.1

View file

@ -1,6 +1,8 @@
package cc.reconnected.server; package cc.reconnected.server;
import cc.reconnected.server.commands.AfkCommand; import cc.reconnected.server.commands.AfkCommand;
import cc.reconnected.server.commands.ReplyCommand;
import cc.reconnected.server.commands.TellCommand;
import cc.reconnected.server.database.PlayerData; import cc.reconnected.server.database.PlayerData;
import cc.reconnected.server.events.PlayerActivityEvents; import cc.reconnected.server.events.PlayerActivityEvents;
import cc.reconnected.server.events.PlayerWelcome; import cc.reconnected.server.events.PlayerWelcome;
@ -82,6 +84,8 @@ public class RccServer implements ModInitializer {
LOGGER.info("Starting rcc-server"); LOGGER.info("Starting rcc-server");
CommandRegistrationCallback.EVENT.register(AfkCommand::register); CommandRegistrationCallback.EVENT.register(AfkCommand::register);
CommandRegistrationCallback.EVENT.register(TellCommand::register);
CommandRegistrationCallback.EVENT.register(ReplyCommand::register);
ServerLifecycleEvents.SERVER_STARTED.register(server -> { ServerLifecycleEvents.SERVER_STARTED.register(server -> {
luckPerms = LuckPermsProvider.get(); luckPerms = LuckPermsProvider.get();

View file

@ -11,4 +11,7 @@ public class RccServerConfigModel {
public String afkMessage = "<gray><displayname> is now AFK</gray>"; public String afkMessage = "<gray><displayname> is now AFK</gray>";
public String afkReturnMessage = "<gray><displayname> is no longer AFK</gray>"; public String afkReturnMessage = "<gray><displayname> is no longer AFK</gray>";
public String tellMessage = "<gold>[</gold><source> <gray>→</gray> <target><gold>]</gold> <message>";
public String tellMessageSpy = "\uD83D\uDC41 <gray>[<source> → <target>]</gray> <message>";
} }

View file

@ -0,0 +1,43 @@
package cc.reconnected.server.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.*;
public class ReplyCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var messageNode = dispatcher.register(literal("reply")
.then(argument("message", StringArgumentType.greedyString())
.executes(ReplyCommand::execute)));
dispatcher.register(literal("r").redirect(messageNode));
}
private static int execute(CommandContext<ServerCommandSource> context) {
var source = context.getSource();
var senderName = source.getName();
var message = StringArgumentType.getString(context, "message");
if(!TellCommand.lastSender.containsKey(senderName)) {
source.sendFeedback(() -> Text.literal("You have no one to reply to.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return 1;
}
var targetName = TellCommand.lastSender.get(senderName);
var playerManager = source.getServer().getPlayerManager();
TellCommand.sendDirectMessage(targetName, source, message);
return 1;
}
}

View file

@ -0,0 +1,107 @@
package cc.reconnected.server.commands;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.parser.MarkdownParser;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.CommandSource;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import static net.minecraft.server.command.CommandManager.*;
public class TellCommand {
public static final HashMap<String, String> lastSender = new HashMap<>();
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var messageNode = dispatcher.register(literal("msg")
.then(argument("player", StringArgumentType.word())
.suggests((context, builder) -> {
var playerManager = context.getSource().getServer().getPlayerManager();
return CommandSource.suggestMatching(
playerManager.getPlayerNames(),
builder);
})
.then(argument("message", StringArgumentType.greedyString())
.executes(TellCommand::execute))));
dispatcher.register(literal("tell").redirect(messageNode));
dispatcher.register(literal("w").redirect(messageNode));
dispatcher.register(literal("dm").redirect(messageNode));
}
private static int execute(CommandContext<ServerCommandSource> context) {
var source = context.getSource();
var targetName = StringArgumentType.getString(context, "player");
var message = StringArgumentType.getString(context, "message");
sendDirectMessage(targetName, source, message);
return 1;
}
public static void sendDirectMessage(String targetName, ServerCommandSource source, String message) {
Text targetDisplayName;
ServerPlayerEntity targetPlayer = null;
if (targetName.equalsIgnoreCase("server")) {
targetDisplayName = Text.of("Server");
} else {
targetPlayer = source.getServer().getPlayerManager().getPlayer(targetName);
if (targetPlayer == null) {
source.sendFeedback(() -> Text.literal("Player \"" + targetName + "\" not found").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return;
}
targetDisplayName = targetPlayer.getDisplayName();
}
var parsedMessage = MarkdownParser.defaultParser.parseNode(message);
var text = MiniMessage.miniMessage().deserialize(RccServer.CONFIG.tellMessage(),
Placeholder.component("source", source.getDisplayName()),
Placeholder.component("target", targetDisplayName),
Placeholder.component("message", parsedMessage.toText()));
lastSender.put(targetName, source.getName());
if (!source.getName().equals(targetName)) {
source.sendMessage(text);
}
if(targetPlayer != null) {
targetPlayer.sendMessage(text);
if(source.isExecutedByPlayer()) {
source.getServer().sendMessage(text);
}
} else {
// avoid duped message
source.getServer().sendMessage(text);
}
var lp = RccServer.getInstance().luckPerms();
var playerAdapter = lp.getPlayerAdapter(ServerPlayerEntity.class);
var spyText = MiniMessage.miniMessage().deserialize(RccServer.CONFIG.tellMessageSpy(),
Placeholder.component("source", source.getDisplayName()),
Placeholder.component("target", targetDisplayName),
Placeholder.component("message", parsedMessage.toText()));
source.getServer().getPlayerManager().getPlayerList().forEach(player -> {
var playerName = player.getGameProfile().getName();
if(playerName.equals(targetName) || playerName.equals(source.getName())) {
return;
}
var playerPerms = playerAdapter.getPermissionData(player);
if(playerPerms.checkPermission("rcc.tell.spy").asBoolean()) {
player.sendMessage(spyText);
};
});
}
}

View file

@ -0,0 +1,26 @@
package cc.reconnected.server.mixin;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.server.command.MessageCommand;
import net.minecraft.server.command.ServerCommandSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.annotation.Target;
@Mixin(MessageCommand.class)
public class MessageCommandMixin {
/**
* @author Alex
* @reason Implementing custom tell command
*/
@Overwrite
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
}
}

View file

@ -0,0 +1,40 @@
package cc.reconnected.server.parser;
import eu.pb4.placeholders.api.node.TextNode;
import eu.pb4.placeholders.api.node.parent.ClickActionNode;
import eu.pb4.placeholders.api.node.parent.FormattingNode;
import eu.pb4.placeholders.api.node.parent.HoverNode;
import net.minecraft.text.ClickEvent;
import net.minecraft.util.Formatting;
public class MarkdownComponentParser {
public static TextNode spoilerFormatting(TextNode[] textNodes) {
var text = TextNode.asSingle(textNodes);
return new HoverNode<>(
TextNode.array(
new FormattingNode(TextNode.array(TextNode.of("\u258C".repeat(text.toText().getString().length()))), Formatting.DARK_GRAY)
),
HoverNode.Action.TEXT, text);
}
public static TextNode quoteFormatting(TextNode[] textNodes) {
return new ClickActionNode(
TextNode.array(
new HoverNode<>(
TextNode.array(new FormattingNode(textNodes, Formatting.GRAY)),
HoverNode.Action.TEXT, TextNode.of("Click to copy"))
),
ClickEvent.Action.COPY_TO_CLIPBOARD, TextNode.asSingle(textNodes)
);
}
public static TextNode urlFormatting(TextNode[] textNodes, TextNode url) {
return new HoverNode<>(TextNode.array(
new ClickActionNode(
TextNode.array(
new FormattingNode(textNodes, Formatting.BLUE, Formatting.UNDERLINE)),
ClickEvent.Action.OPEN_URL, url)),
HoverNode.Action.TEXT, TextNode.of("Click to open: " + url.toText().getString())
);
}
}

View file

@ -0,0 +1,30 @@
package cc.reconnected.server.parser;
import eu.pb4.placeholders.api.parsers.MarkdownLiteParserV1;
import eu.pb4.placeholders.api.parsers.NodeParser;
import static eu.pb4.placeholders.api.parsers.MarkdownLiteParserV1.MarkdownFormat;
public class MarkdownParser {
public static final MarkdownFormat[] ALL = new MarkdownFormat[] {
MarkdownFormat.QUOTE,
MarkdownFormat.BOLD,
MarkdownFormat.ITALIC,
MarkdownFormat.UNDERLINE,
MarkdownFormat.STRIKETHROUGH,
MarkdownFormat.SPOILER,
MarkdownFormat.URL
};
public static final NodeParser defaultParser = createParser(ALL);
public static NodeParser createParser(MarkdownFormat[] capabilities) {
return new MarkdownLiteParserV1(
MarkdownComponentParser::spoilerFormatting,
MarkdownComponentParser::quoteFormatting,
MarkdownComponentParser::urlFormatting,
capabilities
);
}
}

View file

@ -21,6 +21,7 @@
] ]
}, },
"mixins": [ "mixins": [
"rcc-server.mixins.json"
], ],
"depends": { "depends": {
"fabricloader": ">=0.16.0", "fabricloader": ">=0.16.0",

View file

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "cc.reconnected.server.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [],
"client": [],
"server": [
"MessageCommandMixin"
],
"injectors": {
"defaultRequire": 1
}
}