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 loader_version=0.16.7
# Mod Properties # Mod Properties
mod_version=1.13.0 mod_version=1.14.0
maven_group=cc.reconnected maven_group=cc.reconnected
archives_base_name=rcc-server archives_base_name=rcc-server

View file

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

View file

@ -28,4 +28,6 @@ public class RccServerConfigModel {
public ArrayList<String> tabFooter = new ArrayList<>(List.of( public ArrayList<String> tabFooter = new ArrayList<>(List.of(
"<gradient:#DEDE6C:#CC4C4C:{phase}><st> </st></gradient>" "<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 cc.reconnected.server.core.AfkTracker;
import com.mojang.brigadier.CommandDispatcher; 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 cc.reconnected.server.core.BackTracker;
import com.mojang.brigadier.CommandDispatcher; 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.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import me.lucko.fabric.api.permissions.v0.Permissions; import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandRegistryAccess; 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.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType; 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 cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher; 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.core.TeleportTracker;
import cc.reconnected.server.struct.ServerPosition;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import net.minecraft.command.CommandRegistryAccess; 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); 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.core.TeleportTracker;
import cc.reconnected.server.util.Components;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
@ -60,9 +61,9 @@ public class TeleportAskCommand {
.appendSpace() .appendSpace()
.append(Component.text("requested to teleport to you.", NamedTextColor.GOLD)) .append(Component.text("requested to teleport to you.", NamedTextColor.GOLD))
.appendNewline().appendSpace() .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() .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); 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.core.TeleportTracker;
import cc.reconnected.server.util.Components;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
@ -61,9 +62,9 @@ public class TeleportAskHereCommand {
.appendSpace() .appendSpace()
.append(Component.text("requested you to teleport to them.", NamedTextColor.GOLD)) .append(Component.text("requested you to teleport to them.", NamedTextColor.GOLD))
.appendNewline().appendSpace() .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() .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); 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 cc.reconnected.server.core.TeleportTracker;
import com.mojang.brigadier.CommandDispatcher; 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.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType; 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.RccServer;
import cc.reconnected.server.parser.MarkdownParser; 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.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import me.lucko.fabric.api.permissions.v0.Permissions; 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.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.minecraft.command.CommandRegistryAccess; import net.minecraft.command.CommandRegistryAccess;
@ -67,6 +70,17 @@ public class TellCommand {
} }
var parsedMessage = MarkdownParser.defaultParser.parseNode(message); 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(), var text = MiniMessage.miniMessage().deserialize(RccServer.CONFIG.tellMessage(),
Placeholder.component("source", source.getDisplayName()), Placeholder.component("source", source.getDisplayName()),
Placeholder.component("target", targetDisplayName), Placeholder.component("target", targetDisplayName),
@ -76,16 +90,16 @@ public class TellCommand {
lastSender.put(source.getName(), targetName); lastSender.put(source.getName(), targetName);
if (!source.getName().equals(targetName)) { if (!source.getName().equals(targetName)) {
source.sendMessage(text); source.sendMessage(sourceText);
} }
if (targetPlayer != null) { if (targetPlayer != null) {
targetPlayer.sendMessage(text); targetPlayer.sendMessage(targetText);
if (source.isExecutedByPlayer()) { if (source.isExecutedByPlayer()) {
source.getServer().sendMessage(text); source.getServer().sendMessage(text);
} }
} else { } else {
// avoid duped message // avoid duped message
source.getServer().sendMessage(text); source.getServer().sendMessage(targetText);
} }
var lp = RccServer.getInstance().luckPerms(); var lp = RccServer.getInstance().luckPerms();
@ -103,7 +117,6 @@ public class TellCommand {
if (playerPerms.checkPermission("rcc.tell.spy").asBoolean()) { if (playerPerms.checkPermission("rcc.tell.spy").asBoolean()) {
player.sendMessage(spyText); 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; package cc.reconnected.server.core;
import cc.reconnected.server.RccServer; import cc.reconnected.server.RccServer;
import cc.reconnected.server.data.StateSaverAndLoader;
import cc.reconnected.server.events.PlayerActivityEvents; import cc.reconnected.server.events.PlayerActivityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.*; 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.ActionResult;
import net.minecraft.util.TypedActionResult; import net.minecraft.util.TypedActionResult;
import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class AfkTracker { public class AfkTracker {
private static final int cycleDelay = 1; private static final int cycleDelay = 1;
private static final int absentTimeTrigger = RccServer.CONFIG.afkTimeTrigger() * 20; // seconds * 20 ticks 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(); private static final AfkTracker instance = new AfkTracker();
public static AfkTracker getInstance() { public static AfkTracker getInstance() {
return instance; return instance;
} }
@ -63,20 +63,16 @@ public class AfkTracker {
} }
private AfkTracker() { private AfkTracker() {
ServerTickEvents.END_SERVER_TICK.register(server -> { ServerTickEvents.END_SERVER_TICK.register(this::updatePlayers);
if (server.getTicks() % cycleDelay == 0) {
updatePlayers(server);
}
});
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
final var player = handler.getPlayer(); 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) -> { ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
updatePlayerActiveTime(handler.getPlayer(), server.getTicks()); updatePlayerActiveTime(handler.getPlayer(), server.getTicks());
playerStates.remove(handler.getPlayer().getUuid()); playerActivityStates.remove(handler.getPlayer().getUuid());
// sync to LP // sync to LP
//var activeTime = String.valueOf(getActiveTime(handler.getPlayer())); //var activeTime = String.valueOf(getActiveTime(handler.getPlayer()));
@ -125,7 +121,7 @@ public class AfkTracker {
private void updatePlayer(ServerPlayerEntity player, MinecraftServer server) { private void updatePlayer(ServerPlayerEntity player, MinecraftServer server) {
var currentTick = server.getTicks(); 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 oldPosition = playerState.position;
var newPosition = new PlayerPosition(player); var newPosition = new PlayerPosition(player);
@ -147,11 +143,11 @@ public class AfkTracker {
} }
private void updatePlayerActiveTime(ServerPlayerEntity player, int currentTick) { private void updatePlayerActiveTime(ServerPlayerEntity player, int currentTick) {
var playerState = playerStates.get(player.getUuid()); var playerActivityState = playerActivityStates.get(player.getUuid());
if (!playerState.isAfk) { if (!playerActivityState.isAfk) {
var worldPlayerData = StateSaverAndLoader.getPlayerState(player); var playerState = RccServer.state.getPlayerState(player.getUuid());
var interval = currentTick - playerState.activeStart; var interval = currentTick - playerActivityState.activeStart;
worldPlayerData.activeTime += interval / 20; playerState.activeTime += interval / 20;
} }
} }
@ -163,10 +159,10 @@ public class AfkTracker {
} }
private void resetAfkState(ServerPlayerEntity player, MinecraftServer server) { private void resetAfkState(ServerPlayerEntity player, MinecraftServer server) {
if (!playerStates.containsKey(player.getUuid())) if (!playerActivityStates.containsKey(player.getUuid()))
return; return;
var playerState = playerStates.get(player.getUuid()); var playerState = playerActivityStates.get(player.getUuid());
playerState.lastUpdate = server.getTicks(); playerState.lastUpdate = server.getTicks();
if (playerState.isAfk) { if (playerState.isAfk) {
playerState.isAfk = false; playerState.isAfk = false;
@ -200,13 +196,13 @@ public class AfkTracker {
} }
} }
public static class PlayerState { public static class PlayerActivityState {
public PlayerPosition position; public PlayerPosition position;
public int lastUpdate; public int lastUpdate;
public boolean isAfk; public boolean isAfk;
public int activeStart; public int activeStart;
public PlayerState(ServerPlayerEntity player, int lastUpdate) { public PlayerActivityState(ServerPlayerEntity player, int lastUpdate) {
this.position = new PlayerPosition(player); this.position = new PlayerPosition(player);
this.lastUpdate = lastUpdate; this.lastUpdate = lastUpdate;
this.isAfk = false; this.isAfk = false;
@ -215,21 +211,21 @@ public class AfkTracker {
} }
public boolean isPlayerAfk(UUID playerUuid) { public boolean isPlayerAfk(UUID playerUuid) {
if (!playerStates.containsKey(playerUuid)) { if (!playerActivityStates.containsKey(playerUuid)) {
return false; return false;
} }
return playerStates.get(playerUuid).isAfk; return playerActivityStates.get(playerUuid).isAfk;
} }
public void setPlayerAfk(ServerPlayerEntity player, boolean afk) { public void setPlayerAfk(ServerPlayerEntity player, boolean afk) {
if (!playerStates.containsKey(player.getUuid())) { if (!playerActivityStates.containsKey(player.getUuid())) {
return; return;
} }
var server = player.getWorld().getServer(); var server = player.getWorld().getServer();
if (afk) { 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 { } else {
resetAfkState(player, server); resetAfkState(player, server);
} }
@ -238,7 +234,7 @@ public class AfkTracker {
} }
public int getActiveTime(ServerPlayerEntity player) { public int getActiveTime(ServerPlayerEntity player) {
var worldPlayerData = StateSaverAndLoader.getPlayerState(player); var playerState = RccServer.state.getPlayerState(player.getUuid());
return worldPlayerData.activeTime; return playerState.activeTime;
} }
} }

View file

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

View file

@ -1,13 +1,12 @@
package cc.reconnected.server.core; package cc.reconnected.server.core;
import cc.reconnected.server.RccServer; import cc.reconnected.server.RccServer;
import cc.reconnected.server.util.Components;
import eu.pb4.placeholders.api.PlaceholderContext; import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.Placeholders; import eu.pb4.placeholders.api.Placeholders;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.minecraft.text.Text;
public class TabList { public class TabList {
public static void register() { public static void register() {
@ -42,8 +41,8 @@ public class TabList {
footerComponent = footerComponent.append(minimessage.deserialize(line)); footerComponent = footerComponent.append(minimessage.deserialize(line));
} }
var parsedHeader = Placeholders.parseText(toText(headerComponent), playerContext); var parsedHeader = Placeholders.parseText(Components.toText(headerComponent), playerContext);
var parsedFooter = Placeholders.parseText(toText(footerComponent), playerContext); var parsedFooter = Placeholders.parseText(Components.toText(footerComponent), playerContext);
var audience = RccServer.getInstance().adventure().player(player.getUuid()); var audience = RccServer.getInstance().adventure().player(player.getUuid());
audience.sendPlayerListHeaderAndFooter(parsedHeader, parsedFooter); 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; package cc.reconnected.server.core;
import cc.reconnected.server.RccServer; 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.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; 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.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -30,36 +22,9 @@ public class TeleportTracker {
}); });
}); });
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> teleportRequests.put(handler.getPlayer().getUuid(), new ConcurrentLinkedDeque<>()));
teleportRequests.put(handler.getPlayer().getUuid(), new ConcurrentLinkedDeque<>());
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> TeleportTracker.teleportRequests.remove(handler.getPlayer().getUuid()));
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()
);
} }
public static class TeleportRequest { 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; package cc.reconnected.server.events;
import cc.reconnected.server.database.PlayerData;
import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory; import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
@ -8,11 +7,11 @@ import net.minecraft.server.network.ServerPlayerEntity;
public interface PlayerWelcome { public interface PlayerWelcome {
Event<PlayerWelcome> PLAYER_WELCOME = EventFactory.createArrayBacked(PlayerWelcome.class, Event<PlayerWelcome> PLAYER_WELCOME = EventFactory.createArrayBacked(PlayerWelcome.class,
(listeners) -> (player, playerData, server) -> { (listeners) -> (player, server) -> {
for (PlayerWelcome listener : listeners) { 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; package cc.reconnected.server.struct;
import cc.reconnected.server.core.BackTracker; 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.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
public class ServerPosition { public class ServerPosition {
@Expose
public double x; public double x;
@Expose
public double y; public double y;
@Expose
public double z; public double z;
@Expose
public float yaw; public float yaw;
@Expose
public float pitch; public float pitch;
public ServerWorld world; @Expose
public String world;
public ServerPosition(double x, double y, double z, float yaw, float pitch, ServerWorld world) { public ServerPosition(double x, double y, double z, float yaw, float pitch, ServerWorld world) {
this.x = x; this.x = x;
@ -18,7 +28,7 @@ public class ServerPosition {
this.z = z; this.z = z;
this.yaw = yaw; this.yaw = yaw;
this.pitch = pitch; this.pitch = pitch;
this.world = world; this.world = world.getRegistryKey().getValue().toString();
} }
public ServerPosition(ServerPlayerEntity player) { public ServerPosition(ServerPlayerEntity player) {
@ -27,15 +37,19 @@ public class ServerPosition {
this.z = player.getZ(); this.z = player.getZ();
this.yaw = player.getYaw(); this.yaw = player.getYaw();
this.pitch = player.getPitch(); this.pitch = player.getPitch();
this.world = player.getServerWorld(); this.world = player.getServerWorld().getRegistryKey().getValue().toString();
} }
public void teleport(ServerPlayerEntity player) { public void teleport(ServerPlayerEntity player, boolean setBackPosition) {
if (setBackPosition) {
var currentPosition = new ServerPosition(player); var currentPosition = new ServerPosition(player);
BackTracker.lastPlayerPositions.put(player.getUuid(), currentPosition); BackTracker.lastPlayerPositions.put(player.getUuid(), currentPosition);
}
var serverWorld = player.getServer().getWorld(RegistryKey.of(RegistryKeys.WORLD, new Identifier(this.world)));
player.teleport( player.teleport(
this.world, serverWorld,
this.x, this.x,
this.y, this.y,
this.z, this.z,
@ -43,4 +57,8 @@ public class ServerPosition {
this.pitch 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", "minVersion": "0.8",
"package": "cc.reconnected.server.mixin", "package": "cc.reconnected.server.mixin",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"mixins": [], "mixins": [
"ServerPlayNetworkManagerMixin"
],
"client": [], "client": [],
"server": [ "server": [
"MessageCommandMixin" "MessageCommandMixin",
"ServerPlayerEntityMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1