Skip to content

Commit

Permalink
tablist command (restrictedActions)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcmonkey4eva committed Apr 5, 2022
1 parent 5f9d817 commit 83ea30d
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 1 deletion.
Expand Up @@ -45,7 +45,7 @@ public class ListPingScriptEvent extends BukkitScriptEvent implements Listener {
// "PROTOCOL_VERSION:" + ElementTag(Number) to change the protocol ID number of the server's version (only on Paper).
// "VERSION_NAME:" + ElementTag to change the server's version name (only on Paper).
// "EXCLUDE_PLAYERS:" + ListTag(PlayerTag) to exclude a set of players from showing in the player count or preview of online players (only on Paper).
// "ALTERNATE_PLAYER_TEXT:" + ListTag to set custom text for the player list section of the server status (only on Paper). (Requires "Allow restricted actions" in Denizen/config.yml)
// "ALTERNATE_PLAYER_TEXT:" + ListTag to set custom text for the player list section of the server status (only on Paper). (Requires "Allow restricted actions" in Denizen/config.yml). Usage of this to present lines that look like player names (but aren't) is forbidden.
// ElementTag to change the MOTD that will show.
//
// -->
Expand Down
Expand Up @@ -103,4 +103,14 @@ public void setSpawnForced(Player player, boolean forced) {
public long getLastActionTime(Player player) {
throw new UnsupportedOperationException();
}

public enum ProfileEditMode { ADD, UPDATE_DISPLAY, UPDATE_LATENCY }

public void sendPlayerInfoAddPacket(Player player, ProfileEditMode mode, String name, String display, UUID id, String texture, String signature, int latency, GameMode gameMode) {
throw new UnsupportedOperationException();
}

public void sendPlayerRemovePacket(Player player, UUID id) {
throw new UnsupportedOperationException();
}
}
Expand Up @@ -148,6 +148,7 @@ public void registerCommands() {
registerCommand(ShowFakeCommand.class);
registerCommand(SidebarCommand.class);
registerCommand(StatisticCommand.class);
registerCommand(TablistCommand.class);
registerCommand(TeamCommand.class);
registerCommand(TitleCommand.class);
registerCommand(ToastCommand.class);
Expand Down
@@ -0,0 +1,166 @@
package com.denizenscript.denizen.scripts.commands.player;

import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.nms.interfaces.PlayerHelper;
import com.denizenscript.denizen.utilities.Utilities;
import com.denizenscript.denizen.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.utilities.CoreConfiguration;
import com.denizenscript.denizencore.utilities.CoreUtilities;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;

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);
isProcedural = false;
setPrefixesHandled("name", "display", "uuid", "skin_blob", "latency", "gamemode");
}

// <--[command]
// @Name TabList
// @Syntax tablist [add/remove/update] (name:<name>) (display:<display>) (uuid:<uuid>) (skin_blob:<blob>) (latency:<#>) (gamemode:creative/survival/adventure/spectator)
// @Required 2
// @Maximum 7
// @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.
// '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).
// '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.
//
// 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'
//
// 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.
//
// @Tags
// None.
//
// @Usage
// Use to add a new empty entry to the player's tab list to fill space.
// - tablist add name:<empty> display:<empty> gamemode:spectator
//
// -->

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!");
}
}

@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");
Player player = Utilities.getEntryPlayer(scriptEntry).getPlayerEntity();
if (scriptEntry.dbCallShouldDebug()) {
Debug.report(scriptEntry, getName(), mode, name, display, uuid, gamemode, latency, skinBlob);
}
UUID id = null;
if (uuid != null) {
try {
id = UUID.fromString(uuid.asString());
}
catch (IllegalArgumentException ex) {
Debug.echoError("Invalid UUID '" + uuid + "'");
return;
}
}
else {
id = UUID.randomUUID();
}
String texture = null, signature = null;
if (skinBlob != null) {
int semicolon = skinBlob.asString().indexOf(';');
if (semicolon == -1) {
Debug.echoError("Invalid skinblob '" + skinBlob + "'");
return;
}
texture = skinBlob.asString().substring(0, semicolon);
signature = skinBlob.asString().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:
if (name == null) {
throw new InvalidArgumentsRuntimeException("'name' wasn't specified but is required for 'add'");
}
if ((display == null || display.asString().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.getPlayerHelper().sendPlayerInfoAddPacket(player, PlayerHelper.ProfileEditMode.ADD, name.asString(), CoreUtilities.stringifyNullPass(display), id, texture, signature, latencyNum, gameModeBukkit);
break;
case REMOVE:
if (uuid == null) {
throw new InvalidArgumentsRuntimeException("'uuid' wasn't specified but is required for 'remove'");
}
NMSHandler.getPlayerHelper().sendPlayerRemovePacket(player, id);
break;
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) {
Debug.echoError("Cannot use 'tablist update' to create a non-empty named entry: 'Allow restricted actions' is disabled in Denizen config.yml.");
return;
}
if (display != null) {
NMSHandler.getPlayerHelper().sendPlayerInfoAddPacket(player, PlayerHelper.ProfileEditMode.UPDATE_DISPLAY, CoreUtilities.stringifyNullPass(name), CoreUtilities.stringifyNullPass(display), id, texture, signature, latencyNum, gameModeBukkit);
}
if (latency != null) {
NMSHandler.getPlayerHelper().sendPlayerInfoAddPacket(player, PlayerHelper.ProfileEditMode.UPDATE_LATENCY, CoreUtilities.stringifyNullPass(name), CoreUtilities.stringifyNullPass(display), id, texture, signature, latencyNum, gameModeBukkit);
}
break;
}
}
}
Expand Up @@ -18,11 +18,13 @@
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.mojang.authlib.GameProfile;
import com.denizenscript.denizen.nms.abstracts.ImprovedOfflinePlayer;
import com.denizenscript.denizen.nms.interfaces.PlayerHelper;
import com.denizenscript.denizencore.utilities.ReflectionHelper;
import com.denizenscript.denizencore.utilities.debugging.Debug;
import com.mojang.authlib.properties.Property;
import net.md_5.bungee.api.ChatColor;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
Expand All @@ -39,6 +41,7 @@
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.GameType;
import org.bukkit.*;
import org.bukkit.boss.BossBar;
import org.bukkit.craftbukkit.v1_18_R2.CraftServer;
Expand Down Expand Up @@ -394,4 +397,25 @@ public void setSpawnForced(Player player, boolean forced) {
public long getLastActionTime(Player player) {
return ((CraftPlayer) player).getHandle().getLastActionTime();
}

@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);
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))));
PacketHelperImpl.send(player, packet);
}

@Override
public void sendPlayerRemovePacket(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));
PacketHelperImpl.send(player, packet);
}
}

0 comments on commit 83ea30d

Please sign in to comment.