Skip to content

Commit

Permalink
Fix tablist command on 1.19.3 (#2434)
Browse files Browse the repository at this point in the history
* Initial fix & cleanup

* Update meta, add tab-complete

* Update examples

* Cleaner error when there's no linked player

* Cleanups

* Game mode can be `null`

* Field renames, better meta example

* Remove our default gamemode for `add`
  • Loading branch information
tal5 committed Feb 17, 2023
1 parent a628bc9 commit 2608bcc
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 78 deletions.
Expand Up @@ -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;

Expand Down Expand Up @@ -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<ProfileEditMode> 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();
}

Expand Down
@@ -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:<name>) (display:<display>) (uuid:<uuid>) (skin_blob:<blob>) (latency:<#>) (gamemode:creative/survival/adventure/spectator)");
setRequiredArguments(2, 7);
setSyntax("tablist [add/remove/update] (name:<name>) (display:<display>) (uuid:<uuid>) (skin_blob:<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:<name>) (display:<display>) (uuid:<uuid>) (skin_blob:<blob>) (latency:<#>) (gamemode:creative/survival/adventure/spectator)
// @Syntax tablist [add/remove/update] (name:<name>) (display:<display>) (uuid:<uuid>) (skin_blob:<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.
Expand All @@ -64,46 +67,41 @@ public TablistCommand() {
// - tablist add name:<empty> display:<empty> 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:<empty> 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 + "'");
Expand All @@ -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<PlayerHelper.ProfileEditMode> 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<PlayerHelper.ProfileEditMode> 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());
}
}
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ProfileEditMode> 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));
Expand Down

0 comments on commit 2608bcc

Please sign in to comment.