Skip to content
This repository has been archived by the owner on Nov 22, 2024. It is now read-only.

Commit

Permalink
Merge pull request #7
Browse files Browse the repository at this point in the history
* Initial mixins for custom chat

* More experiment

* Custom chat messages support + join, leave, /me

* Add custom death message

* Bump version
  • Loading branch information
Ale32bit authored Nov 4, 2024
1 parent 59fc43c commit 362132f
Show file tree
Hide file tree
Showing 17 changed files with 420 additions and 10 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ yarn_mappings=1.20.1+build.10
loader_version=0.16.8

# Mod Properties
mod_version=1.16.4
mod_version=1.17.0
maven_group=cc.reconnected
archives_base_name=rcc-server

Expand Down
9 changes: 9 additions & 0 deletions src/main/java/cc/reconnected/server/RccServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
import net.kyori.adventure.text.Component;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.minecraft.network.message.MessageType;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.util.WorldSavePath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -52,6 +56,8 @@ public LuckPerms luckPerms() {
return luckPerms;
}

public static MinecraftServer server;

private volatile FabricServerAudiences adventure;

public FabricServerAudiences adventure() {
Expand All @@ -62,11 +68,14 @@ public FabricServerAudiences adventure() {
return ret;
}

public static final RegistryKey<MessageType> CHAT_TYPE = RegistryKey.of(RegistryKeys.MESSAGE_TYPE, new Identifier(MOD_ID, "chat"));

@Override
public void onInitialize() {
LOGGER.info("Starting rcc-server");

ServerLifecycleEvents.SERVER_STARTING.register(server -> {
RccServer.server = server;
state.register(server.getSavePath(WorldSavePath.ROOT).resolve("data").resolve(RccServer.MOD_ID));
this.adventure = FabricServerAudiences.of(server);
});
Expand Down
26 changes: 24 additions & 2 deletions src/main/java/cc/reconnected/server/RccServerConfigModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import io.wispforest.owo.config.annotation.Config;
import io.wispforest.owo.config.annotation.Nest;

import java.util.ArrayList;
import java.util.List;
import java.util.*;

@Config(name = "rcc-server-config", wrapperName = "RccServerConfig")
public class RccServerConfigModel {
Expand Down Expand Up @@ -32,6 +31,12 @@ public class RccServerConfigModel {
@Nest
public AutoRestart autoRestart = new AutoRestart();

@Nest
public CustomNameConfig customName = new CustomNameConfig();

@Nest
public CustomChatFormat customChatFormat = new CustomChatFormat();

public static class HttpApi {
public boolean enableHttpApi = true;
public int httpPort = 25581;
Expand Down Expand Up @@ -106,4 +111,21 @@ public static class AutoRestart {
1
));
}

public static class CustomNameConfig {
public LinkedHashMap<String, String> formats = new LinkedHashMap<>(Map.of(
"admin", "<red><username></red>",
"default", "<green><username></green>"
));
}

public static class CustomChatFormat {
public boolean enableMarkdown = true;
public String chatFormat = "<display_name><gray>:</gray> <message>";
public String emoteFormat = "<gray>\uD83D\uDC64 <display_name> <i><message></i></gray>";
public String joinFormat = "<green>+</green> <display_name> <yellow>joined!</yellow>";
public String joinRenamedFormat = "<green>+</green> <display_name> <yellow>joined! <i>(Previously known as <previous_name>)</i></yellow>";
public String leaveFormat = "<red>-</red> <display_name> <yellow>left!</yellow>";
public String deathFormat = "<gray>\u2620 <death_message></gray>";
}
}
43 changes: 43 additions & 0 deletions src/main/java/cc/reconnected/server/core/CustomNameFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package cc.reconnected.server.core;

import cc.reconnected.server.RccServer;
import cc.reconnected.server.util.Components;
import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.Placeholders;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.luckperms.api.model.group.Group;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;

public class CustomNameFormat {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();
public static MutableText getNameForPlayer(ServerPlayerEntity player) {
var formats = RccServer.CONFIG.customName.formats().entrySet();
var lp = RccServer.getInstance().luckPerms();
var playerContext = PlaceholderContext.of(player);

var user = lp.getPlayerAdapter(ServerPlayerEntity.class).getUser(player);

var groups = user.getInheritedGroups(user.getQueryOptions()).stream().map(Group::getName).toList();

String format = null;
for (var entry : formats) {
if (groups.contains(entry.getKey())) {
format = entry.getValue();
break;
}
}

if (format == null) {
format = "<username>";
}

var displayName = miniMessage.deserialize(format, TagResolver.resolver(
Placeholder.parsed("username", player.getGameProfile().getName())
));

return Placeholders.parseText(Components.toText(displayName), playerContext).copy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cc.reconnected.server.core.customChat;

import cc.reconnected.server.RccServer;
import cc.reconnected.server.parser.MarkdownParser;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;

public class CustomChatMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();

public static void sendChatMessage(ServerPlayerEntity receiver, SignedMessage message, MessageType.Parameters params) {
var playerUuid = message.link().sender();
var player = RccServer.server.getPlayerManager().getPlayer(playerUuid);

Text messageText;
if (RccServer.CONFIG.customChatFormat.enableMarkdown()) {
messageText = MarkdownParser.defaultParser.parseNode(message.getSignedContent()).toText();
} else {
messageText = message.getContent();
}

var component = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.chatFormat(), TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName()),
Placeholder.component("message", messageText)
));

var text = Components.toText(component);

var msgType = RccServer.server.getRegistryManager().get(RegistryKeys.MESSAGE_TYPE).getOrThrow(RccServer.CHAT_TYPE);
var newParams = new MessageType.Parameters(msgType, text, null);

receiver.networkHandler.sendChatMessage(message, newParams);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cc.reconnected.server.core.customChat;

import cc.reconnected.server.RccServer;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;

public class CustomConnectionMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();

public static Text onJoin(ServerPlayerEntity player) {
var joinMessage = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.joinFormat(),
TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName())
));

return Components.toText(joinMessage);
}

public static Text onJoinRenamed(ServerPlayerEntity player, String previousName) {
var joinMessage = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.joinRenamedFormat(),
TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName()),
Placeholder.component("previous_name", Text.of(previousName))
));

return Components.toText(joinMessage);
}

public static Text onLeave(ServerPlayerEntity player) {
var leaveMessage = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.leaveFormat(),
TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName())
));

return Components.toText(leaveMessage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cc.reconnected.server.core.customChat;

import cc.reconnected.server.RccServer;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.entity.damage.DamageTracker;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;

public class CustomDeathMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();

public static Text onDeath(ServerPlayerEntity player, DamageTracker instance) {
var deathMessage = instance.getDeathMessage();
var deathComponent = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.deathFormat(),
TagResolver.resolver(
Placeholder.component("death_message", deathMessage)
));

return Components.toText(deathComponent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cc.reconnected.server.core.customChat;

import cc.reconnected.server.RccServer;
import cc.reconnected.server.parser.MarkdownParser;
import cc.reconnected.server.util.Components;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;

public class CustomEmoteMessage {
private static final MiniMessage miniMessage = MiniMessage.miniMessage();

public static void sendEmoteMessage(ServerPlayerEntity receiver, SignedMessage message, MessageType.Parameters params) {
var playerUuid = message.link().sender();
var player = RccServer.server.getPlayerManager().getPlayer(playerUuid);

Text messageText;
if (RccServer.CONFIG.customChatFormat.enableMarkdown()) {
messageText = MarkdownParser.defaultParser.parseNode(message.getSignedContent()).toText();
} else {
messageText = message.getContent();
}

var component = miniMessage.deserialize(RccServer.CONFIG.customChatFormat.emoteFormat(), TagResolver.resolver(
Placeholder.component("display_name", player.getDisplayName()),
Placeholder.component("message", messageText)
));

var text = Components.toText(component);

var msgType = RccServer.server.getRegistryManager().get(RegistryKeys.MESSAGE_TYPE).getOrThrow(RccServer.CHAT_TYPE);
var newParams = new MessageType.Parameters(msgType, text, null);

receiver.networkHandler.sendChatMessage(message, newParams);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cc.reconnected.server.core.customChat;

import cc.reconnected.server.RccServer;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SentMessage;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;

public record CustomSentMessage(SignedMessage message) implements SentMessage {

@Override
public Text getContent() {
return this.message.getContent();
}

@Override
public void send(ServerPlayerEntity receiver, boolean filterMaskEnabled, MessageType.Parameters params) {
SignedMessage signedMessage = this.message.withFilterMaskEnabled(filterMaskEnabled);
RccServer.LOGGER.info("Message params type: {}", params.type().chat().translationKey());
if (!signedMessage.isFullyFiltered()) {
switch (params.type().chat().translationKey()) {
case "chat.type.text":
CustomChatMessage.sendChatMessage(receiver, message, params);
break;
case "chat.type.emote":
CustomEmoteMessage.sendEmoteMessage(receiver, message, params);
break;
default:
receiver.networkHandler.sendChatMessage(this.message, params);
break;
}
}
}

}
33 changes: 33 additions & 0 deletions src/main/java/cc/reconnected/server/mixin/PlayerEntityMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cc.reconnected.server.mixin;

import cc.reconnected.server.core.CustomNameFormat;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin {
@Shadow
private MutableText addTellClickEvent(MutableText component) {
return null;
}

@Shadow
public abstract Text getName();

@Shadow
public abstract GameProfile getGameProfile();

@Inject(method = "getDisplayName", at = @At("HEAD"), cancellable = true)
public void getDisplayName(CallbackInfoReturnable<MutableText> cir) {
var name = CustomNameFormat.getNameForPlayer((ServerPlayerEntity) (Object) this);
cir.setReturnValue(addTellClickEvent(name));
}
}
42 changes: 42 additions & 0 deletions src/main/java/cc/reconnected/server/mixin/PlayerManagerMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cc.reconnected.server.mixin;

import cc.reconnected.server.core.customChat.CustomConnectionMessage;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableTextContent;
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.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(PlayerManager.class)
public class PlayerManagerMixin {
@Unique
private ServerPlayerEntity rccServer$player = null;

@Inject(method="onPlayerConnect", at = @At("HEAD"))
private void rccServer$onJoin(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
rccServer$player = player;
}

@Inject(method="onPlayerConnect", at = @At("RETURN"))
private void rccServer$onJoinReturn(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
rccServer$player = null;
}

@ModifyArg(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
public Text rccServer$getPlayerJoinMessage(Text message) {
var ogText = (TranslatableTextContent) message.getContent();
var args = ogText.getArgs();

if (args.length == 1) {
return CustomConnectionMessage.onJoin(rccServer$player);
} else {
return CustomConnectionMessage.onJoinRenamed(rccServer$player, (String) args[1]);
}
}
}
Loading

0 comments on commit 362132f

Please sign in to comment.