Skip to content

Commit

Permalink
feat: Add commands to investigate waystones of any player as OP (#758)
Browse files Browse the repository at this point in the history
* Add commands to investigate waystones of any player as OP

 - `/waystones [count|owned|activated] <player>` to count/list
 the target's waystones in chat
 - `/waystones gui <player>` 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
  • Loading branch information
edralzar authored Dec 14, 2023
1 parent 85c0394 commit b9a3515
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 7 deletions.
2 changes: 2 additions & 0 deletions shared/src/main/java/net/blay09/mods/waystones/Waystones.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,5 +30,6 @@ public static void initialize() {
ModMenus.initialize(Balm.getMenus());
ModWorldGen.initialize(Balm.getWorldGen());
ModRecipes.initialize(Balm.getRecipes());
ModCommands.initialize(Balm.getCommands());
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<CommandSourceStack> {
@Override
public int run(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException {
ServerPlayer player = ctx.getArgument("player", EntitySelector.class).findSinglePlayer(ctx.getSource());
List<IWaystone> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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<CommandSourceStack> {

private final boolean listNotOwned;
public ListWaystonesCommand(boolean listNotOwned) {
this.listNotOwned = listNotOwned;
}


@Override
public int run(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException {
ServerPlayer player = ctx.getArgument("player", EntitySelector.class).findSinglePlayer(ctx.getSource());
ServerPlayer op = ctx.getSource().getPlayerOrException();

Map<WaystoneOwnership, List<IWaystone>> all = ownedOrActivatedByDistance(player, op);
List<IWaystone> owned = all.get(WaystoneOwnership.OWNED);
List<IWaystone> 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<WaystoneOwnership, List<IWaystone>> ownedOrActivatedByDistance(Player target, Player commandOp) {
Comparator<IWaystone> distanceComparator = createDistanceComparator(commandOp);

EnumMap<WaystoneOwnership, List<IWaystone>> 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<IWaystone> owned = ownedAndActivated.computeIfAbsent(WaystoneOwnership.OWNED, k -> Collections.emptyList());
List<IWaystone> activated = ownedAndActivated.computeIfAbsent(WaystoneOwnership.ACTIVATED, k -> Collections.emptyList());
owned.sort(distanceComparator);
activated.sort(distanceComparator);
return ownedAndActivated;
}

public static Comparator<IWaystone> createDistanceComparator(final Player player) {
return Comparator.comparingDouble(w -> {
ResourceKey<Level> 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<IWaystone> 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);
});
}
}
Original file line number Diff line number Diff line change
@@ -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.<CommandSourceStack>literal("waystones")
.requires(source -> source.isPlayer() && source.hasPermission(2))
.then(LiteralArgumentBuilder.<CommandSourceStack>literal("count")
.then(argument("player", EntityArgument.player()).executes(new CountWaystonesCommand())))
.then(LiteralArgumentBuilder.<CommandSourceStack>literal("owned")
.then(argument("player", EntityArgument.player()).executes(new ListWaystonesCommand(false))))
.then(LiteralArgumentBuilder.<CommandSourceStack>literal("activated")
.then(argument("player", EntityArgument.player()).executes(new ListWaystonesCommand(true))))
.then(LiteralArgumentBuilder.<CommandSourceStack>literal("gui")
.then(argument("player", EntityArgument.player()).executes(new OpenPlayerWaystonesGuiCommand())))
));
}
}
Original file line number Diff line number Diff line change
@@ -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<CommandSourceStack> {
@Override
public int run(CommandContext<CommandSourceStack> 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<WaystoneOwnership, List<IWaystone>> all = ListWaystonesCommand.ownedOrActivatedByDistance(player, op);
List<IWaystone> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.blay09.mods.waystones.command;

public enum WaystoneOwnership {
OWNED,
ACTIVATED
}
11 changes: 11 additions & 0 deletions shared/src/main/java/net/blay09/mods/waystones/menu/ModMenus.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

public class ModMenus {
public static DeferredObject<MenuType<WaystoneSelectionMenu>> waystoneSelection;
public static DeferredObject<MenuType<WaystoneSelectionMenu>> adminSelection;
public static DeferredObject<MenuType<WaystoneSelectionMenu>> sharestoneSelection;
public static DeferredObject<MenuType<WarpPlateContainer>> warpPlate;
public static DeferredObject<MenuType<WaystoneSettingsMenu>> waystoneSettings;
Expand Down Expand Up @@ -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<IWaystone> 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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<WaystoneOwnership, List<IWaystone>> all = ListWaystonesCommand.ownedOrActivatedByDistance(target, op);
List<IWaystone> 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);
}
}
1 change: 1 addition & 0 deletions shared/src/main/resources/assets/waystones/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit b9a3515

Please sign in to comment.