For 2.3-SNAPSHOT, text update method (#11)!

This commit is contained in:
Jitse Boonstra
2019-10-29 12:43:54 +01:00
parent d9558eef63
commit 5650cbd1e2
32 changed files with 287 additions and 471 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<artifactId>npclib</artifactId>
<groupId>net.jitse</groupId>
<version>2.2-SNAPSHOT</version>
<version>2.3-SNAPSHOT</version>
</parent>
<artifactId>npclib-api</artifactId>
@@ -1,17 +1,216 @@
/*
* Copyright (c) 2018 Jitse Boonstra
*/
package net.jitse.npclib.hologram;
import com.comphenix.tinyprotocol.Reflection;
import net.jitse.npclib.internal.MinecraftVersion;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public interface Hologram {
public class Hologram {
void show(Player player);
private final List<Object> armorStands = new ArrayList<>();
private final List<Object> showPackets = new ArrayList<>();
private final List<Object> hidePackets = new ArrayList<>();
void hide(Player player);
private static final double DELTA = 0.3;
void silentHide(UUID uuid);
// Classes:
private static final Class<?> CHAT_COMPONENT_TEXT_CLAZZ = Reflection.getMinecraftClass("ChatComponentText");
private static final Class<?> CHAT_BASE_COMPONENT_CLAZZ = Reflection.getMinecraftClass("IChatBaseComponent");
private static final Class<?> ENTITY_ARMOR_STAND_CLAZZ = Reflection.getMinecraftClass("EntityArmorStand");
private static final Class<?> ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass("EntityLiving");
private static final Class<?> ENTITY_CLAZZ = Reflection.getMinecraftClass("Entity");
private static final Class<?> CRAFT_BUKKIT_CLASS = Reflection.getCraftBukkitClass("CraftWorld");
private static final Class<?> CRAFT_PLAYER_CLAZZ = Reflection.getCraftBukkitClass("entity.CraftPlayer");
private static final Class<?> PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass(
"PacketPlayOutSpawnEntityLiving");
private static final Class<?> PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ = Reflection.getMinecraftClass(
"PacketPlayOutEntityDestroy");
private static final Class<?> PACKET_PLAY_OUT_ENTITY_METADATA_CLAZZ = Reflection.getMinecraftClass(
"PacketPlayOutEntityMetadata");
private static final Class<?> DATAWATCHER_CLAZZ = Reflection.getMinecraftClass("DataWatcher");
private static final Class<?> ENTITY_PLAYER_CLAZZ = Reflection.getMinecraftClass("EntityPlayer");
private static final Class<?> PLAYER_CONNECTION_CLAZZ = Reflection.getMinecraftClass("PlayerConnection");
private static final Class<?> PACKET_CLAZZ = Reflection.getMinecraftClass("Packet");
void updateText(List<String> text);
// Constructors:
private static final Reflection.ConstructorInvoker CHAT_COMPONENT_TEXT_CONSTRUCTOR = Reflection
.getConstructor(CHAT_COMPONENT_TEXT_CLAZZ, String.class);
private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR = Reflection
.getConstructor(PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ, ENTITY_LIVING_CLAZZ);
private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR = Reflection
.getConstructor(PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ, int[].class);
private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_ENTITY_METADATA_CONSTRUCTOR = Reflection
.getConstructor(PACKET_PLAY_OUT_ENTITY_METADATA_CLAZZ, int.class, DATAWATCHER_CLAZZ, boolean.class);
// Fields:
private static final Reflection.FieldAccessor playerConnectionField = Reflection.getField(ENTITY_PLAYER_CLAZZ,
"playerConnection", PLAYER_CONNECTION_CLAZZ);
// Methods:
private static final Reflection.MethodInvoker SET_LOCATION_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setLocation", double.class, double.class, double.class, float.class, float.class);
private static final Reflection.MethodInvoker SET_SMALL_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setSmall", boolean.class);
private static final Reflection.MethodInvoker SET_INVISIBLE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setInvisible", boolean.class);
private static final Reflection.MethodInvoker SET_BASE_PLATE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setBasePlate", boolean.class);
private static final Reflection.MethodInvoker SET_ARMS_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setArms", boolean.class);
private static final Reflection.MethodInvoker PLAYER_GET_HANDLE_METHOD = Reflection.getMethod(CRAFT_PLAYER_CLAZZ,
"getHandle");
private static final Reflection.MethodInvoker SEND_PACKET_METHOD = Reflection.getMethod(PLAYER_CONNECTION_CLAZZ,
"sendPacket", PACKET_CLAZZ);
private static final Reflection.MethodInvoker GET_ID_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"getId");
private static final Reflection.MethodInvoker GET_DATAWATCHER_METHOD = Reflection.getMethod(ENTITY_CLAZZ,
"getDataWatcher");
private final MinecraftVersion version;
private final Location start;
private final Object worldServer;
private List<String> text;
public Hologram(MinecraftVersion version, Location location, List<String> text) {
this.version = version;
this.start = location;
this.text = text;
this.worldServer = Reflection.getMethod(CRAFT_BUKKIT_CLASS, "getHandle")
.invoke(CRAFT_BUKKIT_CLASS.cast(location.getWorld()));
createPackets();
}
private void createPackets() {
// TODO: Check when this method was changed (1.9 R2 is giving an exception...)
Reflection.MethodInvoker gravityMethod = (version.isAboveOrEqual(MinecraftVersion.V1_9_R2) ?
Reflection.getMethod(ENTITY_CLAZZ, "setNoGravity", boolean.class) :
Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setGravity", boolean.class));
Reflection.MethodInvoker customNameMethod = (version.isAboveOrEqual(MinecraftVersion.V1_12_R1)
? Reflection.getMethod(ENTITY_CLAZZ, "setCustomName", version.isAboveOrEqual(MinecraftVersion.V1_13_R1) ? CHAT_BASE_COMPONENT_CLAZZ : String.class)
: Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setCustomName", String.class));
Reflection.MethodInvoker customNameVisibilityMethod = (version.isAboveOrEqual(MinecraftVersion.V1_12_R1) ?
Reflection.getMethod(ENTITY_CLAZZ, "setCustomNameVisible", boolean.class) :
Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setCustomNameVisible", boolean.class));
Location location = start.clone().add(0, DELTA * text.size(), 0);
Class<?> worldClass = worldServer.getClass().getSuperclass();
if (start.getWorld().getEnvironment() != World.Environment.NORMAL) {
worldClass = worldClass.getSuperclass();
}
Reflection.ConstructorInvoker entityArmorStandConstructor = (version.isAboveOrEqual(MinecraftVersion.V1_14_R1) ?
Reflection.getConstructor(ENTITY_ARMOR_STAND_CLAZZ, worldClass, double.class, double.class, double.class) :
Reflection.getConstructor(ENTITY_ARMOR_STAND_CLAZZ, worldClass));
for (String line : text) {
Object entityArmorStand = (version.isAboveOrEqual(MinecraftVersion.V1_14_R1) ?
entityArmorStandConstructor.invoke(worldServer, location.getX(), location.getY(), location.getZ()) :
entityArmorStandConstructor.invoke(worldServer));
if (!version.isAboveOrEqual(MinecraftVersion.V1_14_R1)) {
SET_LOCATION_METHOD.invoke(entityArmorStand, location.getX(), location.getY(), location.getZ(), 0, 0);
}
customNameMethod.invoke(entityArmorStand, version.isAboveOrEqual(MinecraftVersion.V1_13_R1) ?
CHAT_COMPONENT_TEXT_CONSTRUCTOR.invoke(line) : line);
customNameVisibilityMethod.invoke(entityArmorStand, true);
gravityMethod.invoke(entityArmorStand, version.isAboveOrEqual(MinecraftVersion.V1_9_R2));
SET_SMALL_METHOD.invoke(entityArmorStand, true);
SET_INVISIBLE_METHOD.invoke(entityArmorStand, true);
SET_BASE_PLATE_METHOD.invoke(entityArmorStand, false);
SET_ARMS_METHOD.invoke(entityArmorStand, false);
armorStands.add(entityArmorStand);
// Create and add the associated show and hide packets.
showPackets.add(PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR.invoke(entityArmorStand));
hidePackets.add(PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR
.invoke(new int[]{(int) GET_ID_METHOD.invoke(entityArmorStand)}));
location.subtract(0, DELTA, 0);
}
}
public List<Object> getUpdatePackets(List<String> text) {
List<Object> updatePackets = new ArrayList<>();
if (this.text.size() != text.size()) {
throw new IllegalArgumentException("When updating the text, the old and new text should have the same amount of lines");
}
Reflection.MethodInvoker customNameMethod = (version.isAboveOrEqual(MinecraftVersion.V1_12_R1)
? Reflection.getMethod(ENTITY_CLAZZ, "setCustomName", version.isAboveOrEqual(MinecraftVersion.V1_13_R1) ? CHAT_BASE_COMPONENT_CLAZZ : String.class)
: Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setCustomName", String.class));
for (int i = 0; i < text.size(); i++) {
Object entityArmorStand = armorStands.get(i);
String oldLine = this.text.get(i);
String newLine = text.get(i);
customNameMethod.invoke(entityArmorStand, version.isAboveOrEqual(MinecraftVersion.V1_13_R1) ?
CHAT_COMPONENT_TEXT_CONSTRUCTOR.invoke(newLine) : newLine); // Update the DataWatcher object.
showPackets.set(i, PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR.invoke(entityArmorStand));
if (newLine.isEmpty() && !oldLine.isEmpty()) {
updatePackets.add(hidePackets.get(i));
} else if (!newLine.isEmpty() && oldLine.isEmpty()) {
updatePackets.add(showPackets.get(i));
} else if (!oldLine.equals(newLine)) {
// Update the line for all players using a Metadata packet.
updatePackets.add(PACKET_PLAY_OUT_ENTITY_METADATA_CONSTRUCTOR.invoke(
GET_ID_METHOD.invoke(entityArmorStand),
GET_DATAWATCHER_METHOD.invoke(entityArmorStand),
true
));
}
}
this.text = text;
return updatePackets;
}
public void update(Player player, List<Object> updatePackets) {
Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD
.invoke(CRAFT_PLAYER_CLAZZ.cast(player)));
for (Object packet : updatePackets) {
SEND_PACKET_METHOD.invoke(playerConnection, packet);
}
}
public void show(Player player) {
Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD
.invoke(CRAFT_PLAYER_CLAZZ.cast(player)));
for (int i = 0; i < text.size(); i++) {
if (text.get(i).isEmpty()) continue; // No need to spawn the line.
SEND_PACKET_METHOD.invoke(playerConnection, showPackets.get(i));
}
}
public void hide(Player player) {
Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD
.invoke(CRAFT_PLAYER_CLAZZ.cast(player)));
for (int i = 0; i < text.size(); i++) {
if (text.get(i).isEmpty()) continue; // No need to hide the line (as it was never spawned).
SEND_PACKET_METHOD.invoke(playerConnection, hidePackets.get(i));
}
}
}
@@ -1,58 +0,0 @@
package net.jitse.npclib.hologram;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class HologramBase implements Hologram, HologramPacketHandler {
protected final static double DELTA = 0.3;
protected final Set<UUID> shown = new HashSet<>();
protected final Location start;
protected List<String> text;
public HologramBase(Location start, List<String> text) {
this.start = start;
this.text = text;
// Generate the necessary show and hide packets.
createPackets();
}
@Override
public void show(Player player) {
UUID uuid = player.getUniqueId();
if (shown.contains(uuid))
throw new IllegalArgumentException("Hologram is already shown to player");
sendShowPackets(player);
this.shown.add(uuid);
}
@Override
public void hide(Player player) {
UUID uuid = player.getUniqueId();
if (!shown.contains(uuid))
throw new IllegalArgumentException("Hologram is not shown to player");
sendHidePackets(player);
this.shown.remove(uuid);
}
@Override
public void silentHide(UUID uuid) {
if (!shown.contains(uuid))
throw new IllegalArgumentException("Hologram is not shown to player");
this.shown.remove(uuid);
}
}
@@ -1,12 +0,0 @@
package net.jitse.npclib.hologram;
import org.bukkit.entity.Player;
public interface HologramPacketHandler {
void createPackets();
void sendShowPackets(Player player);
void sendHidePackets(Player player);
}
@@ -1,179 +0,0 @@
/*
* Copyright (c) 2018 Jitse Boonstra
*/
package net.jitse.npclib.hologram;
import com.comphenix.tinyprotocol.Reflection;
import net.jitse.npclib.internal.MinecraftVersion;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class _Hologram {
private final double delta = 0.3;
private List<Object> armorStands = new ArrayList<>();
private Set<Object> spawnPackets = new HashSet<>();
private Set<Object> destroyPackets = new HashSet<>();
// Classes:
private static final Class<?> CHAT_COMPONENT_TEXT_CLAZZ = Reflection.getMinecraftClass("ChatComponentText");
private static final Class<?> CHAT_BASE_COMPONENT_CLAZZ = Reflection.getMinecraftClass("IChatBaseComponent");
private static final Class<?> ENTITY_ARMOR_STAND_CLAZZ = Reflection.getMinecraftClass("EntityArmorStand");
private static final Class<?> ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass("EntityLiving");
private static final Class<?> ENTITY_CLAZZ = Reflection.getMinecraftClass("Entity");
private static final Class<?> CRAFT_BUKKIT_CLASS = Reflection.getCraftBukkitClass("CraftWorld");
private static final Class<?> CRAFT_PLAYER_CLAZZ = Reflection.getCraftBukkitClass("entity.CraftPlayer");
private static final Class<?> PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass(
"PacketPlayOutSpawnEntityLiving");
private static final Class<?> PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ = Reflection.getMinecraftClass(
"PacketPlayOutEntityDestroy");
private static final Class<?> ENTITY_PLAYER_CLAZZ = Reflection.getMinecraftClass("EntityPlayer");
private static final Class<?> PLAYER_CONNECTION_CLAZZ = Reflection.getMinecraftClass("PlayerConnection");
private static final Class<?> PACKET_CLAZZ = Reflection.getMinecraftClass("Packet");
// Constructors:
private static final Reflection.ConstructorInvoker CHAT_COMPONENT_TEXT_CONSTRUCTOR = Reflection
.getConstructor(CHAT_COMPONENT_TEXT_CLAZZ, String.class);
private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR = Reflection
.getConstructor(PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ, ENTITY_LIVING_CLAZZ);
private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR = Reflection
.getConstructor(PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ, int[].class);
// Fields:
private static final Reflection.FieldAccessor playerConnectionField = Reflection.getField(ENTITY_PLAYER_CLAZZ,
"playerConnection", PLAYER_CONNECTION_CLAZZ);
// Methods:
private static final Reflection.MethodInvoker SET_LOCATION_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setLocation", double.class, double.class, double.class, float.class, float.class);
private static final Reflection.MethodInvoker SET_SMALL_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setSmall", boolean.class);
private static final Reflection.MethodInvoker SET_INVISIBLE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setInvisible", boolean.class);
private static final Reflection.MethodInvoker SET_BASE_PLATE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setBasePlate", boolean.class);
private static final Reflection.MethodInvoker SET_ARMS_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"setArms", boolean.class);
private static final Reflection.MethodInvoker PLAYER_GET_HANDLE_METHOD = Reflection.getMethod(CRAFT_PLAYER_CLAZZ,
"getHandle");
private static final Reflection.MethodInvoker SEND_PACKET_METHOD = Reflection.getMethod(PLAYER_CONNECTION_CLAZZ,
"sendPacket", PACKET_CLAZZ);
private static final Reflection.MethodInvoker GET_ID_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
"getId");
private final Location start;
private final List<String> lines;
private final Object worldServer;
public _Hologram(Location location, List<String> lines) {
this.start = location;
this.lines = lines;
this.worldServer = Reflection.getMethod(CRAFT_BUKKIT_CLASS, "getHandle")
.invoke(CRAFT_BUKKIT_CLASS.cast(location.getWorld()));
}
public void generatePackets(MinecraftVersion version) {
// TODO: Check when this method was changed (1.9 R2 is giving an exception...
Reflection.MethodInvoker gravityMethod = (version.isAboveOrEqual(MinecraftVersion.V1_9_R2) ?
Reflection.getMethod(ENTITY_CLAZZ, "setNoGravity", boolean.class) :
Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setGravity", boolean.class));
Reflection.MethodInvoker customNameMethod = (version.isAboveOrEqual(MinecraftVersion.V1_12_R1)
? Reflection.getMethod(ENTITY_CLAZZ, "setCustomName", version.isAboveOrEqual(MinecraftVersion.V1_13_R1) ? CHAT_BASE_COMPONENT_CLAZZ : String.class)
: Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setCustomName", String.class));
Reflection.MethodInvoker customNameVisibilityMethod = (version.isAboveOrEqual(MinecraftVersion.V1_12_R1) ?
Reflection.getMethod(ENTITY_CLAZZ, "setCustomNameVisible", boolean.class) :
Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setCustomNameVisible", boolean.class));
Location location = start.clone().add(0, delta * lines.size(), 0);
Class<?> worldClass = worldServer.getClass().getSuperclass();
if (start.getWorld().getEnvironment() != World.Environment.NORMAL) {
worldClass = worldClass.getSuperclass();
}
Reflection.ConstructorInvoker entityArmorStandConstructor = (version.isAboveOrEqual(MinecraftVersion.V1_14_R1) ?
Reflection.getConstructor(ENTITY_ARMOR_STAND_CLAZZ, worldClass, double.class, double.class, double.class) :
Reflection.getConstructor(ENTITY_ARMOR_STAND_CLAZZ, worldClass));
for (String line : lines) {
Object entityArmorStand = (version.isAboveOrEqual(MinecraftVersion.V1_14_R1) ?
entityArmorStandConstructor.invoke(worldServer, location.getX(), location.getY(), location.getZ()) :
entityArmorStandConstructor.invoke(worldServer));
if (!version.isAboveOrEqual(MinecraftVersion.V1_14_R1)) {
SET_LOCATION_METHOD.invoke(entityArmorStand, location.getX(), location.getY(), location.getZ(), 0, 0);
}
customNameMethod.invoke(entityArmorStand, version.isAboveOrEqual(MinecraftVersion.V1_13_R1) ?
CHAT_COMPONENT_TEXT_CONSTRUCTOR.invoke(line) : line);
customNameVisibilityMethod.invoke(entityArmorStand, true);
gravityMethod.invoke(entityArmorStand, version.isAboveOrEqual(MinecraftVersion.V1_9_R2));
SET_SMALL_METHOD.invoke(entityArmorStand, true);
SET_INVISIBLE_METHOD.invoke(entityArmorStand, true);
SET_BASE_PLATE_METHOD.invoke(entityArmorStand, false);
SET_ARMS_METHOD.invoke(entityArmorStand, false);
location.subtract(0, delta, 0);
if (line.isEmpty()) {
continue;
}
armorStands.add(entityArmorStand);
Object spawnPacket = PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR.invoke(entityArmorStand);
spawnPackets.add(spawnPacket);
Object destroyPacket = PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR
.invoke(new int[]{(int) GET_ID_METHOD.invoke(entityArmorStand)});
destroyPackets.add(destroyPacket);
}
}
// public void updateText(List<String> newLines) {
// if (lines.size() != newLines.size()) {
// throw new IllegalArgumentException("New NPC text cannot differ in size from old text.");
// return;
// }
//
// int i = 0;
// for (String oldLine : lines) {
// if (oldLine.isEmpty() && !newLines.get(i).isEmpty()) {
// // Need to spawn
// }
// i++;
// }
// customNameMethod.invoke(entityArmorStand, above_1_12_r1 ? CHAT_COMPONENT_TEXT_CONSTRUCTOR.invoke(line) : line);
// }
public void spawn(Player player) {
Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD
.invoke(CRAFT_PLAYER_CLAZZ.cast(player)));
for (Object packet : spawnPackets) {
SEND_PACKET_METHOD.invoke(playerConnection, packet);
}
}
public void destroy(Player player) {
Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD
.invoke(CRAFT_PLAYER_CLAZZ.cast(player)));
for (Object packet : destroyPackets) {
SEND_PACKET_METHOD.invoke(playerConnection, packet);
}
}
}
@@ -303,12 +303,15 @@ public abstract class NPCBase implements NPC, NPCPacketHandler {
@Override
public NPC setText(List<String> text) {
List<Object> updatePackets = hologram.getUpdatePackets(text);
for (UUID shownUuid : shown) {
Player player = Bukkit.getPlayer(shownUuid);
if (player != null && isShown(player)) {
hologram.updateText(text);
hologram.update(player, updatePackets);
}
}
this.text = text;
return this;
}