Add new armorstand option for player disguise names, clean up config cos of the player name options
Added ability to set multiple names on a player disguise, also normal mob but that is untested and has no command accessibility Permission to do this is libsdisguises.multiname Renamed "Now disguised as a %s" to "Now disguised as %s" for messages
This commit is contained in:
		| @@ -95,9 +95,6 @@ public class DisguiseConfig { | ||||
|     private static boolean explicitDisguisePermissions; | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private static boolean extendedDisguiseNames; | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private static boolean hideDisguisedPlayers; | ||||
|     @Getter | ||||
|     @Setter | ||||
| @@ -228,9 +225,6 @@ public class DisguiseConfig { | ||||
|     private static PermissionDefault commandVisibility = PermissionDefault.TRUE; | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private static boolean scoreboardDisguiseNames; | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private static int tablistRemoveDelay; | ||||
|     @Getter | ||||
|     private static boolean usingReleaseBuild = true; | ||||
| @@ -246,6 +240,17 @@ public class DisguiseConfig { | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private static boolean hideTallSelfDisguises; | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private static PlayerNameType playerNameType = PlayerNameType.ARMORSTANDS; | ||||
|  | ||||
|     public static boolean isArmorstandsName() { | ||||
|         return getPlayerNameType() == PlayerNameType.ARMORSTANDS; | ||||
|     } | ||||
|  | ||||
|     public static boolean isExtendedNames() { | ||||
|         return getPlayerNameType() == PlayerNameType.TEAMS_EXTENDED; | ||||
|     } | ||||
|  | ||||
|     public static void setAutoUpdate(boolean update) { | ||||
|         if (isAutoUpdate() == update) { | ||||
| @@ -447,6 +452,10 @@ public class DisguiseConfig { | ||||
|         return new HashMap.SimpleEntry(entry.getKey(), DisguiseParser.parseDisguise(invoker, target, entry.getValue())); | ||||
|     } | ||||
|  | ||||
|     public static boolean isScoreboardNames() { | ||||
|         return getPlayerNameType() != PlayerNameType.VANILLA; | ||||
|     } | ||||
|  | ||||
|     public static void removeCustomDisguise(String disguise) { | ||||
|         for (DisguisePerm entry : customDisguises.keySet()) { | ||||
|             String name = entry.toReadable(); | ||||
| @@ -596,8 +605,6 @@ public class DisguiseConfig { | ||||
|         setEntityStatusPacketsEnabled(config.getBoolean("PacketsEnabled.EntityStatus")); | ||||
|         setEquipmentPacketsEnabled(config.getBoolean("PacketsEnabled.Equipment")); | ||||
|         setExplicitDisguisePermissions(config.getBoolean("Permissions.ExplicitDisguises")); | ||||
|         // The default value shall be false if you don't update config | ||||
|         setExtendedDisguiseNames(config.contains("ScoreboardNames") && config.getBoolean("ExtendedNames")); | ||||
|         setHideArmorFromSelf(config.getBoolean("RemoveArmor")); | ||||
|         setHideDisguisedPlayers(config.getBoolean("HideDisguisedPlayersFromTab")); | ||||
|         setHideHeldItemFromSelf(config.getBoolean("RemoveHeldItem")); | ||||
| @@ -634,7 +641,6 @@ public class DisguiseConfig { | ||||
|         setWarnScoreboardConflict(config.getBoolean("Scoreboard.WarnConflict")); | ||||
|         setWitherSkullPacketsEnabled(config.getBoolean("PacketsEnabled.WitherSkull")); | ||||
|         setWolfDyeable(config.getBoolean("DyeableWolf")); | ||||
|         setScoreboardDisguiseNames(config.getBoolean("ScoreboardNames")); | ||||
|         setTablistRemoveDelay(config.getInt("TablistRemoveDelay")); | ||||
|         setAutoUpdate(config.getBoolean("AutoUpdate")); | ||||
|         setHideTallSelfDisguises(config.getBoolean("HideTallSelfDisguises")); | ||||
| @@ -643,6 +649,14 @@ public class DisguiseConfig { | ||||
|             DisguiseUtilities.getLogger().warning("You must purchase the plugin to use saved disguises!"); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             setPlayerNameType(PlayerNameType.valueOf(config.getString("PlayerNames").toUpperCase())); | ||||
|         } | ||||
|         catch (Exception ex) { | ||||
|             DisguiseUtilities.getLogger() | ||||
|                     .warning("Cannot parse '" + config.getString("PlayerNames") + "' to a valid option for PlayerNames"); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             setNotifyBar(NotifyBar.valueOf(config.getString("NotifyBar").toUpperCase())); | ||||
|  | ||||
| @@ -1077,6 +1091,17 @@ public class DisguiseConfig { | ||||
|         CREATE_SCOREBOARD | ||||
|     } | ||||
|  | ||||
|     public enum PlayerNameType { | ||||
|         VANILLA, | ||||
|         TEAMS, | ||||
|         TEAMS_EXTENDED, | ||||
|         ARMORSTANDS; | ||||
|  | ||||
|         public boolean isTeams() { | ||||
|             return this == TEAMS || this == TEAMS_EXTENDED; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public enum UpdatesBranch { | ||||
|         SAME_BUILDS, | ||||
|         SNAPSHOTS, | ||||
|   | ||||
| @@ -36,7 +36,7 @@ public class LDScoreboard implements LDCommand { | ||||
|  | ||||
|     @Override | ||||
|     public void onCommand(CommandSender sender, String[] args) { | ||||
|         if (DisguiseConfig.isScoreboardDisguiseNames()) { | ||||
|         if (DisguiseConfig.isScoreboardNames()) { | ||||
|             int issuesFound = 0; | ||||
|             int unexpected = 0; | ||||
|  | ||||
|   | ||||
| @@ -96,16 +96,59 @@ public abstract class Disguise { | ||||
|      */ | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private boolean customName = true; | ||||
|     private boolean customDisguiseName = true; | ||||
|     @Getter | ||||
|     @Setter | ||||
|     private boolean tallDisguisesVisible = !DisguiseConfig.isHideTallSelfDisguises(); | ||||
|     @Getter | ||||
|     private String[] multiName = new String[0]; | ||||
|     private transient int[] armorstandIds = new int[0]; | ||||
|  | ||||
|     public Disguise(DisguiseType disguiseType) { | ||||
|         this.disguiseType = disguiseType; | ||||
|         this.disguiseName = disguiseType.toReadable(); | ||||
|     } | ||||
|  | ||||
|     public void setMultiName(String... name) { | ||||
|         String[] oldName = getMultiName(); | ||||
|         multiName = name; | ||||
|  | ||||
|         if (!isDisguiseInUse()) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         sendArmorStands(oldName); | ||||
|     } | ||||
|  | ||||
|     protected void sendArmorStands(String[] oldName) { | ||||
|         ArrayList<PacketContainer> packets = DisguiseUtilities.getNamePackets(this, oldName); | ||||
|  | ||||
|         try { | ||||
|             for (Player player : DisguiseUtilities.getPerverts(this)) { | ||||
|                 for (PacketContainer packet : packets) { | ||||
|                     ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         catch (InvocationTargetException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public int[] getArmorstandIds() { | ||||
|         if (getMultiName().length > armorstandIds.length) { | ||||
|             int oldLen = armorstandIds.length; | ||||
|  | ||||
|             armorstandIds = Arrays.copyOf(armorstandIds, getMultiName().length); | ||||
|  | ||||
|             for (int i = oldLen; i < armorstandIds.length; i++) { | ||||
|                 armorstandIds[i] = ReflectionManager.getNewEntityId(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return armorstandIds; | ||||
|     } | ||||
|  | ||||
|     public void addCustomData(String key, Object data) { | ||||
|         customData.put(key, data); | ||||
|     } | ||||
| @@ -123,7 +166,7 @@ public abstract class Disguise { | ||||
|  | ||||
|     protected void clone(Disguise disguise) { | ||||
|         disguise.setDisguiseName(getDisguiseName()); | ||||
|         disguise.setCustomName(isCustomName()); | ||||
|         disguise.setCustomDisguiseName(isCustomDisguiseName()); | ||||
|         disguise.setTallDisguisesVisible(isTallDisguisesVisible()); | ||||
|  | ||||
|         disguise.setReplaceSounds(isSoundsReplaced()); | ||||
| @@ -133,6 +176,7 @@ public abstract class Disguise { | ||||
|         disguise.setHideHeldItemFromSelf(isHidingHeldItemFromSelf()); | ||||
|         disguise.setVelocitySent(isVelocitySent()); | ||||
|         disguise.setModifyBoundingBox(isModifyBoundingBox()); | ||||
|         disguise.multiName = Arrays.copyOf(multiName, multiName.length); | ||||
|  | ||||
|         if (getWatcher() != null) { | ||||
|             disguise.setWatcher(getWatcher().clone(disguise)); | ||||
| @@ -795,20 +839,13 @@ public abstract class Disguise { | ||||
|             task = null; | ||||
|         } | ||||
|  | ||||
|         // If this disguise has a entity set | ||||
|         // If this disguise hasn't a entity set | ||||
|         if (getEntity() == null) { | ||||
|             // Loop through the disguises because it could be used with a unknown entity id. | ||||
|             HashMap<Integer, HashSet<TargetedDisguise>> future = DisguiseUtilities.getFutureDisguises(); | ||||
|  | ||||
|             Iterator<Integer> itel = DisguiseUtilities.getFutureDisguises().keySet().iterator(); | ||||
|  | ||||
|             while (itel.hasNext()) { | ||||
|                 int id = itel.next(); | ||||
|  | ||||
|                 if (future.get(id).remove(this) && future.get(id).isEmpty()) { | ||||
|                     itel.remove(); | ||||
|                 } | ||||
|             } | ||||
|             DisguiseUtilities.getFutureDisguises().keySet() | ||||
|                     .removeIf(id -> future.get(id).remove(this) && future.get(id).isEmpty()); | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
| @@ -842,7 +879,7 @@ public abstract class Disguise { | ||||
|         // Remove the disguise from the current disguises. | ||||
|         if (DisguiseUtilities.removeDisguise((TargetedDisguise) this)) { | ||||
|             if (getEntity() instanceof Player) { | ||||
|                 DisguiseUtilities.removeSelfDisguise((Player) getEntity()); | ||||
|                 DisguiseUtilities.removeSelfDisguise(this); | ||||
|             } | ||||
|  | ||||
|             // Better refresh the entity to undisguise it | ||||
| @@ -893,6 +930,20 @@ public abstract class Disguise { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (getMultiName().length > 0) { | ||||
|             PacketContainer packet = new PacketContainer(Server.ENTITY_DESTROY); | ||||
|             packet.getIntegerArrays().write(0, Arrays.copyOf(getArmorstandIds(), getMultiName().length)); | ||||
|  | ||||
|             try { | ||||
|                 for (Player player : DisguiseUtilities.getPerverts(this)) { | ||||
|                     ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); | ||||
|                 } | ||||
|             } | ||||
|             catch (InvocationTargetException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| @@ -970,7 +1021,7 @@ public abstract class Disguise { | ||||
|                     if (isSelfDisguiseVisible()) { | ||||
|                         DisguiseUtilities.setupFakeDisguise(this); | ||||
|                     } else { | ||||
|                         DisguiseUtilities.removeSelfDisguise((Player) getEntity()); | ||||
|                         DisguiseUtilities.removeSelfDisguise(this); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -1053,7 +1104,7 @@ public abstract class Disguise { | ||||
|         DisguiseUtilities.addDisguise(entity.getUniqueId(), (TargetedDisguise) this); | ||||
|  | ||||
|         if (isSelfDisguiseVisible() && getEntity() instanceof Player) { | ||||
|             DisguiseUtilities.removeSelfDisguise((Player) getEntity()); | ||||
|             DisguiseUtilities.removeSelfDisguise(this); | ||||
|         } | ||||
|  | ||||
|         // Resend the disguised entity's packet | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import me.libraryaddict.disguise.disguisetypes.watchers.PlayerWatcher; | ||||
| import me.libraryaddict.disguise.utilities.DisguiseUtilities; | ||||
| import me.libraryaddict.disguise.utilities.reflection.LibsProfileLookup; | ||||
| import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; | ||||
| import org.apache.commons.lang.Validate; | ||||
| import org.bukkit.Bukkit; | ||||
| import org.bukkit.entity.Entity; | ||||
| import org.bukkit.entity.Player; | ||||
| @@ -27,6 +26,10 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|     private String playerName = "Herobrine"; | ||||
|     private String skinToUse; | ||||
|     private boolean nameVisible = true; | ||||
|     /** | ||||
|      * Has someone set name visible explicitly? | ||||
|      */ | ||||
|     private boolean explicitNameVisible = false; | ||||
|     private UUID uuid = UUID.randomUUID(); | ||||
|     @Getter | ||||
|     @Setter | ||||
| @@ -82,8 +85,7 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|  | ||||
|     @Deprecated | ||||
|     public DisguiseUtilities.DScoreTeam getScoreboardName() { | ||||
|         if (getName().length() <= 32 ? !DisguiseConfig.isScoreboardDisguiseNames() : | ||||
|                 !DisguiseConfig.isExtendedDisguiseNames()) { | ||||
|         if (!DisguiseConfig.isScoreboardNames()) { | ||||
|             throw new IllegalStateException("Cannot use this method when it's been disabled in config!"); | ||||
|         } | ||||
|  | ||||
| @@ -103,11 +105,11 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|     } | ||||
|  | ||||
|     public boolean hasScoreboardName() { | ||||
|         if (isStaticName(getName())) { | ||||
|         if (!DisguiseConfig.isArmorstandsName() && isStaticName(getName())) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return scoreboardName != null || DisguiseConfig.isScoreboardDisguiseNames() || getName().length() > 16; | ||||
|         return DisguiseConfig.isScoreboardNames(); | ||||
|     } | ||||
|  | ||||
|     public String getProfileName() { | ||||
| @@ -123,12 +125,23 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|     } | ||||
|  | ||||
|     public PlayerDisguise setNameVisible(boolean nameVisible) { | ||||
|         if (isNameVisible() == nameVisible) { | ||||
|         return setNameVisible(nameVisible, false); | ||||
|     } | ||||
|  | ||||
|     private PlayerDisguise setNameVisible(boolean nameVisible, boolean setInternally) { | ||||
|         if (isNameVisible() == nameVisible || (setInternally && explicitNameVisible)) { | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         if (!setInternally) { | ||||
|             explicitNameVisible = true; | ||||
|         } | ||||
|  | ||||
|         if (isDisguiseInUse()) { | ||||
|             if (!DisguiseConfig.isScoreboardDisguiseNames()) { | ||||
|             if (DisguiseConfig.isArmorstandsName()) { | ||||
|                 this.nameVisible = nameVisible; | ||||
|                 sendArmorStands(isNameVisible() ? getMultiName() : new String[0]); | ||||
|             } else if (!DisguiseConfig.isScoreboardNames()) { | ||||
|                 if (stopDisguise()) { | ||||
|                     this.nameVisible = nameVisible; | ||||
|  | ||||
| @@ -172,7 +185,8 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|         } | ||||
|  | ||||
|         disguise.setName(getName()); | ||||
|         disguise.setNameVisible(isNameVisible()); | ||||
|         disguise.nameVisible = isNameVisible(); | ||||
|         disguise.explicitNameVisible = explicitNameVisible; | ||||
|         disguise.setDynamicName(isDynamicName()); | ||||
|  | ||||
|         clone(disguise); | ||||
| @@ -214,16 +228,37 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         int cLimit = DisguiseConfig.isExtendedDisguiseNames() ? 16 * 3 : | ||||
|                 DisguiseConfig.isScoreboardDisguiseNames() ? 16 * 2 : 16; | ||||
|         int cLimit; | ||||
|  | ||||
|         switch (DisguiseConfig.getPlayerNameType()) { | ||||
|             case TEAMS: | ||||
|                 cLimit = 16 * 2; | ||||
|                 break; | ||||
|             case TEAMS_EXTENDED: | ||||
|                 cLimit = 16 * 3; | ||||
|                 break; | ||||
|             case ARMORSTANDS: | ||||
|                 cLimit = 256; | ||||
|                 break; | ||||
|             default: | ||||
|                 cLimit = 16; | ||||
|                 break; | ||||
|         } | ||||
|  | ||||
|         if (name.length() > cLimit) { | ||||
|             name = name.substring(0, cLimit); | ||||
|         } | ||||
|  | ||||
|         if (isDisguiseInUse()) { | ||||
|             if (DisguiseConfig.isArmorstandsName()) { | ||||
|                 playerName = name; | ||||
|  | ||||
|                 setNameVisible(!name.isEmpty(), true); | ||||
|                 setMultiName(DisguiseUtilities.splitNewLine(name)); | ||||
|             } else { | ||||
|                 boolean resendDisguise = false; | ||||
|  | ||||
|             if (DisguiseConfig.isScoreboardDisguiseNames() && !isStaticName(name)) { | ||||
|                 if (DisguiseConfig.isScoreboardNames() && !isStaticName(name)) { | ||||
|                     DisguiseUtilities.DScoreTeam team = getScoreboardName(); | ||||
|                     String[] split = DisguiseUtilities.getExtendedNameSplit(team.getPlayer(), name); | ||||
|  | ||||
| @@ -231,16 +266,15 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|                     setScoreboardName(split); | ||||
|                 } | ||||
|  | ||||
|             resendDisguise = | ||||
|                     !DisguiseConfig.isScoreboardDisguiseNames() || isStaticName(name) || isStaticName(getName()) || | ||||
|                 resendDisguise = !DisguiseConfig.isScoreboardNames() || isStaticName(name) || isStaticName(getName()) || | ||||
|                         resendDisguise; | ||||
|  | ||||
|                 if (resendDisguise) { | ||||
|                     if (stopDisguise()) { | ||||
|                         if (getName().isEmpty() && !name.isEmpty()) { | ||||
|                         setNameVisible(true); | ||||
|                             setNameVisible(true, true); | ||||
|                         } else if (!getName().isEmpty() && name.isEmpty()) { | ||||
|                         setNameVisible(false); | ||||
|                             setNameVisible(false, true); | ||||
|                         } | ||||
|  | ||||
|                         playerName = name; | ||||
| @@ -258,15 +292,16 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (getName().isEmpty() && !name.isEmpty() && !isNameVisible()) { | ||||
|                     setNameVisible(true); | ||||
|                         setNameVisible(true, true); | ||||
|                     } else if (!getName().isEmpty() && name.isEmpty() && isNameVisible()) { | ||||
|                     setNameVisible(false); | ||||
|                         setNameVisible(false, true); | ||||
|                     } else { | ||||
|                         DisguiseUtilities.updateExtendedName(this); | ||||
|                     } | ||||
|  | ||||
|                     playerName = name; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (isDisplayedInTab()) { | ||||
|                 PacketContainer addTab = DisguiseUtilities.getTabPacket(this, PlayerInfoAction.UPDATE_DISPLAY_NAME); | ||||
| @@ -291,12 +326,11 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|                 setScoreboardName(split); | ||||
|             } | ||||
|  | ||||
|             if (name.isEmpty()) { | ||||
|                 setNameVisible(false); | ||||
|             } else { | ||||
|                 setNameVisible(true); | ||||
|             if (DisguiseConfig.isScoreboardNames()) { | ||||
|                 setMultiName(DisguiseUtilities.splitNewLine(name)); | ||||
|             } | ||||
|  | ||||
|             setNameVisible(!name.isEmpty(), true); | ||||
|             playerName = name; | ||||
|  | ||||
|             if (gameProfile != null) { | ||||
| @@ -310,19 +344,23 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|     } | ||||
|  | ||||
|     public PlayerDisguise setSkin(String newSkin) { | ||||
|         if (newSkin != null && newSkin.length() > 70) { | ||||
|         if (newSkin != null && newSkin.length() > 70 && newSkin.startsWith("{") && newSkin.endsWith("}")) { | ||||
|             try { | ||||
|                 return setSkin(DisguiseUtilities.getGson().fromJson(newSkin, WrappedGameProfile.class)); | ||||
|             } | ||||
|             catch (Exception ex) { | ||||
|                 if (!"12345".equals("%%__USER__%%")) { | ||||
|                     throw new IllegalArgumentException(String.format( | ||||
|                             "The skin %s is too long to be a playername, but cannot be parsed to a GameProfile!", | ||||
|                             newSkin)); | ||||
|                             "The skin %s is too long to normally be a playername, but cannot be parsed to a " + | ||||
|                                     "GameProfile!", newSkin)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (newSkin != null && newSkin.length() > 16) { | ||||
|             newSkin = null; | ||||
|         } | ||||
|  | ||||
|         String oldSkin = skinToUse; | ||||
|         skinToUse = newSkin; | ||||
|  | ||||
| @@ -376,8 +414,6 @@ public class PlayerDisguise extends TargetedDisguise { | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         Validate.notEmpty(gameProfile.getName(), "Name must be set"); | ||||
|  | ||||
|         currentLookup = null; | ||||
|  | ||||
|         this.skinToUse = gameProfile.getName(); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ public class DroppedItemWatcher extends FlagWatcher { | ||||
|         setData(MetaIndex.DROPPED_ITEM, item); | ||||
|         sendData(MetaIndex.DROPPED_ITEM); | ||||
|  | ||||
|         if (!getDisguise().isCustomName()) { | ||||
|         if (!getDisguise().isCustomDisguiseName()) { | ||||
|             getDisguise().setDisguiseName(TranslateType.DISGUISES.get(DisguiseType.DROPPED_ITEM.toReadable()) + " " + | ||||
|                     TranslateType.DISGUISE_OPTIONS_PARAMETERS | ||||
|                             .get(ReflectionManager.toReadable((item == null ? Material.AIR : item.getType()).name()))); | ||||
|   | ||||
| @@ -87,7 +87,7 @@ public class FallingBlockWatcher extends FlagWatcher { | ||||
|  | ||||
|         this.block = block; | ||||
|  | ||||
|         if (!getDisguise().isCustomName()) { | ||||
|         if (!getDisguise().isCustomDisguiseName()) { | ||||
|             getDisguise().setDisguiseName(TranslateType.DISGUISE_OPTIONS_PARAMETERS.get("Block") + " " + | ||||
|                     TranslateType.DISGUISE_OPTIONS_PARAMETERS | ||||
|                             .get(ReflectionManager.toReadable(block.getType().name()))); | ||||
|   | ||||
| @@ -22,6 +22,7 @@ import me.libraryaddict.disguise.LibsDisguises; | ||||
| import me.libraryaddict.disguise.disguisetypes.*; | ||||
| import me.libraryaddict.disguise.disguisetypes.TargetedDisguise.TargetType; | ||||
| import me.libraryaddict.disguise.disguisetypes.watchers.AgeableWatcher; | ||||
| import me.libraryaddict.disguise.disguisetypes.watchers.LivingWatcher; | ||||
| import me.libraryaddict.disguise.disguisetypes.watchers.ZombieWatcher; | ||||
| import me.libraryaddict.disguise.utilities.json.*; | ||||
| import me.libraryaddict.disguise.utilities.mineskin.MineSkinAPI; | ||||
| @@ -84,6 +85,7 @@ public class DisguiseUtilities { | ||||
|         } | ||||
|  | ||||
|         public void handleTeam(Scoreboard board, boolean nameVisible) { | ||||
|             nameVisible = !DisguiseConfig.isArmorstandsName() && nameVisible; | ||||
|             Team team = board.getTeam(getTeamName()); | ||||
|  | ||||
|             if (team == null) { | ||||
| @@ -1037,7 +1039,7 @@ public class DisguiseUtilities { | ||||
|  | ||||
|             if (disguise.isDisguiseInUse() && disguise.getEntity() instanceof Player && | ||||
|                     disguise.getEntity().getName().equalsIgnoreCase(player)) { | ||||
|                 removeSelfDisguise((Player) disguise.getEntity()); | ||||
|                 removeSelfDisguise(disguise); | ||||
|  | ||||
|                 if (disguise.isSelfDisguiseVisible()) { | ||||
|                     selfDisguised.add(disguise.getEntity().getUniqueId()); | ||||
| @@ -1167,7 +1169,7 @@ public class DisguiseUtilities { | ||||
|  | ||||
|         try { | ||||
|             if (selfDisguised.contains(disguise.getEntity().getUniqueId()) && disguise.isDisguiseInUse()) { | ||||
|                 removeSelfDisguise((Player) disguise.getEntity()); | ||||
|                 removeSelfDisguise(disguise); | ||||
|  | ||||
|                 selfDisguised.add(disguise.getEntity().getUniqueId()); | ||||
|  | ||||
| @@ -1257,16 +1259,21 @@ public class DisguiseUtilities { | ||||
|         file.delete(); | ||||
|     } | ||||
|  | ||||
|     public static void removeSelfDisguise(Player player) { | ||||
|     public static void removeSelfDisguise(Disguise disguise) { | ||||
|         if (!Bukkit.isPrimaryThread()) | ||||
|             throw new IllegalStateException("Cannot modify disguises on an async thread"); | ||||
|  | ||||
|         Player player = (Player) disguise.getEntity(); | ||||
|  | ||||
|         if (!selfDisguised.contains(player.getUniqueId())) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         int[] ids = Arrays.copyOf(disguise.getArmorstandIds(), 1 + disguise.getMultiName().length); | ||||
|         ids[ids.length - 1] = DisguiseAPI.getSelfDisguiseId(); | ||||
|  | ||||
|         // Send a packet to destroy the fake entity | ||||
|         PacketContainer packet = getDestroyPacket(DisguiseAPI.getSelfDisguiseId()); | ||||
|         PacketContainer packet = getDestroyPacket(ids); | ||||
|  | ||||
|         try { | ||||
|             ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); | ||||
| @@ -1439,7 +1446,7 @@ public class DisguiseUtilities { | ||||
|     } | ||||
|  | ||||
|     public static String[] getExtendedNameSplit(String playerName, String name) { | ||||
|         if (name.length() <= 16 && !DisguiseConfig.isScoreboardDisguiseNames()) { | ||||
|         if (name.length() <= 16 && !DisguiseConfig.isScoreboardNames()) { | ||||
|             throw new IllegalStateException("This can only be used for names longer than 16 characters!"); | ||||
|         } | ||||
|  | ||||
| @@ -1450,7 +1457,7 @@ public class DisguiseUtilities { | ||||
|         Scoreboard board = Bukkit.getScoreboardManager().getMainScoreboard(); | ||||
|  | ||||
|         // If name is short enough to be used outside of player name | ||||
|         if (DisguiseConfig.isScoreboardDisguiseNames() && name.length() <= 32) { | ||||
|         if (DisguiseConfig.isScoreboardNames() && name.length() <= 32) { | ||||
|             String[] newName = new String[]{name, playerName, ""}; | ||||
|  | ||||
|             if (name.length() > 16) { | ||||
| @@ -1749,6 +1756,39 @@ public class DisguiseUtilities { | ||||
|                 "\""; | ||||
|     } | ||||
|  | ||||
|     public static String quoteNewLine(String string) { | ||||
|         return string.replaceAll("\\\\(?=\\\\+n)", "\\\\\\\\"); | ||||
|     } | ||||
|  | ||||
|     public static String[] splitNewLine(String string) { | ||||
|         Pattern regex = Pattern.compile("\\\\+n"); | ||||
|         Matcher result = regex.matcher(string); | ||||
|  | ||||
|         ArrayList<String> lines = new ArrayList<>(); | ||||
|         StringBuilder builder = new StringBuilder(); | ||||
|         int last = 0; | ||||
|  | ||||
|         while (result.find()) { | ||||
|             builder.append(string, last, result.start()); | ||||
|             last = result.end(); | ||||
|  | ||||
|             if (result.group().matches("(\\\\\\\\)+n")) { | ||||
|                 builder.append(result.group().replace("\\\\", "\\")); | ||||
|             } else { | ||||
|                 String group = result.group().replace("\\\\", "\\"); | ||||
|  | ||||
|                 builder.append(group, 0, group.length() - 2); | ||||
|  | ||||
|                 lines.add(builder.toString()); | ||||
|                 builder = new StringBuilder(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         lines.add(builder.toString() + string.substring(last)); | ||||
|  | ||||
|         return lines.toArray(new String[0]); | ||||
|     } | ||||
|  | ||||
|     public static String[] split(String string) { | ||||
|         // Regex where we first match any character that isn't a slash, if it is a slash then it must not have more | ||||
|         // slashes until it hits the quote | ||||
| @@ -2020,7 +2060,7 @@ public class DisguiseUtilities { | ||||
|         } | ||||
|  | ||||
|         // Remove the old disguise, else we have weird disguises around the place | ||||
|         DisguiseUtilities.removeSelfDisguise(player); | ||||
|         DisguiseUtilities.removeSelfDisguise(disguise); | ||||
|  | ||||
|         // If the disguised player can't see himself. Return | ||||
|         if (!disguise.isSelfDisguiseVisible() || !PacketsManager.isViewDisguisesListenerEnabled() || | ||||
| @@ -2237,6 +2277,100 @@ public class DisguiseUtilities { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static ArrayList<PacketContainer> getNamePackets(Disguise disguise, String[] oldNames) { | ||||
|         ArrayList<PacketContainer> packets = new ArrayList<>(); | ||||
|         String[] newNames = | ||||
|                 (disguise instanceof PlayerDisguise && !((PlayerDisguise) disguise).isNameVisible()) ? new String[0] : | ||||
|                         disguise.getMultiName(); | ||||
|         int[] standIds = disguise.getArmorstandIds(); | ||||
|         int[] destroyIds = new int[0]; | ||||
|  | ||||
|         if (oldNames.length > newNames.length) { | ||||
|             // Destroy packet | ||||
|             destroyIds = Arrays.copyOfRange(standIds, newNames.length, oldNames.length); | ||||
|         } | ||||
|  | ||||
|         for (int i = 0; i < newNames.length; i++) { | ||||
|             if (i < oldNames.length) { | ||||
|                 if (newNames[i].equals(oldNames[i]) || newNames[i].isEmpty()) { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 WrappedDataWatcher watcher = new WrappedDataWatcher(); | ||||
|  | ||||
|                 Object name = NmsVersion.v1_13.isSupported() ? Optional.of(WrappedChatComponent.fromText(newNames[i])) : | ||||
|                         newNames[i]; | ||||
|  | ||||
|                 WrappedDataWatcher.WrappedDataWatcherObject obj = ReflectionManager.createDataWatcherObject( | ||||
|                         NmsVersion.v1_13.isSupported() ? MetaIndex.ENTITY_CUSTOM_NAME : | ||||
|                                 MetaIndex.ENTITY_CUSTOM_NAME_OLD, name); | ||||
|  | ||||
|                 watcher.setObject(obj, ReflectionManager.convertInvalidMeta(name)); | ||||
|  | ||||
|                 PacketContainer metaPacket = ProtocolLibrary.getProtocolManager() | ||||
|                         .createPacketConstructor(PacketType.Play.Server.ENTITY_METADATA, 0, watcher, true) | ||||
|                         .createPacket(standIds[i], watcher, true); | ||||
|  | ||||
|                 packets.add(metaPacket); | ||||
|             } else if (newNames[i].isEmpty()) { | ||||
|                 destroyIds = Arrays.copyOf(destroyIds, destroyIds.length + 1); | ||||
|                 destroyIds[destroyIds.length - 1] = standIds[i]; | ||||
|             } else { | ||||
|                 PacketContainer packet = new PacketContainer(Server.SPAWN_ENTITY_LIVING); | ||||
|                 packet.getIntegers().write(0, standIds[i]); | ||||
|                 packet.getIntegers().write(1, DisguiseType.ARMOR_STAND.getTypeId()); | ||||
|  | ||||
|                 packet.getUUIDs().write(0, UUID.randomUUID()); | ||||
|  | ||||
|                 Location loc = disguise.getEntity().getLocation(); | ||||
|  | ||||
|                 packet.getDoubles().write(0, loc.getX()); | ||||
|                 packet.getDoubles().write(1, loc.getY() + -0.175 + (0.28 * i)); | ||||
|                 packet.getDoubles().write(2, loc.getZ()); | ||||
|                 packets.add(packet); | ||||
|  | ||||
|                 WrappedDataWatcher watcher = new WrappedDataWatcher(); | ||||
|  | ||||
|                 for (MetaIndex index : MetaIndex.getMetaIndexes(LivingWatcher.class)) { | ||||
|                     Object val = index.getDefault(); | ||||
|  | ||||
|                     if (index == MetaIndex.ENTITY_META) { | ||||
|                         val = (byte) 32; | ||||
|                     } else if (index == MetaIndex.ARMORSTAND_META) { | ||||
|                         val = (byte) 17; | ||||
|                     } else if (index == MetaIndex.ENTITY_CUSTOM_NAME) { | ||||
|                         val = Optional.of(WrappedChatComponent.fromText(newNames[i])); | ||||
|                     } else if (index == MetaIndex.ENTITY_CUSTOM_NAME_OLD) { | ||||
|                         val = newNames[i]; | ||||
|                     } else if (index == MetaIndex.ENTITY_CUSTOM_NAME_VISIBLE) { | ||||
|                         val = true; | ||||
|                     } | ||||
|  | ||||
|                     WrappedDataWatcher.WrappedDataWatcherObject obj = ReflectionManager | ||||
|                             .createDataWatcherObject(index, val); | ||||
|  | ||||
|                     watcher.setObject(obj, ReflectionManager.convertInvalidMeta(val)); | ||||
|                 } | ||||
|  | ||||
|                 if (NmsVersion.v1_15.isSupported()) { | ||||
|                     PacketContainer metaPacket = ProtocolLibrary.getProtocolManager() | ||||
|                             .createPacketConstructor(PacketType.Play.Server.ENTITY_METADATA, 0, watcher, true) | ||||
|                             .createPacket(standIds[i], watcher, true); | ||||
|  | ||||
|                     packets.add(metaPacket); | ||||
|                 } else { | ||||
|                     packet.getDataWatcherModifier().write(0, watcher); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (destroyIds.length > 0) { | ||||
|             packets.add(getDestroyPacket(destroyIds)); | ||||
|         } | ||||
|  | ||||
|         return packets; | ||||
|     } | ||||
|  | ||||
|     public static Disguise getDisguise(Player observer, int entityId) { | ||||
|         // If the entity ID is the same as self disguises id, then it needs to be set to the observers id | ||||
|         if (entityId == DisguiseAPI.getSelfDisguiseId()) { | ||||
|   | ||||
| @@ -583,13 +583,20 @@ public class DisguiseListener implements Listener { | ||||
|  | ||||
|     @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | ||||
|     public void onVehicleEnter(VehicleEnterEvent event) { | ||||
|         if (event.getEntered() instanceof Player && | ||||
|                 DisguiseAPI.isDisguised((Player) event.getEntered(), event.getEntered())) { | ||||
|             DisguiseUtilities.removeSelfDisguise((Player) event.getEntered()); | ||||
|         if (!(event.getEntered() instanceof Player)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Disguise disguise = DisguiseAPI.getDisguise((Player) event.getEntered(), event.getEntered()); | ||||
|  | ||||
|         if (disguise == null) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         DisguiseUtilities.removeSelfDisguise(disguise); | ||||
|  | ||||
|         ((Player) event.getEntered()).updateInventory(); | ||||
|     } | ||||
|     } | ||||
|  | ||||
|     @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) | ||||
|     public void onVehicleLeave(VehicleExitEvent event) { | ||||
|   | ||||
| @@ -25,6 +25,7 @@ public class PacketsManager { | ||||
|     private static PacketListener viewDisguisesListener; | ||||
|     private static boolean viewDisguisesListenerEnabled; | ||||
|     private static PacketsHandler packetsHandler; | ||||
|     private static PacketListener destroyListener; | ||||
|  | ||||
|     public static void addPacketListeners() { | ||||
|         // Add a client listener to cancel them interacting with uninteractable disguised entitys. | ||||
| @@ -160,8 +161,10 @@ public class PacketsManager { | ||||
|             } | ||||
|  | ||||
|             mainListener = new PacketListenerMain(LibsDisguises.getInstance(), packetsToListen); | ||||
|             destroyListener = new PacketListenerDestroyEntity(LibsDisguises.getInstance()); | ||||
|  | ||||
|             ProtocolLibrary.getProtocolManager().addPacketListener(mainListener); | ||||
|             ProtocolLibrary.getProtocolManager().addPacketListener(destroyListener); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -183,7 +186,7 @@ public class PacketsManager { | ||||
|                         if (enabled) { | ||||
|                             DisguiseUtilities.setupFakeDisguise(disguise); | ||||
|                         } else { | ||||
|                             DisguiseUtilities.removeSelfDisguise(player); | ||||
|                             DisguiseUtilities.removeSelfDisguise(disguise); | ||||
|                         } | ||||
|  | ||||
|                         if (inventoryModifierEnabled && | ||||
|   | ||||
| @@ -18,6 +18,8 @@ import org.bukkit.entity.Player; | ||||
| import org.bukkit.metadata.FixedMetadataValue; | ||||
| import org.bukkit.util.Vector; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
|  | ||||
| /** | ||||
|  * Created by libraryaddict on 3/01/2019. | ||||
|  */ | ||||
| @@ -38,6 +40,36 @@ public class PacketHandlerMovement implements IPacketHandler { | ||||
|     @Override | ||||
|     public void handle(Disguise disguise, PacketContainer sentPacket, LibsPackets packets, Player observer, | ||||
|             Entity entity) { | ||||
|         handle2(disguise, sentPacket, packets, observer, entity); | ||||
|  | ||||
|         int len = disguise.getMultiName().length; | ||||
|  | ||||
|         if (len == 0) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         ArrayList<PacketContainer> toAdd = new ArrayList<>(); | ||||
|  | ||||
|         for (PacketContainer packet : packets.getPackets()) { | ||||
|             for (int i = 0; i < len; i++) { | ||||
|                 for (int standId : disguise.getArmorstandIds()) { | ||||
|                     PacketContainer packet2 = packet.shallowClone(); | ||||
|                     packet2.getIntegers().write(0, standId); | ||||
|  | ||||
|                     if (packet2.getType() == PacketType.Play.Server.ENTITY_TELEPORT) { | ||||
|                         packet2.getDoubles().write(1, packet2.getDoubles().read(1) + -0.175 + (0.28 * i)); | ||||
|                     } | ||||
|  | ||||
|                     toAdd.add(packet2); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         packets.getPackets().addAll(toAdd); | ||||
|     } | ||||
|  | ||||
|     public void handle2(Disguise disguise, PacketContainer sentPacket, LibsPackets packets, Player observer, | ||||
|             Entity entity) { | ||||
|         if (invalid && RandomUtils.nextDouble() < 0.1) { | ||||
|             packets.clear(); | ||||
|             return; | ||||
|   | ||||
| @@ -151,7 +151,7 @@ public class PacketHandlerSpawn implements IPacketHandler { | ||||
|             mods.write(5, pitch); | ||||
|         } else if (disguise.getType().isPlayer()) { | ||||
|             PlayerDisguise playerDisguise = (PlayerDisguise) disguise; | ||||
|             boolean visibleOrNewCompat = playerDisguise.isNameVisible() || DisguiseConfig.isScoreboardDisguiseNames(); | ||||
|             boolean visibleOrNewCompat = playerDisguise.isNameVisible() || DisguiseConfig.isScoreboardNames(); | ||||
|  | ||||
|             WrappedGameProfile spawnProfile = visibleOrNewCompat ? playerDisguise.getGameProfile() : ReflectionManager | ||||
|                     .getGameProfileWithThisSkin(UUID.randomUUID(), visibleOrNewCompat ? playerDisguise.getName() : "", | ||||
| @@ -387,6 +387,8 @@ public class PacketHandlerSpawn implements IPacketHandler { | ||||
|             packets.addPacket(newPacket); | ||||
|         } | ||||
|  | ||||
|         DisguiseUtilities.getNamePackets(disguise, new String[0]).forEach(packets::addPacket); | ||||
|  | ||||
|         // If armor must be sent because its currently not displayed and would've been sent normally | ||||
|  | ||||
|         // This sends the armor packets so that the player isn't naked. | ||||
|   | ||||
| @@ -78,7 +78,8 @@ public class PacketListenerViewSelfDisguise extends PacketAdapter { | ||||
|             } | ||||
|  | ||||
|             for (PacketContainer newPacket : transformed.getPackets()) { | ||||
|                 if (newPacket.getType() != Server.PLAYER_INFO) { | ||||
|                 if (newPacket.getType() != Server.PLAYER_INFO && | ||||
|                         newPacket.getIntegers().read(0) == observer.getEntityId()) { | ||||
|                     if (newPacket == packet) { | ||||
|                         newPacket = newPacket.shallowClone(); | ||||
|                     } | ||||
|   | ||||
| @@ -698,6 +698,10 @@ public class DisguiseParser { | ||||
|  | ||||
|                         args[1] = args[1].replace("\\_", " "); | ||||
|  | ||||
|                         if (DisguiseConfig.isArmorstandsName() && !sender.hasPermission("libsdisguises.multiname")) { | ||||
|                             args[1] = DisguiseUtilities.quoteNewLine(args[1]); | ||||
|                         } | ||||
|  | ||||
|                         // Construct the player disguise | ||||
|                         disguise = new PlayerDisguise(ChatColor.translateAlternateColorCodes('&', args[1])); | ||||
|  | ||||
| @@ -904,6 +908,10 @@ public class DisguiseParser { | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (DisguiseConfig.isArmorstandsName() && methodToUse.getName().equals("setName") && !sender.hasPermission("libsdisguises.multiname")) { | ||||
|                 valueToSet = DisguiseUtilities.quoteNewLine((String) valueToSet); | ||||
|             } | ||||
|  | ||||
|             if (FlagWatcher.class.isAssignableFrom(methodToUse.getDeclaringClass())) { | ||||
|                 methodToUse.invoke(disguise.getWatcher(), valueToSet); | ||||
|             } else { | ||||
|   | ||||
| @@ -59,7 +59,7 @@ public enum LibsMsg { | ||||
|     DISG_HELP4(ChatColor.DARK_GREEN + "/disguise <Dropped_Item/Falling_Block> <Id> <Durability>"), | ||||
|     DISG_PLAYER_AS_DISG(ChatColor.RED + "Successfully disguised %s as a %s!"), | ||||
|     DISG_PLAYER_AS_DISG_FAIL(ChatColor.RED + "Failed to disguise %s as a %s!"), | ||||
|     DISGUISED(ChatColor.RED + "Now disguised as a %s"), | ||||
|     DISGUISED(ChatColor.RED + "Now disguised as %s"), | ||||
|     DISRADIUS(ChatColor.RED + "Successfully disguised %s entities!"), | ||||
|     DISRADIUS_FAIL(ChatColor.RED + "Couldn't find any entities to disguise!"), | ||||
|     DMODENT_HELP1(ChatColor.DARK_GREEN + "Choose the options for a disguise then right click a entity to modify it!"), | ||||
| @@ -116,7 +116,7 @@ public enum LibsMsg { | ||||
|             "disabled in the config"), | ||||
|     DRADIUS_NEEDOPTIONS(ChatColor.RED + "You need to supply a disguise as well as the radius"), | ||||
|     DRADIUS_NEEDOPTIONS_ENTITY(ChatColor.RED + "You need to supply a disguise as well as the radius and EntityType"), | ||||
|     FAILED_DISGIUSE(ChatColor.RED + "Failed to disguise as a %s"), | ||||
|     FAILED_DISGIUSE(ChatColor.RED + "Failed to disguise as %s"), | ||||
|     GRABBED_SKIN(ChatColor.GOLD + "Grabbed skin and saved as %s!"), | ||||
|     PLEASE_WAIT(ChatColor.GRAY + "Please wait..."), | ||||
|     INVALID_CLONE(ChatColor.DARK_RED + "Unknown method '%s' - Valid methods are 'IgnoreEquipment' 'DoSneakSprint' " + | ||||
|   | ||||
| @@ -66,13 +66,14 @@ SaveDisguises: | ||||
|   Players: false | ||||
|   Entities: false | ||||
|  | ||||
| # Do names go beyond the 16 char limit for player disguises? | ||||
| ExtendedNames: false | ||||
| # Do names resolve into scoreboard teams so changing the player name is flawless? | ||||
| # This will only work properly for names 28 chars or less in length. | ||||
| # Notch [DragonSlayer lvl: 28] | ||||
| # It's recommended to leave this on unless you need it off | ||||
| ScoreboardNames: true | ||||
| # There are four options you can use | ||||
| # VANILLA - Names are limited to 16 chars but can't be changed without resending disguise | ||||
| # TEAMS - Names are limited to 32 chars but can be changed willy nilly | ||||
| # EXTENDED - Names are limited to 48 chars but can't be changed without resending disguise | ||||
| # ARMORSTANDS - Names are limited to 256 chars, uses a mix of armorstands and teams to do this. Slightly hacky. | ||||
| # Downside of armorstand names is that there's a chance of it becoming desynced from the player disguise | ||||
| PlayerNames: TEAMS | ||||
|  | ||||
| # How many ticks before tab packet is sent to remove from tablist. This shouldn't need to be touched | ||||
| TablistRemoveDelay: 2 | ||||
|  | ||||
|   | ||||
| @@ -121,6 +121,8 @@ permissions: | ||||
|   libsdisguises.noactionbar: | ||||
|     description: Hides the action bar even if enabled in config | ||||
|     default: false | ||||
|   libsdisguises.multiname: | ||||
|     description: Allows the command user to set names on different heights | ||||
|   libsdisguises.seecmd: | ||||
|     description: See all commands in tab-completion | ||||
|     default: true | ||||
|   | ||||
		Reference in New Issue
	
	Block a user