diff --git a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/PlayerHelper.java b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/PlayerHelper.java index fb09247d88..4941497ff8 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/PlayerHelper.java +++ b/plugin/src/main/java/com/denizenscript/denizen/nms/interfaces/PlayerHelper.java @@ -12,6 +12,7 @@ import org.bukkit.entity.Player; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.UUID; @@ -96,13 +97,13 @@ public long getLastActionTime(Player player) { throw new UnsupportedOperationException(); } - public enum ProfileEditMode { ADD, UPDATE_DISPLAY, UPDATE_LATENCY } + public enum ProfileEditMode { ADD, UPDATE_DISPLAY, UPDATE_LATENCY, UPDATE_GAME_MODE, UPDATE_LISTED } - public void sendPlayerInfoAddPacket(Player player, ProfileEditMode mode, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode) { + public void sendPlayerInfoAddPacket(Player player, EnumSet editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) { // TODO: once minimum version is 1.19 or higher, rename to 'sendPlayerInfoUpdatePacket' throw new UnsupportedOperationException(); } - public void sendPlayerRemovePacket(Player player, UUID id) { + public void sendPlayerInfoRemovePacket(Player player, UUID id) { throw new UnsupportedOperationException(); } diff --git a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TablistCommand.java b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TablistCommand.java index 215fbb9d9f..cb615c62db 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TablistCommand.java +++ b/plugin/src/main/java/com/denizenscript/denizen/scripts/commands/player/TablistCommand.java @@ -1,57 +1,60 @@ package com.denizenscript.denizen.scripts.commands.player; import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.nms.interfaces.PlayerHelper; import com.denizenscript.denizen.utilities.Utilities; -import com.denizenscript.denizencore.utilities.debugging.Debug; -import com.denizenscript.denizencore.exceptions.InvalidArgumentsException; import com.denizenscript.denizencore.exceptions.InvalidArgumentsRuntimeException; -import com.denizenscript.denizencore.objects.Argument; import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.scripts.ScriptEntry; import com.denizenscript.denizencore.scripts.commands.AbstractCommand; +import com.denizenscript.denizencore.scripts.commands.generator.ArgDefaultNull; +import com.denizenscript.denizencore.scripts.commands.generator.ArgName; +import com.denizenscript.denizencore.scripts.commands.generator.ArgPrefixed; import com.denizenscript.denizencore.utilities.CoreConfiguration; -import com.denizenscript.denizencore.utilities.CoreUtilities; +import com.denizenscript.denizencore.utilities.debugging.Debug; import org.bukkit.GameMode; import org.bukkit.entity.Player; +import java.util.EnumSet; import java.util.UUID; public class TablistCommand extends AbstractCommand { public TablistCommand() { setName("tablist"); - setSyntax("tablist [add/remove/update] (name:) (display:) (uuid:) (skin_blob:) (latency:<#>) (gamemode:creative/survival/adventure/spectator)"); - setRequiredArguments(2, 7); + setSyntax("tablist [add/remove/update] (name:) (display:) (uuid:) (skin_blob:) (latency:<#>) (gamemode:creative/survival/adventure/spectator) (listed:true/false)"); + setRequiredArguments(2, 8); isProcedural = false; - setPrefixesHandled("name", "display", "uuid", "skin_blob", "latency", "gamemode"); + autoCompile(); } // <--[command] // @Name TabList - // @Syntax tablist [add/remove/update] (name:) (display:) (uuid:) (skin_blob:) (latency:<#>) (gamemode:creative/survival/adventure/spectator) + // @Syntax tablist [add/remove/update] (name:) (display:) (uuid:) (skin_blob:) (latency:<#>) (gamemode:creative/survival/adventure/spectator) (listed:true/false) // @Required 2 - // @Maximum 7 + // @Maximum 8 // @Short Modifies values in the player's tablist. // @Group player // // @Description // Adds, removes, or updates a player profile entry in the player's tab-list view. // - // Using 'add' will add a new entry to the tab list. + // Using 'add' will add a new entry to the client's player list. // 'name' must be specified. // 'display' if unspecified will be the same as the name. // 'uuid' if unspecified will be randomly generated. - // 'skin_blob' if unspecified will be default (steve). Skin blob should be in format "texture;signature" (separated by semicolon). + // 'skin_blob' if unspecified will be a default Minecraft skin. Skin blob should be in format "texture;signature" (separated by semicolon). // 'latency' is a number representing the players ping, if unspecified will be 0. 0 renders as full ping, -1 renders an "X", 500 renders orange (3 bars), 1000 renders red (1 bar). // 'gamemode' if unspecified will be creative. 'spectator' renders as faded and is pushed below all non-spectator entries. + // 'listed' determines whether the entry will show up in the tab list, defaults to 'true'. // // Using 'remove' will remove an entry from the tab list. // 'uuid' must be specified. // // Using 'update' will update an existing entry in the tab list. // 'uuid' must be specified. - // Only values that can be updated are 'display' or 'latency' + // Only 'display', 'latency', 'gamemode', and 'listed' can be updated. // // Usage of display names that are not empty requires enabling Denizen/config.yml option "Allow restricted actions". // Using this tool to add entries that look like real players (but aren't) is forbidden. @@ -64,46 +67,41 @@ public TablistCommand() { // - tablist add name: display: gamemode:spectator // // @Usage - // Use to add a custom tab completion in the in-game chat, by adding an empty entry to the bottom of the tab list. - // - tablist add name:my_tab_complete display: gamemode:spectator + // Use to update an existing entry + // - tablist update uuid:<[uuid]> gamemode:spectator latency:200 // // --> - public enum Mode { ADD, REMOVE, UPDATE } @Override - public void parseArgs(ScriptEntry scriptEntry) throws InvalidArgumentsException { - for (Argument arg : scriptEntry) { - if (!scriptEntry.hasObject("mode") - && arg.matchesEnum(Mode.class)) { - scriptEntry.addObject("mode", arg.asElement()); - } - else { - arg.reportUnhandled(); - } - } - if (!scriptEntry.hasObject("mode")) { - throw new InvalidArgumentsException("Missing add/remove/update argument!"); - } + public void addCustomTabCompletions(TabCompletionsBuilder tab) { + tab.addWithPrefix("gamemode:", GameMode.values()); } - @Override - public void execute(ScriptEntry scriptEntry) { - Mode mode = scriptEntry.getElement("mode").asEnum(Mode.class); - ElementTag name = scriptEntry.argForPrefixAsElement("name", null); - ElementTag display = scriptEntry.argForPrefixAsElement("display", null); - ElementTag uuid = scriptEntry.argForPrefixAsElement("uuid", null); - ElementTag skinBlob = scriptEntry.argForPrefixAsElement("skin_blob", null); - ElementTag latency = scriptEntry.argForPrefixAsElement("latency", null); - ElementTag gamemode = scriptEntry.argForPrefixAsElement("gamemode", "creative"); + public enum Mode { ADD, REMOVE, UPDATE } + + public static void autoExecute(ScriptEntry scriptEntry, + @ArgName("mode") Mode mode, + @ArgDefaultNull @ArgPrefixed @ArgName("name") String name, + @ArgDefaultNull @ArgPrefixed @ArgName("display") String display, + @ArgDefaultNull @ArgPrefixed @ArgName("uuid") String uuid, + @ArgDefaultNull @ArgPrefixed @ArgName("skin_blob") String skinBlob, + @ArgDefaultNull @ArgPrefixed @ArgName("latency") ElementTag latency, + @ArgDefaultNull @ArgPrefixed @ArgName("gamemode") GameMode gamemode, + @ArgDefaultNull @ArgPrefixed @ArgName("listed") ElementTag listed) { + if (!Utilities.entryHasPlayer(scriptEntry)) { + Debug.echoError("Must have a linked player!"); + return; + } Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity(); - if (scriptEntry.dbCallShouldDebug()) { - Debug.report(scriptEntry, getName(), mode, name, display, uuid, gamemode, latency, skinBlob); + if (listed != null && !listed.isBoolean()) { + Debug.echoError("Invalid input '" + listed + "' to 'listed': must be a boolean"); + return; } - UUID id = null; + UUID id; if (uuid != null) { try { - id = UUID.fromString(uuid.asString()); + id = UUID.fromString(uuid); } catch (IllegalArgumentException ex) { Debug.echoError("Invalid UUID '" + uuid + "'"); @@ -115,56 +113,86 @@ public void execute(ScriptEntry scriptEntry) { } String texture = null, signature = null; if (skinBlob != null) { - int semicolon = skinBlob.asString().indexOf(';'); + int semicolon = skinBlob.indexOf(';'); if (semicolon == -1) { Debug.echoError("Invalid skinblob '" + skinBlob + "'"); return; } - texture = skinBlob.asString().substring(0, semicolon); - signature = skinBlob.asString().substring(semicolon + 1); + texture = skinBlob.substring(0, semicolon); + signature = skinBlob.substring(semicolon + 1); } if (latency != null && !latency.isInt()) { Debug.echoError("Invalid latency, not a number '" + latency + "'"); return; } int latencyNum = latency == null ? 0 : latency.asInt(); - if (!gamemode.matchesEnum(GameMode.class)) { - Debug.echoError("Invalid gamemode '" + gamemode + "'"); - return; - } - GameMode gameModeBukkit = gamemode.asEnum(GameMode.class); switch (mode) { - case ADD: + case ADD -> { if (name == null) { throw new InvalidArgumentsRuntimeException("'name' wasn't specified but is required for 'add'"); } - if ((display == null || display.asString().length() > 0) && !CoreConfiguration.allowRestrictedActions) { + if ((display == null || display.length() > 0) && !CoreConfiguration.allowRestrictedActions) { Debug.echoError("Cannot use 'tablist add' to add a non-empty display-named entry: 'Allow restricted actions' is disabled in Denizen config.yml."); return; } - NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, PlayerHelper.ProfileEditMode.ADD, name.asString(), CoreUtilities.stringifyNullPass(display), id, texture, signature, latencyNum, gameModeBukkit); - break; - case REMOVE: + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) { + NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, EnumSet.of(PlayerHelper.ProfileEditMode.ADD), name, display, id, texture, signature, latencyNum, gamemode == null ? GameMode.CREATIVE : gamemode, false); + return; + } + EnumSet editModes = EnumSet.of(PlayerHelper.ProfileEditMode.ADD); + boolean listedBool = listed == null || listed.asBoolean(); + if (listedBool) { + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LISTED); + } + if (display != null) { + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_DISPLAY); + } + if (latency != null) { + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LATENCY); + } + if (gamemode != null) { + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_GAME_MODE); + } + NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, editModes, name, display, id, texture, signature, latencyNum, gamemode, listedBool); + } + case REMOVE -> { if (uuid == null) { throw new InvalidArgumentsRuntimeException("'uuid' wasn't specified but is required for 'remove'"); } - NMSHandler.playerHelper.sendPlayerRemovePacket(player, id); - break; - case UPDATE: + NMSHandler.playerHelper.sendPlayerInfoRemovePacket(player, id); + } + case UPDATE -> { if (uuid == null) { throw new InvalidArgumentsRuntimeException("'uuid' wasn't specified but is required for 'update'"); } - if ((display == null || display.asString().length() > 0) && !CoreConfiguration.allowRestrictedActions) { + if ((display == null || display.length() > 0) && !CoreConfiguration.allowRestrictedActions) { Debug.echoError("Cannot use 'tablist update' to create a non-empty named entry: 'Allow restricted actions' is disabled in Denizen config.yml."); return; } + if (NMSHandler.getVersion().isAtMost(NMSVersion.v1_18)) { + if (display != null) { + NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, EnumSet.of(PlayerHelper.ProfileEditMode.UPDATE_DISPLAY), name, display, id, texture, signature, latencyNum, gamemode, false); + } + if (latency != null) { + NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, EnumSet.of(PlayerHelper.ProfileEditMode.UPDATE_LATENCY), name, display, id, texture, signature, latencyNum, gamemode, false); + } + return; + } + EnumSet editModes = EnumSet.noneOf(PlayerHelper.ProfileEditMode.class); if (display != null) { - NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, PlayerHelper.ProfileEditMode.UPDATE_DISPLAY, CoreUtilities.stringifyNullPass(name), CoreUtilities.stringifyNullPass(display), id, texture, signature, latencyNum, gameModeBukkit); + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_DISPLAY); } if (latency != null) { - NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, PlayerHelper.ProfileEditMode.UPDATE_LATENCY, CoreUtilities.stringifyNullPass(name), CoreUtilities.stringifyNullPass(display), id, texture, signature, latencyNum, gameModeBukkit); + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LATENCY); } - break; + if (gamemode != null) { + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_GAME_MODE); + } + if (listed != null) { + editModes.add(PlayerHelper.ProfileEditMode.UPDATE_LISTED); + } + NMSHandler.playerHelper.sendPlayerInfoAddPacket(player, editModes, name, display, id, texture, signature, latencyNum, gamemode, listed == null || listed.asBoolean()); + } } } } diff --git a/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/PlayerHelperImpl.java b/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/PlayerHelperImpl.java index b485a962da..153a48c812 100644 --- a/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/PlayerHelperImpl.java +++ b/v1_18/src/main/java/com/denizenscript/denizen/nms/v1_18/helpers/PlayerHelperImpl.java @@ -20,7 +20,6 @@ import com.denizenscript.denizen.utilities.entity.DenizenEntityType; import com.denizenscript.denizen.utilities.entity.FakeEntity; import com.denizenscript.denizencore.objects.Mechanism; -import com.denizenscript.denizencore.utilities.CoreUtilities; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; import com.mojang.authlib.GameProfile; @@ -389,20 +388,21 @@ public long getLastActionTime(Player player) { } @Override - public void sendPlayerInfoAddPacket(Player player, ProfileEditMode mode, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode) { - ClientboundPlayerInfoPacket.Action action = mode == ProfileEditMode.ADD ? ClientboundPlayerInfoPacket.Action.ADD_PLAYER : - (mode == ProfileEditMode.UPDATE_DISPLAY ? ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME : ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY); + public void sendPlayerInfoAddPacket(Player player, EnumSet editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) { + ProfileEditMode editMode = editModes.stream().findFirst().get(); + ClientboundPlayerInfoPacket.Action action = editMode == ProfileEditMode.ADD ? ClientboundPlayerInfoPacket.Action.ADD_PLAYER : + (editMode == ProfileEditMode.UPDATE_DISPLAY ? ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME : ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY); ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(action); GameProfile profile = new GameProfile(id, name); if (texture != null) { profile.getProperties().put("textures", new Property("textures", texture, signature)); } - packet.getEntries().add(new ClientboundPlayerInfoPacket.PlayerUpdate(profile, latency, GameType.byName(CoreUtilities.toLowerCase(gameMode.name())), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)))); + packet.getEntries().add(new ClientboundPlayerInfoPacket.PlayerUpdate(profile, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)))); PacketHelperImpl.send(player, packet); } @Override - public void sendPlayerRemovePacket(Player player, UUID id) { + public void sendPlayerInfoRemovePacket(Player player, UUID id) { ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER); GameProfile profile = new GameProfile(id, "name"); packet.getEntries().add(new ClientboundPlayerInfoPacket.PlayerUpdate(profile, 0, null, null)); diff --git a/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/PlayerHelperImpl.java b/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/PlayerHelperImpl.java index 62860f3be9..877ca01320 100644 --- a/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/PlayerHelperImpl.java +++ b/v1_19/src/main/java/com/denizenscript/denizen/nms/v1_19/helpers/PlayerHelperImpl.java @@ -21,7 +21,6 @@ import com.denizenscript.denizen.utilities.entity.DenizenEntityType; import com.denizenscript.denizen.utilities.entity.FakeEntity; import com.denizenscript.denizencore.objects.Mechanism; -import com.denizenscript.denizencore.utilities.CoreUtilities; import com.denizenscript.denizencore.utilities.ReflectionHelper; import com.denizenscript.denizencore.utilities.debugging.Debug; import com.mojang.authlib.GameProfile; @@ -394,20 +393,28 @@ public long getLastActionTime(Player player) { } @Override - public void sendPlayerInfoAddPacket(Player player, ProfileEditMode mode, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode) { - boolean listed = true; // TODO: 1.19.3: Add 'listed' input - ClientboundPlayerInfoUpdatePacket.Action action = mode == ProfileEditMode.ADD ? ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER : (mode == ProfileEditMode.UPDATE_DISPLAY ? ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME : ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY); + public void sendPlayerInfoAddPacket(Player player, EnumSet editModes, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode, boolean listed) { + EnumSet actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action.class); + for (ProfileEditMode editMode : editModes) { + actions.add(switch (editMode) { + case ADD -> ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER; + case UPDATE_DISPLAY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME; + case UPDATE_LATENCY -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY; + case UPDATE_GAME_MODE -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE; + case UPDATE_LISTED -> ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED; + }); + } GameProfile profile = new GameProfile(id, name); if (texture != null) { profile.getProperties().put("textures", new Property("textures", texture, signature)); } - ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, GameType.byName(CoreUtilities.toLowerCase(gameMode.name())), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), null); - PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(EnumSet.of(action), Collections.singletonList(entry))); + ClientboundPlayerInfoUpdatePacket.Entry entry = new ClientboundPlayerInfoUpdatePacket.Entry(id, profile, listed, latency, gameMode == null ? null : GameType.byId(gameMode.getValue()), display == null ? null : Handler.componentToNMS(FormattedTextHelper.parse(display, ChatColor.WHITE)), null); + PacketHelperImpl.send(player, ProfileEditorImpl.createInfoPacket(actions, List.of(entry))); } @Override - public void sendPlayerRemovePacket(Player player, UUID id) { - PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(Collections.singletonList(id))); + public void sendPlayerInfoRemovePacket(Player player, UUID id) { + PacketHelperImpl.send(player, new ClientboundPlayerInfoRemovePacket(List.of(id))); } @Override