diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5667b35 --- /dev/null +++ b/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + net.t2code + T2C-AllayDuplicate + 1.3 + jar + + T2C AllayDuplicate + + + 1.8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + + Builders-Paradise + https://repo.t2code.net/repository/Builders-Paradise/ + + + T2Code + https://repo.t2code.net/repository/T2Code/ + + + + + jitpack.io + https://jitpack.io + + + + enginehub-maven + https://maven.enginehub.org/repo/ + + + + + + + net.t2code.minecraft.1_19.r1 + spigot + 1.19r1 + + + net.t2code + T2CodeLib + DEV-13.0 + dev-5 + + + net.t2code.plotsquared + plotsquared + 6.9.2 + + + + com.github.angeschossen + LandsAPI + 6.5.1 + provided + + + + com.fastasyncworldedit + FastAsyncWorldEdit-Core + 2.4.1 + provided + + + + com.fastasyncworldedit + FastAsyncWorldEdit-Bukkit + 2.4.1 + provided + + + FastAsyncWorldEdit-Core + * + + + + + + diff --git a/src/main/java/net/t2code/t2callayduplicate/Hooks/LandsIntegratior.java b/src/main/java/net/t2code/t2callayduplicate/Hooks/LandsIntegratior.java new file mode 100644 index 0000000..98367e4 --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/Hooks/LandsIntegratior.java @@ -0,0 +1,27 @@ +package net.t2code.t2callayduplicate.Hooks; + +import me.angeschossen.lands.api.flags.Flags; +import me.angeschossen.lands.api.integration.LandsIntegration; +import me.angeschossen.lands.api.land.Area; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import javax.annotation.Nullable; + +public class LandsIntegratior { + private LandsIntegration landsIntegration; + public LandsIntegratior(Plugin yourPlugin) { + this.landsIntegration = new LandsIntegration(yourPlugin); + } + public boolean canInteract(Location location, Player player) { + if(!landsIntegration.isClaimed(location)){ + return true; + } + final @Nullable Area area = landsIntegration.getAreaByLoc(location); + if (area != null) { + return area.hasFlag(player, Flags.INTERACT_GENERAL, false); + } + return true; + } +} diff --git a/src/main/java/net/t2code/t2callayduplicate/Hooks/PlotSquaredIntegration.java b/src/main/java/net/t2code/t2callayduplicate/Hooks/PlotSquaredIntegration.java new file mode 100644 index 0000000..03b5d30 --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/Hooks/PlotSquaredIntegration.java @@ -0,0 +1,25 @@ +package net.t2code.t2callayduplicate.Hooks; + +import com.plotsquared.bukkit.player.BukkitPlayer; +import com.plotsquared.bukkit.util.BukkitUtil; +import com.plotsquared.core.PlotAPI; +import com.plotsquared.core.listener.PlayerBlockEventType; +import com.plotsquared.core.location.Location; +import com.plotsquared.core.util.EventDispatcher; +import org.bukkit.entity.Player; + +public class PlotSquaredIntegration { + private final EventDispatcher eventDispatcher = new PlotAPI().getPlotSquared().getEventDispatcher(); + + public PlotSquaredIntegration(){ + } + + public boolean isProtected(Player player, PlayerBlockEventType type){ + BukkitPlayer pp = BukkitUtil.adapt(player); + Location location = BukkitUtil.adapt(player.getLocation()); + if (!eventDispatcher.checkPlayerBlockEvent(pp, type, location, null, true)) { + return true; + } + return false; + } +} diff --git a/src/main/java/net/t2code/t2callayduplicate/Util.java b/src/main/java/net/t2code/t2callayduplicate/Util.java new file mode 100644 index 0000000..bbdaa53 --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/Util.java @@ -0,0 +1,33 @@ +package net.t2code.t2callayduplicate; + +public class Util { + + public static String getInfoText() { + return ""; + } + + public static String getRequiredT2CodeLibVersion() { + return "13.0"; + } + + public static String getPrefix() { + return "§8[§4T2C§bAllay§6Duplicate§8]"; + } + + public static Integer getSpigotID() { + return 103745; + } + + public static Integer getBstatsID() { + return 15932; + } + + public static String getSpigot() { + return "https://www.spigotmc.org/resources/" + getSpigotID(); + } + + public static String getDiscord() { + return net.t2code.t2codelib.Util.getDiscord(); + } + +} diff --git a/src/main/java/net/t2code/t2callayduplicate/commands/CmdExecuter.java b/src/main/java/net/t2code/t2callayduplicate/commands/CmdExecuter.java new file mode 100644 index 0000000..849d0ec --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/commands/CmdExecuter.java @@ -0,0 +1,107 @@ +package net.t2code.t2callayduplicate.commands; + +import net.t2code.t2callayduplicate.Util; +import net.t2code.t2callayduplicate.config.ConfigFile; +import net.t2code.t2callayduplicate.event.Event; +import net.t2code.t2callayduplicate.system.Main; +import net.t2code.t2codelib.SPIGOT.api.commands.T2Ctab; +import net.t2code.t2codelib.SPIGOT.api.messages.T2Csend; +import net.t2code.t2codelib.SPIGOT.api.messages.T2Ctemplate; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class CmdExecuter implements CommandExecutor, TabCompleter { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length == 0) { + if (!sender.hasPermission("t2code.allayduplicate.info")) { + T2Csend.sender(sender, ConfigFile.getNoPerm()); + return false; + } + T2Ctemplate.sendInfo(sender,Main.getPlugin(),Util.getSpigotID(),Util.getDiscord(),Util.getInfoText()); + } else { + switch (args[0].toLowerCase()) { + case "reload": + case "rl": + if (!sender.hasPermission("t2code.allayduplicate.reload")) { + T2Csend.sender(sender, ConfigFile.getNoPerm()); + return false; + } + T2Csend.console(Util.getPrefix() + "§8-------------------------------"); + T2Csend.console(Util.getPrefix() + " §6Plugin reload..."); + T2Csend.console(Util.getPrefix() + "§8-------------------------------"); + T2Csend.sender(sender, Util.getPrefix() + " §6Plugin is reloaded..."); + if (ConfigFile.getDelayResetOnReload()) Event.clearCash(); + ConfigFile.create(); + ConfigFile.select(); + T2Csend.sender(sender, Util.getPrefix() + " §6Plugin was successfully reloaded."); + T2Csend.console(Util.getPrefix() + "§8-------------------------------"); + T2Csend.console(Util.getPrefix() + " §2Plugin successfully reloaded."); + T2Csend.console(Util.getPrefix() + "§8-------------------------------"); + break; + case "reset": + if (!sender.hasPermission("t2code.allayduplicate.reset")) { + T2Csend.sender(sender, ConfigFile.getNoPerm()); + return false; + } + String name = args[1]; + if (Bukkit.getPlayer(name) == null && !(name.equals("*") || name.equals("all"))) { + T2Csend.sender(sender, ConfigFile.getErrorReset().replace("[player]", name)); + return false; + } + if (name.equals("*") || name.equals("all")) { + for (Player player : Bukkit.getOnlinePlayers()) { + Event.removePlayer(player); + } + T2Csend.sender(sender, ConfigFile.getResetAll()); + } else { + Event.removePlayer(Bukkit.getPlayer(name)); + T2Csend.sender(sender, ConfigFile.getReset().replace("[player]", name)); + } + break; + case "info": + case "plugin": + case "pl": + case "version": + case "ver": + default: + if (!sender.hasPermission("t2code.allayduplicate.info")) { + T2Csend.sender(sender, ConfigFile.getNoPerm()); + return false; + } + T2Ctemplate.sendInfo(sender,Main.getPlugin(),Util.getSpigotID(),Util.getDiscord(),Util.getInfoText()); + break; + } + } + return false; + } + + private static HashMap arg1 = new HashMap() {{ + put("reload", "t2code.allayduplicate.reload"); + put("rl", "t2code.allayduplicate.reload"); + put("info", "t2code.allayduplicate.info"); + put("reset", "t2code.allayduplicate.reset"); + }}; + + private static HashMap arg2 = new HashMap() {{ + put("all", "t2code.allayduplicate.admin"); + put("*", "t2code.allayduplicate.admin"); + }}; + + @Override + public List onTabComplete(CommandSender sender, Command cmd, String s, String[] args) { + List list = new ArrayList<>(); + T2Ctab.tab(list, sender, 0, args, arg1); + T2Ctab.tab(list, sender, 1, args, arg2); + T2Ctab.tab(list, sender, 1, args, "t2code.allayduplicate.admin", true); + return list; + } +} diff --git a/src/main/java/net/t2code/t2callayduplicate/config/ConfigFile.java b/src/main/java/net/t2code/t2callayduplicate/config/ConfigFile.java new file mode 100644 index 0000000..6998e73 --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/config/ConfigFile.java @@ -0,0 +1,266 @@ +package net.t2code.t2callayduplicate.config; + +import net.t2code.t2callayduplicate.system.Main; +import net.t2code.t2codelib.SPIGOT.api.messages.T2Creplace; +import net.t2code.t2codelib.SPIGOT.api.messages.T2Csend; +import net.t2code.t2codelib.SPIGOT.api.yaml.T2Cconfig; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; + +public class ConfigFile { + public static void create() { + File config = new File(Main.getPath(), "config.yml"); + YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(config); + T2Cconfig.set("Plugin.Language", "ENGLISH", yamlConfiguration); + + T2Cconfig.set("Duplicate.SneakRequired", true, yamlConfiguration); + T2Cconfig.set("Duplicate.Item.Enable", true, yamlConfiguration); + T2Cconfig.set("Duplicate.Item.Material", Material.AMETHYST_SHARD.toString(), yamlConfiguration); + T2Cconfig.set("Duplicate.Item.Amount", 1, yamlConfiguration); + + T2Cconfig.set("Duplicate.Particle.Enable", true, yamlConfiguration); + T2Cconfig.set("Duplicate.Particle.Particle", "HEART", yamlConfiguration); + + T2Cconfig.set("Duplicate.Sound.Enable", true, yamlConfiguration); + T2Cconfig.set("Duplicate.Sound.Sound", "ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM", yamlConfiguration); + T2Cconfig.set("Duplicate.Sound.Volume", 3, yamlConfiguration); + + T2Cconfig.set("Duplicate.Delay.Enable", true, yamlConfiguration); + T2Cconfig.set("Duplicate.Delay.ProAllay", false, yamlConfiguration); + T2Cconfig.set("Duplicate.Delay.ResetOnReload", true, yamlConfiguration); + T2Cconfig.set("Duplicate.Delay.Seconds", 300, yamlConfiguration); + + T2Cconfig.set("Duplicate.Cost.Enable", true, yamlConfiguration); + T2Cconfig.set("Duplicate.Cost.Price", 10.00, yamlConfiguration); + + T2Cconfig.set("Messages.MiniMessagesEditorLink", "https://webui.adventure.kyori.net", yamlConfiguration); + T2Cconfig.set("Messages.Prefix", "[T2CAllayDuplicate]", yamlConfiguration); + + T2Cconfig.set("Messages.ENGLISH.Plugin.NoPerm", "[prefix] You are not authorized to do that!", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.Duplicate", "[prefix] Du You have duplicated an allay.", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.Error.Delay", "[prefix] You have to wait [min] minutes, [sec] seconds before you can duplicate the next allay.", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.Error.IncorrectItem", "[prefix] You need [amount] [item] to be able to duplicate an allay.", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.Error.NoMoney", "[prefix] You need [price] $ to be able to duplicate an allay.", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.Error.Reset", "[prefix] Player [player] not online!", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.Error.CanNotBuild", "[prefix] You are not authorized to do so at this area.", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.Reset", "[prefix] The delay of [player] has been reset.", yamlConfiguration); + T2Cconfig.set("Messages.ENGLISH.ResetAll", "[prefix] The delay of all players has been reset.", yamlConfiguration); + + T2Cconfig.set("Messages.GERMAN.Plugin.NoPerm", "[prefix] Dazu bist du nicht berechtigt!", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.Duplicate", "[prefix] Du hast ein Allay verdoppelt.", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.Error.Delay", "[prefix] Du musst noch [min] Minuten, [sec] Sekunden warten, befor du das nächste Allay duplizieren kannst.", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.Error.IncorrectItem", "[prefix] Du benötigst [amount] [item] um einen Allay duplizieren kannst.", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.Error.NoMoney", "[prefix] Du benötigst [price] $ um einen Allay duplizieren kannst.", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.Error.Reset", "[prefix] Spieler [player] nicht Online!", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.Error.CanNotBuild", "[prefix] Du bist an diesem Bereich nicht dazu berechtigt.", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.Reset", "[prefix] Das Delay von [player] wurde zurückgesetzt.", yamlConfiguration); + T2Cconfig.set("Messages.GERMAN.ResetAll", "[prefix] Das Delay von allen Spielern wurde zurückgesetzt.", yamlConfiguration); + try { + yamlConfiguration.save(config); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void select() { + File config = new File(Main.getPath(), "config.yml"); + YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(config); + language = yamlConfiguration.getString("Plugin.Language"); + + sneakRequired = yamlConfiguration.getBoolean("Duplicate.SneakRequired"); + itemEnable = yamlConfiguration.getBoolean("Duplicate.Item.Enable"); + itemMaterial = yamlConfiguration.getString("Duplicate.Item.Material"); + itemAmount = yamlConfiguration.getInt("Duplicate.Item.Amount"); + + particleEnable = yamlConfiguration.getBoolean("Duplicate.Particle.Enable"); + try { + particleParticle = Particle.valueOf(yamlConfiguration.getString("Duplicate.Particle.Particle")); + } catch (Exception ex) { + T2Csend.error(Main.getPlugin(), ""); + T2Csend.console(String.format("§4The particle %s does not exist, please check your config. The %s is used as particle.", yamlConfiguration.getString("Duplicate.Particle.Particle"), "HEART")); + T2Csend.error(Main.getPlugin(), ""); + particleParticle = Particle.HEART; + } + + soundEnable = yamlConfiguration.getBoolean("Duplicate.Sound.Enable"); + try { + soundSound = Sound.valueOf(yamlConfiguration.getString("Duplicate.Sound.Sound")); + } catch (Exception ex) { + T2Csend.error(Main.getPlugin(), ""); + T2Csend.console(String.format("§4The sound %s does not exist, please check your config. The %s is used as sound.", yamlConfiguration.getString("Duplicate.Sound.Sound"), "ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM")); + T2Csend.error(Main.getPlugin(), ""); + soundSound = Sound.ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM; + } + soundVolume = yamlConfiguration.getInt("Duplicate.Sound.Volume"); + + delayEnable = yamlConfiguration.getBoolean("Duplicate.Delay.Enable"); + delayProAllay = yamlConfiguration.getBoolean("Duplicate.Delay.ProAllay"); + delayResetOnReload = yamlConfiguration.getBoolean("Duplicate.Delay.ResetOnReload"); + delaySeconds = yamlConfiguration.getInt("Duplicate.Delay.Seconds"); + + costEnable = yamlConfiguration.getBoolean("Duplicate.Cost.Enable"); + costPrice = yamlConfiguration.getDouble("Duplicate.Cost.Price"); + + prefix = yamlConfiguration.getString("Messages.Prefix"); + + noPerm = getMSG("Messages." + language + ".Plugin.NoPerm", yamlConfiguration); + duplicate = getMSG("Messages." + language + ".Duplicate", yamlConfiguration); + errorDelay = getMSG("Messages." + language + ".Error.Delay", yamlConfiguration); + errorIncorrectItem = getMSG("Messages." + language + ".Error.IncorrectItem", yamlConfiguration); + errorNoMoney = getMSG("Messages." + language + ".Error.NoMoney", yamlConfiguration); + errorReset = getMSG("Messages." + language + ".Error.Reset", yamlConfiguration); + errorCanNotBuild = getMSG("Messages." + language + ".Error.CanNotBuild", yamlConfiguration); + reset = getMSG("Messages." + language + ".Reset", yamlConfiguration); + ResetAll = getMSG("Messages." + language + ".ResetAll", yamlConfiguration); + + + } + + private static String language; + private static Boolean sneakRequired; + private static Boolean itemEnable; + private static String itemMaterial; + private static Integer itemAmount; + + private static Boolean particleEnable; + private static Particle particleParticle; + + private static Boolean soundEnable; + private static Sound soundSound; + private static Integer soundVolume; + + private static Boolean delayEnable; + private static Boolean delayProAllay; + private static Boolean delayResetOnReload; + private static Integer delaySeconds; + + private static Boolean costEnable; + private static Double costPrice; + + private static String prefix; + private static String noPerm; + private static String duplicate; + private static String errorDelay; + private static String errorIncorrectItem; + private static String errorNoMoney; + private static String errorReset; + private static String errorCanNotBuild; + private static String reset; + private static String ResetAll; + + public static Boolean getSneakRequired() { + return sneakRequired; + } + + public static Boolean getItemEnable() { + return itemEnable; + } + + public static String getItemMaterial() { + return itemMaterial; + } + + public static Integer getItemAmount() { + return itemAmount; + } + + public static Boolean getParticleEnable() { + return particleEnable; + } + + public static Particle getParticleParticle() { + return particleParticle; + } + + public static Boolean getSoundEnable() { + return soundEnable; + } + + public static Sound getSoundSound() { + return soundSound; + } + + public static Integer getSoundVolume() { + return soundVolume; + } + + public static Boolean getDelayEnable() { + return delayEnable; + } + + public static Boolean getDelayProAllay() { + return delayProAllay; + } + + public static Boolean getDelayResetOnReload() { + return delayResetOnReload; + } + + public static Integer getDelaySeconds() { + return delaySeconds; + } + + public static Boolean getCostEnable() { + return costEnable; + } + + public static Double getCostPrice() { + return costPrice; + } + + public static String getPrefix() { + return prefix; + } + + public static String getNoPerm() { + return noPerm; + } + + public static String getDuplicate() { + return duplicate; + } + + public static String getErrorDelay() { + return errorDelay; + } + + public static String getErrorIncorrectItem() { + return errorIncorrectItem; + } + + public static String getErrorNoMoney() { + return errorNoMoney; + } + + public static String getErrorReset() { + return errorReset; + } + public static String getErrorCanNotBuild() { + return errorCanNotBuild; + } + + public static String getReset() { + return reset; + } + + public static String getResetAll() { + return ResetAll; + } + + private static String getMSG(String path, YamlConfiguration yamlConfiguration) { + String out; + if (yamlConfiguration.get(path) != null) { + out = T2Creplace.replace(prefix, yamlConfiguration.getString(path)); + } else { + T2Csend.error(Main.getPlugin(), String.format("The message on the path [%s] was not found. Please check if the language [%s] exists.", path, language)); + out = path; + } + return out; + } + +} diff --git a/src/main/java/net/t2code/t2callayduplicate/event/BuildCheck.java b/src/main/java/net/t2code/t2callayduplicate/event/BuildCheck.java new file mode 100644 index 0000000..4bb7c98 --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/event/BuildCheck.java @@ -0,0 +1,25 @@ +package net.t2code.t2callayduplicate.event; + +import com.plotsquared.core.listener.PlayerBlockEventType; +import net.t2code.t2callayduplicate.system.Main; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class BuildCheck { + protected static Boolean canBuild(Player player, Location location){ + if (Main.isLands()) { + return canInteractLands(player, location); + } + if (Main.isPlotsquared()) { + return !Main.getPlotSquaredIntegration().isProtected(player, PlayerBlockEventType.INTERACT_BLOCK); + } + return true; + } + + private static boolean canInteractLands(Player player, org.bukkit.Location location){ + if(Main.getLandsintegrator() == null){ + return true; + } + return Main.getLandsintegrator().canInteract(location, player); + } +} diff --git a/src/main/java/net/t2code/t2callayduplicate/event/Event.java b/src/main/java/net/t2code/t2callayduplicate/event/Event.java new file mode 100644 index 0000000..5f236c8 --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/event/Event.java @@ -0,0 +1,118 @@ +package net.t2code.t2callayduplicate.event; + +import net.t2code.t2callayduplicate.Util; +import net.t2code.t2callayduplicate.config.ConfigFile; +import net.t2code.t2callayduplicate.system.Main; +import net.t2code.t2codelib.SPIGOT.api.eco.T2Ceco; +import net.t2code.t2codelib.SPIGOT.api.messages.T2Csend; +import net.t2code.t2codelib.SPIGOT.api.update.T2CupdateAPI; +import org.bukkit.*; +import org.bukkit.entity.Allay; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.inventory.EquipmentSlot; + +import java.time.Instant; +import java.util.HashMap; +import java.util.UUID; + + +public class Event implements Listener { + + @EventHandler + public void onJoinEvent(PlayerLoginEvent event) { + T2CupdateAPI.join(Main.getPlugin(), Util.getPrefix(), "t2code.allayduplicate.admin", event.getPlayer(), Util.getSpigotID(), Util.getDiscord()); + } + + private static HashMap delay = new HashMap<>(); + + public static void clearCash() { + delay.clear(); + } + + public static void removePlayer(Player player) { + delay.remove(player.getUniqueId()); + } + + + @EventHandler + public void onDuplicate(PlayerInteractAtEntityEvent e) { + Player player = e.getPlayer(); + Location location = e.getRightClicked().getLocation(); + if (!player.hasPermission("t2code.allayduplicate.use")) return; + if (!(e.getRightClicked() instanceof Allay)) return; + if (e.getHand() != EquipmentSlot.HAND) return; + + if (ConfigFile.getSneakRequired() && !player.isSneaking()) return; + UUID uuid = player.getUniqueId(); + if (ConfigFile.getItemEnable()) { + if (player.getItemInHand().getType() != Material.valueOf(ConfigFile.getItemMaterial())) return; + } + e.setCancelled(true); + + if (!BuildCheck.canBuild(player, location)) { + T2Csend.player(player, ConfigFile.getErrorCanNotBuild()); + return; + } + + if (ConfigFile.getDelayEnable()) { + if (delay.containsKey(uuid) || delay.containsKey(e.getRightClicked().getUniqueId())) { + Long now = Instant.now().getEpochSecond(); + + long dl; + if (ConfigFile.getDelayProAllay()) { + dl = delay.get(e.getRightClicked().getUniqueId()); + } else dl = delay.get(player.getUniqueId()); + long diff = now - dl; + + long bonustime_min = ConfigFile.getDelaySeconds(); + long remainingMin = (int) ((bonustime_min - diff) / 60); + long remainingSec = (int) ((bonustime_min - diff) % 60); + T2Csend.player(player, ConfigFile.getErrorDelay().replace("&", "§").replace("[min]", String.valueOf(remainingMin)) + .replace("[sec]", String.valueOf(remainingSec))); + return; + } + } + if (ConfigFile.getCostEnable()) { + if (!T2Ceco.moneyRemove(ConfigFile.getPrefix(), player, ConfigFile.getCostPrice())) { + T2Csend.player(player, ConfigFile.getErrorNoMoney().replace("[price]", String.valueOf(ConfigFile.getCostPrice()))); + return; + } + } + + if (!T2Ceco.itemRemove(player, ConfigFile.getItemMaterial(), ConfigFile.getItemAmount())) { + T2Csend.player(player, ConfigFile.getErrorIncorrectItem().replace("[amount]", String.valueOf(ConfigFile.getItemAmount())) + .replace("[item]", ConfigFile.getItemMaterial())); + return; + } + + if (ConfigFile.getParticleEnable()) { + location.getWorld().spawnParticle(ConfigFile.getParticleParticle(), location, 3); + } + + if (ConfigFile.getSoundEnable()) { + location.getWorld().playSound(location, ConfigFile.getSoundSound(), ConfigFile.getSoundVolume(), 0.0F); + } + UUID newAllayID = location.getWorld().spawnEntity(new Location(location.getWorld(), location.getX(), location.getY() + 1, location.getZ()), EntityType.ALLAY).getUniqueId(); + + if (ConfigFile.getDelayProAllay()) { + delay.put(newAllayID, Instant.now().getEpochSecond()); + delay.put(e.getRightClicked().getUniqueId(), Instant.now().getEpochSecond()); + } else delay.put(uuid, Instant.now().getEpochSecond()); + + T2Csend.player(player, ConfigFile.getDuplicate()); + Bukkit.getScheduler().runTaskLaterAsynchronously(Main.getPlugin(), new Runnable() { + @Override + public void run() { + if (ConfigFile.getDelayProAllay()) { + delay.remove(newAllayID); + delay.remove (e.getRightClicked().getUniqueId()); + } else delay.remove(player.getUniqueId()); + } + }, ConfigFile.getDelaySeconds() * 20L); + } +} diff --git a/src/main/java/net/t2code/t2callayduplicate/system/Main.java b/src/main/java/net/t2code/t2callayduplicate/system/Main.java new file mode 100644 index 0000000..655e9ce --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/system/Main.java @@ -0,0 +1,143 @@ +package net.t2code.t2callayduplicate.system; + +import net.t2code.t2callayduplicate.Hooks.LandsIntegratior; +import net.t2code.t2callayduplicate.Hooks.PlotSquaredIntegration; +import net.t2code.t2callayduplicate.Util; +import net.t2code.t2callayduplicate.commands.CmdExecuter; +import net.t2code.t2callayduplicate.config.ConfigFile; +import net.t2code.t2callayduplicate.event.Event; +import net.t2code.t2codelib.SPIGOT.api.messages.T2Csend; +import net.t2code.t2codelib.SPIGOT.api.messages.T2Ctemplate; +import net.t2code.t2codelib.SPIGOT.api.plugins.T2CpluginCheck; +import net.t2code.t2codelib.SPIGOT.api.register.T2Cregister; +import net.t2code.t2codelib.SPIGOT.api.update.T2CupdateAPI; +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.util.logging.Level; + +public final class Main extends JavaPlugin { + + private static Main plugin; + private static Boolean enable = false; + + public static File getPath() { + return plugin.getDataFolder(); + } + + private Event event; + + @Override + public void onEnable() { + // Plugin startup logic + plugin = this; + + if (!Bukkit.getVersion().contains("1.19") || Bukkit.getVersion().contains("1.19.1")) { + plugin.getLogger().log(Level.SEVERE, ""); + plugin.getLogger().log(Level.SEVERE, ""); + plugin.getLogger().log(Level.SEVERE, ""); + plugin.getLogger().log(Level.SEVERE, "This plugin is only for version 1.19!"); + plugin.getLogger().log(Level.SEVERE, "Plugin can not be loaded!"); + plugin.getLogger().log(Level.SEVERE, "Our Discord: http://dc.t2code.net\""); + plugin.getLogger().log(Level.SEVERE, ""); + plugin.getLogger().log(Level.SEVERE, ""); + plugin.getLogger().log(Level.SEVERE, ""); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + if (pluginNotFound("T2CodeLib", 96388, Util.getRequiredT2CodeLibVersion())) return; + enable = true; + long long_ = T2Ctemplate.onLoadHeader(Util.getPrefix(), this.getDescription().getAuthors(), this.getDescription().getVersion(), Util.getSpigot(), Util.getDiscord()); + T2CupdateAPI.onUpdateCheck(plugin, Util.getPrefix(), Util.getSpigotID(), Util.getDiscord()); + Metrics.Bstats(); + + if (T2CpluginCheck.plotSquared()) { + plotsquared = true; + plotSquaredIntegration = new PlotSquaredIntegration(); + T2Csend.console(Util.getPrefix() + " §2Load Hook: §6PlotSquared - " + T2CpluginCheck.pluginInfos("PlotSquared").getDescription().getVersion()); + } + if (T2CpluginCheck.pluginCheck("Lands")) { + lands = true; + landsintegrator = new LandsIntegratior(this); + T2Csend.console(Util.getPrefix() + " §2Load Hook: §6Lands - " + T2CpluginCheck.pluginInfos("Lands").getDescription().getVersion()); + } + + ConfigFile.create(); + ConfigFile.select(); + + this.getCommand("t2c-allayduplicate").setExecutor(new CmdExecuter()); + T2Cregister.listener(new Event(), this); + T2Ctemplate.onLoadFooter(Util.getPrefix(), long_); + + } + + + @Override + public void onDisable() { + // Plugin shutdown logic + HandlerList.unregisterAll(event); + if (enable) T2Ctemplate.onDisable(Util.getPrefix(), this.getDescription().getAuthors(), this.getDescription().getVersion(), Util.getSpigot(), Util.getDiscord()); + } + + public static Boolean pluginNotFound(String pl, Integer spigotID, String ver) { + if (Bukkit.getPluginManager().getPlugin(pl) == null) { + plugin.getLogger().log(Level.SEVERE, "Plugin can not be loaded!"); + Bukkit.getConsoleSender().sendMessage(Util.getPrefix() + " §e" + pl + " §4could not be found. Please download it here: " + + "§6https://spigotmc.org/resources/" + pl + "." + spigotID + " §4to be able to use this plugin."); + Main.plugin.getPluginLoader().disablePlugin(Main.plugin); + return true; + } else { + String plVer = Bukkit.getPluginManager().getPlugin(pl).getDescription().getVersion(); + if (ver.contains("_")) { + if (!plVer.equals(ver)) { + plugin.getLogger().log(Level.SEVERE, "Plugin can not be loaded!"); + Bukkit.getConsoleSender().sendMessage(Util.getPrefix() + " §e" + pl + " §4is out of date! This plugin requires the version §2" + + ver + " §4of §6" + pl + " §4Please use this version! Please download it here: §6https://spigotmc.org/resources/" + + pl + "." + spigotID + " Or contact us in Discord: http://dc.t2code.net"); + Main.plugin.getPluginLoader().disablePlugin(Main.plugin); + return true; + } + return false; + } + String[] split = plVer.split("_"); + if (Double.parseDouble(split[0]) < Double.parseDouble(ver)) { + plugin.getLogger().log(Level.SEVERE, "Plugin can not be loaded!"); + Bukkit.getConsoleSender().sendMessage(Util.getPrefix() + " §e" + pl + " §4is out of date! This plugin requires at least version §2" + + ver + " §4of §6" + pl + " §4Please update it here: §6https://spigotmc.org/resources/" + pl + "." + spigotID + + " §4to use this version of " + plugin.getDescription().getName() + "."); + Main.plugin.getPluginLoader().disablePlugin(Main.plugin); + return true; + } + return false; + } + } + + public static Main getPlugin() { + return plugin; + } + + private static boolean plotsquared = false; + private static boolean lands = false; + + private static PlotSquaredIntegration plotSquaredIntegration = null; + + private static LandsIntegratior landsintegrator = null; + + public static boolean isPlotsquared() { + return plotsquared; + } + + public static PlotSquaredIntegration getPlotSquaredIntegration() { + return plotSquaredIntegration; + } + + public static boolean isLands() { + return lands; + } + + public static LandsIntegratior getLandsintegrator() { + return landsintegrator; + } +} diff --git a/src/main/java/net/t2code/t2callayduplicate/system/Metrics.java b/src/main/java/net/t2code/t2callayduplicate/system/Metrics.java new file mode 100644 index 0000000..7b29e30 --- /dev/null +++ b/src/main/java/net/t2code/t2callayduplicate/system/Metrics.java @@ -0,0 +1,849 @@ + +package net.t2code.t2callayduplicate.system; + +import net.t2code.t2callayduplicate.Util; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; + +public class Metrics { + + public static void Bstats() { + int pluginId = Util.getBstatsID(); // <-- Replace with the id of your plugin! + Metrics metrics = new Metrics(Main.getPlugin(), pluginId); + } + + private final Plugin plugin; + + private final MetricsBase metricsBase; + + /** + * Creates a new Metrics instance. + * + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? + */ + public Metrics(JavaPlugin plugin, int serviceId) { + this.plugin = plugin; + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", false); + config.addDefault("logSentData", false); + config.addDefault("logResponseStatusText", false); + // Inform the server owners about bStats + config + .options() + .header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous.") + .copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + // Load the data + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + metricsBase = + new MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), + plugin::isEnabled, + (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message) -> this.plugin.getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + metricsBase.addCustomChart(chart); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); + } + + private int getPlayerAmount() { + try { + // Around MC 1.8 the return type was changed from an array to a collection, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + return onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + // Just use the new method if the reflection failed + return Bukkit.getOnlinePlayers().size(); + } + } + + public static class MetricsBase { + + /** + * The version of the Metrics class. + */ + public static final String METRICS_VERSION = "2.2.1"; + + private static final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution + // of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial + // and second delay. + // WARNING: You must not modify and part of this Metrics class, including the submit delay or + // frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** + * Checks that the class was properly relocated. + */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little + // "trick" ... :D + final String defaultPackage = + new String(new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong package + // names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[]{entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..b919bca --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,39 @@ +name: T2C-AllayDuplicate +version: '${project.version}' +main: net.t2code.t2callayduplicate.system.Main +api-version: 1.13 +softdepend: [ T2CodeLib ] +authors: [ JaTiTV ] + +commands: + t2c-allayduplicate: + aliases: + - t2c-allaydupe + - allaydupe + - t2c-ad +permissions: + t2code.allayduplicate.admin: + default: op + children: + t2code.allayduplicate.updatemsg: true + t2code.allayduplicate.info: true + t2code.allayduplicate.reset: true + t2code.allayduplicate.reload: true + t2code.allayduplicate.bypass.delay: true + t2code.allayduplicate.bypass.cost: true + t2code.allayduplicate.use: true + + t2code.allayduplicate.updatemsg: + default: op + t2code.allayduplicate.info: + default: op + t2code.allayduplicate.reset: + default: op + t2code.allayduplicate.reload: + default: op + t2code.allayduplicate.bypass.delay: + default: op + t2code.allayduplicate.bypass.cost: + default: op + t2code.allayduplicate.use: + default: true \ No newline at end of file