More refactoring, added custom tab

This commit is contained in:
Alessandro Proto 2024-10-25 22:47:09 +02:00
parent 019da6ebfb
commit 5a879b0800
38 changed files with 938 additions and 192 deletions

View file

@ -9,7 +9,7 @@ yarn_mappings=1.20.1+build.10
loader_version=0.16.7
# Mod Properties
mod_version=1.13.0
mod_version=1.14.0
maven_group=cc.reconnected
archives_base_name=rcc-server

View file

@ -1,8 +1,15 @@
package cc.reconnected.server;
import cc.reconnected.server.commands.*;
import cc.reconnected.server.commands.home.*;
import cc.reconnected.server.commands.misc.*;
import cc.reconnected.server.commands.spawn.*;
import cc.reconnected.server.commands.teleport.*;
import cc.reconnected.server.commands.tell.*;
import cc.reconnected.server.commands.warp.*;
import cc.reconnected.server.core.*;
import cc.reconnected.server.data.StateManager;
import cc.reconnected.server.database.PlayerData;
import cc.reconnected.server.events.PlayerUsernameChange;
import cc.reconnected.server.events.PlayerWelcome;
import cc.reconnected.server.events.Ready;
import net.fabricmc.api.ModInitializer;
@ -17,6 +24,7 @@ import net.luckperms.api.LuckPermsProvider;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.WorldSavePath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -29,6 +37,8 @@ public class RccServer implements ModInitializer {
public static final cc.reconnected.server.RccServerConfig CONFIG = cc.reconnected.server.RccServerConfig.createAndLoad();
public static final StateManager state = new StateManager();
private static RccServer INSTANCE;
public static RccServer getInstance() {
@ -59,21 +69,40 @@ public class RccServer implements ModInitializer {
public void onInitialize() {
LOGGER.info("Starting rcc-server");
ServerLifecycleEvents.SERVER_STARTING.register(server -> this.adventure = FabricServerAudiences.of(server));
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
state.register(server.getSavePath(WorldSavePath.ROOT).resolve("data").resolve(RccServer.MOD_ID));
this.adventure = FabricServerAudiences.of(server);
});
ServerLifecycleEvents.SERVER_STOPPED.register(server -> this.adventure = null);
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
RccCommand.register(dispatcher, registryAccess, environment);
AfkCommand.register(dispatcher, registryAccess, environment);
TellCommand.register(dispatcher, registryAccess, environment);
ReplyCommand.register(dispatcher, registryAccess, environment);
TeleportAskCommand.register(dispatcher, registryAccess, environment);
TeleportAskHereCommand.register(dispatcher, registryAccess, environment);
TeleportAcceptCommand.register(dispatcher, registryAccess, environment);
TeleportDenyCommand.register(dispatcher, registryAccess, environment);
BackCommand.register(dispatcher, registryAccess, environment);
FlyCommand.register(dispatcher, registryAccess, environment);
GodCommand.register(dispatcher, registryAccess, environment);
SetSpawnCommand.register(dispatcher, registryAccess, environment);
SpawnCommand.register(dispatcher, registryAccess, environment);
HomeCommand.register(dispatcher, registryAccess, environment);
SetHomeCommand.register(dispatcher, registryAccess, environment);
DeleteHomeCommand.register(dispatcher, registryAccess, environment);
WarpCommand.register(dispatcher, registryAccess, environment);
SetWarpCommand.register(dispatcher, registryAccess, environment);
DeleteWarpCommand.register(dispatcher, registryAccess, environment);
});
AfkTracker.register();
@ -89,25 +118,32 @@ public class RccServer implements ModInitializer {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
var player = handler.getPlayer();
var playerData = PlayerData.getPlayer(player.getUuid());
playerData.set(PlayerData.KEYS.username, player.getName().getString());
var firstJoinedDate = playerData.getDate(PlayerData.KEYS.firstJoinedDate);
boolean isNewPlayer = false;
if (firstJoinedDate == null) {
playerData.setDate(PlayerData.KEYS.firstJoinedDate, new Date());
isNewPlayer = true;
}
if (isNewPlayer) {
PlayerWelcome.PLAYER_WELCOME.invoker().playerWelcome(player, playerData, server);
LOGGER.info("Player {} joined for the first time!", player.getName().getString());
}
});
}
public void broadcastMessage(MinecraftServer server, Text message) {
for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) {
player.sendMessage(message, false);
}
var playerState = state.getPlayerState(player.getUuid());
var playerData = PlayerData.getPlayer(player.getUuid());
if (playerState.firstJoinedDate == null) {
LOGGER.info("Player {} joined for the first time!", player.getGameProfile().getName());
playerState.firstJoinedDate = new Date();
PlayerWelcome.PLAYER_WELCOME.invoker().playerWelcome(player, server);
var serverState = state.getServerState();
var spawnPosition = serverState.spawn;
if (spawnPosition != null) {
spawnPosition.teleport(player, false);
}
playerData.setDate(PlayerData.KEYS.firstJoinedDate, new Date());
}
if (playerState.username != null && !playerState.username.equals(player.getGameProfile().getName())) {
LOGGER.info("Player {} has changed their username from {}", player.getGameProfile().getName(), playerState.username);
PlayerUsernameChange.PLAYER_USERNAME_CHANGE.invoker().changeUsername(player, playerState.username);
playerData.set(PlayerData.KEYS.username, player.getName().getString());
}
playerState.username = player.getGameProfile().getName();
state.savePlayerState(player.getUuid(), playerState);
});
}
public void broadcastMessage(MinecraftServer server, Component message) {

View file

@ -28,4 +28,6 @@ public class RccServerConfigModel {
public ArrayList<String> tabFooter = new ArrayList<>(List.of(
"<gradient:#DEDE6C:#CC4C4C:{phase}><st> </st></gradient>"
));
public String playerTabName = "%player:displayname_visual% %player:playtime%";
}

View file

@ -0,0 +1,64 @@
package cc.reconnected.server.commands.home;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class DeleteHomeCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("delhome")
.requires(Permissions.require("rcc.command.delhome", true))
.executes(context -> execute(context, "home"))
.then(argument("name", StringArgumentType.word())
.suggests((context, builder) -> {
if (!context.getSource().isExecutedByPlayer())
return CommandSource.suggestMatching(new String[]{}, builder);
var playerState = RccServer.state.getPlayerState(context.getSource().getPlayer().getUuid());
return CommandSource.suggestMatching(playerState.homes.keySet().stream(), builder);
})
.executes(context -> execute(context, StringArgumentType.getString(context, "name"))));
dispatcher.register(rootCommand);
}
private static int execute(CommandContext<ServerCommandSource> context, String name) {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var playerState = RccServer.state.getPlayerState(player.getUuid());
var homes = playerState.homes;
if (!homes.containsKey(name)) {
context.getSource().sendFeedback(() -> Text.literal("The home ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" does not exist!")
.formatted(Formatting.RED), false);
return 1;
}
homes.remove(name);
RccServer.state.savePlayerState(player.getUuid(), playerState);
context.getSource().sendFeedback(() -> Text
.literal("Home ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" deleted!")
.formatted(Formatting.GREEN), false);
return 1;
}
}

View file

@ -0,0 +1,63 @@
package cc.reconnected.server.commands.home;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.*;
public class HomeCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("home")
.requires(Permissions.require("rcc.command.home", true))
.executes(context -> execute(context, "home"))
.then(argument("name", StringArgumentType.word())
.suggests((context, builder) -> {
if (!context.getSource().isExecutedByPlayer())
return CommandSource.suggestMatching(new String[]{}, builder);
var playerState = RccServer.state.getPlayerState(context.getSource().getPlayer().getUuid());
return CommandSource.suggestMatching(playerState.homes.keySet().stream(), builder);
})
.executes(context -> execute(context, StringArgumentType.getString(context, "name"))));
dispatcher.register(rootCommand);
}
private static int execute(CommandContext<ServerCommandSource> context, String name) {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var playerState = RccServer.state.getPlayerState(player.getUuid());
var homes = playerState.homes;
if (!homes.containsKey(name)) {
context.getSource().sendFeedback(() -> Text.literal("The home ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" does not exist!")
.formatted(Formatting.RED), false);
return 1;
}
context.getSource().sendFeedback(() -> Text
.literal("Teleporting to ")
.append(Text.literal(name).formatted(Formatting.GREEN))
.append("...")
.formatted(Formatting.GOLD), false);
var homePosition = homes.get(name);
homePosition.teleport(player);
return 1;
}
}

View file

@ -0,0 +1,76 @@
package cc.reconnected.server.commands.home;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.struct.ServerPosition;
import cc.reconnected.server.util.Components;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.*;
public class SetHomeCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("sethome")
.requires(Permissions.require("rcc.command.sethome", true))
.executes(context -> execute(context,
"home",
false))
.then(argument("name", StringArgumentType.word())
.executes(context -> execute(context,
StringArgumentType.getString(context, "name"),
false))
.then(argument("force", BoolArgumentType.bool())
.executes(context -> execute(context,
StringArgumentType.getString(context, "name"),
BoolArgumentType.getBool(context, "force")))));
dispatcher.register(rootCommand);
}
private static int execute(CommandContext<ServerCommandSource> context, String name, boolean forced) {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var playerState = RccServer.state.getPlayerState(player.getUuid());
var homes = playerState.homes;
if (homes.containsKey(name) && !forced) {
var text = Component.text("You already have set this home.")
.appendNewline().appendSpace()
.append(Components.makeButton(
Component.text("Force set home", NamedTextColor.GOLD),
Component.text("Click to force set the home"),
"/sethome " + name + " true"
));
context.getSource().sendFailure(text);
return 1;
}
var homePosition = new ServerPosition(player);
homes.put(name, homePosition);
RccServer.state.savePlayerState(player.getUuid(), playerState);
context.getSource().sendFeedback(() -> Text.literal("New home ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" set!")
.formatted(Formatting.GREEN), false);
return 1;
}
}

View file

@ -1,4 +1,4 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.misc;
import cc.reconnected.server.core.AfkTracker;
import com.mojang.brigadier.CommandDispatcher;

View file

@ -1,4 +1,4 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.misc;
import cc.reconnected.server.core.BackTracker;
import com.mojang.brigadier.CommandDispatcher;

View file

@ -1,8 +1,6 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.misc;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;

View file

@ -1,4 +1,4 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.misc;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;

View file

@ -1,4 +1,4 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.misc;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;

View file

@ -0,0 +1,42 @@
package cc.reconnected.server.commands.spawn;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.struct.ServerPosition;
import com.mojang.brigadier.CommandDispatcher;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.literal;
public class SetSpawnCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("setspawn")
.requires(Permissions.require("rcc.command.setspawn", 3))
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var spawnPosition = new ServerPosition(player);
var serverState = RccServer.state.getServerState();
serverState.spawn = spawnPosition;
RccServer.state.saveServerState();
context.getSource().sendFeedback(() -> Text.literal("Server spawn set to ")
.append(Text.literal(String.format("%.1f %.1f %.1f", spawnPosition.x, spawnPosition.y, spawnPosition.z))
.formatted(Formatting.GOLD))
.formatted(Formatting.GREEN), true);
return 1;
});
dispatcher.register(rootCommand);
}
}

View file

@ -0,0 +1,42 @@
package cc.reconnected.server.commands.spawn;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.struct.ServerPosition;
import com.mojang.brigadier.CommandDispatcher;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.literal;
public class SpawnCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("spawn")
.requires(Permissions.require("rcc.command.spawn", true))
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var serverState = RccServer.state.getServerState();
var spawnPosition = serverState.spawn;
if(spawnPosition == null) {
var server = context.getSource().getServer();
var spawnPos = server.getOverworld().getSpawnPos();
spawnPosition = new ServerPosition(spawnPos.getX(), spawnPos.getY(), spawnPos.getZ(), 0, 0, server.getOverworld());
}
context.getSource().sendFeedback(() -> Text.literal("Teleporting to spawn...").formatted(Formatting.GOLD), false);
spawnPosition.teleport(player);
return 1;
});
dispatcher.register(rootCommand);
}
}

View file

@ -1,6 +1,7 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.teleport;
import cc.reconnected.server.core.TeleportTracker;
import cc.reconnected.server.struct.ServerPosition;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.command.CommandRegistryAccess;
@ -88,6 +89,7 @@ public class TeleportAcceptCommand {
targetPlayer.sendMessage(Text.empty().append(player.getDisplayName()).append(Text.literal(" accepted your teleport request.").formatted(Formatting.GREEN)), false);
}
TeleportTracker.teleport(sourcePlayer, targetPlayer);
var targetPosition = new ServerPosition(targetPlayer);
targetPosition.teleport(sourcePlayer);
}
}

View file

@ -1,6 +1,7 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.teleport;
import cc.reconnected.server.core.TeleportTracker;
import cc.reconnected.server.util.Components;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
@ -60,9 +61,9 @@ public class TeleportAskCommand {
.appendSpace()
.append(Component.text("requested to teleport to you.", NamedTextColor.GOLD))
.appendNewline().appendSpace()
.append(TeleportTracker.makeButton(Component.text("Accept", NamedTextColor.GREEN), Component.text("Click to accept request"), "/tpaccept " + request.requestId))
.append(Components.makeButton(Component.text("Accept", NamedTextColor.GREEN), Component.text("Click to accept request"), "/tpaccept " + request.requestId))
.appendSpace()
.append(TeleportTracker.makeButton(Component.text("Refuse", NamedTextColor.RED), Component.text("Click to refuse request"), "/tpdeny " + request.requestId));
.append(Components.makeButton(Component.text("Refuse", NamedTextColor.RED), Component.text("Click to refuse request"), "/tpdeny " + request.requestId));
target.sendMessage(requestMessage);

View file

@ -1,6 +1,7 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.teleport;
import cc.reconnected.server.core.TeleportTracker;
import cc.reconnected.server.util.Components;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
@ -61,9 +62,9 @@ public class TeleportAskHereCommand {
.appendSpace()
.append(Component.text("requested you to teleport to them.", NamedTextColor.GOLD))
.appendNewline().appendSpace()
.append(TeleportTracker.makeButton(Component.text("Accept", NamedTextColor.GREEN), Component.text("Click to accept request"), "/tpaccept " + request.requestId))
.append(Components.makeButton(Component.text("Accept", NamedTextColor.GREEN), Component.text("Click to accept request"), "/tpaccept " + request.requestId))
.appendSpace()
.append(TeleportTracker.makeButton(Component.text("Refuse", NamedTextColor.RED), Component.text("Click to refuse request"), "/tpdeny " + request.requestId));
.append(Components.makeButton(Component.text("Refuse", NamedTextColor.RED), Component.text("Click to refuse request"), "/tpdeny " + request.requestId));
target.sendMessage(requestMessage);

View file

@ -1,4 +1,4 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.teleport;
import cc.reconnected.server.core.TeleportTracker;
import com.mojang.brigadier.CommandDispatcher;

View file

@ -1,4 +1,4 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.tell;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;

View file

@ -1,4 +1,4 @@
package cc.reconnected.server.commands;
package cc.reconnected.server.commands.tell;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.parser.MarkdownParser;
@ -6,6 +6,9 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.minecraft.command.CommandRegistryAccess;
@ -67,6 +70,17 @@ public class TellCommand {
}
var parsedMessage = MarkdownParser.defaultParser.parseNode(message);
var you = Component.text("You", NamedTextColor.GRAY, TextDecoration.ITALIC);
var sourceText = MiniMessage.miniMessage().deserialize(RccServer.CONFIG.tellMessage(),
Placeholder.component("source", you),
Placeholder.component("target", targetDisplayName),
Placeholder.component("message", parsedMessage.toText()));
var targetText = MiniMessage.miniMessage().deserialize(RccServer.CONFIG.tellMessage(),
Placeholder.component("source", source.getDisplayName()),
Placeholder.component("target", you),
Placeholder.component("message", parsedMessage.toText()));
var text = MiniMessage.miniMessage().deserialize(RccServer.CONFIG.tellMessage(),
Placeholder.component("source", source.getDisplayName()),
Placeholder.component("target", targetDisplayName),
@ -76,16 +90,16 @@ public class TellCommand {
lastSender.put(source.getName(), targetName);
if (!source.getName().equals(targetName)) {
source.sendMessage(text);
source.sendMessage(sourceText);
}
if (targetPlayer != null) {
targetPlayer.sendMessage(text);
targetPlayer.sendMessage(targetText);
if (source.isExecutedByPlayer()) {
source.getServer().sendMessage(text);
}
} else {
// avoid duped message
source.getServer().sendMessage(text);
source.getServer().sendMessage(targetText);
}
var lp = RccServer.getInstance().luckPerms();
@ -103,7 +117,6 @@ public class TellCommand {
if (playerPerms.checkPermission("rcc.tell.spy").asBoolean()) {
player.sendMessage(spyText);
}
;
});
}
}

View file

@ -0,0 +1,63 @@
package cc.reconnected.server.commands.warp;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class DeleteWarpCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("delwarp")
.requires(Permissions.require("rcc.command.delwarp", 3))
.then(argument("name", StringArgumentType.word())
.suggests((context, builder) -> {
if (!context.getSource().isExecutedByPlayer())
return CommandSource.suggestMatching(new String[]{}, builder);
var serverState = RccServer.state.getServerState();
return CommandSource.suggestMatching(serverState.warps.keySet().stream(), builder);
})
.executes(context -> execute(context, StringArgumentType.getString(context, "name"))));
dispatcher.register(rootCommand);
}
private static int execute(CommandContext<ServerCommandSource> context, String name) {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var serverState = RccServer.state.getServerState();
var warps = serverState.warps;
if (!warps.containsKey(name)) {
context.getSource().sendFeedback(() -> Text.literal("The warp ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" does not exist!")
.formatted(Formatting.RED), false);
return 1;
}
warps.remove(name);
RccServer.state.saveServerState();
context.getSource().sendFeedback(() -> Text
.literal("Warp ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" deleted!")
.formatted(Formatting.GREEN), false);
return 1;
}
}

View file

@ -0,0 +1,50 @@
package cc.reconnected.server.commands.warp;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.struct.ServerPosition;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.*;
public class SetWarpCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("setwarp")
.requires(Permissions.require("rcc.command.setwarp", 3))
.then(argument("name", StringArgumentType.word())
.executes(context -> execute(context,
StringArgumentType.getString(context, "name"))));
dispatcher.register(rootCommand);
}
private static int execute(CommandContext<ServerCommandSource> context, String name) {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var serverState = RccServer.state.getServerState();
var warps = serverState.warps;
var warpPosition = new ServerPosition(player);
warps.put(name, warpPosition);
RccServer.state.saveServerState();
context.getSource().sendFeedback(() -> Text.literal("New warp ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" set!")
.formatted(Formatting.GREEN), false);
return 1;
}
}

View file

@ -0,0 +1,62 @@
package cc.reconnected.server.commands.warp;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import static net.minecraft.server.command.CommandManager.*;
public class WarpCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("warp")
.requires(Permissions.require("rcc.command.warp", true))
.then(argument("name", StringArgumentType.word())
.suggests((context, builder) -> {
if (!context.getSource().isExecutedByPlayer())
return CommandSource.suggestMatching(new String[]{}, builder);
var serverState = RccServer.state.getServerState();
return CommandSource.suggestMatching(serverState.warps.keySet().stream(), builder);
})
.executes(context -> execute(context, StringArgumentType.getString(context, "name"))));
dispatcher.register(rootCommand);
}
private static int execute(CommandContext<ServerCommandSource> context, String name) {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var player = context.getSource().getPlayer();
var serverState = RccServer.state.getServerState();
var warps = serverState.warps;
if (!warps.containsKey(name)) {
context.getSource().sendFeedback(() -> Text.literal("The warp ")
.append(Text.literal(name).formatted(Formatting.GOLD))
.append(" does not exist!")
.formatted(Formatting.RED), false);
return 1;
}
context.getSource().sendFeedback(() -> Text
.literal("Teleporting to ")
.append(Text.literal(name).formatted(Formatting.GREEN))
.append("...")
.formatted(Formatting.GOLD), false);
var warpPosition = warps.get(name);
warpPosition.teleport(player);
return 1;
}
}

View file

@ -1,7 +1,6 @@
package cc.reconnected.server.core;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.data.StateSaverAndLoader;
import cc.reconnected.server.events.PlayerActivityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.*;
@ -16,16 +15,17 @@ import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.TypedActionResult;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class AfkTracker {
private static final int cycleDelay = 1;
private static final int absentTimeTrigger = RccServer.CONFIG.afkTimeTrigger() * 20; // seconds * 20 ticks
private final HashMap<UUID, PlayerState> playerStates = new HashMap<>();
private final ConcurrentHashMap<UUID, PlayerActivityState> playerActivityStates = new ConcurrentHashMap<>();
private static final AfkTracker instance = new AfkTracker();
public static AfkTracker getInstance() {
return instance;
}
@ -63,20 +63,16 @@ public class AfkTracker {
}
private AfkTracker() {
ServerTickEvents.END_SERVER_TICK.register(server -> {
if (server.getTicks() % cycleDelay == 0) {
updatePlayers(server);
}
});
ServerTickEvents.END_SERVER_TICK.register(this::updatePlayers);
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
final var player = handler.getPlayer();
playerStates.put(player.getUuid(), new PlayerState(player, server.getTicks()));
playerActivityStates.put(player.getUuid(), new PlayerActivityState(player, server.getTicks()));
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
updatePlayerActiveTime(handler.getPlayer(), server.getTicks());
playerStates.remove(handler.getPlayer().getUuid());
playerActivityStates.remove(handler.getPlayer().getUuid());
// sync to LP
//var activeTime = String.valueOf(getActiveTime(handler.getPlayer()));
@ -125,7 +121,7 @@ public class AfkTracker {
private void updatePlayer(ServerPlayerEntity player, MinecraftServer server) {
var currentTick = server.getTicks();
var playerState = playerStates.computeIfAbsent(player.getUuid(), uuid -> new PlayerState(player, currentTick));
var playerState = playerActivityStates.computeIfAbsent(player.getUuid(), uuid -> new PlayerActivityState(player, currentTick));
var oldPosition = playerState.position;
var newPosition = new PlayerPosition(player);
@ -147,11 +143,11 @@ public class AfkTracker {
}
private void updatePlayerActiveTime(ServerPlayerEntity player, int currentTick) {
var playerState = playerStates.get(player.getUuid());
if (!playerState.isAfk) {
var worldPlayerData = StateSaverAndLoader.getPlayerState(player);
var interval = currentTick - playerState.activeStart;
worldPlayerData.activeTime += interval / 20;
var playerActivityState = playerActivityStates.get(player.getUuid());
if (!playerActivityState.isAfk) {
var playerState = RccServer.state.getPlayerState(player.getUuid());
var interval = currentTick - playerActivityState.activeStart;
playerState.activeTime += interval / 20;
}
}
@ -163,10 +159,10 @@ public class AfkTracker {
}
private void resetAfkState(ServerPlayerEntity player, MinecraftServer server) {
if (!playerStates.containsKey(player.getUuid()))
if (!playerActivityStates.containsKey(player.getUuid()))
return;
var playerState = playerStates.get(player.getUuid());
var playerState = playerActivityStates.get(player.getUuid());
playerState.lastUpdate = server.getTicks();
if (playerState.isAfk) {
playerState.isAfk = false;
@ -200,13 +196,13 @@ public class AfkTracker {
}
}
public static class PlayerState {
public static class PlayerActivityState {
public PlayerPosition position;
public int lastUpdate;
public boolean isAfk;
public int activeStart;
public PlayerState(ServerPlayerEntity player, int lastUpdate) {
public PlayerActivityState(ServerPlayerEntity player, int lastUpdate) {
this.position = new PlayerPosition(player);
this.lastUpdate = lastUpdate;
this.isAfk = false;
@ -215,21 +211,21 @@ public class AfkTracker {
}
public boolean isPlayerAfk(UUID playerUuid) {
if (!playerStates.containsKey(playerUuid)) {
if (!playerActivityStates.containsKey(playerUuid)) {
return false;
}
return playerStates.get(playerUuid).isAfk;
return playerActivityStates.get(playerUuid).isAfk;
}
public void setPlayerAfk(ServerPlayerEntity player, boolean afk) {
if (!playerStates.containsKey(player.getUuid())) {
if (!playerActivityStates.containsKey(player.getUuid())) {
return;
}
var server = player.getWorld().getServer();
if (afk) {
playerStates.get(player.getUuid()).lastUpdate = -absentTimeTrigger - 20; // just to be sure
playerActivityStates.get(player.getUuid()).lastUpdate = -absentTimeTrigger - 20; // just to be sure
} else {
resetAfkState(player, server);
}
@ -238,7 +234,7 @@ public class AfkTracker {
}
public int getActiveTime(ServerPlayerEntity player) {
var worldPlayerData = StateSaverAndLoader.getPlayerState(player);
return worldPlayerData.activeTime;
var playerState = RccServer.state.getPlayerState(player.getUuid());
return playerState.activeTime;
}
}

View file

@ -14,7 +14,7 @@ public class BackTracker {
public static void register() {
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
BackTracker.lastPlayerPositions.remove(handler.getPlayer().getUuid());
lastPlayerPositions.remove(handler.getPlayer().getUuid());
});
PlayerTeleport.EVENT.register((player, origin, destination) -> {

View file

@ -1,13 +1,12 @@
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.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.minecraft.text.Text;
public class TabList {
public static void register() {
@ -42,8 +41,8 @@ public class TabList {
footerComponent = footerComponent.append(minimessage.deserialize(line));
}
var parsedHeader = Placeholders.parseText(toText(headerComponent), playerContext);
var parsedFooter = Placeholders.parseText(toText(footerComponent), playerContext);
var parsedHeader = Placeholders.parseText(Components.toText(headerComponent), playerContext);
var parsedFooter = Placeholders.parseText(Components.toText(footerComponent), playerContext);
var audience = RccServer.getInstance().adventure().player(player.getUuid());
audience.sendPlayerListHeaderAndFooter(parsedHeader, parsedFooter);
@ -52,8 +51,4 @@ public class TabList {
}
public static Text toText(Component component) {
var json = JSONComponentSerializer.json().serialize(component);
return Text.Serializer.fromJson(json);
}
}

View file

@ -1,16 +1,8 @@
package cc.reconnected.server.core;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.events.PlayerTeleport;
import cc.reconnected.server.struct.ServerPosition;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.server.network.ServerPlayerEntity;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@ -30,36 +22,9 @@ public class TeleportTracker {
});
});
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
teleportRequests.put(handler.getPlayer().getUuid(), new ConcurrentLinkedDeque<>());
});
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> teleportRequests.put(handler.getPlayer().getUuid(), new ConcurrentLinkedDeque<>()));
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
TeleportTracker.teleportRequests.remove(handler.getPlayer().getUuid());
});
}
public static Component makeButton(ComponentLike text, ComponentLike hoverText, String command) {
return Component.empty()
.append(Component.text("["))
.append(text)
.append(Component.text("]"))
.color(NamedTextColor.AQUA)
.hoverEvent(HoverEvent.showText(hoverText))
.clickEvent(ClickEvent.runCommand(command));
}
public static void teleport(ServerPlayerEntity sourcePlayer, ServerPlayerEntity targetPlayer) {
PlayerTeleport.EVENT.invoker().teleport(sourcePlayer, new ServerPosition(sourcePlayer), new ServerPosition(targetPlayer));
sourcePlayer.teleport(
targetPlayer.getServerWorld(),
targetPlayer.getX(),
targetPlayer.getY(),
targetPlayer.getZ(),
targetPlayer.getYaw(),
targetPlayer.getPitch()
);
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> TeleportTracker.teleportRequests.remove(handler.getPlayer().getUuid()));
}
public static class TeleportRequest {

View file

@ -0,0 +1,29 @@
package cc.reconnected.server.data;
import cc.reconnected.server.struct.ServerPosition;
import com.google.gson.annotations.Expose;
import org.jetbrains.annotations.Nullable;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class PlayerState {
public boolean dirty = false;
public boolean saving = false;
@Expose
public UUID uuid;
@Expose
public String username;
@Expose
public @Nullable Date firstJoinedDate;
@Expose
public @Nullable ServerPosition logoffPosition = null;
@Expose
public int activeTime = 0;
@Expose
public ConcurrentHashMap<String, ServerPosition> homes = new ConcurrentHashMap<>();
}

View file

@ -0,0 +1,18 @@
package cc.reconnected.server.data;
import cc.reconnected.server.struct.ServerPosition;
import com.google.gson.annotations.Expose;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.ConcurrentHashMap;
public class ServerState {
public boolean dirty = false;
public boolean saving = false;
@Expose
public @Nullable ServerPosition spawn;
@Expose
public ConcurrentHashMap<String, ServerPosition> warps = new ConcurrentHashMap<>();
}

View file

@ -0,0 +1,161 @@
package cc.reconnected.server.data;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.struct.ServerPosition;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class StateManager {
private static final Charset charset = StandardCharsets.UTF_8;
private ServerState serverState;
private final ConcurrentHashMap<UUID, PlayerState> playerStates = new ConcurrentHashMap<>();
private final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.excludeFieldsWithoutExposeAnnotation()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX") // iso 8601 my beloved
.create();
private Path basePath;
private Path playersPath;
private Path serverDataPath;
public StateManager() {
}
public void register(Path path) {
basePath = path;
playersPath = basePath.resolve("players");
serverDataPath = basePath.resolve("data.json");
if (!basePath.toFile().exists()) {
if (!basePath.toFile().mkdirs()) {
RccServer.LOGGER.error("Could not create directory: {}", basePath.toAbsolutePath());
}
}
if (!playersPath.toFile().exists()) {
if (!playersPath.toFile().mkdirs()) {
RccServer.LOGGER.error("Could not create directory: {}", playersPath.toAbsolutePath());
}
}
if (serverDataPath.toFile().exists()) {
try (var br = new BufferedReader(new FileReader(serverDataPath.toFile(), charset))) {
serverState = gson.fromJson(br, ServerState.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
serverState = new ServerState();
serverState.dirty = true;
}
ServerLifecycleEvents.SERVER_STOPPING.register(serverState -> save());
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> loadPlayerState(handler.player.getUuid()));
ServerPlayConnectionEvents.DISCONNECT.register((handler, client) -> {
var playerState = getPlayerState(handler.getPlayer().getUuid());
playerState.logoffPosition = new ServerPosition(handler.getPlayer());
savePlayerState(handler.getPlayer().getUuid(), playerState);
playerStates.remove(handler.player.getUuid());
});
}
public void saveServerState() {
if (!serverState.dirty)
return;
if (serverState.saving)
return;
serverState.saving = true;
var json = gson.toJson(serverState, ServerState.class);
try (var fw = new FileWriter(serverDataPath.toFile(), charset)) {
fw.write(json);
serverState.dirty = false;
} catch (IOException e) {
RccServer.LOGGER.error("Could not save server state", e);
}
serverState.saving = false;
}
public void savePlayerState(UUID uuid, PlayerState playerState) {
if (!playerState.dirty)
return;
if (playerState.saving)
return;
playerState.saving = true;
var path = playersPath.resolve(uuid.toString() + ".json");
var json = gson.toJson(playerState, PlayerState.class);
try (var fw = new FileWriter(path.toFile(), charset)) {
fw.write(json);
playerState.dirty = false;
} catch (IOException e) {
RccServer.LOGGER.error("Could not save player state", e);
}
playerState.saving = false;
}
public void save() {
saveServerState();
playerStates.forEach(this::savePlayerState);
}
private PlayerState loadPlayerState(UUID uuid) {
var path = playersPath.resolve(uuid.toString() + ".json");
PlayerState playerState;
if (path.toFile().exists()) {
try (var br = new BufferedReader(new FileReader(path.toFile(), charset))) {
playerState = gson.fromJson(br, PlayerState.class);
} catch (Exception e) {
RccServer.LOGGER.error("Could not load player state: " + path.toAbsolutePath(), e);
return null;
}
} else {
playerState = new PlayerState();
playerState.dirty = true;
}
playerStates.put(uuid, playerState);
playerState.uuid = uuid;
return playerState;
}
public PlayerState getPlayerState(UUID uuid) {
PlayerState playerState;
if (playerStates.containsKey(uuid)) {
playerState = playerStates.get(uuid);
} else {
playerState = loadPlayerState(uuid);
}
if (playerState == null)
return null;
playerState.dirty = true;
return playerState;
}
public ServerState getServerState() {
serverState.dirty = true;
return serverState;
}
}

View file

@ -1,59 +0,0 @@
package cc.reconnected.server.data;
import cc.reconnected.server.RccServer;
import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.PersistentState;
import net.minecraft.world.World;
import java.util.HashMap;
import java.util.UUID;
public class StateSaverAndLoader extends PersistentState {
public final HashMap<UUID, WorldPlayerData> players = new HashMap<>();
@Override
public NbtCompound writeNbt(NbtCompound nbt) {
var playersNbt = new NbtCompound();
players.forEach((uuid, data) -> {
var playerNbt = new NbtCompound();
playerNbt.putInt("activeTime", data.activeTime);
playersNbt.put(uuid.toString(), playerNbt);
});
nbt.put("players", playersNbt);
return nbt;
}
public static StateSaverAndLoader createFromNbt(NbtCompound nbt) {
var state = new StateSaverAndLoader();
var playersNbt = nbt.getCompound("players");
playersNbt.getKeys().forEach(key -> {
var playerData = new WorldPlayerData();
playerData.activeTime = playersNbt.getCompound(key).getInt("activeTime");
UUID uuid = UUID.fromString(key);
state.players.put(uuid, playerData);
});
return state;
}
public static StateSaverAndLoader getServerState(MinecraftServer server) {
var persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();
var state = persistentStateManager.getOrCreate(
StateSaverAndLoader::createFromNbt,
StateSaverAndLoader::new,
RccServer.MOD_ID
);
state.markDirty();
return state;
}
public static WorldPlayerData getPlayerState(LivingEntity player) {
var serverState = getServerState(player.getWorld().getServer());
return serverState.players.computeIfAbsent(player.getUuid(), uuid -> new WorldPlayerData());
}
}

View file

@ -1,5 +0,0 @@
package cc.reconnected.server.data;
public class WorldPlayerData {
public int activeTime = 0;
}

View file

@ -0,0 +1,16 @@
package cc.reconnected.server.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
public interface PlayerUsernameChange {
Event<PlayerUsernameChange> PLAYER_USERNAME_CHANGE = EventFactory.createArrayBacked(PlayerUsernameChange.class,
(listeners) -> (player, previousUsername) -> {
for (PlayerUsernameChange listener : listeners) {
listener.changeUsername(player, previousUsername);
}
});
void changeUsername(ServerPlayerEntity player, String previousUsername);
}

View file

@ -1,6 +1,5 @@
package cc.reconnected.server.events;
import cc.reconnected.server.database.PlayerData;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.MinecraftServer;
@ -8,11 +7,11 @@ import net.minecraft.server.network.ServerPlayerEntity;
public interface PlayerWelcome {
Event<PlayerWelcome> PLAYER_WELCOME = EventFactory.createArrayBacked(PlayerWelcome.class,
(listeners) -> (player, playerData, server) -> {
(listeners) -> (player, server) -> {
for (PlayerWelcome listener : listeners) {
listener.playerWelcome(player, playerData, server);
listener.playerWelcome(player, server);
}
});
void playerWelcome(ServerPlayerEntity player, PlayerData playerData, MinecraftServer server);
void playerWelcome(ServerPlayerEntity player, MinecraftServer server);
}

View file

@ -0,0 +1,39 @@
package cc.reconnected.server.mixin;
import cc.reconnected.server.RccServer;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Final;
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.CallbackInfo;
import java.util.EnumSet;
import java.util.List;
@Mixin(ServerPlayNetworkHandler.class)
public abstract class ServerPlayNetworkManagerMixin {
@Shadow
@Final
private MinecraftServer server;
@Shadow
public ServerPlayerEntity player;
@Shadow
public abstract void sendPacket(Packet<?> packet);
@Inject(method = "tick", at = @At("TAIL"))
private void rccServer$updatePlayerList(CallbackInfo ci) {
if(RccServer.CONFIG.enableTabList()) {
var packet = new PlayerListS2CPacket(EnumSet.of(PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME, PlayerListS2CPacket.Action.UPDATE_LISTED), List.of(this.player));
this.sendPacket(packet);
}
}
}

View file

@ -0,0 +1,30 @@
package cc.reconnected.server.mixin;
import cc.reconnected.server.RccServer;
import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.Placeholders;
import eu.pb4.placeholders.api.parsers.NodeParser;
import eu.pb4.placeholders.api.parsers.TextParserV1;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
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.callback.CallbackInfoReturnable;
@Mixin(ServerPlayerEntity.class)
public class ServerPlayerEntityMixin {
@Unique
private static final NodeParser parser = NodeParser.merge(TextParserV1.DEFAULT, Placeholders.DEFAULT_PLACEHOLDER_PARSER);
@Inject(method = "getPlayerListName", at = @At("HEAD"), cancellable = true)
private void rccServer$customizePlayerListName(CallbackInfoReturnable<Text> callback) {
if (RccServer.CONFIG.enableTabList()) {
var player = (ServerPlayerEntity) (Object) this;
var playerContext = PlaceholderContext.of(player);
var text = Placeholders.parseText(parser.parseNode(RccServer.CONFIG.playerTabName()), playerContext);
callback.setReturnValue(text);
}
}
}

View file

@ -1,16 +1,26 @@
package cc.reconnected.server.struct;
import cc.reconnected.server.core.BackTracker;
import com.google.gson.annotations.Expose;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
public class ServerPosition {
@Expose
public double x;
@Expose
public double y;
@Expose
public double z;
@Expose
public float yaw;
@Expose
public float pitch;
public ServerWorld world;
@Expose
public String world;
public ServerPosition(double x, double y, double z, float yaw, float pitch, ServerWorld world) {
this.x = x;
@ -18,7 +28,7 @@ public class ServerPosition {
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.world = world;
this.world = world.getRegistryKey().getValue().toString();
}
public ServerPosition(ServerPlayerEntity player) {
@ -27,15 +37,19 @@ public class ServerPosition {
this.z = player.getZ();
this.yaw = player.getYaw();
this.pitch = player.getPitch();
this.world = player.getServerWorld();
this.world = player.getServerWorld().getRegistryKey().getValue().toString();
}
public void teleport(ServerPlayerEntity player) {
var currentPosition = new ServerPosition(player);
BackTracker.lastPlayerPositions.put(player.getUuid(), currentPosition);
public void teleport(ServerPlayerEntity player, boolean setBackPosition) {
if (setBackPosition) {
var currentPosition = new ServerPosition(player);
BackTracker.lastPlayerPositions.put(player.getUuid(), currentPosition);
}
var serverWorld = player.getServer().getWorld(RegistryKey.of(RegistryKeys.WORLD, new Identifier(this.world)));
player.teleport(
this.world,
serverWorld,
this.x,
this.y,
this.z,
@ -43,4 +57,8 @@ public class ServerPosition {
this.pitch
);
}
public void teleport(ServerPlayerEntity player) {
teleport(player, true);
}
}

View file

@ -0,0 +1,26 @@
package cc.reconnected.server.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.minecraft.text.Text;
public class Components {
public static Component makeButton(ComponentLike text, ComponentLike hoverText, String command) {
return Component.empty()
.append(Component.text("["))
.append(text)
.append(Component.text("]"))
.color(NamedTextColor.AQUA)
.hoverEvent(HoverEvent.showText(hoverText))
.clickEvent(ClickEvent.runCommand(command));
}
public static Text toText(Component component) {
var json = JSONComponentSerializer.json().serialize(component);
return Text.Serializer.fromJson(json);
}
}

View file

@ -3,10 +3,13 @@
"minVersion": "0.8",
"package": "cc.reconnected.server.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [],
"mixins": [
"ServerPlayNetworkManagerMixin"
],
"client": [],
"server": [
"MessageCommandMixin"
"MessageCommandMixin",
"ServerPlayerEntityMixin"
],
"injectors": {
"defaultRequire": 1