Updated the library.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib;
|
||||
|
||||
import net.jitse.npclib.api.NPC;
|
||||
import net.jitse.npclib.api.utilities.Logger;
|
||||
import net.jitse.npclib.listeners.ChunkListener;
|
||||
import net.jitse.npclib.listeners.PacketListener;
|
||||
import net.jitse.npclib.listeners.PlayerListener;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class NPCLib {
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final Logger logger;
|
||||
private final Class<?> npcClass;
|
||||
|
||||
private double autoHideDistance = 50.0;
|
||||
|
||||
public NPCLib(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.logger = new Logger("NPCLib");
|
||||
|
||||
String versionName = plugin.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
Class<?> npcClass = null;
|
||||
|
||||
try {
|
||||
npcClass = Class.forName("net.jitse.npclib.nms." + versionName + ".NPC_" + versionName);
|
||||
} catch (ClassNotFoundException exception) {
|
||||
// Version not supported, error below.
|
||||
}
|
||||
|
||||
this.npcClass = npcClass;
|
||||
|
||||
if (npcClass == null) {
|
||||
logger.severe("Failed to initiate. Your server's version ("
|
||||
+ versionName + ") is not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
PluginManager pluginManager = plugin.getServer().getPluginManager();
|
||||
|
||||
pluginManager.registerEvents(new PlayerListener(this), plugin);
|
||||
pluginManager.registerEvents(new ChunkListener(this), plugin);
|
||||
|
||||
// Boot the according packet listener.
|
||||
new PacketListener().start(this);
|
||||
|
||||
logger.info("Enabled for Minecraft " + versionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The JavaPlugin instance.
|
||||
*/
|
||||
public JavaPlugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for the auto-hide distance.
|
||||
* A recommended value (and default) is 50 blocks.
|
||||
*
|
||||
* @param autoHideDistance The new value.
|
||||
*/
|
||||
public void setAutoHideDistance(double autoHideDistance) {
|
||||
this.autoHideDistance = autoHideDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The auto-hide distance.
|
||||
*/
|
||||
public double getAutoHideDistance() {
|
||||
return autoHideDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The logger NPCLib uses.
|
||||
*/
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new non-player character (NPC).
|
||||
*
|
||||
* @param lines The text you want to sendShowPackets above the NPC (null = no text).
|
||||
* @return The NPC object you may use to sendShowPackets it to players.
|
||||
*/
|
||||
public NPC createNPC(List<String> lines) {
|
||||
try {
|
||||
return (NPC) npcClass.getConstructors()[0].newInstance(this, lines);
|
||||
} catch (Exception exception) {
|
||||
logger.warning("Failed to create NPC. Please report the following stacktrace message: " + exception.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new non-player character (NPC).
|
||||
*
|
||||
* @return The NPC object you may use to sendShowPackets it to players.
|
||||
*/
|
||||
public NPC createNPC() {
|
||||
return createNPC(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.api;
|
||||
|
||||
import net.jitse.npclib.api.skin.Skin;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface NPC {
|
||||
|
||||
/**
|
||||
* Set the NPC's location.
|
||||
* Use this method before using {@link NPC#create}.
|
||||
*
|
||||
* @param location The spawn location for the NPC.
|
||||
* @return object instance.
|
||||
*/
|
||||
NPC setLocation(Location location);
|
||||
|
||||
/**
|
||||
* Set the NPC's skin.
|
||||
* Use this method before using {@link NPC#create}.
|
||||
*
|
||||
* @param skin The skin(data) you'd like to apply.
|
||||
* @return object instance.
|
||||
*/
|
||||
NPC setSkin(Skin skin);
|
||||
|
||||
/**
|
||||
* Get the location of the NPC.
|
||||
*
|
||||
* @return The location of the NPC.
|
||||
*/
|
||||
Location getLocation();
|
||||
|
||||
/**
|
||||
* Create all necessary packets for the NPC so it can be shown to players.
|
||||
*
|
||||
* @return object instance.
|
||||
*/
|
||||
NPC create();
|
||||
|
||||
/**
|
||||
* Get the ID of the NPC.
|
||||
*
|
||||
* @return the ID of the NPC.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Test if a player can see the NPC.
|
||||
* E.g. is the player is out of range, this method will return false as the NPC is automatically hidden by the library.
|
||||
*
|
||||
* @param player The player you'd like to check.
|
||||
* @return Value on whether the player can see the NPC.
|
||||
*/
|
||||
boolean isShown(Player player);
|
||||
|
||||
/**
|
||||
* Show the NPC to a player.
|
||||
* Requires {@link NPC#create} to be used first.
|
||||
*
|
||||
* @param player the player to show the NPC to.
|
||||
*/
|
||||
void show(Player player);
|
||||
|
||||
/**
|
||||
* Hide the NPC from a player.
|
||||
* Will not do anything if NPC isn't shown to the player.
|
||||
* Requires {@link NPC#create} to be used first.
|
||||
*
|
||||
* @param player The player to hide the NPC from.
|
||||
*/
|
||||
void hide(Player player);
|
||||
|
||||
/**
|
||||
* Destroy the NPC, i.e. remove it from the registry.
|
||||
* Requires {@link NPC#create} to be used first.
|
||||
*/
|
||||
void destroy();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.api.events;
|
||||
|
||||
import net.jitse.npclib.api.NPC;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
public class NPCHideEvent extends Event implements Cancellable {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private boolean cancelled = false;
|
||||
|
||||
private final NPC npc;
|
||||
private final Player player;
|
||||
private final boolean automatic;
|
||||
|
||||
public NPCHideEvent(NPC npc, Player player, boolean automatic) {
|
||||
this.npc = npc;
|
||||
this.player = player;
|
||||
this.automatic = automatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
public NPC getNPC() {
|
||||
return npc;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Value on whether the hiding was triggered automatically.
|
||||
*/
|
||||
public boolean isAutomatic() {
|
||||
return automatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.api.events;
|
||||
|
||||
import net.jitse.npclib.api.NPC;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
public class NPCInteractEvent extends Event {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private final Player player;
|
||||
private final ClickType clickType;
|
||||
private final NPC npc;
|
||||
|
||||
public NPCInteractEvent(Player player, ClickType clickType, NPC npc) {
|
||||
this.player = player;
|
||||
this.clickType = clickType;
|
||||
this.npc = npc;
|
||||
}
|
||||
|
||||
public Player getWhoClicked() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
public ClickType getClickType() {
|
||||
return this.clickType;
|
||||
}
|
||||
|
||||
public NPC getNPC() {
|
||||
return this.npc;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public enum ClickType {
|
||||
LEFT_CLICK, RIGHT_CLICK
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.api.events;
|
||||
|
||||
import net.jitse.npclib.api.NPC;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
public class NPCShowEvent extends Event implements Cancellable {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private boolean cancelled = false;
|
||||
|
||||
private final NPC npc;
|
||||
private final Player player;
|
||||
private final boolean automatic;
|
||||
|
||||
public NPCShowEvent(NPC npc, Player player, boolean automatic) {
|
||||
this.npc = npc;
|
||||
this.player = player;
|
||||
this.automatic = automatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
public NPC getNPC() {
|
||||
return npc;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Value on whether the spawn was triggered automatically.
|
||||
*/
|
||||
public boolean isAutomatic() {
|
||||
return automatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.api.skin;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
public class MineSkinFetcher {
|
||||
|
||||
private static final String MINESKIN_API = "https://api.mineskin.org/get/id/";
|
||||
|
||||
public static void fetchSkinFromIdAsync(int id, Callback callback) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(MINESKIN_API + id).openConnection();
|
||||
httpURLConnection.setRequestMethod("GET");
|
||||
httpURLConnection.setDoOutput(true);
|
||||
httpURLConnection.setDoInput(true);
|
||||
httpURLConnection.connect();
|
||||
|
||||
Scanner scanner = new Scanner(httpURLConnection.getInputStream());
|
||||
while (scanner.hasNextLine()) {
|
||||
builder.append(scanner.nextLine());
|
||||
}
|
||||
|
||||
scanner.close();
|
||||
httpURLConnection.disconnect();
|
||||
|
||||
JsonObject jsonObject = (JsonObject) new JsonParser().parse(builder.toString());
|
||||
JsonObject textures = jsonObject.get("data").getAsJsonObject().get("texture").getAsJsonObject();
|
||||
String value = textures.get("value").getAsString();
|
||||
String signature = textures.get("signature").getAsString();
|
||||
|
||||
callback.call(new Skin(value, signature));
|
||||
} catch (IOException exception) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "Could not fetch skin! (Id: " + id + "). Message: " + exception.getMessage());
|
||||
exception.printStackTrace();
|
||||
callback.failed();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
||||
void call(Skin skinData);
|
||||
|
||||
default void failed() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.api.skin;
|
||||
|
||||
public class Skin {
|
||||
|
||||
private final String value, signature;
|
||||
|
||||
public Skin(String value, String signature) {
|
||||
this.value = value;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.api.utilities;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class Logger {
|
||||
|
||||
private final String prefix;
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public Logger(String prefix) {
|
||||
this.prefix = prefix + " ";
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public void info(String info) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bukkit.getLogger().info(prefix + info);
|
||||
}
|
||||
|
||||
public void warning(String warning) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bukkit.getLogger().warning(prefix + warning);
|
||||
}
|
||||
|
||||
public void severe(String severe) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bukkit.getLogger().severe(prefix + severe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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) {
|
||||
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", CHAT_BASE_COMPONENT_CLAZZ) :
|
||||
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_12_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.internal;
|
||||
|
||||
public enum MinecraftVersion {
|
||||
|
||||
V1_8_R1, V1_8_R2, V1_8_R3, V1_9_R1, V1_9_R2, V1_10_R1, V1_11_R1, V1_12_R1, V1_13_R1, V1_13_R2, V1_14_R1;
|
||||
|
||||
|
||||
public boolean isAboveOrEqual(MinecraftVersion compare) {
|
||||
return ordinal() >= compare.ordinal();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.internal;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
public final class NPCManager {
|
||||
|
||||
private static Set<SimpleNPC> npcs = new HashSet<>();
|
||||
|
||||
public static Set<SimpleNPC> getAllNPCs() {
|
||||
return npcs;
|
||||
}
|
||||
|
||||
public static void add(SimpleNPC npc) {
|
||||
npcs.add(npc);
|
||||
}
|
||||
|
||||
public static void remove(SimpleNPC npc) {
|
||||
npcs.remove(npc);
|
||||
}
|
||||
|
||||
private NPCManager() {
|
||||
throw new SecurityException("You cannot initialize this class.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.internal;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
interface PacketHandler {
|
||||
|
||||
void createPackets();
|
||||
|
||||
void sendShowPackets(Player player);
|
||||
|
||||
void sendHidePackets(Player player, boolean scheduler);
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.internal;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import net.jitse.npclib.NPCLib;
|
||||
import net.jitse.npclib.api.NPC;
|
||||
import net.jitse.npclib.api.events.NPCHideEvent;
|
||||
import net.jitse.npclib.api.events.NPCShowEvent;
|
||||
import net.jitse.npclib.api.skin.Skin;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public abstract class SimpleNPC implements NPC, PacketHandler {
|
||||
|
||||
protected final UUID uuid = UUID.randomUUID();
|
||||
protected final int entityId = Integer.MAX_VALUE - NPCManager.getAllNPCs().size();
|
||||
protected final String name = uuid.toString().replace("-", "").substring(0, 10);
|
||||
protected final GameProfile gameProfile = new GameProfile(uuid, name);
|
||||
|
||||
protected final List<String> lines;
|
||||
|
||||
private final Set<UUID> shown = new HashSet<>();
|
||||
private final Set<UUID> autoHidden = new HashSet<>();
|
||||
|
||||
protected double cosFOV = Math.cos(Math.toRadians(60));
|
||||
|
||||
protected NPCLib instance;
|
||||
protected Location location;
|
||||
protected Skin skin;
|
||||
|
||||
public SimpleNPC(NPCLib instance, List<String> lines) {
|
||||
this.instance = instance;
|
||||
this.lines = lines == null ? Collections.emptyList() : lines;
|
||||
|
||||
NPCManager.add(this);
|
||||
}
|
||||
|
||||
protected GameProfile generateGameProfile(UUID uuid, String name) {
|
||||
GameProfile gameProfile = new GameProfile(uuid, name);
|
||||
|
||||
if (skin != null) {
|
||||
gameProfile.getProperties().get("textures").clear();
|
||||
gameProfile.getProperties().get("textures").add(new Property(skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
|
||||
return gameProfile;
|
||||
}
|
||||
|
||||
public NPCLib getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NPC setSkin(Skin skin) {
|
||||
this.skin = skin;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
destroy(true);
|
||||
}
|
||||
|
||||
public void destroy(boolean scheduler) {
|
||||
NPCManager.remove(this);
|
||||
|
||||
// Destroy NPC for every player that is still seeing it.
|
||||
for (UUID uuid : shown) {
|
||||
if (autoHidden.contains(uuid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hide(Bukkit.getPlayer(uuid), true, scheduler);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableFOV() {
|
||||
this.cosFOV = 0; // Or equals Math.cos(1/2 * Math.PI).
|
||||
}
|
||||
|
||||
public void setFOV(double fov) {
|
||||
this.cosFOV = Math.cos(Math.toRadians(fov));
|
||||
}
|
||||
|
||||
public Set<UUID> getShown() {
|
||||
return shown;
|
||||
}
|
||||
|
||||
public Set<UUID> getAutoHidden() {
|
||||
return autoHidden;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShown(Player player) {
|
||||
return shown.contains(player.getUniqueId()) && !autoHidden.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NPC setLocation(Location location) {
|
||||
this.location = location;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NPC create() {
|
||||
createPackets();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Player player) {
|
||||
show(player, false);
|
||||
}
|
||||
|
||||
public void show(Player player, boolean auto) {
|
||||
NPCShowEvent event = new NPCShowEvent(this, player, auto);
|
||||
Bukkit.getServer().getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canSeeNPC(player)) {
|
||||
if (!auto) {
|
||||
shown.add(player.getUniqueId());
|
||||
}
|
||||
|
||||
autoHidden.add(player.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto) {
|
||||
sendShowPackets(player);
|
||||
} else {
|
||||
if (isShown(player)) {
|
||||
throw new RuntimeException("Cannot call show method twice.");
|
||||
}
|
||||
|
||||
if (shown.contains(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
shown.add(player.getUniqueId());
|
||||
|
||||
if (player.getWorld().equals(location.getWorld()) && player.getLocation().distance(location)
|
||||
<= instance.getAutoHideDistance()) {
|
||||
sendShowPackets(player);
|
||||
} else {
|
||||
autoHidden.add(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canSeeNPC(Player player) {
|
||||
Vector dir = location.toVector().subtract(player.getEyeLocation().toVector()).normalize();
|
||||
return dir.dot(player.getLocation().getDirection()) >= cosFOV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide(Player player) {
|
||||
hide(player, false, true);
|
||||
}
|
||||
|
||||
public void hide(Player player, boolean auto, boolean scheduler) {
|
||||
NPCHideEvent event = new NPCHideEvent(this, player, auto);
|
||||
Bukkit.getServer().getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto) {
|
||||
sendHidePackets(player, scheduler);
|
||||
} else {
|
||||
if (!shown.contains(player.getUniqueId())) {
|
||||
throw new RuntimeException("Cannot call hide method without calling NPC#show.");
|
||||
}
|
||||
|
||||
shown.remove(player.getUniqueId());
|
||||
|
||||
if (player.getWorld().equals(location.getWorld()) && player.getLocation().distance(location)
|
||||
<= instance.getAutoHideDistance()) {
|
||||
sendHidePackets(player, scheduler);
|
||||
} else {
|
||||
autoHidden.remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.listeners;
|
||||
|
||||
import net.jitse.npclib.NPCLib;
|
||||
import net.jitse.npclib.internal.NPCManager;
|
||||
import net.jitse.npclib.internal.SimpleNPC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstras
|
||||
*/
|
||||
public class ChunkListener implements Listener {
|
||||
|
||||
private final NPCLib instance;
|
||||
|
||||
public ChunkListener(NPCLib instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChunkUnload(ChunkUnloadEvent event) {
|
||||
Chunk chunk = event.getChunk();
|
||||
|
||||
for (SimpleNPC npc : NPCManager.getAllNPCs()) {
|
||||
Chunk npcChunk = npc.getLocation().getChunk();
|
||||
|
||||
if (chunk.equals(npcChunk)) {
|
||||
// Unloaded chunk with NPC in it. Hiding it from all players currently shown to.
|
||||
|
||||
for (UUID uuid : npc.getShown()) {
|
||||
// Safety check so it doesn't send packets if the NPC has already
|
||||
// been automatically despawned by the system.
|
||||
if (npc.getAutoHidden().contains(uuid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
npc.hide(Bukkit.getPlayer(uuid), true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChunkLoad(ChunkLoadEvent event) {
|
||||
Chunk chunk = event.getChunk();
|
||||
|
||||
for (SimpleNPC npc : NPCManager.getAllNPCs()) {
|
||||
Chunk npcChunk = npc.getLocation().getChunk();
|
||||
|
||||
if (chunk.equals(npcChunk)) {
|
||||
// Loaded chunk with NPC in it. Showing it to the players again.
|
||||
|
||||
for (UUID uuid : npc.getShown()) {
|
||||
// Make sure not to respawn a not-hidden NPC.
|
||||
if (!npc.getAutoHidden().contains(uuid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
|
||||
if (!npcChunk.getWorld().equals(player.getWorld())) {
|
||||
continue; // Player and NPC are not in the same world.
|
||||
}
|
||||
|
||||
double hideDistance = instance.getAutoHideDistance();
|
||||
double distanceSquared = player.getLocation().distanceSquared(npc.getLocation());
|
||||
boolean inRange = distanceSquared <= (hideDistance * hideDistance) || distanceSquared <= (Bukkit.getViewDistance() << 4);
|
||||
|
||||
// Show the NPC (if in range).
|
||||
if (inRange) {
|
||||
npc.show(player, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.listeners;
|
||||
|
||||
import com.comphenix.tinyprotocol.Reflection;
|
||||
import com.comphenix.tinyprotocol.TinyProtocol;
|
||||
import net.jitse.npclib.NPCLib;
|
||||
import net.jitse.npclib.api.events.NPCInteractEvent;
|
||||
import net.jitse.npclib.internal.NPCManager;
|
||||
import net.jitse.npclib.internal.SimpleNPC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
public class PacketListener {
|
||||
|
||||
// Classes:
|
||||
private final Class<?> packetPlayInUseEntityClazz = Reflection.getMinecraftClass("PacketPlayInUseEntity");
|
||||
|
||||
// Fields:
|
||||
private final Reflection.FieldAccessor entityIdField = Reflection.getField(packetPlayInUseEntityClazz, "a", int.class);
|
||||
private final Reflection.FieldAccessor actionField = Reflection.getField(packetPlayInUseEntityClazz, "action", Object.class);
|
||||
|
||||
// Prevent players from clicking at very high speeds.
|
||||
private final Set<UUID> delay = new HashSet<>();
|
||||
|
||||
private Plugin plugin;
|
||||
|
||||
public void start(NPCLib instance) {
|
||||
this.plugin = instance.getPlugin();
|
||||
|
||||
new TinyProtocol(instance) {
|
||||
|
||||
@Override
|
||||
public Object onPacketInAsync(Player player, Object packet) {
|
||||
return handleInteractPacket(player, packet) ? super.onPacketInAsync(player, packet) : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean handleInteractPacket(Player player, Object packet) {
|
||||
if (packetPlayInUseEntityClazz.isInstance(packet)) {
|
||||
SimpleNPC npc = NPCManager.getAllNPCs().stream().filter(
|
||||
check -> check.isShown(player) && check.getEntityId() == (int) entityIdField.get(packet))
|
||||
.findFirst().orElse(null);
|
||||
|
||||
if (npc == null) {
|
||||
// Default player, not doing magic with the packet.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (delay.contains(player.getUniqueId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NPCInteractEvent.ClickType clickType = actionField.get(packet).toString().equals("ATTACK")
|
||||
? NPCInteractEvent.ClickType.LEFT_CLICK : NPCInteractEvent.ClickType.RIGHT_CLICK;
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
Bukkit.getPluginManager().callEvent(new NPCInteractEvent(player, clickType, npc)));
|
||||
|
||||
UUID uuid = player.getUniqueId();
|
||||
delay.add(uuid);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> delay.remove(uuid));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Jitse Boonstra
|
||||
*/
|
||||
|
||||
package net.jitse.npclib.listeners;
|
||||
|
||||
import net.jitse.npclib.NPCLib;
|
||||
import net.jitse.npclib.internal.NPCManager;
|
||||
import net.jitse.npclib.internal.SimpleNPC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
|
||||
/**
|
||||
* @author Jitse Boonstra
|
||||
*/
|
||||
public class PlayerListener implements Listener {
|
||||
|
||||
private final NPCLib instance;
|
||||
|
||||
public PlayerListener(NPCLib instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
for (SimpleNPC npc : NPCManager.getAllNPCs()) {
|
||||
npc.getAutoHidden().remove(player.getUniqueId());
|
||||
|
||||
// Don't need to use NPC#hide since the entity is not registered in the NMS server.
|
||||
npc.getShown().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
World from = event.getFrom();
|
||||
|
||||
// The PlayerTeleportEvent is call, and will handle visibility in the new world.
|
||||
for (SimpleNPC npc : NPCManager.getAllNPCs()) {
|
||||
if (npc.getLocation().getWorld().equals(from)) {
|
||||
if (!npc.getAutoHidden().contains(player.getUniqueId())) {
|
||||
npc.getAutoHidden().add(player.getUniqueId());
|
||||
npc.hide(player, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerMove(PlayerMoveEvent event) {
|
||||
handleMove(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerTeleport(PlayerTeleportEvent event) {
|
||||
handleMove(event.getPlayer());
|
||||
}
|
||||
|
||||
private void handleMove(Player player) {
|
||||
World world = player.getWorld();
|
||||
for (SimpleNPC npc : NPCManager.getAllNPCs()) {
|
||||
if (!npc.getShown().contains(player.getUniqueId())) {
|
||||
continue; // NPC was never supposed to be shown to the player.
|
||||
}
|
||||
|
||||
if (!npc.getLocation().getWorld().equals(world)) {
|
||||
continue; // NPC is not in the same world.
|
||||
}
|
||||
|
||||
// If Bukkit doesn't track the NPC entity anymore, bypass the hiding distance variable.
|
||||
// This will cause issues otherwise (e.g. custom skin disappearing).
|
||||
double hideDistance = instance.getAutoHideDistance();
|
||||
double distanceSquared = player.getLocation().distanceSquared(npc.getLocation());
|
||||
boolean inRange = distanceSquared <= (Math.pow(hideDistance, 2))
|
||||
&& distanceSquared <= (Math.pow(Bukkit.getViewDistance() << 4, 2));
|
||||
if (npc.getAutoHidden().contains(player.getUniqueId())) {
|
||||
// Check if the player and NPC are within the range to sendShowPackets it again.
|
||||
if (inRange) {
|
||||
npc.getAutoHidden().remove(player.getUniqueId());
|
||||
npc.show(player, true);
|
||||
}
|
||||
} else {
|
||||
// Check if the player and NPC are out of range to sendHidePackets it.
|
||||
if (!inRange) {
|
||||
npc.getAutoHidden().add(player.getUniqueId());
|
||||
npc.hide(player, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user