From b9a3515c852140859307b14c35330012fbc2b908 Mon Sep 17 00:00:00 2001 From: Simon B Date: Thu, 14 Dec 2023 09:34:44 +0100 Subject: [PATCH] feat: Add commands to investigate waystones of any player as OP (#758) * Add commands to investigate waystones of any player as OP - `/waystones [count|owned|activated] ` to count/list the target's waystones in chat - `/waystones gui ` open a Waystone selection menu listing the target's waystones A waystone is considered `owned` if the target Player UUID matches the waystone's `getOwnerUuid()`. Commands that list both owned and activated waystones of a given target player will list their owned waystones first. Within these categories, waystones are sorted by distance from the OP. * fix review comments + use Dimension in comparator --- .../net/blay09/mods/waystones/Waystones.java | 2 + .../mods/waystones/client/ModScreens.java | 6 +- .../gui/screen/AdminSelectionScreen.java | 22 +++ .../command/CountWaystonesCommand.java | 25 ++++ .../command/ListWaystonesCommand.java | 129 ++++++++++++++++++ .../mods/waystones/command/ModCommands.java | 24 ++++ .../OpenPlayerWaystonesGuiCommand.java | 54 ++++++++ .../waystones/command/WaystoneOwnership.java | 6 + .../blay09/mods/waystones/menu/ModMenus.java | 11 ++ .../waystones/menu/WaystoneSelectionMenu.java | 18 ++- .../assets/waystones/lang/en_us.json | 1 + 11 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 shared/src/main/java/net/blay09/mods/waystones/client/gui/screen/AdminSelectionScreen.java create mode 100644 shared/src/main/java/net/blay09/mods/waystones/command/CountWaystonesCommand.java create mode 100644 shared/src/main/java/net/blay09/mods/waystones/command/ListWaystonesCommand.java create mode 100644 shared/src/main/java/net/blay09/mods/waystones/command/ModCommands.java create mode 100644 shared/src/main/java/net/blay09/mods/waystones/command/OpenPlayerWaystonesGuiCommand.java create mode 100644 shared/src/main/java/net/blay09/mods/waystones/command/WaystoneOwnership.java diff --git a/shared/src/main/java/net/blay09/mods/waystones/Waystones.java b/shared/src/main/java/net/blay09/mods/waystones/Waystones.java index 1418a286..b1f53807 100644 --- a/shared/src/main/java/net/blay09/mods/waystones/Waystones.java +++ b/shared/src/main/java/net/blay09/mods/waystones/Waystones.java @@ -4,6 +4,7 @@ import net.blay09.mods.waystones.api.WaystonesAPI; import net.blay09.mods.waystones.block.ModBlocks; import net.blay09.mods.waystones.block.entity.ModBlockEntities; +import net.blay09.mods.waystones.command.ModCommands; import net.blay09.mods.waystones.config.WaystonesConfig; import net.blay09.mods.waystones.handler.ModEventHandlers; import net.blay09.mods.waystones.item.ModItems; @@ -29,5 +30,6 @@ public static void initialize() { ModMenus.initialize(Balm.getMenus()); ModWorldGen.initialize(Balm.getWorldGen()); ModRecipes.initialize(Balm.getRecipes()); + ModCommands.initialize(Balm.getCommands()); } } diff --git a/shared/src/main/java/net/blay09/mods/waystones/client/ModScreens.java b/shared/src/main/java/net/blay09/mods/waystones/client/ModScreens.java index 3ae31c36..3a0f9c84 100644 --- a/shared/src/main/java/net/blay09/mods/waystones/client/ModScreens.java +++ b/shared/src/main/java/net/blay09/mods/waystones/client/ModScreens.java @@ -1,10 +1,7 @@ package net.blay09.mods.waystones.client; import net.blay09.mods.balm.api.client.screen.BalmScreens; -import net.blay09.mods.waystones.client.gui.screen.SharestoneSelectionScreen; -import net.blay09.mods.waystones.client.gui.screen.WarpPlateScreen; -import net.blay09.mods.waystones.client.gui.screen.WaystoneSelectionScreen; -import net.blay09.mods.waystones.client.gui.screen.WaystoneSettingsScreen; +import net.blay09.mods.waystones.client.gui.screen.*; import net.blay09.mods.waystones.menu.ModMenus; public class ModScreens { @@ -13,5 +10,6 @@ public static void initialize(BalmScreens screens) { screens.registerScreen(ModMenus.sharestoneSelection::get, SharestoneSelectionScreen::new); screens.registerScreen(ModMenus.warpPlate::get, WarpPlateScreen::new); screens.registerScreen(ModMenus.waystoneSettings::get, WaystoneSettingsScreen::new); + screens.registerScreen(ModMenus.adminSelection::get, AdminSelectionScreen::new); } } diff --git a/shared/src/main/java/net/blay09/mods/waystones/client/gui/screen/AdminSelectionScreen.java b/shared/src/main/java/net/blay09/mods/waystones/client/gui/screen/AdminSelectionScreen.java new file mode 100644 index 00000000..e2aa2bde --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/client/gui/screen/AdminSelectionScreen.java @@ -0,0 +1,22 @@ +package net.blay09.mods.waystones.client.gui.screen; + +import net.blay09.mods.waystones.menu.WaystoneSelectionMenu; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; + +public class AdminSelectionScreen extends WaystoneSelectionScreenBase { + + public AdminSelectionScreen(WaystoneSelectionMenu container, Inventory playerInventory, Component title) { + super(container, playerInventory, title); + } + + @Override + protected boolean allowSorting() { + return false; + } + + @Override + protected boolean allowDeletion() { + return false; + } +} diff --git a/shared/src/main/java/net/blay09/mods/waystones/command/CountWaystonesCommand.java b/shared/src/main/java/net/blay09/mods/waystones/command/CountWaystonesCommand.java new file mode 100644 index 00000000..ba30697d --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/command/CountWaystonesCommand.java @@ -0,0 +1,25 @@ +package net.blay09.mods.waystones.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.blay09.mods.waystones.api.IWaystone; +import net.blay09.mods.waystones.core.PlayerWaystoneManager; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.selector.EntitySelector; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; + +import java.util.List; + +public class CountWaystonesCommand implements Command { + @Override + public int run(CommandContext ctx) throws CommandSyntaxException { + ServerPlayer player = ctx.getArgument("player", EntitySelector.class).findSinglePlayer(ctx.getSource()); + List waystones = PlayerWaystoneManager.getWaystones(player); + int total = waystones.size(); + long owned = waystones.stream().filter(w -> w.isOwner(player)).count(); + ctx.getSource().sendSuccess(() -> Component.literal("Player " + player.getScoreboardName() + " has " + total + " waystones activated and owns " + owned + " of these"), false); + return waystones.size(); + } +} diff --git a/shared/src/main/java/net/blay09/mods/waystones/command/ListWaystonesCommand.java b/shared/src/main/java/net/blay09/mods/waystones/command/ListWaystonesCommand.java new file mode 100644 index 00000000..5df11a19 --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/command/ListWaystonesCommand.java @@ -0,0 +1,129 @@ +package net.blay09.mods.waystones.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.blay09.mods.waystones.api.IWaystone; +import net.blay09.mods.waystones.core.PlayerWaystoneManager; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.selector.EntitySelector; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +import java.util.*; +import java.util.stream.Collectors; + +public class ListWaystonesCommand implements Command { + + private final boolean listNotOwned; + public ListWaystonesCommand(boolean listNotOwned) { + this.listNotOwned = listNotOwned; + } + + + @Override + public int run(CommandContext ctx) throws CommandSyntaxException { + ServerPlayer player = ctx.getArgument("player", EntitySelector.class).findSinglePlayer(ctx.getSource()); + ServerPlayer op = ctx.getSource().getPlayerOrException(); + + Map> all = ownedOrActivatedByDistance(player, op); + List owned = all.get(WaystoneOwnership.OWNED); + List others = all.get(WaystoneOwnership.ACTIVATED); + + String headerPart; + String footerPart; + if (this.listNotOwned) { + headerPart = " (including not owned):"; + footerPart = "total, " + owned.size() + " owned"; + } else { + others = Collections.emptyList(); + headerPart = ":"; + footerPart = "owned"; + } + + ctx.getSource().sendSystemMessage(Component.literal("----")); + ctx.getSource().sendSystemMessage(Component.literal("Player ").append(player.getScoreboardName()) + .append(" waystones (x y z) coordinates").append(headerPart)); + sendWaystoneList(ctx.getSource(), op, owned, true); + if (this.listNotOwned) { + sendWaystoneList(ctx.getSource(), op, others, false); + } + + int total = (owned.size() + others.size()); + ctx.getSource().sendSuccess(() -> Component.literal(total + " waystones " + footerPart), false); + ctx.getSource().sendSystemMessage(Component.literal("----")); + + return total; + } + + public static Map> ownedOrActivatedByDistance(Player target, Player commandOp) { + Comparator distanceComparator = createDistanceComparator(commandOp); + + EnumMap> ownedAndActivated = PlayerWaystoneManager.getWaystones(target) + .stream() + //we only mark as owned the waystones that are truly bound to the target player's uuid + .collect(Collectors.groupingBy( + w -> target.getGameProfile().getId().equals(w.getOwnerUid()) ? WaystoneOwnership.OWNED : WaystoneOwnership.ACTIVATED, + () -> new EnumMap<>(WaystoneOwnership.class), + Collectors.toList()) + ); + + List owned = ownedAndActivated.computeIfAbsent(WaystoneOwnership.OWNED, k -> Collections.emptyList()); + List activated = ownedAndActivated.computeIfAbsent(WaystoneOwnership.ACTIVATED, k -> Collections.emptyList()); + owned.sort(distanceComparator); + activated.sort(distanceComparator); + return ownedAndActivated; + } + + public static Comparator createDistanceComparator(final Player player) { + return Comparator.comparingDouble(w -> { + ResourceKey targetDimension = w.getDimension(); + if (targetDimension == null) return Double.MAX_VALUE; + if (!targetDimension.equals(player.level().dimension())) return Double.MAX_VALUE - 1; + + return player.position().distanceTo(w.getPos().getCenter()); + }); + } + + private void sendWaystoneList(CommandSourceStack source, ServerPlayer op, List waystones, boolean owned) { + final String ownedHeader; + if (!owned) ownedHeader = "activated"; + else if (this.listNotOwned) ownedHeader = " owned"; + else ownedHeader = " "; + waystones.forEach(w -> { + MutableComponent c = Component.literal(" - ") + .append(ownedHeader); + + MutableComponent coordinates = Component.literal(w.getPos().toShortString()).withStyle(ChatFormatting.YELLOW); + MutableComponent distance; + + if (w.getDimension() != op.level().dimension()) { + distance = Component.literal(w.getDimension().location().getPath()) + .withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW); + c.append(" in ").append(distance).append(" at (").append(coordinates); + } else { + distance = Component.literal(String.valueOf((int) op.position().distanceTo(w.getPos().getCenter()))) + .withStyle(ChatFormatting.BOLD); + c.append(" at ").append(distance).append(" blocks away (").append(coordinates); + } + + String suggestedCommand = "/execute in " + w.getDimension().location() + " run teleport " + + w.getPos().toShortString().replaceAll(",", ""); + c.append("): \"") + .append(Component.literal(w.getName()) + .withStyle(ChatFormatting.GREEN, ChatFormatting.UNDERLINE) + .withStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, suggestedCommand)))) + .append("\""); + + source.sendSystemMessage(c); + }); + } +} diff --git a/shared/src/main/java/net/blay09/mods/waystones/command/ModCommands.java b/shared/src/main/java/net/blay09/mods/waystones/command/ModCommands.java new file mode 100644 index 00000000..55f57cda --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/command/ModCommands.java @@ -0,0 +1,24 @@ +package net.blay09.mods.waystones.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.blay09.mods.balm.api.command.BalmCommands; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; + +import static net.minecraft.commands.Commands.argument; + +public class ModCommands { + public static void initialize(BalmCommands commands) { + commands.register(dispatcher -> dispatcher.register(LiteralArgumentBuilder.literal("waystones") + .requires(source -> source.isPlayer() && source.hasPermission(2)) + .then(LiteralArgumentBuilder.literal("count") + .then(argument("player", EntityArgument.player()).executes(new CountWaystonesCommand()))) + .then(LiteralArgumentBuilder.literal("owned") + .then(argument("player", EntityArgument.player()).executes(new ListWaystonesCommand(false)))) + .then(LiteralArgumentBuilder.literal("activated") + .then(argument("player", EntityArgument.player()).executes(new ListWaystonesCommand(true)))) + .then(LiteralArgumentBuilder.literal("gui") + .then(argument("player", EntityArgument.player()).executes(new OpenPlayerWaystonesGuiCommand()))) + )); + } +} diff --git a/shared/src/main/java/net/blay09/mods/waystones/command/OpenPlayerWaystonesGuiCommand.java b/shared/src/main/java/net/blay09/mods/waystones/command/OpenPlayerWaystonesGuiCommand.java new file mode 100644 index 00000000..cd5a6826 --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/command/OpenPlayerWaystonesGuiCommand.java @@ -0,0 +1,54 @@ +package net.blay09.mods.waystones.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.blay09.mods.balm.api.Balm; +import net.blay09.mods.balm.api.menu.BalmMenuProvider; +import net.blay09.mods.waystones.api.IWaystone; +import net.blay09.mods.waystones.core.Waystone; +import net.blay09.mods.waystones.menu.WaystoneSelectionMenu; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.selector.EntitySelector; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OpenPlayerWaystonesGuiCommand implements Command { + @Override + public int run(CommandContext ctx) throws CommandSyntaxException { + ServerPlayer target = ctx.getArgument("player", EntitySelector.class).findSinglePlayer(ctx.getSource()); + ServerPlayer op = ctx.getSource().getPlayerOrException(); + BalmMenuProvider menuProvider = new BalmMenuProvider() { + @Override + public Component getDisplayName() { + return Component.translatable( "container.waystones.waystone_admin_selection", target.getScoreboardName()); + } + + @Override + public AbstractContainerMenu createMenu(int i, Inventory playerInventory, Player playerEntity) { + return WaystoneSelectionMenu.createAdminSelection(i, op, target); + } + + @Override + public void writeScreenOpeningData(ServerPlayer player, FriendlyByteBuf buf) { + Map> all = ListWaystonesCommand.ownedOrActivatedByDistance(player, op); + List waystones = new ArrayList<>(); + waystones.addAll(all.get(WaystoneOwnership.OWNED)); + waystones.addAll(all.get(WaystoneOwnership.ACTIVATED)); + buf.writeInt(waystones.size()); + waystones.forEach(w -> Waystone.write(buf, w)); + } + }; + Balm.getNetworking().openGui(op, menuProvider); + + return 0; + } +} diff --git a/shared/src/main/java/net/blay09/mods/waystones/command/WaystoneOwnership.java b/shared/src/main/java/net/blay09/mods/waystones/command/WaystoneOwnership.java new file mode 100644 index 00000000..5c7d7287 --- /dev/null +++ b/shared/src/main/java/net/blay09/mods/waystones/command/WaystoneOwnership.java @@ -0,0 +1,6 @@ +package net.blay09.mods.waystones.command; + +public enum WaystoneOwnership { + OWNED, + ACTIVATED +} diff --git a/shared/src/main/java/net/blay09/mods/waystones/menu/ModMenus.java b/shared/src/main/java/net/blay09/mods/waystones/menu/ModMenus.java index 9e8827e0..7c68ab8e 100644 --- a/shared/src/main/java/net/blay09/mods/waystones/menu/ModMenus.java +++ b/shared/src/main/java/net/blay09/mods/waystones/menu/ModMenus.java @@ -20,6 +20,7 @@ public class ModMenus { public static DeferredObject> waystoneSelection; + public static DeferredObject> adminSelection; public static DeferredObject> sharestoneSelection; public static DeferredObject> warpPlate; public static DeferredObject> waystoneSettings; @@ -56,6 +57,16 @@ public static void initialize(BalmMenus menus) { return null; }); + adminSelection = menus.registerMenu(id("waystone_op_selection"), (syncId, inventory, buf) -> { + int count = buf.readInt(); + List waystones = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + waystones.add(Waystone.read(buf)); + } + + return new WaystoneSelectionMenu(ModMenus.adminSelection.get(), WarpMode.CUSTOM, null, syncId, waystones); + }); + warpPlate = menus.registerMenu(id("warp_plate"), (windowId, inv, data) -> { BlockPos pos = data.readBlockPos(); diff --git a/shared/src/main/java/net/blay09/mods/waystones/menu/WaystoneSelectionMenu.java b/shared/src/main/java/net/blay09/mods/waystones/menu/WaystoneSelectionMenu.java index 6efac90f..7779b587 100644 --- a/shared/src/main/java/net/blay09/mods/waystones/menu/WaystoneSelectionMenu.java +++ b/shared/src/main/java/net/blay09/mods/waystones/menu/WaystoneSelectionMenu.java @@ -2,20 +2,25 @@ import net.blay09.mods.waystones.api.IWaystone; import net.blay09.mods.waystones.block.SharestoneBlock; -import net.blay09.mods.waystones.core.*; +import net.blay09.mods.waystones.command.ListWaystonesCommand; +import net.blay09.mods.waystones.command.WaystoneOwnership; +import net.blay09.mods.waystones.core.PlayerWaystoneManager; +import net.blay09.mods.waystones.core.WarpMode; +import net.blay09.mods.waystones.core.WaystoneManager; +import net.blay09.mods.waystones.core.WaystoneTypes; import net.minecraft.core.BlockPos; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; public class WaystoneSelectionMenu extends AbstractContainerMenu { @@ -71,4 +76,11 @@ public static WaystoneSelectionMenu createSharestoneSelection(MinecraftServer se return new WaystoneSelectionMenu(ModMenus.sharestoneSelection.get(), WarpMode.SHARESTONE_TO_SHARESTONE, fromWaystone, windowId, waystones); } + public static WaystoneSelectionMenu createAdminSelection(int windowId, Player op, Player target) { + Map> all = ListWaystonesCommand.ownedOrActivatedByDistance(target, op); + List waystones = new ArrayList<>(); + waystones.addAll(all.get(WaystoneOwnership.OWNED)); + waystones.addAll(all.get(WaystoneOwnership.ACTIVATED)); + return new WaystoneSelectionMenu(ModMenus.adminSelection.get(), WarpMode.CUSTOM, null, windowId, waystones); + } } diff --git a/shared/src/main/resources/assets/waystones/lang/en_us.json b/shared/src/main/resources/assets/waystones/lang/en_us.json index 341d0e16..b27ae598 100644 --- a/shared/src/main/resources/assets/waystones/lang/en_us.json +++ b/shared/src/main/resources/assets/waystones/lang/en_us.json @@ -31,6 +31,7 @@ "item.waystones.attuned_shard": "Attuned Shard", "item.waystones.crumbling_attuned_shard": "Crumbling Attuned Shard", "container.waystones.waystone_selection": "Select your destination:", + "container.waystones.waystone_admin_selection": "Select which waystone of player <%s> you would like to visit:", "container.waystones.waystone_settings": "Enter waystone name:", "container.waystones.warp_plate": "Warp Plate", "gui.waystones.waystone_selection.next_page": "Next",