diff --git a/pom.xml b/pom.xml
index 790fdd6..211c45a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
${jdkVersion}
- 1.12.3
+ 1.12.4
${version.number}-${version.git}
diff --git a/src/main/java/de/epiceric/shopchest/ShopChest.java b/src/main/java/de/epiceric/shopchest/ShopChest.java
index 2a1b9fa..0f65aa2 100644
--- a/src/main/java/de/epiceric/shopchest/ShopChest.java
+++ b/src/main/java/de/epiceric/shopchest/ShopChest.java
@@ -204,7 +204,7 @@ public class ShopChest extends JavaPlugin {
}
if (database != null) {
- for (Shop shop : shopUtils.getShops()) {
+ for (Shop shop : shopUtils.getShopsCopy()) {
shopUtils.removeShop(shop, false);
debug("Removed shop (#" + shop.getID() + ")");
}
diff --git a/src/main/java/de/epiceric/shopchest/config/HologramFormat.java b/src/main/java/de/epiceric/shopchest/config/HologramFormat.java
index cf63ee6..ad8ac1f 100644
--- a/src/main/java/de/epiceric/shopchest/config/HologramFormat.java
+++ b/src/main/java/de/epiceric/shopchest/config/HologramFormat.java
@@ -1,6 +1,7 @@
package de.epiceric.shopchest.config;
import de.epiceric.shopchest.ShopChest;
+import de.epiceric.shopchest.utils.Operator;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -21,6 +22,13 @@ public class HologramFormat {
NORMAL_SHOP, IN_STOCK, MAX_STACK, CHEST_SPACE, DURABILITY
}
+ // no "-" sign since no variable can be negative
+ // e.g.: 100.0 >= 50.0
+ private static final Pattern SIMPLE_NUMERIC_CONDITION = Pattern.compile("^(\\d+(?:\\.\\d+)?) ([<>][=]?|[=!]=) (\\d+(?:\\.\\d+)?)$");
+
+ // e.g.: "STONE" == "DIAMOND_SWORD"
+ private static final Pattern SIMPLE_STRING_CONDITION = Pattern.compile("^\"([^\"]*)\" ([=!]=) \"([^\"]*)\"$");
+
private ShopChest plugin;
private File configFile;
private YamlConfiguration config;
@@ -110,32 +118,78 @@ public class HologramFormat {
* @return Result of the condition
*/
public boolean evalRequirement(String condition, Map values) {
- try {
- ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("JavaScript");
+ String cond = condition;
- String cond = condition;
+ for (HologramFormat.Requirement req : HologramFormat.Requirement.values()) {
+ if (cond.contains(req.toString()) && values.containsKey(req)) {
+ Object val = values.get(req);
+ String sVal = String.valueOf(val);
- for (HologramFormat.Requirement req : HologramFormat.Requirement.values()) {
- if (cond.contains(req.toString()) && values.containsKey(req)) {
- Object val = values.get(req);
- String sVal = String.valueOf(val);
+ if (val instanceof String && !(sVal.startsWith("\"") && sVal.endsWith("\""))) {
+ sVal = String.format("\"%s\"", sVal);
+ }
- if (val instanceof String && !(sVal.startsWith("\"") && sVal.endsWith("\""))) {
- sVal = String.format("\"%s\"", sVal);
+ cond = cond.replace(req.toString(), sVal);
+ }
+ }
+
+ if (cond.equals("true")) {
+ // e.g.: ADMIN_SHOP
+ return true;
+ } else if (cond.equals("false")) {
+ return false;
+ } else {
+ char firstChar = cond.charAt(0);
+
+ // numeric cond: first char must be a digit (no variable can be negative)
+ if (firstChar >= '0' && firstChar <= '9') {
+ Matcher matcher = SIMPLE_NUMERIC_CONDITION.matcher(cond);
+
+ if (matcher.find()) {
+ Double a, b;
+ Operator operator;
+ try {
+ a = Double.valueOf(matcher.group(1));
+ operator = Operator.from(matcher.group(2));
+ b = Double.valueOf(matcher.group(3));
+
+ return operator.compare(a, b);
+ } catch (IllegalArgumentException ignored) {
+ // should not happen, since regex checked that there is valid number and valid operator
}
-
- cond = cond.replace(req.toString(), sVal);
}
}
- return (boolean) engine.eval(cond);
- } catch (ScriptException e) {
- plugin.debug("Failed to eval condition: " + condition);
- plugin.debug(e);
- }
+ // string cond: first char must be a: "
+ if (firstChar == '"') {
+ Matcher matcher = SIMPLE_STRING_CONDITION.matcher(cond);
- return false;
+ if (matcher.find()) {
+ String a, b;
+ Operator operator;
+ try {
+ a = matcher.group(1);
+ operator = Operator.from(matcher.group(2));
+ b = matcher.group(3);
+
+ return operator.compare(a, b);
+ } catch (IllegalArgumentException | UnsupportedOperationException ignored) {
+ // should not happen, since regex checked that there is valid operator
+ }
+ }
+ }
+
+ // complex comparison
+ try {
+ ScriptEngineManager manager = new ScriptEngineManager();
+ ScriptEngine engine = manager.getEngineByName("JavaScript");
+ return (boolean) engine.eval(cond);
+ } catch (ScriptException e) {
+ plugin.debug("Failed to eval condition: " + condition);
+ plugin.debug(e);
+ return false;
+ }
+ }
}
/**
diff --git a/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java b/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java
index 967bb74..446dd67 100644
--- a/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java
+++ b/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java
@@ -691,17 +691,9 @@ public class ShopInteractListener implements Listener {
executor.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_CREATED, placeholder));
}
- for (Player p : location.getWorld().getPlayers()) {
- if (p.getLocation().distanceSquared(location) <= Math.pow(config.maximal_distance, 2)) {
- if (shop.getHologram() != null) {
- shop.getHologram().showPlayer(p);
- }
- }
- if (p.getLocation().distanceSquared(location) <= Math.pow(config.maximal_item_distance, 2)) {
- if (shop.getItem() != null) {
- shop.getItem().setVisible(p, true);
- }
- }
+ // next update will display the new shop
+ for (Player player : location.getWorld().getPlayers()) {
+ plugin.getShopUtils().resetPlayerLocation(player);
}
}
diff --git a/src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java b/src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java
index 016bc1f..154bac8 100644
--- a/src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java
+++ b/src/main/java/de/epiceric/shopchest/listeners/ShopItemListener.java
@@ -27,18 +27,6 @@ public class ShopItemListener implements Listener {
this.shopUtils = plugin.getShopUtils();
}
- @EventHandler
- public void onPlayerLeave(PlayerQuitEvent e) {
- for (Shop shop : plugin.getShopUtils().getShops()) {
- if (shop.getItem() != null) {
- shop.getItem().setVisible(e.getPlayer(), false);
- }
- if (shop.getHologram() != null) {
- shop.getHologram().hidePlayer(e.getPlayer());
- }
- }
- }
-
@EventHandler(priority = EventPriority.HIGH)
public void onBlockPlace(BlockPlaceEvent e) {
Block b = e.getBlockPlaced();
diff --git a/src/main/java/de/epiceric/shopchest/listeners/ShopUpdateListener.java b/src/main/java/de/epiceric/shopchest/listeners/ShopUpdateListener.java
index 063f7b9..28599fa 100644
--- a/src/main/java/de/epiceric/shopchest/listeners/ShopUpdateListener.java
+++ b/src/main/java/de/epiceric/shopchest/listeners/ShopUpdateListener.java
@@ -8,6 +8,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.scheduler.BukkitRunnable;
@@ -20,6 +21,20 @@ public class ShopUpdateListener implements Listener {
this.plugin = plugin;
}
+ @EventHandler
+ public void onPlayerLeave(PlayerQuitEvent e) {
+ for (Shop shop : plugin.getShopUtils().getShops()) {
+ if (shop.hasItem()) {
+ shop.getItem().resetVisible(e.getPlayer());
+ }
+ if (shop.hasHologram()) {
+ shop.getHologram().resetVisible(e.getPlayer());
+ }
+ }
+
+ plugin.getShopUtils().resetPlayerLocation(e.getPlayer());
+ }
+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerTeleport(PlayerTeleportEvent e) {
Location from = e.getFrom();
@@ -28,7 +43,7 @@ public class ShopUpdateListener implements Listener {
// Wait till the chunk should have loaded on the client
// Update IF worlds are different OR chunks are different (as many teleports are in same chunk)
- if (!from.getWorld().equals(to.getWorld())
+ if (!from.getWorld().getName().equals(to.getWorld().getName())
|| from.getChunk().getX() != to.getChunk().getX()
|| from.getChunk().getZ() != to.getChunk().getZ()) {
// Wait for 15 ticks before we actually put it in the queue
@@ -40,13 +55,15 @@ public class ShopUpdateListener implements Listener {
public void run() {
if (p.isOnline()) {
for (Shop shop : plugin.getShopUtils().getShops()) {
- if (shop.getItem() != null) {
- shop.getItem().setVisible(p, false);
+ if (shop.hasItem()) {
+ shop.getItem().hidePlayer(p);
}
- if (shop.getHologram() != null) {
+ if (shop.hasHologram()) {
shop.getHologram().hidePlayer(p);
}
}
+ // so next update will update correctly
+ plugin.getShopUtils().resetPlayerLocation(p);
}
}
});
diff --git a/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java b/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java
index ec5fe7d..d76f596 100644
--- a/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java
+++ b/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java
@@ -32,7 +32,7 @@ public class ArmorStandWrapper {
private UUID uuid;
private int entityId;
- public ArmorStandWrapper(ShopChest plugin, Location location, String customName) {
+ public ArmorStandWrapper(ShopChest plugin, Location location, String customName, boolean interactable) {
this.plugin = plugin;
this.location = location;
this.customName = customName;
@@ -58,9 +58,12 @@ public class ArmorStandWrapper {
entityArmorStandClass.getMethod("setInvisible", boolean.class).invoke(entity, true);
// Adds the entity to some lists so it can call interact events
- Method addEntityMethod = worldServerClass.getDeclaredMethod((Utils.getMajorVersion() == 8 ? "a" : "b"), entityClass);
- addEntityMethod.setAccessible(true);
- addEntityMethod.invoke(worldServerClass.cast(nmsWorld), entity);
+ // It will also automatically load/unload it when far away
+ if (interactable) {
+ Method addEntityMethod = worldServerClass.getDeclaredMethod((Utils.getMajorVersion() == 8 ? "a" : "b"), entityClass);
+ addEntityMethod.setAccessible(true);
+ addEntityMethod.invoke(worldServerClass.cast(nmsWorld), entity);
+ }
uuid = (UUID) entityClass.getMethod("getUniqueID").invoke(entity);
entityId = (int) entityArmorStandClass.getMethod("getId").invoke(entity);
diff --git a/src/main/java/de/epiceric/shopchest/nms/Hologram.java b/src/main/java/de/epiceric/shopchest/nms/Hologram.java
index a4dd3c0..04b831e 100644
--- a/src/main/java/de/epiceric/shopchest/nms/Hologram.java
+++ b/src/main/java/de/epiceric/shopchest/nms/Hologram.java
@@ -2,27 +2,49 @@ package de.epiceric.shopchest.nms;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.config.Config;
-import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
-import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class Hologram {
- private static List holograms = new ArrayList<>();
+ private static final List HOLOGRAMS = new ArrayList<>();
- private final Set visibility = Collections.newSetFromMap(new ConcurrentHashMap());
+ /**
+ * @param armorStand Armor stand that's part of a hologram
+ * @return Hologram, the armor stand is part of
+ */
+ public static Hologram getHologram(ArmorStand armorStand) {
+ for (Hologram hologram : HOLOGRAMS) {
+ if (hologram.contains(armorStand)) {
+ return hologram;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param armorStand Armor stand to check
+ * @return Whether the armor stand is part of a hologram
+ */
+ public static boolean isPartOfHologram(ArmorStand armorStand) {
+ return getHologram(armorStand) != null;
+ }
+
+ // concurrent since update task is in async thread
+ // since this is a fake entity, hologram is hidden per default
+ private final Set viewers = Collections.newSetFromMap(new ConcurrentHashMap());
private final List wrappers = new ArrayList<>();
private final Location location;
private final ShopChest plugin;
private final Config config;
- private boolean exists = false;
+ private boolean exists;
private ArmorStandWrapper interactArmorStandWrapper;
public Hologram(ShopChest plugin, String[] lines, Location location) {
@@ -30,98 +52,6 @@ public class Hologram {
this.config = plugin.getShopChestConfig();
this.location = location;
- create(lines);
- }
-
- public void addLine(int line, String text) {
- addLine(line, text, false);
- }
-
- private void addLine(int line, String text, boolean forceUpdateLine) {
- if (text == null || text.isEmpty()) return;
-
- if (line >= wrappers.size()) {
- line = wrappers.size();
- }
-
- text = ChatColor.translateAlternateColorCodes('&', text);
-
- if (config.hologram_fixed_bottom) {
- for (int i = 0; i < line; i++) {
- ArmorStandWrapper wrapper = wrappers.get(i);
- wrapper.setLocation(wrapper.getLocation().add(0, 0.25, 0));
- }
- } else {
- for (int i = line; i < wrappers.size(); i++) {
- ArmorStandWrapper wrapper = wrappers.get(i);
- wrapper.setLocation(wrapper.getLocation().subtract(0, 0.25, 0));
- }
- }
-
- Location loc = getLocation();
-
- if (!config.hologram_fixed_bottom) {
- loc.subtract(0, line * 0.25, 0);
- }
-
- ArmorStandWrapper wrapper = new ArmorStandWrapper(plugin, loc, text);
- wrappers.add(line, wrapper);
-
- if (forceUpdateLine) {
- for (UUID uuid : visibility) {
- Player player = Bukkit.getPlayer(uuid);
- if (player != null) {
- wrapper.setVisible(player, true);
- }
- }
- }
- }
-
- public void setLine(int line, String text) {
- if (text == null ||text.isEmpty()) {
- removeLine(line);
- return;
- }
-
- text = ChatColor.translateAlternateColorCodes('&', text);
-
- if (line >= wrappers.size()) {
- addLine(line, text, true);
- return;
- }
-
- wrappers.get(line).setCustomName(text);
- }
-
- public void removeLine(int line) {
- if (line < wrappers.size()) {
- if (config.hologram_fixed_bottom) {
- for (int i = 0; i < line; i++) {
- ArmorStandWrapper wrapper = wrappers.get(i);
- wrapper.setLocation(wrapper.getLocation().subtract(0, 0.25, 0));
- }
- } else {
- for (int i = line + 1; i < wrappers.size(); i++) {
- ArmorStandWrapper wrapper = wrappers.get(i);
- wrapper.setLocation(wrapper.getLocation().add(0, 0.25, 0));
- }
- }
-
- wrappers.get(line).remove();
- wrappers.remove(line);
- }
- }
-
- public String[] getLines() {
- List lines = new ArrayList<>();
- for (ArmorStandWrapper wrapper : wrappers) {
- lines.add(wrapper.getCustomName());
- }
-
- return lines.toArray(new String[lines.size()]);
- }
-
- private void create(String[] lines) {
for (int i = 0; i < lines.length; i++) {
addLine(i, lines[i]);
}
@@ -131,15 +61,11 @@ public class Hologram {
if (config.hologram_fixed_bottom) y = 0.85;
Location loc = getLocation().add(0, y, 0);
- interactArmorStandWrapper = new ArmorStandWrapper(plugin, loc, null);
+ interactArmorStandWrapper = new ArmorStandWrapper(plugin, loc, null, true);
}
- for (Player player : location.getWorld().getPlayers()) {
- plugin.getShopUtils().updateShops(player, true);
- }
-
- holograms.add(this);
- exists = true;
+ this.exists = true;
+ HOLOGRAMS.add(this);
}
/**
@@ -149,58 +75,6 @@ public class Hologram {
return location.clone();
}
- /**
- * @param p Player to which the hologram should be shown
- */
- public void showPlayer(final Player p) {
- if (!isVisible(p)) {
- new BukkitRunnable() {
- @Override
- public void run() {
- for (ArmorStandWrapper wrapper : wrappers) {
- wrapper.setVisible(p, true);
- }
-
- if (interactArmorStandWrapper != null) {
- interactArmorStandWrapper.setVisible(p, true);
- }
- }
- }.runTaskAsynchronously(plugin);
-
- visibility.add(p.getUniqueId());
- }
- }
-
- /**
- * @param p Player from which the hologram should be hidden
- */
- public void hidePlayer(final Player p) {
- if (isVisible(p)) {
- new BukkitRunnable() {
- @Override
- public void run() {
- for (ArmorStandWrapper wrapper : wrappers) {
- wrapper.setVisible(p, false);
- }
-
- if (interactArmorStandWrapper != null) {
- interactArmorStandWrapper.setVisible(p, false);
- }
- }
- }.runTaskAsynchronously(plugin);
-
- visibility.remove(p.getUniqueId());
- }
- }
-
- /**
- * @param p Player to check
- * @return Whether the hologram is visible to the player
- */
- public boolean isVisible(Player p) {
- return visibility.contains(p.getUniqueId());
- }
-
/**
* @return Whether the hologram exists and is not dead
*/
@@ -231,45 +105,189 @@ public class Hologram {
return interactArmorStandWrapper;
}
+ /**
+ * @param p Player to check
+ * @return Whether the hologram is visible to the player
+ */
+ public boolean isVisible(Player p) {
+ return viewers.contains(p.getUniqueId());
+ }
+
+ /**
+ * @param p Player to which the hologram should be shown
+ */
+ public void showPlayer(Player p) {
+ showPlayer(p, false);
+ }
+
+ /**
+ * @param p Player to which the hologram should be shown
+ * @param force whether to force or not
+ */
+ public void showPlayer(Player p, boolean force) {
+ if (viewers.add(p.getUniqueId()) || force) {
+ togglePlayer(p, true);
+ }
+ }
+
+ /**
+ * @param p Player from which the hologram should be hidden
+ */
+ public void hidePlayer(Player p) {
+ hidePlayer(p, false);
+ }
+
+ /**
+ * @param p Player from which the hologram should be hidden
+ * @param force whether to force or not
+ */
+ public void hidePlayer(Player p, boolean force) {
+ if (viewers.remove(p.getUniqueId()) || force) {
+ togglePlayer(p, false);
+ }
+ }
+
/**
* Removes the hologram.
* Hologram will be hidden from all players and will be killed
*/
public void remove() {
+ viewers.clear();
+
for (ArmorStandWrapper wrapper : wrappers) {
wrapper.remove();
}
+ wrappers.clear();
if (interactArmorStandWrapper != null) {
interactArmorStandWrapper.remove();
}
-
- wrappers.clear();
+ interactArmorStandWrapper = null;
exists = false;
- holograms.remove(this);
+ HOLOGRAMS.remove(this);
+ }
+
+ public void resetVisible(Player p) {
+ viewers.remove(p.getUniqueId());
+ }
+
+ private void togglePlayer(Player p, boolean visible) {
+ for (ArmorStandWrapper wrapper : wrappers) {
+ wrapper.setVisible(p, visible);
+ }
+
+ if (interactArmorStandWrapper != null) {
+ interactArmorStandWrapper.setVisible(p, visible);
+ }
}
/**
- * @param armorStand Armor stand that's part of a hologram
- * @return Hologram, the armor stand is part of
+ * Get all hologram lines
+ *
+ * @return Hologram lines
*/
- public static Hologram getHologram(ArmorStand armorStand) {
- for (Hologram hologram : holograms) {
- if (hologram.contains(armorStand)) {
- return hologram;
+ public String[] getLines() {
+ List lines = new ArrayList<>();
+ for (ArmorStandWrapper wrapper : wrappers) {
+ lines.add(wrapper.getCustomName());
+ }
+
+ return lines.toArray(new String[lines.size()]);
+ }
+
+ /**
+ * Add a line
+ *
+ * @param line where to insert
+ * @param text text to display
+ */
+ public void addLine(int line, String text) {
+ addLine(line, text, false);
+ }
+
+ private void addLine(int line, String text, boolean forceUpdateLine) {
+ if (text == null || text.isEmpty()) return;
+
+ if (line >= wrappers.size()) {
+ line = wrappers.size();
+ }
+
+ text = ChatColor.translateAlternateColorCodes('&', text);
+
+ if (config.hologram_fixed_bottom) {
+ for (int i = 0; i < line; i++) {
+ ArmorStandWrapper wrapper = wrappers.get(i);
+ wrapper.setLocation(wrapper.getLocation().add(0, 0.25, 0));
+ }
+ } else {
+ for (int i = line; i < wrappers.size(); i++) {
+ ArmorStandWrapper wrapper = wrappers.get(i);
+ wrapper.setLocation(wrapper.getLocation().subtract(0, 0.25, 0));
}
}
- return null;
+ Location loc = getLocation();
+
+ if (!config.hologram_fixed_bottom) {
+ loc.subtract(0, line * 0.25, 0);
+ }
+
+ ArmorStandWrapper wrapper = new ArmorStandWrapper(plugin, loc, text, false);
+ wrappers.add(line, wrapper);
+
+ if (forceUpdateLine) {
+ for (Player player : location.getWorld().getPlayers()) {
+ if (viewers.contains(player.getUniqueId())) {
+ wrapper.setVisible(player, true);
+ }
+ }
+ }
}
/**
- * @param armorStand Armor stand to check
- * @return Whether the armor stand is part of a hologram
+ * Set a line
+ *
+ * @param line index to change
+ * @param text text to display
*/
- public static boolean isPartOfHologram(ArmorStand armorStand) {
- return getHologram(armorStand) != null;
+ public void setLine(int line, String text) {
+ if (text == null ||text.isEmpty()) {
+ removeLine(line);
+ return;
+ }
+
+ text = ChatColor.translateAlternateColorCodes('&', text);
+
+ if (line >= wrappers.size()) {
+ addLine(line, text, true);
+ return;
+ }
+
+ wrappers.get(line).setCustomName(text);
}
+ /**
+ * Remove a line
+ *
+ * @param line index to remove
+ */
+ public void removeLine(int line) {
+ if (line < wrappers.size()) {
+ if (config.hologram_fixed_bottom) {
+ for (int i = 0; i < line; i++) {
+ ArmorStandWrapper wrapper = wrappers.get(i);
+ wrapper.setLocation(wrapper.getLocation().subtract(0, 0.25, 0));
+ }
+ } else {
+ for (int i = line + 1; i < wrappers.size(); i++) {
+ ArmorStandWrapper wrapper = wrappers.get(i);
+ wrapper.setLocation(wrapper.getLocation().add(0, 0.25, 0));
+ }
+ }
+
+ wrappers.get(line).remove();
+ wrappers.remove(line);
+ }
+ }
}
diff --git a/src/main/java/de/epiceric/shopchest/shop/Shop.java b/src/main/java/de/epiceric/shopchest/shop/Shop.java
index 2a4d438..e75ddfa 100644
--- a/src/main/java/de/epiceric/shopchest/shop/Shop.java
+++ b/src/main/java/de/epiceric/shopchest/shop/Shop.java
@@ -10,7 +10,6 @@ import de.epiceric.shopchest.language.LanguageUtils;
import de.epiceric.shopchest.nms.Hologram;
import de.epiceric.shopchest.utils.ItemUtils;
import de.epiceric.shopchest.utils.Utils;
-import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
@@ -19,29 +18,31 @@ import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Chest;
import org.bukkit.block.DoubleChest;
-import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
public class Shop {
+ public enum ShopType {
+ NORMAL,
+ ADMIN,
+ }
+
+ private final ShopChest plugin;
+ private final OfflinePlayer vendor;
+ private final ItemStack product;
+ private final Location location;
+ private final double buyPrice;
+ private final double sellPrice;
+ private final ShopType shopType;
+ private final Config config;
+
private boolean created;
private int id;
- private ShopChest plugin;
- private OfflinePlayer vendor;
- private ItemStack product;
- private Location location;
private Hologram hologram;
private ShopItem item;
- private double buyPrice;
- private double sellPrice;
- private ShopType shopType;
- private Config config;
public Shop(int id, ShopChest plugin, OfflinePlayer vendor, ItemStack product, Location location, double buyPrice, double sellPrice, ShopType shopType) {
this.id = id;
@@ -59,6 +60,34 @@ public class Shop {
this(-1, plugin, vendor, product, location, buyPrice, sellPrice, shopType);
}
+ /**
+ * Test if this shop is equals to another
+ *
+ * @param o Other object to test against
+ * @return true if we are sure they are the same, false otherwise
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Shop shop = (Shop) o;
+
+ // id = -1 means temp shop
+ return id != -1 && id == shop.id;
+ }
+
+ @Override
+ public int hashCode() {
+ return id != -1 ? id : super.hashCode();
+ }
+
+ /**
+ * Create the shop
+ *
+ * @param showConsoleMessages to log exceptions to console
+ * @return Whether is was created or not
+ */
public boolean create(boolean showConsoleMessages) {
if (created) return false;
@@ -123,11 +152,7 @@ public class Shop {
itemStack = product.clone();
itemStack.setAmount(1);
- this.item = new ShopItem(plugin, itemStack, itemLocation);
-
- for (Player p : Bukkit.getOnlinePlayers()) {
- item.setVisible(p, true);
- }
+ item = new ShopItem(plugin, itemStack, itemLocation);
}
}
@@ -163,6 +188,9 @@ public class Shop {
hologram = new Hologram(plugin, holoText, holoLocation);
}
+ /**
+ * Keep hologram text up to date
+ */
public void updateHologramText() {
String[] lines = getHologramText();
String[] currentLines = hologram.getLines();
@@ -181,7 +209,7 @@ public class Shop {
private String[] getHologramText() {
List lines = new ArrayList<>();
- Map requirements = new HashMap<>();
+ Map requirements = new EnumMap<>(HologramFormat.Requirement.class);
requirements.put(HologramFormat.Requirement.VENDOR, getVendor().getName());
requirements.put(HologramFormat.Requirement.AMOUNT, getProduct().getAmount());
requirements.put(HologramFormat.Requirement.ITEM_TYPE, getProduct().getType() + (getProduct().getDurability() > 0 ? ":" + getProduct().getDurability() : ""));
@@ -200,7 +228,7 @@ public class Shop {
requirements.put(HologramFormat.Requirement.CHEST_SPACE, Utils.getFreeSpaceForItem(getInventoryHolder().getInventory(), getProduct()));
requirements.put(HologramFormat.Requirement.DURABILITY, getProduct().getDurability());
- Map placeholders = new HashMap<>();
+ Map placeholders = new EnumMap<>(Placeholder.class);
placeholders.put(Placeholder.VENDOR, getVendor().getName());
placeholders.put(Placeholder.AMOUNT, getProduct().getAmount());
placeholders.put(Placeholder.ITEM_NAME, LanguageUtils.getItemName(getProduct()));
@@ -220,7 +248,7 @@ public class Shop {
for (int i = 0; i < lineCount; i++) {
String format = plugin.getHologramFormat().getFormat(i, requirements, placeholders);
for (Placeholder placeholder : placeholders.keySet()) {
- String replace = "";
+ String replace;
switch (placeholder) {
case BUY_PRICE:
@@ -241,7 +269,7 @@ public class Shop {
}
}
- return lines.toArray(new String[lines.size()]);
+ return lines.toArray(new String[0]);
}
private Location getHologramLocation(boolean doubleChest, Chest[] chests) {
@@ -374,6 +402,14 @@ public class Shop {
return item;
}
+ public boolean hasHologram() {
+ return hologram != null;
+ }
+
+ public boolean hasItem() {
+ return item != null;
+ }
+
/**
* @return {@link InventoryHolder} of the shop or null if the shop has no chest.
*/
@@ -388,9 +424,4 @@ public class Shop {
return null;
}
- public enum ShopType {
- NORMAL,
- ADMIN
- }
-
}
diff --git a/src/main/java/de/epiceric/shopchest/shop/ShopItem.java b/src/main/java/de/epiceric/shopchest/shop/ShopItem.java
index add75dc..e1c06c1 100644
--- a/src/main/java/de/epiceric/shopchest/shop/ShopItem.java
+++ b/src/main/java/de/epiceric/shopchest/shop/ShopItem.java
@@ -10,39 +10,40 @@ import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
-import java.util.Collections;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ShopItem {
private final ShopChest plugin;
- private final Set visibility = Collections.newSetFromMap(new ConcurrentHashMap());
+
+ // concurrent since update task is in async thread
+ // since this is a fake entity, item is hidden per default
+ private final Set viewers = Collections.newSetFromMap(new ConcurrentHashMap());
private final ItemStack itemStack;
private final Location location;
- private Object entityItem;
- private int entityId;
- private Object[] creationPackets = new Object[3];
-
- private Class> nmsWorldClass = Utils.getNMSClass("World");
- private Class> craftWorldClass = Utils.getCraftClass("CraftWorld");
- private Class> nmsItemStackClass = Utils.getNMSClass("ItemStack");
- private Class> craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack");
- private Class> entityItemClass = Utils.getNMSClass("EntityItem");
- private Class> entityClass = Utils.getNMSClass("Entity");
- private Class> packetPlayOutSpawnEntityClass = Utils.getNMSClass("PacketPlayOutSpawnEntity");
- private Class> packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata");
- private Class> dataWatcherClass = Utils.getNMSClass("DataWatcher");
- private Class> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy");
- private Class> packetPlayOutEntityVelocityClass = Utils.getNMSClass("PacketPlayOutEntityVelocity");
+ private final Class> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy");
+ private final Object[] creationPackets = new Object[3];
+ private final Object entityItem;
+ private final int entityId;
public ShopItem(ShopChest plugin, ItemStack itemStack, Location location) {
this.plugin = plugin;
this.itemStack = itemStack;
this.location = location;
+ Class> packetPlayOutEntityVelocityClass = Utils.getNMSClass("PacketPlayOutEntityVelocity");
+ Class> dataWatcherClass = Utils.getNMSClass("DataWatcher");
+ Class> packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata");
+ Class> packetPlayOutSpawnEntityClass = Utils.getNMSClass("PacketPlayOutSpawnEntity");
+ Class> entityClass = Utils.getNMSClass("Entity");
+ Class> entityItemClass = Utils.getNMSClass("EntityItem");
+ Class> craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack");
+ Class> nmsItemStackClass = Utils.getNMSClass("ItemStack");
+ Class> craftWorldClass = Utils.getCraftClass("CraftWorld");
+ Class> nmsWorldClass = Utils.getNMSClass("World");
+
Class[] requiredClasses = new Class[] {
nmsWorldClass, craftWorldClass, nmsItemStackClass, craftItemStackClass, entityItemClass,
packetPlayOutSpawnEntityClass, packetPlayOutEntityMetadataClass, dataWatcherClass,
@@ -52,14 +53,15 @@ public class ShopItem {
for (Class c : requiredClasses) {
if (c == null) {
plugin.debug("Failed to create shop item: Could not find all required classes");
+ entityItem = null;
+ entityId = -1;
return;
}
}
- create();
- }
+ Object tmpEntityItem = null;
+ int tmpEntityId = -1;
- private void create() {
try {
Object craftWorld = craftWorldClass.cast(location.getWorld());
Object nmsWorld = craftWorldClass.getMethod("getHandle").invoke(craftWorld);
@@ -67,74 +69,32 @@ public class ShopItem {
Object nmsItemStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, itemStack);
Constructor> entityItemConstructor = entityItemClass.getConstructor(nmsWorldClass);
- entityItem = entityItemConstructor.newInstance(nmsWorld);
+ tmpEntityItem = entityItemConstructor.newInstance(nmsWorld);
- entityItemClass.getMethod("setPosition", double.class, double.class, double.class).invoke(entityItem, location.getX(), location.getY(), location.getZ());
- entityItemClass.getMethod("setItemStack", nmsItemStackClass).invoke(entityItem, nmsItemStack);
- if (Utils.getMajorVersion() >= 10) entityItemClass.getMethod("setNoGravity", boolean.class).invoke(entityItem, true);
+ entityItemClass.getMethod("setPosition", double.class, double.class, double.class).invoke(tmpEntityItem, location.getX(), location.getY(), location.getZ());
+ entityItemClass.getMethod("setItemStack", nmsItemStackClass).invoke(tmpEntityItem, nmsItemStack);
+ if (Utils.getMajorVersion() >= 10) entityItemClass.getMethod("setNoGravity", boolean.class).invoke(tmpEntityItem, true);
Field ageField = entityItemClass.getDeclaredField("age");
ageField.setAccessible(true);
- ageField.setInt(entityItem, -32768);
+ ageField.setInt(tmpEntityItem, -32768);
- entityId = (int) entityItemClass.getMethod("getId").invoke(entityItem);
- Object dataWatcher = entityItemClass.getMethod("getDataWatcher").invoke(entityItem);
+ tmpEntityId = (int) entityItemClass.getMethod("getId").invoke(tmpEntityItem);
+ Object dataWatcher = entityItemClass.getMethod("getDataWatcher").invoke(tmpEntityItem);
- creationPackets[0] = packetPlayOutSpawnEntityClass.getConstructor(entityClass, int.class).newInstance(entityItem, 2);
- creationPackets[1] = packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class).newInstance(entityId, dataWatcher, true);
- creationPackets[2] = packetPlayOutEntityVelocityClass.getConstructor(int.class, double.class, double.class, double.class).newInstance(entityId, 0D, 0D, 0D);
+ creationPackets[0] = packetPlayOutSpawnEntityClass.getConstructor(entityClass, int.class).newInstance(tmpEntityItem, 2);
+ creationPackets[1] = packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class).newInstance(tmpEntityId, dataWatcher, true);
+ creationPackets[2] = packetPlayOutEntityVelocityClass.getConstructor(int.class, double.class, double.class, double.class).newInstance(tmpEntityId, 0D, 0D, 0D);
} catch (NoSuchMethodException | NoSuchFieldException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
plugin.getLogger().severe("Failed to create shop item");
plugin.debug("Failed to create shop item with reflection");
plugin.debug(e);
}
+
+ entityItem = tmpEntityItem;
+ entityId = tmpEntityId;
}
- public void remove() {
- for (UUID uuid : visibility) {
- Player p = Bukkit.getPlayer(uuid);
- if (p != null) setVisible(p, false);
- }
- }
-
- /**
- * Respawns the item at the set location for a player
- * @param p Player, for which the item should be reset
- */
- public void resetForPlayer(Player p) {
- setVisible(p, false);
- setVisible(p, true);
- }
-
- public boolean isVisible(Player p) {
- return visibility.contains(p.getUniqueId());
- }
-
- public void setVisible(final Player p, boolean visible) {
- if (isVisible(p) == visible)
- return;
-
- if (visible) {
- for (Object packet : this.creationPackets) {
- Utils.sendPacket(plugin, packet, p);
- }
- visibility.add(p.getUniqueId());
- } else {
- try {
- if (p.isOnline()) {
- Object packetPlayOutEntityDestroy = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[]{entityId});
- Utils.sendPacket(plugin, packetPlayOutEntityDestroy, p);
- }
- } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
- plugin.getLogger().severe("Failed to destroy shop item");
- plugin.debug("Failed to destroy shop item with reflection");
- plugin.debug(e);
- }
- visibility.remove(p.getUniqueId());
- }
- }
-
-
/**
* @return Clone of the location, where the shop item should be (it could have been moved by something, even though it shouldn't)
* To get the exact location, use reflection and extract the location of the {@code EntityItem}
@@ -157,4 +117,83 @@ public class ShopItem {
public ItemStack getItemStack() {
return itemStack.clone();
}
+
+ /**
+ * @param p Player to check
+ * @return Whether the item is visible to the player
+ */
+ public boolean isVisible(Player p) {
+ return viewers.contains(p.getUniqueId());
+ }
+
+ /**
+ * @param p Player to which the item should be shown
+ */
+ public void showPlayer(Player p) {
+ showPlayer(p, false);
+ }
+
+ /**
+ * @param p Player to which the item should be shown
+ * @param force whether to force or not
+ */
+ public void showPlayer(Player p, boolean force) {
+ if (viewers.add(p.getUniqueId()) || force) {
+ for (Object packet : creationPackets) {
+ Utils.sendPacket(plugin, packet, p);
+ }
+ }
+ }
+
+ /**
+ * @param p Player from which the item should be hidden
+ */
+ public void hidePlayer(Player p) {
+ hidePlayer(p, false);
+ }
+
+ /**
+ * @param p Player from which the item should be hidden
+ * @param force whether to force or not
+ */
+ public void hidePlayer(Player p, boolean force) {
+ if (viewers.remove(p.getUniqueId()) || force) {
+ try {
+ if (p.isOnline()) {
+ Object packetPlayOutEntityDestroy = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[]{entityId});
+ Utils.sendPacket(plugin, packetPlayOutEntityDestroy, p);
+ }
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
+ plugin.getLogger().severe("Failed to destroy shop item");
+ plugin.debug("Failed to destroy shop item with reflection");
+ plugin.debug(e);
+ }
+ }
+ }
+
+ public void resetVisible(Player p) {
+ viewers.remove(p.getUniqueId());
+ }
+
+ /**
+ * Removes the item.
+ * Item will be hidden from all players
+ */
+ public void remove() {
+ // Avoid ConcurrentModificationException
+ for (UUID uuid : new ArrayList<>(viewers)) {
+ Player p = Bukkit.getPlayer(uuid);
+ if (p != null) hidePlayer(p);
+ }
+ }
+
+ /**
+ * Respawns the item at the set location for a player
+ * @param p Player, for which the item should be reset
+ */
+ public void resetForPlayer(Player p) {
+ hidePlayer(p);
+ showPlayer(p);
+ }
+
}
diff --git a/src/main/java/de/epiceric/shopchest/utils/FastMath.java b/src/main/java/de/epiceric/shopchest/utils/FastMath.java
new file mode 100644
index 0000000..a99d8a4
--- /dev/null
+++ b/src/main/java/de/epiceric/shopchest/utils/FastMath.java
@@ -0,0 +1,42 @@
+package de.epiceric.shopchest.utils;
+
+public class FastMath {
+
+ /**
+ * Fast sqrt, 1.57% precision
+ *
+ * @param n value to calculate square root from
+ * @return the square root of n
+ */
+ public static double sqrt(double n) {
+ return n * Double.longBitsToDouble(6910470738111508698L - (Double.doubleToRawLongBits(n) >> 1));
+ }
+
+ /**
+ * Fast acos, 2.9% precision
+ *
+ * @param n value to calculate arc cosine from
+ * @return the arc cosine of n
+ */
+ public static double acos(double n) {
+ int v = (int) (n * MULTIPLIER + OFFSET);
+ while (v > PRECISION) v -= PRECISION;
+ while (v < 0) v += PRECISION;
+ return acos[v];
+ }
+
+ // Below is lookup table generation
+ // It is only executed once at initialization
+
+ private static final int PRECISION = 512;
+ private static final double MULTIPLIER = PRECISION / 2D;
+ private static final double OFFSET = MULTIPLIER + 0.5D; // + 0.5 as cast truncate and don't round
+ private static final double[] acos = new double[PRECISION + 1];
+
+ static {
+ for (int i = 0; i <= PRECISION; i++) {
+ acos[i] = Math.acos(i * (2D / PRECISION) - 1);
+ }
+ }
+
+}
diff --git a/src/main/java/de/epiceric/shopchest/utils/Operator.java b/src/main/java/de/epiceric/shopchest/utils/Operator.java
new file mode 100644
index 0000000..ebb7f96
--- /dev/null
+++ b/src/main/java/de/epiceric/shopchest/utils/Operator.java
@@ -0,0 +1,75 @@
+package de.epiceric.shopchest.utils;
+
+public enum Operator {
+
+ EQUAL("==") {
+ @Override
+ public boolean compare(double a, double b) {
+ return Double.compare(a, b) == 0;
+ }
+ @Override
+ public boolean compare(String a, String b) {
+ return a.equals(b);
+ }
+ },
+
+ NOT_EQUAL("!=") {
+ @Override
+ public boolean compare(double a, double b) {
+ return Double.compare(a, b) != 0;
+ }
+ @Override
+ public boolean compare(String a, String b) {
+ return !a.equals(b);
+ }
+ },
+
+ GREATER_THAN(">") {
+ @Override
+ public boolean compare(double a, double b) {
+ return a > b;
+ }
+ },
+
+ GREATER_THAN_OR_EQUAL(">=") {
+ @Override
+ public boolean compare(double a, double b) {
+ return a >= b;
+ }
+ },
+
+ LESS_THAN("<") {
+ @Override
+ public boolean compare(double a, double b) {
+ return a < b;
+ }
+ },
+
+ LESS_THAN_OR_EQUAL("<=") {
+ @Override
+ public boolean compare(double a, double b) {
+ return a <= b;
+ }
+ };
+
+ private final String symbol;
+
+ Operator(String symbol) {
+ this.symbol = symbol;
+ }
+
+ public static Operator from(String symbol) {
+ for (Operator operator : values()) {
+ if (operator.symbol.equals(symbol)) {
+ return operator;
+ }
+ }
+ throw new IllegalArgumentException();
+ }
+
+ public abstract boolean compare(double a, double b);
+
+ public boolean compare(String a, String b) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java
index 9b78364..ac03584 100644
--- a/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java
+++ b/src/main/java/de/epiceric/shopchest/utils/ShopUtils.java
@@ -13,18 +13,15 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.util.Vector;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
public class ShopUtils {
- private final HashMap shopLocation = new HashMap<>();
- private final HashMap playerLocation = new HashMap<>();
+ // concurrent since it is updated in async task
+ private final Map playerLocation = new ConcurrentHashMap<>();
+ private final Map shopLocation = new HashMap<>();
+ private final Collection shopLocationValues = Collections.unmodifiableCollection(shopLocation.values());
private final ShopChest plugin;
public ShopUtils(ShopChest plugin) {
@@ -55,10 +52,24 @@ public class ShopUtils {
/**
* Get all shops
+ * Do not use for removing while iteration!
+ *
+ * @see #getShopsCopy()
* @return Read-only collection of all shops, may contain duplicates
*/
public Collection getShops() {
- return Collections.unmodifiableCollection(new ArrayList<>(shopLocation.values()));
+ return shopLocationValues;
+ }
+
+ /**
+ * Get all shops
+ * Same as {@link #getShops()} but this is safe to remove while iterating
+ *
+ * @see #getShops()
+ * @return Copy of collection of all shops, may contain duplicates
+ */
+ public Collection getShopsCopy() {
+ return new ArrayList<>(getShops());
}
/**
@@ -226,7 +237,7 @@ public class ShopUtils {
plugin.getShopDatabase().connect(new Callback(plugin) {
@Override
public void onResult(Integer result) {
- for (Shop shop : getShops()) {
+ for (Shop shop : getShopsCopy()) {
removeShop(shop, false);
plugin.debug("Removed shop (#" + shop.getID() + ")");
}
@@ -278,36 +289,7 @@ public class ShopUtils {
}
if (plugin.getShopChestConfig().only_show_shops_in_sight) {
- Set sight = getShopsInSight(player);
-
- Set _sight = new HashSet<>();
-
- for (Shop shop : sight) {
- _sight.add(shop);
- if (shop.getHologram() != null && !shop.getHologram().isVisible(player)) {
- shop.getHologram().showPlayer(player);
- }
-
- if (plugin.getShopChestConfig().only_show_first_shop_in_sight) break;
- }
-
- double itemDistSqr = Math.pow(plugin.getShopChestConfig().maximal_item_distance, 2);
-
- for (Shop shop : getShops()) {
- if (shop.getItem() != null && shop.getLocation().getWorld().getName().equals(player.getWorld().getName())) {
- if (shop.getLocation().distanceSquared(player.getEyeLocation()) <= itemDistSqr) {
- shop.getItem().setVisible(player, true);
- } else {
- shop.getItem().setVisible(player, false);
- }
- }
-
- if (!_sight.contains(shop)) {
- if (shop.getHologram() != null) {
- shop.getHologram().hidePlayer(player);
- }
- }
- }
+ updateVisibleShops(player);
} else {
updateNearestShops(player);
}
@@ -315,55 +297,109 @@ public class ShopUtils {
playerLocation.put(player.getUniqueId(), player.getLocation());
}
- private Set getShopsInSight(Player player) {
- double dist = plugin.getShopChestConfig().maximal_distance;
-
- Location loc = player.getEyeLocation();
- Vector direction = loc.getDirection();
-
- Set shops = new HashSet<>();
-
- double i = 0;
-
- do {
- Location below = loc.clone().subtract(0, 1, 0);
-
- Shop shop = getShop(loc);
- if (shop != null) {
- shops.add(shop);
- } else if ((shop = getShop(below)) != null) {
- shops.add(shop);
- }
-
- loc.add(direction);
- i++;
- } while (i <= dist - (dist%1));
-
- direction.multiply(dist - (dist%1));
- loc.add(direction);
-
- Location below = loc.clone().subtract(0, 1, 0);
-
- Shop shop = getShop(loc);
- if (shop != null) {
- shops.add(shop);
- } else if ((shop = getShop(below)) != null) {
- shops.add(shop);
- }
-
- return shops;
+ /**
+ * Remove a player from the {@code playerLocation} map.
+ * This should only be called when really needed
+ */
+ public void resetPlayerLocation(Player player) {
+ playerLocation.remove(player.getUniqueId());
}
- /**
- * Update hologram and item of the shop for a player based on their distance to each other
- * @param shop Shop to update
- * @param player Player to show the update
- */
- public void updateShop(Shop shop, Player player) {
- double holoDistSqr = Math.pow(plugin.getShopChestConfig().maximal_distance, 2);
- double itemDistSqr = Math.pow(plugin.getShopChestConfig().maximal_item_distance, 2);
+ private static final double TARGET_THRESHOLD = 1;
- updateShop(shop, player, holoDistSqr, itemDistSqr);
+ private void updateVisibleShops(Player player) {
+ double itemDistSquared = Math.pow(plugin.getShopChestConfig().maximal_item_distance, 2);
+ double hologramDistSquared = Math.pow(plugin.getShopChestConfig().maximal_distance, 2);
+
+ boolean firstShopInSight = plugin.getShopChestConfig().only_show_first_shop_in_sight;
+
+ // used if only_show_first_shop_in_sight
+ List otherShopsInSight = firstShopInSight ? new ArrayList() : null;
+ double nearestDistance = 0;
+ Shop nearestShop = null;
+
+ Location pLoc = player.getEyeLocation();
+ double pX = pLoc.getX();
+ double pY = pLoc.getY();
+ double pZ = pLoc.getZ();
+ Vector pDir = pLoc.getDirection();
+ double dirLength = pDir.length();
+
+ for (Shop shop : getShops()) {
+ Location shopLocation = shop.getLocation();
+
+ if (shopLocation.getWorld().getName().equals(player.getWorld().getName())) {
+ double distanceSquared = shop.getLocation().distanceSquared(player.getLocation());
+
+ // Display item based on distance
+ if (shop.hasItem()) {
+ if (distanceSquared <= itemDistSquared) {
+ shop.getItem().showPlayer(player);
+ } else {
+ shop.getItem().hidePlayer(player);
+ }
+ }
+
+ // Display hologram based on sight
+ if (shop.hasHologram()) {
+ if (distanceSquared < hologramDistSquared) {
+ Location holoLocation = shop.getHologram().getLocation();
+
+ double x = holoLocation.getX() - pX;
+ double y = shopLocation.getY() - pY + 1.15; // chest block + item offset
+ double z = holoLocation.getZ() - pZ;
+
+ // See: org.bukkit.util.Vector#angle(Vector)
+ double angle = FastMath.acos(
+ (x * pDir.getX() + y * pDir.getY() + z * pDir.getZ())
+ / (FastMath.sqrt(x * x + y * y + z * z) * dirLength)
+ );
+
+ double distance = FastMath.sqrt(distanceSquared);
+
+ // Check if is targeted
+ if (angle * distance < TARGET_THRESHOLD) {
+ // Display even if not the nearest
+ if (!firstShopInSight) {
+ shop.getHologram().showPlayer(player);
+ continue;
+ }
+
+ if (nearestShop == null) {
+ // nearestShop is null
+ // => we guess this one will be the nearest
+ nearestShop = shop;
+ nearestDistance = distance;
+ continue;
+ } else if (distance < nearestDistance) {
+ // nearestShop is NOT null && this shop is nearest
+ // => we'll hide nearestShop, and guess this one will be the nearest
+ otherShopsInSight.add(nearestShop);
+ nearestShop = shop;
+ nearestDistance = distance;
+ continue;
+ }
+ // else: hologram is farther than nearest, so we hide it
+ }
+ }
+
+ // If not in sight
+ shop.getHologram().hidePlayer(player);
+ }
+ }
+ }
+
+ if (firstShopInSight) {
+ // we hide other shop as we wan't to display only the first
+ for (Shop shop : otherShopsInSight) {
+ // we already checked hasHologram() before adding it
+ shop.getHologram().hidePlayer(player);
+ }
+ }
+
+ if (nearestShop != null && nearestShop.hasHologram()) {
+ nearestShop.getHologram().showPlayer(player);
+ }
}
private void updateNearestShops(Player p) {
@@ -371,27 +407,23 @@ public class ShopUtils {
double itemDistSqr = Math.pow(plugin.getShopChestConfig().maximal_item_distance, 2);
for (Shop shop : getShops()) {
- updateShop(shop, p, holoDistSqr, itemDistSqr);
- }
- }
+ if (p.getLocation().getWorld().getName().equals(shop.getLocation().getWorld().getName())) {
+ double distSqr = shop.getLocation().distanceSquared(p.getLocation());
- private void updateShop(Shop shop, Player player, double holoDistSqr, double itemDistSqr) {
- if (player.getLocation().getWorld().getName().equals(shop.getLocation().getWorld().getName())) {
- double distSqr = shop.getLocation().distanceSquared(player.getLocation());
-
- if (shop.getHologram() != null) {
- if (distSqr <= holoDistSqr) {
- shop.getHologram().showPlayer(player);
- } else {
- shop.getHologram().hidePlayer(player);
+ if (shop.hasHologram()) {
+ if (distSqr <= holoDistSqr) {
+ shop.getHologram().showPlayer(p);
+ } else {
+ shop.getHologram().hidePlayer(p);
+ }
}
- }
- if (shop.getItem() != null) {
- if (distSqr <= itemDistSqr) {
- shop.getItem().setVisible(player, true);
- } else {
- shop.getItem().setVisible(player, false);
+ if (shop.hasItem()) {
+ if (distSqr <= itemDistSqr) {
+ shop.getItem().showPlayer(p);
+ } else {
+ shop.getItem().hidePlayer(p);
+ }
}
}
}