mirror of
https://github.com/amalthea-mc/ShopChest.git
synced 2024-12-23 01:21:47 +00:00
Few improvements (#135)
* Fix: Few improvements * Few improvements * Hologram/item can wait after shop creation * Compare worlds using their name * Fix holograms display * Changed version to 1.12.4 * Display shop after creation * Fix requested changed * Improve performance for simple hologram conditions
This commit is contained in:
parent
efced89eb1
commit
3b9d26c079
2
pom.xml
2
pom.xml
@ -25,7 +25,7 @@
|
||||
<maven.compiler.target>${jdkVersion}</maven.compiler.target>
|
||||
|
||||
<!-- Versioning -->
|
||||
<version.number>1.12.3</version.number>
|
||||
<version.number>1.12.4</version.number>
|
||||
<version.final>${version.number}-${version.git}</version.final>
|
||||
|
||||
<!-- Others -->
|
||||
|
@ -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() + ")");
|
||||
}
|
||||
|
@ -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<Requirement, Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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<Hologram> holograms = new ArrayList<>();
|
||||
private static final List<Hologram> HOLOGRAMS = new ArrayList<>();
|
||||
|
||||
private final Set<UUID> visibility = Collections.newSetFromMap(new ConcurrentHashMap<UUID, Boolean>());
|
||||
/**
|
||||
* @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<UUID> viewers = Collections.newSetFromMap(new ConcurrentHashMap<UUID, Boolean>());
|
||||
private final List<ArmorStandWrapper> 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<String> 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. <br>
|
||||
* 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String> lines = new ArrayList<>();
|
||||
|
||||
Map<HologramFormat.Requirement, Object> requirements = new HashMap<>();
|
||||
Map<HologramFormat.Requirement, Object> 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<Placeholder, Object> placeholders = new HashMap<>();
|
||||
Map<Placeholder, Object> 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 <b>null</b> if the shop has no chest.
|
||||
*/
|
||||
@ -388,9 +424,4 @@ public class Shop {
|
||||
return null;
|
||||
}
|
||||
|
||||
public enum ShopType {
|
||||
NORMAL,
|
||||
ADMIN
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<UUID> visibility = Collections.newSetFromMap(new ConcurrentHashMap<UUID, Boolean>());
|
||||
|
||||
// concurrent since update task is in async thread
|
||||
// since this is a fake entity, item is hidden per default
|
||||
private final Set<UUID> viewers = Collections.newSetFromMap(new ConcurrentHashMap<UUID, Boolean>());
|
||||
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. <br>
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
42
src/main/java/de/epiceric/shopchest/utils/FastMath.java
Normal file
42
src/main/java/de/epiceric/shopchest/utils/FastMath.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
75
src/main/java/de/epiceric/shopchest/utils/Operator.java
Normal file
75
src/main/java/de/epiceric/shopchest/utils/Operator.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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<Location, Shop> shopLocation = new HashMap<>();
|
||||
private final HashMap<UUID, Location> playerLocation = new HashMap<>();
|
||||
// concurrent since it is updated in async task
|
||||
private final Map<UUID, Location> playerLocation = new ConcurrentHashMap<>();
|
||||
private final Map<Location, Shop> shopLocation = new HashMap<>();
|
||||
private final Collection<Shop> 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<Shop> 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<Shop> getShopsCopy() {
|
||||
return new ArrayList<>(getShops());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,7 +237,7 @@ public class ShopUtils {
|
||||
plugin.getShopDatabase().connect(new Callback<Integer>(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<Shop> sight = getShopsInSight(player);
|
||||
|
||||
Set<Shop> _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<Shop> getShopsInSight(Player player) {
|
||||
double dist = plugin.getShopChestConfig().maximal_distance;
|
||||
|
||||
Location loc = player.getEyeLocation();
|
||||
Vector direction = loc.getDirection();
|
||||
|
||||
Set<Shop> 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<Shop> otherShopsInSight = firstShopInSight ? new ArrayList<Shop>() : 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user