Vastly extended hologram configuration

- You can now completely customize every part of the hologram
- Added new placeholders "%STOCK%" and "%MAX-STACK%"
  => Now uses "%STOCK%" instead of "%AMOUNT%" in the in-stock message)
- Armor Stands are no longer spawned with NMS and reflection
- Hologram texts can dynamically change (e.g. with in-stock info)
- Might contain a few issues
This commit is contained in:
Eric 2017-05-20 20:10:07 +02:00
parent e85b11d274
commit e1f076bfcd
14 changed files with 555 additions and 228 deletions

View File

@ -4,8 +4,8 @@ import com.palmergames.bukkit.towny.Towny;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.wasteofplastic.askyblock.ASkyBlock;
import de.epiceric.shopchest.config.Config;
import de.epiceric.shopchest.config.HologramFormat;
import de.epiceric.shopchest.config.Regex;
import de.epiceric.shopchest.event.ShopReloadEvent;
import de.epiceric.shopchest.external.PlotSquaredShopFlag;
import de.epiceric.shopchest.language.LanguageUtils;
import de.epiceric.shopchest.language.LocalizedMessage;
@ -43,7 +43,8 @@ public class ShopChest extends JavaPlugin {
private static ShopChest instance;
private Config config = null;
private Config config;
private HologramFormat hologramFormat;
private ShopCommand shopCommand;
private Economy econ = null;
private Database database;
@ -153,8 +154,16 @@ public class ShopChest extends JavaPlugin {
debug("Loading utils and extras...");
LanguageUtils.load();
saveResource("item_names.txt", true);
File hologramFormatFile = new File(getDataFolder(), "hologram-format.yml");
if (!hologramFormatFile.exists()) {
saveResource("hologram-format.yml", false);
}
hologramFormat = new HologramFormat(this);
loadMetrics();
checkForUpdates();
@ -406,9 +415,10 @@ public class ShopChest extends JavaPlugin {
}
}
/**
* @return The {@link ShopCommand}
*/
public HologramFormat getHologramFormat() {
return hologramFormat;
}
public ShopCommand getShopCommand() {
return shopCommand;
}

View File

@ -141,9 +141,6 @@ public class Config {
/** Whether admin shops should be excluded of the shop limits **/
public boolean exclude_admin_shops;
/** Whether the buy- and sell price should be arranged below each other **/
public boolean two_line_prices;
/** Whether the extension of a potion or tipped arrow (if available) should be appended to the item name. **/
public boolean append_potion_level_to_item_name;
@ -177,11 +174,8 @@ public class Config {
**/
public boolean invert_mouse_buttons;
/** Amount a hologram with two price-lines should be lifted **/
public double two_line_hologram_lift;
/** Amount a hologram with one price-line should be lifted **/
public double one_line_hologram_lift;
/** Whether the hologram's location should be fixed at the bottom **/
public boolean hologram_fixed_bottom;
/** Amount every hologram should be lifted **/
public double hologram_lift;
@ -362,7 +356,6 @@ public class Config {
blacklist = (plugin.getConfig().getStringList("blacklist") == null) ? new ArrayList<String>() : plugin.getConfig().getStringList("blacklist");
buy_greater_or_equal_sell = plugin.getConfig().getBoolean("buy-greater-or-equal-sell");
hopper_protection = plugin.getConfig().getBoolean("hopper-protection");
two_line_prices = plugin.getConfig().getBoolean("two-line-prices");
enable_quality_mode = plugin.getConfig().getBoolean("enable-quality-mode");
enable_hologram_interaction = plugin.getConfig().getBoolean("enable-hologram-interaction");
enable_debug_log = plugin.getConfig().getBoolean("enable-debug-log");
@ -384,8 +377,7 @@ public class Config {
show_shop_items = plugin.getConfig().getBoolean("show-shop-items");
remove_shop_on_error = plugin.getConfig().getBoolean("remove-shop-on-error");
invert_mouse_buttons = plugin.getConfig().getBoolean("invert-mouse-buttons");
two_line_hologram_lift = plugin.getConfig().getDouble("two-line-hologram-lift");
one_line_hologram_lift = plugin.getConfig().getDouble("one-line-hologram-lift");
hologram_fixed_bottom = plugin.getConfig().getBoolean("hologram-fixed-bottom");
hologram_lift = plugin.getConfig().getDouble("hologram-lift");
maximal_distance = plugin.getConfig().getDouble("maximal-distance");
maximal_item_distance = plugin.getConfig().getDouble("maximal-item-distance");

View File

@ -0,0 +1,124 @@
package de.epiceric.shopchest.config;
import de.epiceric.shopchest.ShopChest;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.File;
import java.util.List;
import java.util.Map;
public class HologramFormat {
public enum Requirement {
VENDOR, AMOUNT, ITEM_TYPE, ITEM_NAME, HAS_ENCHANTMENT, BUY_PRICE,
SELL_PRICE, HAS_POTION_EFFECT, IS_MUSIC_DISC, IS_POTION_EXTENDED, IS_BOOK, ADMIN_SHOP,
NORMAL_SHOP, IN_STOCK, MAX_STACK
}
private ShopChest plugin;
private YamlConfiguration config;
public HologramFormat(ShopChest plugin) {
File configFile = new File(plugin.getDataFolder(), "hologram-format.yml");
this.config = YamlConfiguration.loadConfiguration(configFile);
this.plugin = plugin;
}
/**
* Get the format for the given line of the hologram
* @param line Line of the hologram
* @param values Values of the requirements that might be needed by the format (contains {@code null} if not comparable)
* @return The format of the first working option, or an empty String if no option is working
* because of not fulfilled requirements
*/
public String getFormat(int line, Map<Requirement, Object> values) {
ConfigurationSection options = config.getConfigurationSection("lines." + line + ".options");
optionLoop:
for (String key : options.getKeys(false)) {
ConfigurationSection option = options.getConfigurationSection(key);
List<String> requirements = option.getStringList("requirements");
String format = option.getString("format");
for (String sReq : requirements) {
for (Requirement req : values.keySet()) {
if (sReq.contains(req.toString())) {
if (!sReq.replace(req.toString(), "").trim().isEmpty()) {
if (!eval(sReq, values)) {
continue optionLoop;
}
}
}
}
}
return format;
}
return "";
}
/** Returns whether the hologram text has to change dynamically without reloading */
public boolean isDynamic() {
int count = getLineCount();
for (int i = 0; i < count; i++) {
ConfigurationSection options = config.getConfigurationSection("lines." + i + ".options");
for (String key : options.getKeys(false)) {
String format = options.getConfigurationSection(key).getString("format");
if (format.contains(Regex.STOCK.getName())) {
return true;
}
}
}
return false;
}
/** Returns the amount of lines in a hologram */
public int getLineCount() {
return config.getConfigurationSection("lines").getKeys(false).size();
}
/** Returns the configuration of the "hologram-format.yml" file */
public YamlConfiguration getConfig() {
return config;
}
/**
* Parse and evaluate a condition
* @param condition Condition to evaluate
* @param values Values of the requirements
* @return Result of the condition
*/
public boolean eval(String condition, Map<Requirement, Object> values) {
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
String c = condition;
for (HologramFormat.Requirement req : HologramFormat.Requirement.values()) {
if (c.contains(req.toString()) && values.containsKey(req)) {
String replace = String.valueOf(values.get(req));
if (values.get(req) instanceof String) {
replace = String.format("\"%s\"", replace);
}
c = c.replace(req.toString(), replace);
}
}
return (boolean) engine.eval(c);
} catch (ScriptException e) {
plugin.debug("Failed to eval condition: " + condition);
plugin.debug(e);
}
return false;
}
}

View File

@ -21,7 +21,9 @@ public enum Regex {
VALUE("%VALUE%"),
EXTENDED("%EXTENDED%"),
REVENUE("%REVENUE%"),
GENERATION("%GENERATION%");
GENERATION("%GENERATION%"),
STOCK("%STOCK%"),
MAX_STACK("%MAX-STACK%");
private String name;

View File

@ -19,6 +19,7 @@ import org.bukkit.potion.Potion;
import org.bukkit.potion.PotionType;
import java.util.ArrayList;
import java.util.Map;
public class LanguageUtils {
@ -1058,10 +1059,6 @@ public class LanguageUtils {
messages.add(new LocalizedMessage(LocalizedMessage.Message.UPDATE_NO_UPDATE, langConfig.getString("message.update.no-update", "&6&lNo new update available.")));
messages.add(new LocalizedMessage(LocalizedMessage.Message.UPDATE_CHECKING, langConfig.getString("message.update.checking", "&6&lChecking for updates...")));
messages.add(new LocalizedMessage(LocalizedMessage.Message.UPDATE_ERROR, langConfig.getString("message.update.error", "&c&lError while checking for updates.")));
messages.add(new LocalizedMessage(LocalizedMessage.Message.HOLOGRAM_FORMAT, langConfig.getString("message.hologram.format", "%AMOUNT% * %ITEMNAME%"), Regex.AMOUNT, Regex.ITEM_NAME));
messages.add(new LocalizedMessage(LocalizedMessage.Message.HOLOGRAM_BUY_SELL, langConfig.getString("message.hologram.buy-and-sell", "Buy %BUY-PRICE% | %SELL-PRICE% Sell"), Regex.BUY_PRICE, Regex.SELL_PRICE));
messages.add(new LocalizedMessage(LocalizedMessage.Message.HOLOGRAM_BUY, langConfig.getString("message.hologram.only-buy", "Buy %BUY-PRICE%"), Regex.BUY_PRICE));
messages.add(new LocalizedMessage(LocalizedMessage.Message.HOLOGRAM_SELL, langConfig.getString("message.hologram.only-sell", "Sell %SELL-PRICE%"), Regex.SELL_PRICE));
messages.add(new LocalizedMessage(LocalizedMessage.Message.NO_PERMISSION_CREATE, langConfig.getString("message.noPermission.create", "&cYou don't have permission to create a shop.")));
messages.add(new LocalizedMessage(LocalizedMessage.Message.NO_PERMISSION_CREATE_ADMIN, langConfig.getString("message.noPermission.create-admin", "&cYou don't have permission to create an admin shop.")));
messages.add(new LocalizedMessage(LocalizedMessage.Message.NO_PERMISSION_CREATE_PROTECTED, langConfig.getString("message.noPermission.create-protected", "&cYou don't have permission to create a shop on a protected chest.")));
@ -1208,11 +1205,35 @@ public class LanguageUtils {
return enchantmentString + " " + levelString;
}
/**
* @param enchantmentMap Map of enchantments of an item
* @return Comma separated list of localized enchantments
*/
public static String getEnchantmentString(Map<Enchantment, Integer> enchantmentMap) {
Enchantment[] enchantments = enchantmentMap.keySet().toArray(new Enchantment[enchantmentMap.size()]);
StringBuilder enchantmentList = new StringBuilder();
for (int i = 0; i < enchantments.length; i++) {
Enchantment enchantment = enchantments[i];
if (i == enchantments.length - 1) {
enchantmentList.append(LanguageUtils.getEnchantmentName(enchantment, enchantmentMap.get(enchantment)));
} else {
enchantmentList.append(LanguageUtils.getEnchantmentName(enchantment, enchantmentMap.get(enchantment)));
enchantmentList.append(", ");
}
}
return enchantmentList.toString();
}
/**
* @param itemStack Potion Item whose base effect name should be looked up
* @return Localized Name of the Base Potion Effect
*/
public static String getPotionEffectName(ItemStack itemStack) {
if (!(itemStack.getItemMeta() instanceof PotionMeta)) return "";
PotionMeta potionMeta = (PotionMeta) itemStack.getItemMeta();
PotionType potionType;
boolean upgraded;

View File

@ -104,10 +104,6 @@ public class LocalizedMessage {
UPDATE_NO_UPDATE,
UPDATE_CHECKING,
UPDATE_ERROR,
HOLOGRAM_FORMAT,
HOLOGRAM_BUY_SELL,
HOLOGRAM_BUY,
HOLOGRAM_SELL,
NO_PERMISSION_CREATE,
NO_PERMISSION_CREATE_ADMIN,
NO_PERMISSION_CREATE_PROTECTED,

View File

@ -26,6 +26,7 @@ import de.epiceric.shopchest.shop.Shop;
import de.epiceric.shopchest.shop.Shop.ShopType;
import de.epiceric.shopchest.sql.Database;
import de.epiceric.shopchest.utils.ClickType;
import de.epiceric.shopchest.utils.ItemUtils;
import de.epiceric.shopchest.utils.Permissions;
import de.epiceric.shopchest.utils.ShopUtils;
import de.epiceric.shopchest.utils.Utils;
@ -49,14 +50,13 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.*;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.Potion;
import org.bukkit.scheduler.BukkitRunnable;
import pl.islandworld.api.IslandWorldApi;
import us.talabrek.ultimateskyblock.api.IslandInfo;
@ -81,6 +81,29 @@ public class ShopInteractListener implements Listener {
this.worldGuard = plugin.getWorldGuard();
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent e) {
if (!plugin.getHologramFormat().isDynamic()) return;
Inventory chestInv = e.getInventory();
if (!(e.getInventory().getHolder() instanceof Chest || e.getInventory().getHolder() instanceof DoubleChest)) {
return;
}
Location loc = chestInv.getLocation();
final Shop shop = plugin.getShopUtils().getShop(loc);
if (shop == null) return;
new BukkitRunnable() {
@Override
public void run() {
shop.updateHologramText();
}
}.runTaskLater(plugin, 1L);
}
@EventHandler(ignoreCancelled = true)
public void onPlayerManipulateArmorStand(PlayerArmorStandManipulateEvent e) {
// When clicking an armor stand with an armor item, the armor stand will take it.
@ -715,39 +738,13 @@ public class ShopInteractListener implements Listener {
LocalizedMessage.Message.SHOP_INFO_NORMAL : LocalizedMessage.Message.SHOP_INFO_ADMIN);
String stock = LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_INFO_STOCK,
new LocalizedMessage.ReplacedRegex(Regex.AMOUNT, String.valueOf(amount)));
new LocalizedMessage.ReplacedRegex(Regex.STOCK, String.valueOf(amount)));
boolean potionExtended = false;
Map<Enchantment, Integer> enchantmentMap;
String potionEffectName = "";
if (Utils.getMajorVersion() >= 9) {
if (type == Material.TIPPED_ARROW || type == Material.LINGERING_POTION || type == Material.SPLASH_POTION) {
potionEffectName = LanguageUtils.getPotionEffectName(shop.getProduct());
PotionMeta potionMeta = (PotionMeta) shop.getProduct().getItemMeta();
potionExtended = potionMeta.getBasePotionData().isExtended();
if (potionEffectName.trim().isEmpty())
potionEffectName = LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_INFO_NONE);
}
}
if (type == Material.POTION) {
potionEffectName = LanguageUtils.getPotionEffectName(shop.getProduct());
if (Utils.getMajorVersion() < 9) {
Potion potion = Potion.fromItemStack(shop.getProduct());
potionExtended = potion.hasExtendedDuration();
} else {
PotionMeta potionMeta = (PotionMeta) shop.getProduct().getItemMeta();
potionExtended = potionMeta.getBasePotionData().isExtended();
}
if (potionEffectName.trim().isEmpty())
potionEffectName = LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_INFO_NONE);
}
String potionEffectName = LanguageUtils.getPotionEffectName(shop.getProduct());
if (potionEffectName.length() > 0) {
boolean potionExtended = ItemUtils.isExtendedPotion(shop.getProduct());
String extended = potionExtended ? LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_INFO_EXTENDED) : "";
potionEffectString = LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_INFO_POTION_EFFECT,
new LocalizedMessage.ReplacedRegex(Regex.POTION_EFFECT, potionEffectName),
@ -779,25 +776,8 @@ public class ShopInteractListener implements Listener {
new LocalizedMessage.ReplacedRegex(Regex.MUSIC_TITLE, musicDiscName));
}
String enchantmentList = "";
if (shop.getProduct().getItemMeta() instanceof EnchantmentStorageMeta) {
EnchantmentStorageMeta esm = (EnchantmentStorageMeta) shop.getProduct().getItemMeta();
enchantmentMap = esm.getStoredEnchants();
} else {
enchantmentMap = shop.getProduct().getEnchantments();
}
Enchantment[] enchantments = enchantmentMap.keySet().toArray(new Enchantment[enchantmentMap.size()]);
for (int i = 0; i < enchantments.length; i++) {
Enchantment enchantment = enchantments[i];
if (i == enchantments.length - 1) {
enchantmentList += LanguageUtils.getEnchantmentName(enchantment, enchantmentMap.get(enchantment));
} else {
enchantmentList += LanguageUtils.getEnchantmentName(enchantment, enchantmentMap.get(enchantment)) + ", ";
}
}
Map<Enchantment, Integer> enchantmentMap = ItemUtils.getEnchantments(shop.getProduct());
String enchantmentList = LanguageUtils.getEnchantmentString(enchantmentMap);
if (enchantmentList.length() > 0) {
enchantmentString = LanguageUtils.getMessage(LocalizedMessage.Message.SHOP_INFO_ENCHANTMENTS,

View File

@ -2,47 +2,41 @@ package de.epiceric.shopchest.nms;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.utils.Utils;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Hologram {
private static List<Hologram> holograms = new ArrayList<>();
private boolean exists = false;
private List<Object> entityList = new ArrayList<>();
private List<UUID> entityUuidList = new ArrayList<>();
private String[] text;
private List<Object> nmsArmorStands = new ArrayList<>();
private List<ArmorStand> armorStands = new ArrayList<>();
private ArmorStand interactArmorStand;
private Location location;
private List<Player> visible = new ArrayList<>();
private ShopChest plugin;
private Class<?> entityArmorStandClass = Utils.getNMSClass("EntityArmorStand");
private Class<?> worldClass = Utils.getNMSClass("World");
private Class<?> packetPlayOutSpawnEntityLivingClass = Utils.getNMSClass("PacketPlayOutSpawnEntityLiving");
private Class<?> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy");
private Class<?> entityClass = Utils.getNMSClass("Entity");
private Class<?> entityLivingClass = Utils.getNMSClass("EntityLiving");
private Class<?> worldServerClass = Utils.getNMSClass("WorldServer");
public Hologram(ShopChest plugin, String[] text, Location location) {
public Hologram(ShopChest plugin, String[] lines, Location location) {
this.plugin = plugin;
this.text = text;
this.location = location;
Class[] requiredClasses = new Class[] {
worldClass, entityArmorStandClass, entityLivingClass, entityClass,
packetPlayOutSpawnEntityLivingClass, packetPlayOutEntityDestroyClass,
worldServerClass
entityArmorStandClass, entityLivingClass, packetPlayOutSpawnEntityLivingClass,
packetPlayOutEntityDestroyClass,
};
for (Class c : requiredClasses) {
@ -52,59 +46,101 @@ public class Hologram {
}
}
create();
create(lines);
}
private void create() {
Location loc = location.clone();
public void addLine(int line, String text) {
if (text == null || text.isEmpty()) return;
for (int i = 0; i <= text.length; i++) {
String text = null;
text = ChatColor.translateAlternateColorCodes('&', text);
if (i != this.text.length) {
text = this.text[i];
if (text == null || text.isEmpty()) continue;
} else {
if (plugin.getShopChestConfig().enable_hologram_interaction) {
loc = location.clone();
loc.add(0, 0.4, 0);
} else {
continue;
}
}
for (int i = line; i < armorStands.size(); i++) {
ArmorStand stand = armorStands.get(i);
stand.teleport(stand.getLocation().subtract(0, 0.25, 0));
}
if (line >= armorStands.size()) {
line = armorStands.size();
}
Location location = this.location.clone().subtract(0, line * 0.25, 0);
try {
ArmorStand armorStand = (ArmorStand) location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND);
armorStand.setGravity(false);
armorStand.setVisible(false);
armorStand.setCustomName(text);
armorStand.setCustomNameVisible(true);
Object craftArmorStand = armorStand.getClass().cast(armorStand);
Object nmsArmorStand = craftArmorStand.getClass().getMethod("getHandle").invoke(craftArmorStand);
nmsArmorStands.add(line, nmsArmorStand);
armorStands.add(line, armorStand);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
plugin.getLogger().severe("Could not create Hologram with reflection");
plugin.debug("Could not create Hologram with reflection");
plugin.debug(e);
}
}
public void setLine(int line, String text) {
if (text == null ||text.isEmpty()) {
removeLine(line);
return;
}
text = ChatColor.translateAlternateColorCodes('&', text);
if (armorStands.size() <= line) {
addLine(line, text);
return;
}
armorStands.get(line).setCustomName(text);
}
public void removeLine(int line) {
for (int i = line + 1; i < armorStands.size(); i++) {
ArmorStand stand = armorStands.get(i);
stand.teleport(stand.getLocation().add(0, 0.25, 0));
}
if (armorStands.size() > line) {
armorStands.get(line).remove();
armorStands.remove(line);
nmsArmorStands.remove(line);
}
}
public String[] getLines() {
List<String> lines = new ArrayList<>();
for (ArmorStand armorStand : armorStands) {
lines.add(armorStand.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]);
}
if (plugin.getShopChestConfig().enable_hologram_interaction) {
Location loc = location.clone().add(0, 0.4, 0);
try {
Object craftWorld = loc.getWorld().getClass().cast(loc.getWorld());
Object worldServer = craftWorld.getClass().getMethod("getHandle").invoke(craftWorld);
ArmorStand armorStand = (ArmorStand) location.getWorld().spawnEntity(loc, EntityType.ARMOR_STAND);
armorStand.setGravity(false);
armorStand.setVisible(false);
Constructor entityArmorStandConstructor = entityArmorStandClass.getConstructor(worldClass, double.class, double.class, double.class);
Object entityArmorStand = entityArmorStandConstructor.newInstance(worldServer, loc.getX(), loc.getY(),loc.getZ());
Object craftArmorStand = armorStand.getClass().cast(armorStand);
Object nmsArmorStand = craftArmorStand.getClass().getMethod("getHandle").invoke(craftArmorStand);
if (text != null) {
entityArmorStandClass.getMethod("setCustomName", String.class).invoke(entityArmorStand, text);
entityArmorStandClass.getMethod("setCustomNameVisible", boolean.class).invoke(entityArmorStand, true);
}
entityArmorStandClass.getMethod("setInvisible", boolean.class).invoke(entityArmorStand, true);
if (Utils.getMajorVersion() < 10) {
entityArmorStandClass.getMethod("setGravity", boolean.class).invoke(entityArmorStand, false);
} else {
entityArmorStandClass.getMethod("setNoGravity", boolean.class).invoke(entityArmorStand, 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(worldServer), entityArmorStand);
Object uuid = entityClass.getMethod("getUniqueID").invoke(entityArmorStand);
entityUuidList.add((UUID) uuid);
entityList.add(entityArmorStand);
loc.subtract(0, 0.25, 0);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
nmsArmorStands.add(nmsArmorStand);
interactArmorStand = armorStand;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
plugin.getLogger().severe("Could not create Hologram with reflection");
plugin.debug("Could not create Hologram with reflection");
plugin.debug(e);
@ -119,7 +155,7 @@ public class Hologram {
* @return Location of the hologram
*/
public Location getLocation() {
return location;
return location.clone();
}
/**
@ -129,7 +165,7 @@ public class Hologram {
new BukkitRunnable() {
@Override
public void run() {
for (Object o : entityList) {
for (Object o : nmsArmorStands) {
try {
Object entityLiving = entityLivingClass.cast(o);
Object packet = packetPlayOutSpawnEntityLivingClass.getConstructor(entityLivingClass).newInstance(entityLiving);
@ -166,7 +202,7 @@ public class Hologram {
}
private void sendDestroyPackets(Player p) {
for (Object o : entityList) {
for (Object o : nmsArmorStands) {
try {
int id = (int) entityArmorStandClass.getMethod("getId").invoke(o);
@ -208,7 +244,17 @@ public class Hologram {
* @return Whether the given armor stand is part of the hologram
*/
public boolean contains(ArmorStand armorStand) {
return entityUuidList.contains(armorStand.getUniqueId());
return armorStands.contains(armorStand);
}
/** Returns the ArmorStands of this hologram */
public List<ArmorStand> getArmorStands() {
return armorStands;
}
/** Returns the ArmorStand of this hologram that is responsible for interaction */
public ArmorStand getInteractArmorStand() {
return interactArmorStand;
}
/**
@ -216,15 +262,10 @@ public class Hologram {
* Hologram will be hidden from all players and will be killed
*/
public void remove() {
for (Object o : entityList) {
try {
o.getClass().getMethod("die").invoke(o);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
plugin.getLogger().severe("Could not remove Hologram with reflection");
plugin.debug("Could not remove Hologram with reflection");
plugin.debug(e);
}
for (ArmorStand armorStand : armorStands) {
armorStand.remove();
}
exists = false;
holograms.remove(this);
}

View File

@ -2,12 +2,15 @@ package de.epiceric.shopchest.shop;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.config.Config;
import de.epiceric.shopchest.config.HologramFormat;
import de.epiceric.shopchest.config.Regex;
import de.epiceric.shopchest.exceptions.ChestNotFoundException;
import de.epiceric.shopchest.exceptions.NotEnoughSpaceException;
import de.epiceric.shopchest.language.LanguageUtils;
import de.epiceric.shopchest.language.LocalizedMessage;
import de.epiceric.shopchest.nms.CustomBookMeta;
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;
@ -21,6 +24,11 @@ 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;
public class Shop {
private boolean created;
@ -124,7 +132,7 @@ public class Shop {
Location itemLocation;
ItemStack itemStack;
itemLocation = new Location(location.getWorld(), hologram.getLocation().getX(), location.getY() + 1, hologram.getLocation().getZ());
itemLocation = new Location(location.getWorld(), hologram.getLocation().getX(), location.getY() + 0.9, hologram.getLocation().getZ());
itemStack = product.clone();
itemStack.setAmount(1);
@ -162,44 +170,99 @@ public class Shop {
doubleChest = false;
}
boolean twoLinePrices = config.two_line_prices;
String[] holoText = getHologramText(twoLinePrices ? 3 : 2);
Location holoLocation = getHologramLocation(doubleChest, chests, holoText);
String[] holoText = getHologramText();
Location holoLocation = getHologramLocation(doubleChest, chests);
hologram = new Hologram(plugin, holoText, holoLocation);
}
private String[] getHologramText(int length) {
String[] holoText = new String[length];
public void updateHologramText() {
String[] lines = getHologramText();
String[] currentLines = hologram.getLines();
holoText[0] = LanguageUtils.getMessage(LocalizedMessage.Message.HOLOGRAM_FORMAT,
new LocalizedMessage.ReplacedRegex(Regex.AMOUNT, String.valueOf(product.getAmount())),
new LocalizedMessage.ReplacedRegex(Regex.ITEM_NAME, LanguageUtils.getItemName(product)));
int max = Math.max(lines.length, currentLines.length);
if ((buyPrice <= 0) && (sellPrice > 0)) {
holoText[1] = LanguageUtils.getMessage(LocalizedMessage.Message.HOLOGRAM_SELL,
new LocalizedMessage.ReplacedRegex(Regex.SELL_PRICE, String.valueOf(sellPrice)));
} else if ((buyPrice > 0) && (sellPrice <= 0)) {
holoText[1] = LanguageUtils.getMessage(LocalizedMessage.Message.HOLOGRAM_BUY,
new LocalizedMessage.ReplacedRegex(Regex.BUY_PRICE, String.valueOf(buyPrice)));
} else {
if (length == 2) {
holoText[1] = LanguageUtils.getMessage(LocalizedMessage.Message.HOLOGRAM_BUY_SELL,
new LocalizedMessage.ReplacedRegex(Regex.BUY_PRICE, String.valueOf(buyPrice)),
new LocalizedMessage.ReplacedRegex(Regex.SELL_PRICE, String.valueOf(sellPrice)));
for (int i = 0; i < max; i++) {
if (i < lines.length) {
hologram.setLine(i, lines[i]);
} else {
holoText[1] = LanguageUtils.getMessage(LocalizedMessage.Message.HOLOGRAM_BUY,
new LocalizedMessage.ReplacedRegex(Regex.BUY_PRICE, String.valueOf(buyPrice)));
holoText[2] = LanguageUtils.getMessage(LocalizedMessage.Message.HOLOGRAM_SELL,
new LocalizedMessage.ReplacedRegex(Regex.SELL_PRICE, String.valueOf(sellPrice)));
hologram.removeLine(i);
}
}
return holoText;
}
private Location getHologramLocation(boolean doubleChest, Chest[] chests, String[] holoText) {
private String[] getHologramText() {
List<String> lines = new ArrayList<>();
Map<HologramFormat.Requirement, Object> requirements = new HashMap<>();
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() : ""));
requirements.put(HologramFormat.Requirement.ITEM_NAME, getProduct().getItemMeta().getDisplayName());
requirements.put(HologramFormat.Requirement.HAS_ENCHANTMENT, !LanguageUtils.getEnchantmentString(ItemUtils.getEnchantments(getProduct())).isEmpty());
requirements.put(HologramFormat.Requirement.BUY_PRICE, getBuyPrice());
requirements.put(HologramFormat.Requirement.SELL_PRICE, getSellPrice());
requirements.put(HologramFormat.Requirement.HAS_POTION_EFFECT, ItemUtils.getPotionEffect(getProduct()) != null);
requirements.put(HologramFormat.Requirement.IS_MUSIC_DISC, ItemUtils.isMusicDisc(getProduct()));
requirements.put(HologramFormat.Requirement.IS_POTION_EXTENDED, ItemUtils.isExtendedPotion(getProduct()));
requirements.put(HologramFormat.Requirement.IS_BOOK, ItemUtils.getBookGeneration(getProduct()) != null);
requirements.put(HologramFormat.Requirement.ADMIN_SHOP, getShopType() == ShopType.ADMIN);
requirements.put(HologramFormat.Requirement.NORMAL_SHOP, getShopType() == ShopType.NORMAL);
requirements.put(HologramFormat.Requirement.IN_STOCK, Utils.getAmount(getInventoryHolder().getInventory(), getProduct()));
requirements.put(HologramFormat.Requirement.MAX_STACK, getProduct().getMaxStackSize());
int lineCount = plugin.getHologramFormat().getLineCount();
for (int i = 0; i < lineCount; i++) {
String format = plugin.getHologramFormat().getFormat(i, requirements);
for (Regex regex : Regex.values()) {
String replace = "";
switch (regex) {
case VENDOR:
replace = getVendor().getName();
break;
case AMOUNT:
replace = String.valueOf(getProduct().getAmount());
break;
case ITEM_NAME:
replace = LanguageUtils.getItemName(getProduct());
break;
case ENCHANTMENT:
replace = LanguageUtils.getEnchantmentString(ItemUtils.getEnchantments(getProduct()));
break;
case BUY_PRICE:
replace = plugin.getEconomy().format(getBuyPrice());
break;
case SELL_PRICE:
replace = plugin.getEconomy().format(getSellPrice());
break;
case POTION_EFFECT:
replace = LanguageUtils.getPotionEffectName(getProduct());
break;
case MUSIC_TITLE:
replace = LanguageUtils.getMusicDiscName(getProduct().getType());
break;
case GENERATION:
CustomBookMeta.Generation gen = ItemUtils.getBookGeneration(getProduct());
if (gen != null) replace = LanguageUtils.getBookGenerationName(gen);
break;
case STOCK:
replace = String.valueOf(Utils.getAmount(getInventoryHolder().getInventory(), getProduct()));
break;
}
format = format.replace(regex.getName(), replace);
}
lines.add(format);
}
return lines.toArray(new String[lines.size()]);
}
private Location getHologramLocation(boolean doubleChest, Chest[] chests) {
Block b = location.getBlock();
Location holoLocation;
@ -235,14 +298,6 @@ public class Shop {
holoLocation.add(0, config.hologram_lift, 0);
if (config.two_line_prices) {
if (holoText.length == 3 && holoText[2] != null) {
holoLocation.add(0, config.two_line_hologram_lift, 0);
} else {
holoLocation.add(0, config.one_line_hologram_lift, 0);
}
}
return holoLocation;
}

View File

@ -0,0 +1,71 @@
package de.epiceric.shopchest.utils;
import com.google.common.collect.Lists;
import de.epiceric.shopchest.nms.CustomBookMeta;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.Potion;
import org.bukkit.potion.PotionType;
import java.util.List;
import java.util.Map;
public class ItemUtils {
public static Map<Enchantment, Integer> getEnchantments(ItemStack itemStack) {
if (itemStack.getItemMeta() instanceof EnchantmentStorageMeta) {
EnchantmentStorageMeta esm = (EnchantmentStorageMeta) itemStack.getItemMeta();
return esm.getStoredEnchants();
} else {
return itemStack.getEnchantments();
}
}
public static PotionType getPotionEffect(ItemStack itemStack) {
if (itemStack.getItemMeta() instanceof PotionMeta) {
if (Utils.getMajorVersion() < 9) {
return Potion.fromItemStack(itemStack).getType();
} else {
return ((PotionMeta) itemStack.getItemMeta()).getBasePotionData().getType();
}
}
return null;
}
public static boolean isExtendedPotion(ItemStack itemStack) {
if (itemStack.getItemMeta() instanceof PotionMeta) {
if (Utils.getMajorVersion() >= 9) {
PotionMeta potionMeta = (PotionMeta) itemStack.getItemMeta();
return potionMeta.getBasePotionData().isExtended();
} else {
Potion potion = Potion.fromItemStack(itemStack);
return potion.hasExtendedDuration();
}
}
return false;
}
public static boolean isMusicDisc(ItemStack itemStack) {
List<Material> musicDiscMaterials = Lists.newArrayList(
Material.GOLD_RECORD, Material.GREEN_RECORD, Material.RECORD_3, Material.RECORD_4,
Material.RECORD_5, Material.RECORD_6, Material.RECORD_7, Material.RECORD_8,
Material.RECORD_9, Material.RECORD_10, Material.RECORD_11, Material.RECORD_12
);
return musicDiscMaterials.contains(itemStack.getType());
}
public static CustomBookMeta.Generation getBookGeneration(ItemStack itemStack) {
if (itemStack.getType() == Material.WRITTEN_BOOK) {
return CustomBookMeta.getGeneration(itemStack);
}
return null;
}
}

View File

@ -82,26 +82,10 @@ only-show-shops-in-sight: true
# This only has effect if 'only-show-shops-in-sight' is enabled
only-show-first-shop-in-sight: true
# Set whether the buy- and sell price should be arranged below each other.
# The first line will be the buy price with the message
# "message.hologram.only-buy", the second line will be the sell price
# with the message "message.hologram.only-sell".
# If buying or selling is disabled, a line for that price will not be created.
# (The messages can be found and modified in the specified language file)
two-line-prices: false
# Set the amount (may be negative) a hologram should be lifted in the y-axis,
# when "two-line-prices" is set to true and buying and selling is enabled
# at the shop.
# The higher the number, the higher will the hologram be.
# A value of '1' equals to one block, and a value of '0.25' is equal to the
# height of one line.
two-line-hologram-lift: 0.25
# Set the amount (may be negative) a hologram should be lifted in the y-axis,
# when "two-line-prices" is set to true and only buying or selling is
# enabled at the shop.
one-line-hologram-lift: 0
# Set whether the hologram's location should be fixed at the bottom,
# so when it gets more lines, it won't interfere with the item or chest,
# but goes higher.
hologram-fixed-bottom: true
# Set the amount (may be negative) a hologram should be lifted in the y-axis.
# If the hologram lift is already increased by the values above,

View File

@ -0,0 +1,71 @@
# ===============================================
# === ShopChest's hologram configuration file ===
# ===============================================
#
# Valid requirements are:
# VENDOR, AMOUNT, ITEM_TYPE, ITEM_NAME, HAS_ENCHANTMENT, BUY_PRICE,
# SELL_PRICE, HAS_POTION_EFFECT, IS_MUSIC_DISC, IS_POTION_EXTENDED,
# IS_BOOK, ADMIN_SHOP, NORMAL_SHOP, IN_STOCK, MAX_STACK
#
# You can also use the requirements for conditions.
# ITEM_TYPE will return the type of the item (-> item_names.txt),
# ITEM_NAME can be compared against a custom named item's name (may be null).
#
# Examples:
# - IN_STOCK > 0
# - VENDOR == "EpicEric"
# - BUY_PRICE <= SELL_PRICE
# - ITEM_TYPE == "STONE:2"
# - ITEM_TYPE != "IRON_INGOT"
# - ITEM_NAME == "The Mighty Sword"
# - (AMOUNT > 10) && (AMOUNT <= 20)
# - (IN_STOCK > 0) || ADMIN_SHOP
#
# Valid placeholders are:
# %VENDOR%, %AMOUNT%, %ITEM-NAME%, %ENCHANTMENT%, %BUY-PRICE%,
# %SELL-PRICE%, %POTION-EFFECT%, %MUSIC-TITLE%, %GENERATION%,
# %STOCK%, %MAX-STACK%
#
# Other information:
# - Options can be called however you want.
# - Color codes can be used in the format.
# - Options are checked from top to bottom; the first to
# fulfill the requirements will be taken.
# - Lines start with 0.
lines:
0:
options:
normal-shop:
format: "%VENDOR%"
requirements:
- NORMAL_SHOP
admin-shop:
format: "&cAdmin Shop"
requirements:
- ADMIN_SHOP
1:
options:
default:
format: "%AMOUNT% x %ITEMNAME%"
requirements:
2:
options:
buy-and-sell:
format: "Buy %BUY-PRICE% | %SELL-PRICE% Sell"
requirements:
- BUY_PRICE > 0
- SELL_PRICE > 0
only-buy:
format: "Buy %BUY-PRICE%"
requirements:
- BUY_PRICE > 0
only-sell:
format: "Sell %SELL-PRICE%"
requirements:
- SELL_PRICE > 0

View File

@ -7,7 +7,7 @@ message.chest-no-shop=&cTruhe ist kein Shop.
message.shop-create-not-enough-money=&cNicht genug Geld. Du brauchst &6%CREATION-PRICE% &cum einen Shop zu erstellen.
message.shopInfo.vendor=&6Verkäufer: &e%VENDOR%
message.shopInfo.product=&6Produkt: &e%AMOUNT% x %ITEMNAME%
message.shopInfo.stock=&6Auf Lager: &e%AMOUNT%
message.shopInfo.stock=&6Auf Lager: &e%STOCK%
message.shopInfo.enchantments=&6Verzauberungen: &e%ENCHANTMENT%
message.shopInfo.potion-effect=&6Trank-Effekte: &e%POTION-EFFECT% %EXTENDED%
message.shopInfo.music-disc-title=&6Schallplattentitel: &e%MUSIC-TITLE%
@ -60,10 +60,6 @@ message.update.click-to-download=Klicke hier zum Herunterladen
message.update.no-update=&6&lKeine neue Aktualisierung verfügbar.
message.update.checking=&6&lSuche nach Aktualisierungen...
message.update.error=&c&lFehler beim Suchen nach Aktualisierungen.
message.hologram.format=%AMOUNT% * %ITEMNAME%
message.hologram.buy-and-sell=Kauf %BUY-PRICE% | %SELL-PRICE% Verkauf
message.hologram.only-buy=Kauf %BUY-PRICE%
message.hologram.only-sell=Verkauf %SELL-PRICE%
message.noPermission.create=&cDu hast keine Berechtigung einen Shop zu erstellen.
message.noPermission.create-admin=&cDu hast keine Berechtigung einen Admin-Shop zu erstellen.
message.noPermission.create-protected=&cDu hast keine Berechtigung hier einen Shop zu erstellen.

View File

@ -30,8 +30,8 @@ message.shopInfo.vendor=&6Vendor: &e%VENDOR%
message.shopInfo.product=&6Product: &e%AMOUNT% x %ITEMNAME%
# Set the in-stock message the player after entering '/shop info'.
# Usable Placeholders=%AMOUNT%
message.shopInfo.stock=&6In Stock: &e%AMOUNT%
# Usable Placeholders=%STOCK%
message.shopInfo.stock=&6In Stock: &e%STOCK%
# Set the enchantments message the player gets after entering '/shop info' if the product is enchanted
# Usable Placeholders: %ENCHANTMENT%
@ -215,22 +215,6 @@ message.update.checking=&6&lChecking for updates...
# Set the message when an error occurs while checking for updates.
message.update.error=&c&lError while checking for updates.
# Set the text in the first row of the shop's hologram
# Usable Placeholders: %ITEMNAME%, %AMOUNT%
message.hologram.format=%AMOUNT% * %ITEMNAME%
# Set the text in the second row of the shop's hologram when the player can buy and sell an item.
# Usable Placeholders: %BUY-PRICE%, %SELL-PRICE%
message.hologram.buy-and-sell=Buy %BUY-PRICE% | %SELL-PRICE% Sell
# Set the text in the second row of the shop's hologram when the player can only buy an item.
# Usable Placeholders: %BUY-PRICE%
message.hologram.only-buy=Buy %BUY-PRICE%
# Set the text in the second row of the shop's hologram when the player can only sell an item.
# Usable Placeholders: %SELL-PRICE%
message.hologram.only-sell=Sell %SELL-PRICE%
# Set the message when a not permitted player tries to create a shop.
message.noPermission.create=&cYou don't have permission to create a shop.