Implement teleport commands

This commit is contained in:
Alessandro Proto 2024-10-23 22:05:36 +02:00
parent 2d8322f0bd
commit abb5a00182
8 changed files with 503 additions and 1 deletions

View file

@ -6,6 +6,7 @@ import cc.reconnected.server.events.PlayerActivityEvents;
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 cc.reconnected.server.http.ServiceServer; import cc.reconnected.server.http.ServiceServer;
import cc.reconnected.server.struct.ServerPosition;
import cc.reconnected.server.trackers.AfkTracker; import cc.reconnected.server.trackers.AfkTracker;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
@ -28,6 +29,9 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
public class RccServer implements ModInitializer { public class RccServer implements ModInitializer {
@ -80,15 +84,22 @@ public class RccServer implements ModInitializer {
INSTANCE = this; INSTANCE = this;
} }
public static final ConcurrentHashMap<UUID, ConcurrentLinkedDeque<TeleportAskCommand.TeleportRequest>> teleportRequests = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<UUID, ServerPosition> lastPlayerPositions = new ConcurrentHashMap<>();
@Override @Override
public void onInitialize() { public void onInitialize() {
LOGGER.info("Starting rcc-server"); LOGGER.info("Starting rcc-server");
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { CommandRegistrationCallback.EVENT.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);
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); FlyCommand.register(dispatcher, registryAccess, environment);
GodCommand.register(dispatcher, registryAccess, environment); GodCommand.register(dispatcher, registryAccess, environment);
}); });
@ -112,6 +123,14 @@ public class RccServer implements ModInitializer {
if (currentMspt != 0) { if (currentMspt != 0) {
currentTps = Math.min(20, 1000 / currentMspt); currentTps = Math.min(20, 1000 / currentMspt);
} }
teleportRequests.forEach((recipient, requestList) -> {
requestList.forEach(request -> {
if (request.remainingTicks-- == 0) {
requestList.remove(request);
}
});
});
}); });
ServerLifecycleEvents.SERVER_STOPPING.register(server -> { ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
@ -136,10 +155,15 @@ public class RccServer implements ModInitializer {
PlayerWelcome.PLAYER_WELCOME.invoker().playerWelcome(player, playerData, server); PlayerWelcome.PLAYER_WELCOME.invoker().playerWelcome(player, playerData, server);
LOGGER.info("Player {} joined for the first time!", player.getName().getString()); LOGGER.info("Player {} joined for the first time!", player.getName().getString());
} }
teleportRequests.put(player.getUuid(), new ConcurrentLinkedDeque<>());
}); });
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
currentPlayerCount = server.getCurrentPlayerCount() - 1; currentPlayerCount = server.getCurrentPlayerCount() - 1;
teleportRequests.remove(handler.getPlayer().getUuid());
lastPlayerPositions.remove(handler.getPlayer().getUuid());
}); });
PlayerActivityEvents.AFK.register((player, server) -> { PlayerActivityEvents.AFK.register((player, server) -> {

View file

@ -14,4 +14,6 @@ public class RccServerConfigModel {
public String tellMessage = "<gold>[</gold><source> <gray>→</gray> <target><gold>]</gold> <message>"; public String tellMessage = "<gold>[</gold><source> <gray>→</gray> <target><gold>]</gold> <message>";
public String tellMessageSpy = "\uD83D\uDC41 <gray>[<source> → <target>]</gray> <message>"; public String tellMessageSpy = "\uD83D\uDC41 <gray>[<source> → <target>]</gray> <message>";
public int teleportRequestTimeout = 120;
} }

View file

@ -0,0 +1,41 @@
package cc.reconnected.server.commands;
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 BackCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var rootCommand = literal("back")
.requires(Permissions.require("rcc.command.back", 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 lastPosition = RccServer.lastPlayerPositions.get(player.getUuid());
if(lastPosition == null) {
context.getSource().sendFeedback(() -> Text.literal("There is no position to return back to.").formatted(Formatting.RED), false);
return 1;
}
context.getSource().sendFeedback(() -> Text.literal("Teleporting to previous position...").formatted(Formatting.GOLD), false);
lastPosition.teleport(player);
return 1;
});
dispatcher.register(rootCommand);
}
}

View file

@ -0,0 +1,100 @@
package cc.reconnected.server.commands;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.UuidArgumentType;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import java.util.HashMap;
import static net.minecraft.server.command.CommandManager.*;
public class TeleportAcceptCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var node = dispatcher.register(literal("tpaccept")
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var playerUuid = context.getSource().getPlayer().getUuid();
var playerRequests = RccServer.teleportRequests.get(playerUuid);
var request = playerRequests.pollLast();
if (request == null) {
context.getSource().sendFeedback(() -> Text.literal("You have no pending teleport requests.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return 1;
}
execute(context, request);
return 1;
})
.then(argument("uuid", UuidArgumentType.uuid())
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var uuid = UuidArgumentType.getUuid(context, "uuid");
var playerUuid = context.getSource().getPlayer().getUuid();
var playerRequests = RccServer.teleportRequests.get(playerUuid);
var request = playerRequests.stream().filter(req -> req.requestId.equals(uuid)).findFirst().orElse(null);
if (request == null) {
context.getSource().sendFeedback(() -> Text.literal("This request expired or is no longer available.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return 1;
}
execute(context, request);
return 1;
})));
dispatcher.register(literal("tpyes").redirect(node));
}
private static void execute(CommandContext<ServerCommandSource> context, TeleportAskCommand.TeleportRequest request) {
var source = context.getSource();
request.expire();
var player = source.getPlayer();
var playerManager = context.getSource().getServer().getPlayerManager();
var sourcePlayer = playerManager.getPlayer(request.player);
var targetPlayer = playerManager.getPlayer(request.target);
if (sourcePlayer == null || targetPlayer == null) {
context.getSource().sendFeedback(() -> Text.literal("The other player is no longer available.").formatted(Formatting.RED), false);
return;
}
if(player.getUuid().equals(request.target)) {
// accepted a tpa from other to self
context.getSource().sendFeedback(() -> Text.literal("Teleport request accepted.").formatted(Formatting.GREEN), false);
sourcePlayer.sendMessage(Text.literal("Teleporting...").formatted(Formatting.GOLD), false);
} else {
// accepted a tpa from self to other
context.getSource().sendFeedback(() -> Text.literal("Teleporting...").formatted(Formatting.GOLD), false);
targetPlayer.sendMessage(Text.empty().append(player.getDisplayName()).append(Text.literal(" accepted your teleport request.").formatted(Formatting.GREEN)), false);
}
TeleportAskCommand.teleport(sourcePlayer, targetPlayer);
}
}

View file

@ -0,0 +1,128 @@
package cc.reconnected.server.commands;
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 net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.event.ClickCallback;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
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.server.network.ServerPlayerEntity;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.time.Duration;
import java.util.UUID;
import static net.minecraft.server.command.CommandManager.*;
public class TeleportAskCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var node = dispatcher.register(literal("tpa")
.then(argument("player", StringArgumentType.word())
.suggests((context, builder) -> {
var playerManager = context.getSource().getServer().getPlayerManager();
return CommandSource.suggestMatching(
playerManager.getPlayerNames(),
builder);
})
.executes(context -> {
execute(context);
return 1;
})));
dispatcher.register(literal("tpask").redirect(node));
}
private static void execute(CommandContext<ServerCommandSource> context) {
var source = context.getSource();
if (!source.isExecutedByPlayer()) {
source.sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return;
}
var server = source.getServer();
var player = source.getPlayer();
var targetName = StringArgumentType.getString(context, "player");
var playerManager = server.getPlayerManager();
var target = playerManager.getPlayer(targetName);
if (target == null) {
source.sendFeedback(() -> Text.literal("Player \"" + targetName + "\" not found!").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return;
}
var request = new TeleportRequest(player.getUuid(), target.getUuid());
var targetRequests = RccServer.teleportRequests.get(target.getUuid());
targetRequests.addLast(request);
var requestMessage = Component.empty()
.append(player.getDisplayName())
.appendSpace()
.append(Component.text("requested to teleport to you.", NamedTextColor.GOLD))
.appendNewline().appendSpace()
.append(makeButton(Component.text("Accept", NamedTextColor.GREEN), Component.text("Click to accept request"), "/tpaccept " + request.requestId))
.appendSpace()
.append(makeButton(Component.text("Refuse", NamedTextColor.RED), Component.text("Click to refuse request"), "/tpdeny " + request.requestId));
target.sendMessage(requestMessage);
source.sendFeedback(() -> Text.literal("Teleport request sent.").setStyle(Style.EMPTY.withColor(Formatting.GREEN)), false);
}
public static Component makeButton(ComponentLike text, ComponentLike hoverText, String command) {
var options = ClickCallback.Options.builder()
.uses(1)
.lifetime(Duration.ofSeconds(RccServer.CONFIG.teleportRequestTimeout()))
.build();
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) {
RccServer.lastPlayerPositions.put(sourcePlayer.getUuid(), new ServerPosition(sourcePlayer));
sourcePlayer.teleport(
targetPlayer.getServerWorld(),
targetPlayer.getX(),
targetPlayer.getY(),
targetPlayer.getZ(),
targetPlayer.getYaw(),
targetPlayer.getPitch()
);
}
public static class TeleportRequest {
public UUID requestId = UUID.randomUUID();
public UUID player;
public UUID target;
public int remainingTicks;
public TeleportRequest(UUID player, UUID target) {
this.player = player;
this.target = target;
// Seconds in config per 20 ticks
this.remainingTicks = RccServer.CONFIG.teleportRequestTimeout() * 20;
}
public void expire() {
remainingTicks = 0;
}
}
}

View file

@ -0,0 +1,72 @@
package cc.reconnected.server.commands;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
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.Style;
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 TeleportAskHereCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var node = dispatcher.register(literal("tpahere")
.then(argument("player", StringArgumentType.word())
.suggests((context, builder) -> {
var playerManager = context.getSource().getServer().getPlayerManager();
return CommandSource.suggestMatching(
playerManager.getPlayerNames(),
builder);
})
.executes(context -> {
execute(context);
return 1;
})));
dispatcher.register(literal("tpaskhere").redirect(node));
}
private static void execute(CommandContext<ServerCommandSource> context) {
var source = context.getSource();
if (!source.isExecutedByPlayer()) {
source.sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return;
}
var server = source.getServer();
var player = source.getPlayer();
var targetName = StringArgumentType.getString(context, "player");
var playerManager = server.getPlayerManager();
var target = playerManager.getPlayer(targetName);
if (target == null) {
source.sendFeedback(() -> Text.literal("Player \"" + targetName + "\" not found!").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return;
}
var request = new TeleportAskCommand.TeleportRequest(target.getUuid(), player.getUuid());
var targetRequests = RccServer.teleportRequests.get(target.getUuid());
targetRequests.addLast(request);
var requestMessage = Component.empty()
.append(player.getDisplayName())
.appendSpace()
.append(Component.text("requested you to teleport to them.", NamedTextColor.GOLD))
.appendNewline().appendSpace()
.append(TeleportAskCommand.makeButton(Component.text("Accept", NamedTextColor.GREEN), Component.text("Click to accept request"), "/tpaccept " + request.requestId))
.appendSpace()
.append(TeleportAskCommand.makeButton(Component.text("Refuse", NamedTextColor.RED), Component.text("Click to refuse request"), "/tpdeny " + request.requestId));
target.sendMessage(requestMessage);
source.sendFeedback(() -> Text.literal("Teleport request sent.").setStyle(Style.EMPTY.withColor(Formatting.GREEN)), false);
}
}

View file

@ -0,0 +1,89 @@
package cc.reconnected.server.commands;
import cc.reconnected.server.RccServer;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.command.argument.UuidArgumentType;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import java.util.ArrayList;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class TeleportDenyCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) {
var node = dispatcher.register(literal("tpdeny")
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var playerUuid = context.getSource().getPlayer().getUuid();
var playerRequests = RccServer.teleportRequests.get(playerUuid);
var request = playerRequests.pollLast();
if (request == null) {
context.getSource().sendFeedback(() -> Text.literal("You have no pending teleport requests.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return 1;
}
execute(context, request);
return 1;
})
.then(argument("uuid", UuidArgumentType.uuid())
.executes(context -> {
if (!context.getSource().isExecutedByPlayer()) {
context.getSource().sendFeedback(() -> Text.of("This command can only be executed by players!"), false);
return 1;
}
var uuid = UuidArgumentType.getUuid(context, "uuid");
var playerUuid = context.getSource().getPlayer().getUuid();
var playerRequests = RccServer.teleportRequests.get(playerUuid);
var request = playerRequests.stream().filter(req -> req.requestId.equals(uuid)).findFirst().orElse(null);
if (request == null) {
context.getSource().sendFeedback(() -> Text.literal("This request expired or is no longer available.").setStyle(Style.EMPTY.withColor(Formatting.RED)), false);
return 1;
}
execute(context, request);
return 1;
})));
dispatcher.register(literal("tpno").redirect(node));
dispatcher.register(literal("tprefuse").redirect(node));
}
private static void execute(CommandContext<ServerCommandSource> context, TeleportAskCommand.TeleportRequest request) {
var source = context.getSource();
request.expire();
var player = source.getPlayer();
var playerManager = context.getSource().getServer().getPlayerManager();
ServerPlayerEntity otherPlayer = null;
if (player.getUuid().equals(request.target)) {
otherPlayer = playerManager.getPlayer(request.player);
} else if (player.getUuid().equals(request.player)) {
otherPlayer = playerManager.getPlayer(request.target);
}
if(otherPlayer != null) {
otherPlayer.sendMessage(Text.empty().append(player.getDisplayName()).append(Text.literal(" denied your teleport request.").formatted(Formatting.RED)));
}
context.getSource().sendFeedback(() -> Text.literal("You denied the teleport request.").formatted(Formatting.GOLD), false);
}
}

View file

@ -0,0 +1,46 @@
package cc.reconnected.server.struct;
import cc.reconnected.server.RccServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
public class ServerPosition {
public double x;
public double y;
public double z;
public float yaw;
public float pitch;
public ServerWorld world;
public ServerPosition(double x, double y, double z, float yaw, float pitch, ServerWorld world) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.world = world;
}
public ServerPosition(ServerPlayerEntity player) {
this.x = player.getX();
this.y = player.getY();
this.z = player.getZ();
this.yaw = player.getYaw();
this.pitch = player.getPitch();
this.world = player.getServerWorld();
}
public void teleport(ServerPlayerEntity player) {
var currentPosition = new ServerPosition(player);
RccServer.lastPlayerPositions.put(player.getUuid(), currentPosition);
player.teleport(
this.world,
this.x,
this.y,
this.z,
this.yaw,
this.pitch
);
}
}