diff --git a/MoreMobHeads.java b/MoreMobHeads.java new file mode 100644 index 0000000..52b4b73 --- /dev/null +++ b/MoreMobHeads.java @@ -0,0 +1,3479 @@ +package com.github.joelgodofwar.mmh; +//1.14 + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.bstats.bukkit.Metrics; +import org.bstats.charts.AdvancedPie; +import org.bstats.charts.SimplePie; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Skull; +import org.bukkit.block.TileState; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Axolotl; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Cat; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Fox; +import org.bukkit.entity.Goat; +import org.bukkit.entity.Horse; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Llama; +import org.bukkit.entity.MushroomCow; +import org.bukkit.entity.Panda; +import org.bukkit.entity.Parrot; +import org.bukkit.entity.Player; +import org.bukkit.entity.Rabbit; +import org.bukkit.entity.Sheep; +import org.bukkit.entity.TraderLlama; +import org.bukkit.entity.TropicalFish; +import org.bukkit.entity.TropicalFish.Pattern; +import org.bukkit.entity.Villager; +import org.bukkit.entity.Wolf; +import org.bukkit.entity.ZombieVillager; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFromToEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.profile.PlayerProfile; +import org.bukkit.profile.PlayerTextures; +import org.json.JSONObject; + +import com.earth2me.essentials.Essentials; +import com.github.joelgodofwar.mmh.common.PluginLibrary; +import com.github.joelgodofwar.mmh.common.PluginLogger; +import com.github.joelgodofwar.mmh.common.error.DetailedErrorReporter; +import com.github.joelgodofwar.mmh.common.error.Report; +import com.github.joelgodofwar.mmh.enums.CatHeads; +import com.github.joelgodofwar.mmh.enums.HorseHeads; +import com.github.joelgodofwar.mmh.enums.LlamaHeads; +import com.github.joelgodofwar.mmh.enums.MobHeads; +import com.github.joelgodofwar.mmh.enums.MobHeads117; +import com.github.joelgodofwar.mmh.enums.RabbitHeads; +import com.github.joelgodofwar.mmh.enums.SheepHeads; +import com.github.joelgodofwar.mmh.enums.TropicalFishHeads; +import com.github.joelgodofwar.mmh.enums.VillagerHeads; +import com.github.joelgodofwar.mmh.enums.ZombieVillagerHeads; +import com.github.joelgodofwar.mmh.handlers.EventHandler_1_20_R1; +import com.github.joelgodofwar.mmh.i18n.Translator; +import com.github.joelgodofwar.mmh.util.Ansi; +import com.github.joelgodofwar.mmh.util.ChatColorUtils; +import com.github.joelgodofwar.mmh.util.Format; +//import com.github.joelgodofwar.mmh.util.Metrics; +import com.github.joelgodofwar.mmh.util.MinecraftVersion; +import com.github.joelgodofwar.mmh.util.StrUtils; +import com.github.joelgodofwar.mmh.util.Utils; +import com.github.joelgodofwar.mmh.util.VersionChecker; +import com.github.joelgodofwar.mmh.util.YmlConfiguration; +import com.github.joelgodofwar.mmh.util.datatypes.JsonDataType; +import com.github.joelgodofwar.mmh.util.mob.NameTag; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; + +import de.tr7zw.changeme.nbtapi.NBTCompound; +import de.tr7zw.changeme.nbtapi.NBTItem; +import de.tr7zw.changeme.nbtapi.NBTList; +import de.tr7zw.changeme.nbtapi.NBTListCompound; +import dev.majek.hexnicks.HexNicks; +import io.papermc.lib.PaperLib; +import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult; +import mineverse.Aust1n46.chat.api.MineverseChatAPI; +import mineverse.Aust1n46.chat.api.MineverseChatPlayer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; + +@SuppressWarnings({ "unchecked", "rawtypes", "deprecation" }) +public class MoreMobHeads extends JavaPlugin implements Listener{ + /** Languages: čeština (cs_CZ), Deutsch (de_DE), English (en_US), Español (es_ES), Español (es_MX), Français (fr_FR), Italiano (it_IT), Magyar (hu_HU), 日本語 (ja_JP), 한국어 (ko_KR), Lolcat (lol_US), Melayu (my_MY), Nederlands (nl_NL), Polski (pl_PL), Português (pt_BR), Русский (ru_RU), Svenska (sv_SV), Türkçe (tr_TR), 中文(简体) (zh_CN), 中文(繁體) (zh_TW) */ + //public final static Logger logger = Logger.getLogger("Minecraft"); + static String THIS_NAME; + static String THIS_VERSION; + /** update checker variables */ + public int projectID = 73997; // https://spigotmc.org/resources/71236 + public String githubURL = "https://github.com/JoelGodOfwar/MoreMobHeads/raw/master/versioncheck/1.15/versions.xml"; + boolean UpdateAvailable = false; + public String UColdVers; + public String UCnewVers; + public boolean UpdateCheck; + public String DownloadLink = "https://dev.bukkit.org/projects/moremobheads2"; + /** end update checker variables */ + public boolean isDev = false; + public boolean debug = false; + public static String daLang; + //String updateURL = "https://github.com/JoelGodOfwar/MoreMobHeads/raw/master/versioncheck/1.14/version.txt"; + //File langFile; + //public FileConfiguration lang; + File langNameFile; + public FileConfiguration langName; + public File playerFile; + public FileConfiguration playerHeads; + File blockFile; + File blockFile116; + File blockFile1162; + File blockFile117; + File blockFile119; + File blockFile120; + public FileConfiguration blockHeads = new YamlConfiguration(); + public FileConfiguration blockHeads2 = new YamlConfiguration(); + public FileConfiguration blockHeads3 = new YamlConfiguration(); + public FileConfiguration blockHeads4 = new YamlConfiguration(); + public FileConfiguration blockHeads5 = new YamlConfiguration(); + public File customFile; + public FileConfiguration traderCustom; + File chanceFile; + public YmlConfiguration chanceConfig; + public YmlConfiguration oldchanceConfig; + public YmlConfiguration beheadingMessages = new YmlConfiguration(); + public YamlConfiguration oldMessages; + File mobnameFile; + FileConfiguration mobname; + double defpercent = 0.013; + //static boolean showkiller; + //static boolean showpluginname; + public YmlConfiguration config = new YmlConfiguration(); + YamlConfiguration oldconfig = new YamlConfiguration(); + static String datafolder; + public String world_whitelist; + public String world_blacklist; + public String mob_whitelist; + public String mob_blacklist; + boolean colorful_console; + boolean silent_console; + public final NamespacedKey NAMETAG_KEY = new NamespacedKey(this, "name_tag"); + public final NamespacedKey SHIVERING_KEY = new NamespacedKey(this, "shivering_tag"); + public Map entityPlayers = new HashMap<>(); // Entity UUID, Player UUID + public Map playerWeapons = new HashMap<>(); // Player UUID, Main hand item + public Map endCrystals = new HashMap<>(); + File debugFile; + Random random = new Random(); + String pluginName = THIS_NAME; + Translator lang2; + HashMap namedTropicalFish = new HashMap<>(); + private Map chanceRandoms = new HashMap<>(); + public static DetailedErrorReporter reporter; + public String jarfilename = this.getFile().getAbsoluteFile().toString(); + public PluginLogger LOGGER; + + public String configVersion = "1.0.22"; + public String messagesVersion = "1.0.2"; + public String chanceVersion = "1.0.25"; + public String langVersion = "1.0.2"; + + @SuppressWarnings("unused") @Override // TODO: onEnable + public void onEnable(){ + long startTime = System.currentTimeMillis(); + LOGGER = new PluginLogger(this); + reporter = new DetailedErrorReporter(this); + UpdateCheck = getConfig().getBoolean("plugin_settings.auto_update_check", true); + debug = getConfig().getBoolean("plugin_settings.debug", false); + daLang = getConfig().getString("plugin_settings.lang", "en_US"); + oldconfig = new YamlConfiguration(); + oldMessages = new YamlConfiguration(); + lang2 = new Translator(daLang, getDataFolder().toString()); + THIS_NAME = this.getDescription().getName(); + THIS_VERSION = this.getDescription().getVersion(); + if(!getConfig().getBoolean("global_settings.console.longpluginname", true)) { + pluginName = "MMH"; + }else { + pluginName = THIS_NAME; + } + + datafolder = this.getDataFolder().toString(); + colorful_console = getConfig().getBoolean("global_settings.console.colorful_console", true); + silent_console = getConfig().getBoolean("global_settings.console.silent_console", false); + + // Handle unexpected Minecraft versions + MinecraftVersion checkVersion = this.verifyMinecraftVersion(); + + LOGGER.log(ChatColor.YELLOW + "**************************************" + ChatColor.RESET); + LOGGER.log(ChatColor.GREEN + " v" + THIS_VERSION + ChatColor.RESET + " Loading..."); + LOGGER.log("Server Version: " + getServer().getVersion().toString()); + + /**debugFile = new File(this.getDataFolder() + File.separator + "logs" + File.separator + "mmh_debug.log"); + if(!debugFile.exists()){ + saveResource("logs" + File.separatorChar + "mmh_debug.log", true); + }//*/ + + /** DEV check **/ + File jarfile = this.getFile().getAbsoluteFile(); + if(jarfile.toString().contains("-DEV")){ + debug = true; + LOGGER.warn(ChatColor.RED + "Jar file contains -DEV, debug set to true" + ChatColor.RESET); + LOGGER.warn(ChatColor.RED + "jarfilename= " + StrUtils.Right(jarfilename, jarfilename.length() - jarfilename.lastIndexOf(File.separatorChar)) + ChatColor.RESET); + //log("jarfile contains dev, debug set to true."); + } + + /** Version Check */ + if( !(Double.parseDouble( getMCVersion().substring(0, 4) ) >= 1.14) ){ + // !getMCVersion().startsWith("1.14")&&!getMCVersion().startsWith("1.15")&&!getMCVersion().startsWith("1.16")&&!getMCVersion().startsWith("1.17") + LOGGER.warn(ChatColor.RED + " *!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!" + ChatColor.RESET); + LOGGER.warn(ChatColor.RED + " " + get("mmh.message.server_not_version") + ChatColor.RESET); + LOGGER.warn(ChatColor.RED + " v" + THIS_VERSION + " disabling." + ChatColor.RESET); + LOGGER.warn(ChatColor.RED + " *!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!" + ChatColor.RESET); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + /** Check for config */ + try{ + if(!getDataFolder().exists()){ + LOGGER.log("Data Folder doesn't exist"); + LOGGER.log("Creating Data Folder"); + getDataFolder().mkdirs(); + LOGGER.log("Data Folder Created at " + getDataFolder()); + } + File file = new File(getDataFolder(), "config.yml"); + LOGGER.debug("" + file); + if(!file.exists()){ + LOGGER.log("config.yml not found, creating!"); + saveResource("config.yml", true); + saveResource("chance_config.yml", true); + saveResource("messages.yml", true); + saveResource("custom_trades.yml", true); + saveResource("block_heads_1_17_2.yml", true); + saveResource("block_heads_1_17.yml", true); + saveResource("block_heads_1_20.yml", true); + LOGGER.log("Default files copied to " + getDataFolder()); + } + }catch(Exception exception){ + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_CHECK_CONFIG).error(exception)); + } + LOGGER.log("Loading config file..."); + try { + oldconfig.load(new File(getDataFolder() + "" + File.separatorChar + "config.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_LOAD_CONFIG).error(exception)); + } + String checkconfigversion = oldconfig.getString("version", "1.0.0"); + if(checkconfigversion != null){ + if(checkconfigversion.equalsIgnoreCase("1.0.0")) { + LOGGER.log(Ansi.RED + "Error reading config.yml version" + Ansi.RESET); + } + if(!checkconfigversion.equalsIgnoreCase(configVersion)){ + LOGGER.log("config.yml, Expected version:[" + configVersion + "], Read version:[" + checkconfigversion + "]\nThese should be the same."); + try { + copyFile_Java7(getDataFolder() + "" + File.separatorChar + "config.yml",getDataFolder() + "" + File.separatorChar + "old_config.yml"); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_COPY_FILE).error(exception)); + } + + saveResource("config.yml", true); + + try { + config.load(new File(getDataFolder(), "config.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_CHECK_CONFIG).error(exception)); + } + try { + oldconfig.load(new File(getDataFolder(), "old_config.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_CHECK_CONFIG).error(exception)); + } + config.set("plugin_settings.auto_update_check", oldconfig.get("auto_update_check", true)); + config.set("plugin_settings.debug", oldconfig.get("debug", false)); + config.set("plugin_settings.lang", oldconfig.get("lang", "en_US")); + + config.set("global_settings.console.colorful_console", oldconfig.get("console.colorful_console", true)); + config.set("global_settings.console.silent_console", oldconfig.get("console.silent_console", false)); + config.set("global_settings.console.longpluginname", oldconfig.get("console.longpluginname", true)); + config.set("global_settings.world.whitelist", oldconfig.get("world.whitelist", "")); + config.set("global_settings.world.blacklist", oldconfig.get("world.blacklist", "")); + config.set("global_settings.event.piston_extend", oldconfig.get("event.piston_extend", true)); + + config.set("head_settings.apply_looting", oldconfig.get("apply_looting", true)); + config.set("head_settings.lore.show_killer", oldconfig.get("lore.show_killer", true)); + config.set("head_settings.lore.show_plugin_name", oldconfig.get("lore.show_plugin_name", true)); + config.set("head_settings.player_heads.announce_kill.enabled", oldconfig.get("announce.players.enabled", true)); + config.set("head_settings.player_heads.announce_kill.displayname", oldconfig.get("announce.players.displayname", true)); + config.set("head_settings.player_heads.whitelist.enforce", oldconfig.get("whitelist.enforce", true)); + config.set("head_settings.player_heads.whitelist.player_head_whitelist", oldconfig.get("whitelist.player_head_whitelist", "names_go_here")); + config.set("head_settings.player_heads.blacklist.enforce", oldconfig.get("enforce_blacklist", true)); + config.set("head_settings.player_heads.blacklist.player_head_blacklist", oldconfig.get("blacklist.player_head_blacklist", "names_go_here")); + config.set("head_settings.mob_heads.announce_kill.enabled", oldconfig.get("announce.mobs.enabled", true)); + config.set("head_settings.mob_heads.announce_kill.displayname", oldconfig.get("announce.mobs.displayname", true)); + config.set("head_settings.mob_heads.whitelist", oldconfig.get("mob.whitelist", "")); + config.set("head_settings.mob_heads.blacklist", oldconfig.get("mob.blacklist", "")); + config.set("head_settings.mob_heads.nametag", oldconfig.get("mob.nametag", false)); + config.set("head_settings.mob_heads.vanilla_heads.creeper", oldconfig.get("vanilla_heads.creepers", false)); + config.set("head_settings.mob_heads.vanilla_heads.ender_dragon", oldconfig.get("vanilla_heads.ender_dragon", false)); + config.set("head_settings.mob_heads.vanilla_heads.piglin", oldconfig.get("vanilla_heads.piglin", false)); + config.set("head_settings.mob_heads.vanilla_heads.skeleton", oldconfig.get("vanilla_heads.skeleton", false)); + config.set("head_settings.mob_heads.vanilla_heads.wither_skeleton", oldconfig.get("vanilla_heads.wither_skeleton", false)); + config.set("head_settings.mob_heads.vanilla_heads.zombie", oldconfig.get("vanilla_heads.zombie", false)); + + config.set("wandering_trades.custom_wandering_trader", oldconfig.get("wandering_trades.custom_wandering_trader", true)); + config.set("wandering_trades.keep_default_trades", oldconfig.get("wandering_trades.keep_default_trades", true)); + config.set("wandering_trades.player_heads.enabled", oldconfig.get("wandering_trades.player_heads.enabled", true)); + config.set("wandering_trades.player_heads.min", oldconfig.get("wandering_trades.player_heads.min", 0)); + config.set("wandering_trades.player_heads.max", oldconfig.get("wandering_trades.player_heads.max", 5)); + config.set("wandering_trades.block_heads.enabled", oldconfig.get("wandering_trades.block_heads.enabled", true)); + config.set("wandering_trades.block_heads.pre_116.min", oldconfig.get("wandering_trader_min_block_heads", 0)); + config.set("wandering_trades.block_heads.pre_116.max", oldconfig.get("wandering_trader_max_block_heads", 5)); + config.set("wandering_trades.block_heads.is_116.min", oldconfig.get("wandering_trader_min_block_heads", 0)); + config.set("wandering_trades.block_heads.is_116.max", oldconfig.get("wandering_trader_max_block_heads", 5)); + config.set("wandering_trades.block_heads.is_117.min", oldconfig.get("wandering_trader_min_block_heads", 0)); + config.set("wandering_trades.block_heads.is_117.max", oldconfig.get("wandering_trader_max_block_heads", 5)); + config.set("wandering_trades.custom_trades.enabled", oldconfig.get("wandering_trades.custom_trades.enabled", false)); + config.set("wandering_trades.custom_trades.min", oldconfig.get("wandering_trades.custom_trades.min", 0)); + config.set("wandering_trades.custom_trades.max", oldconfig.get("wandering_trades.custom_trades.max", 5)); + + try { + config.save(new File(getDataFolder(), "config.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_SAVE_CONFIG).error(exception)); + } + LOGGER.log("config.yml Updated! old config saved as old_config.yml"); + LOGGER.log("chance_config.yml saved."); + }else{ + try { + config.load(new File(getDataFolder(), "config.yml")); + } catch (Exception e1) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_LOAD_CONFIG).error(e1)); + } + } + oldconfig = null; + } + /** end config check */ + + LOGGER.log("Loading messages file..."); + try { + oldMessages.load(new File(getDataFolder() + "" + File.separatorChar + "messages.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_MESSAGES_LOAD_ERROR).error(exception)); + } + + String checkmessagesversion = oldMessages.getString("version", "1.0.0"); + if (checkmessagesversion != null) { + if(checkmessagesversion.equalsIgnoreCase("1.0.0")) { + LOGGER.log(Ansi.RED + "Error reading messages.yml version" + Ansi.RESET); + } + if (!checkmessagesversion.equalsIgnoreCase(messagesVersion)) { + LOGGER.log("messages.yml, Expected version:[" + messagesVersion + "], Read version:[" + checkmessagesversion + "] These should be the same."); + try { + copyFile_Java7(getDataFolder() + "" + File.separatorChar + "messages.yml", getDataFolder() + "" + File.separatorChar + "old_messages.yml"); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_MESSAGES_COPY_ERROR).error(exception)); + } + saveResource("messages.yml", true); + + try { + beheadingMessages.load(new File(getDataFolder() + "" + File.separatorChar + "messages.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_MESSAGES_LOAD_ERROR).error(exception)); + } + try { + oldMessages.load(new File(getDataFolder() + "" + File.separatorChar + "old_messages.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_OLDMESSAGES_LOAD_ERROR).error(exception)); + } + + // Update messages + ConfigurationSection oldMessagesSection = oldMessages.getConfigurationSection("messages"); + //ConfigurationSection messagesSection = beheadingMessages.createSection("messages"); + + for (String messageKey : oldMessagesSection.getKeys(false)) { + String messageValue = oldMessagesSection.getString(messageKey); + beheadingMessages.set("messages." + messageKey, messageValue.replace("", "%killerName%") + .replace("", "%entityName%") + .replace("", "%weaponName%")); + } + + try { + beheadingMessages.save(new File(getDataFolder(), "messages.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_OLDMESSAGES_SAVE_ERROR).error(exception)); + } + LOGGER.log("messages.yml Updated! Old messages saved as old_messages.yml"); + } else { + try { + beheadingMessages.load(new File(getDataFolder(), "messages.yml")); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_MESSAGES_LOAD_ERROR).error(exception)); + } + } + oldMessages = null; + } + + if(config.getBoolean("wandering_trades.custom_wandering_trader", true)){ + /** Trader heads load */ + playerFile = new File(getDataFolder() + "" + File.separatorChar + "player_heads.yml");//\ + LOGGER.debug("player_heads=" + playerFile.getPath()); + if(!playerFile.exists()){ // checks if the yaml does not exist + saveResource("player_heads.yml", true); + LOGGER.log("player_heads.yml not found! copied player_heads.yml to " + getDataFolder() + ""); + } + LOGGER.log("Loading player_heads file..."); + playerHeads = new YamlConfiguration(); + try { + playerHeads.load(playerFile); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLAYERHEAD_LOAD_ERROR).error(exception)); + } + + + /** Custom Trades load */ + customFile = new File(getDataFolder() + "" + File.separatorChar + "custom_trades.yml");//\ + LOGGER.debug("customFile=" + customFile.getPath()); + if(!customFile.exists()){ // checks if the yaml does not exist + saveResource("custom_trades.yml", true); + LOGGER.log("custom_trades.yml not found! copied custom_trades.yml to " + getDataFolder() + ""); + //ConfigAPI.copy(getResource("lang.yml"), langFile); // copies the yaml from your jar to the folder /plugin/ + } + LOGGER.log("Loading custom_trades file..."); + traderCustom = new YamlConfiguration(); + try { + traderCustom.load(customFile); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CUSTOM_LOAD_ERROR).error(exception)); + /**stacktraceInfo(); + e.printStackTrace();//*/ + } + } + + /** chanceConfig load */ + chanceFile = new File(getDataFolder() + "" + File.separatorChar + "chance_config.yml");//\ + LOGGER.debug("chanceFile=" + chanceFile.getPath()); + if(!chanceFile.exists()){ // checks if the yaml does not exist + saveResource("chance_config.yml", true); + LOGGER.log("chance_config.yml not found! copied chance_config.yml to " + getDataFolder() + ""); + //ConfigAPI.copy(getResource("lang.yml"), langFile); // copies the yaml from your jar to the folder /plugin/ + } + LOGGER.log("Loading chance_config file..."); + chanceConfig = new YmlConfiguration(); + oldchanceConfig = new YmlConfiguration(); + try { + chanceConfig.load(chanceFile); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CHANCE_LOAD_ERROR).error(exception)); + /**stacktraceInfo(); + e.printStackTrace();//*/ + } + /** chanceConfig update check */ + String checkchanceConfigversion = chanceConfig.getString("version", "1.0.0"); + if(checkchanceConfigversion != null){ + if(!checkchanceConfigversion.equalsIgnoreCase(chanceVersion)){ + LOGGER.log("Expected v: " + chanceVersion + "got v: " + checkchanceConfigversion ); + try { + copyFile_Java7(getDataFolder() + "" + File.separatorChar + "chance_config.yml", getDataFolder() + "" + File.separatorChar + "old_chance_config.yml"); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CHANCE_COPY_ERROR).error(exception)); + } + + saveResource("chance_config.yml", true); + copyChance(getDataFolder() + "" + File.separatorChar + "old_chance_config.yml", chanceFile.getPath()); + LOGGER.log("chance_config.yml updated."); + } + } + + + /** Mob names translation */ + langNameFile = new File(getDataFolder() + "" + File.separatorChar + "lang" + File.separatorChar, daLang + "_mobnames.yml");//\ + LOGGER.debug("langFilePath=" + langNameFile.getPath()); + if(!langNameFile.exists()){ // checks if the yaml does not exist + saveResource("lang" + File.separatorChar + "cs_CZ_mobnames.yml", true); // 1 + saveResource("lang" + File.separatorChar + "de_DE_mobnames.yml", true); // 2 + saveResource("lang" + File.separatorChar + "en_US_mobnames.yml", true); // 3 + saveResource("lang" + File.separatorChar + "es_ES_mobnames.yml", true); // 4 + saveResource("lang" + File.separatorChar + "es_MX_mobnames.yml", true); // 5 + saveResource("lang" + File.separatorChar + "fr_FR_mobnames.yml", true); // 6 + saveResource("lang" + File.separatorChar + "hu_HU_mobnames.yml", true); // 7 + saveResource("lang" + File.separatorChar + "it_IT_mobnames.yml", true); // 8 + saveResource("lang" + File.separatorChar + "ja_JP_mobnames.yml", true); // 9 + saveResource("lang" + File.separatorChar + "ko_KR_mobnames.yml", true); // 0 + saveResource("lang" + File.separatorChar + "lol_US_mobnames.yml", true); // 1 + saveResource("lang" + File.separatorChar + "my_MY_mobnames.yml", true); // 2 + saveResource("lang" + File.separatorChar + "nl_NL_mobnames.yml", true); // 3 + saveResource("lang" + File.separatorChar + "pl_PL_mobnames.yml", true); // 4 + saveResource("lang" + File.separatorChar + "pt_BR_mobnames.yml", true); // 5 + saveResource("lang" + File.separatorChar + "ru_RU_mobnames.yml", true); // 6 + saveResource("lang" + File.separatorChar + "sv_SV_mobnames.yml", true); // 7 + saveResource("lang" + File.separatorChar + "tr_TR_mobnames.yml", true); // 8 + saveResource("lang" + File.separatorChar + "zh_CN_mobnames.yml", true); // 9 + saveResource("lang" + File.separatorChar + "zh_TW_mobnames.yml", true); // 0 + LOGGER.log("lang_mobnames file not found! copied cs_CZ_mobnames.yml, de_DE_mobnames.yml, en_US_mobnames.yml, es_ES_mobnames.yml, es_MX_mobnames.yml, fr_FR_mobnames.yml, hu_HU_mobnames.yml, it_IT_mobnames.yml, ja_JP_mobnames.yml, ko_KR_mobnames.yml, lol_US_mobnames.yml, my_MY_mobnames.yml, nl_NL_mobnames.yml, pl_PL_mobnames.yml, pt_BR_mobnames.yml, ru_RU_mobnames.yml, sv_SV_mobnames.yml, tr_TR_mobnames.yml, zh_CN_mobnames.yml, zh_TW_mobnames.yml to " + getDataFolder() + "" + File.separatorChar + "lang"); + //ConfigAPI.copy(getResource("lang.yml"), langFile); // copies the yaml from your jar to the folder /plugin/ + } + LOGGER.log("Loading language based mobname file..."); + langName = new YamlConfiguration(); + try { + langName.load(langNameFile); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_MOBNAMES_LOAD_ERROR).error(exception)); + /**stacktraceInfo(); + e.printStackTrace();//*/ + } + /** Mob Names update check */ + String checklangConfigversion = langName.getString("version", "1.0.0"); + if(checklangConfigversion != null){ + if(!checklangConfigversion.equalsIgnoreCase(langVersion)){ + LOGGER.log("Expected v: " + langVersion + "got v: " + checklangConfigversion ); + try { + copyFile_Java7(getDataFolder() + "" + File.separatorChar + "lang" + File.separatorChar + daLang + "_mobnames.yml", + getDataFolder() + "" + File.separatorChar + "lang" + File.separatorChar + "old_" + daLang + "_mobnames.yml"); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CHANCE_COPY_ERROR).error(exception)); + } + LOGGER.log("lang_mobnames file outdated! Updating."); + saveResource("lang" + File.separatorChar + "cs_CZ_mobnames.yml", true); // 1 + saveResource("lang" + File.separatorChar + "de_DE_mobnames.yml", true); // 2 + saveResource("lang" + File.separatorChar + "en_US_mobnames.yml", true); // 3 + saveResource("lang" + File.separatorChar + "es_ES_mobnames.yml", true); // 4 + saveResource("lang" + File.separatorChar + "es_MX_mobnames.yml", true); // 5 + saveResource("lang" + File.separatorChar + "fr_FR_mobnames.yml", true); // 6 + saveResource("lang" + File.separatorChar + "hu_HU_mobnames.yml", true); // 7 + saveResource("lang" + File.separatorChar + "it_IT_mobnames.yml", true); // 8 + saveResource("lang" + File.separatorChar + "ja_JP_mobnames.yml", true); // 9 + saveResource("lang" + File.separatorChar + "ko_KR_mobnames.yml", true); // 0 + saveResource("lang" + File.separatorChar + "lol_US_mobnames.yml", true); // 1 + saveResource("lang" + File.separatorChar + "my_MY_mobnames.yml", true); // 2 + saveResource("lang" + File.separatorChar + "nl_NL_mobnames.yml", true); // 3 + saveResource("lang" + File.separatorChar + "pl_PL_mobnames.yml", true); // 4 + saveResource("lang" + File.separatorChar + "pt_BR_mobnames.yml", true); // 5 + saveResource("lang" + File.separatorChar + "ru_RU_mobnames.yml", true); // 6 + saveResource("lang" + File.separatorChar + "sv_SV_mobnames.yml", true); // 7 + saveResource("lang" + File.separatorChar + "tr_TR_mobnames.yml", true); // 8 + saveResource("lang" + File.separatorChar + "zh_CN_mobnames.yml", true); // 9 + saveResource("lang" + File.separatorChar + "zh_TW_mobnames.yml", true); // 0 + copyConfigValues(getDataFolder() + "" + File.separatorChar + "lang" + File.separatorChar + daLang + "_mobnames.yml", + getDataFolder() + "" + File.separatorChar + "lang" + File.separatorChar + "old_" + daLang + "_mobnames.yml"); + LOGGER.log("cs_CZ_mobnames.yml, de_DE_mobnames.yml, en_US_mobnames.yml, es_ES_mobnames.yml, es_MX_mobnames.yml, fr_FR_mobnames.yml, hu_HU_mobnames.yml, it_IT_mobnames.yml, ja_JP_mobnames.yml, ko_KR_mobnames.yml, lol_US_mobnames.yml, my_MY_mobnames.yml, nl_NL_mobnames.yml, pl_PL_mobnames.yml, pt_BR_mobnames.yml, ru_RU_mobnames.yml, sv_SV_mobnames.yml, tr_TR_mobnames.yml, zh_CN_mobnames.yml, zh_TW_mobnames.yml updated."); + try { + langName.load(langNameFile); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_MOBNAMES_LOAD_ERROR).error(exception)); + } + } + } + /** end Mob names translation */ + + world_whitelist = config.getString("global_settings.world.whitelist", ""); + world_blacklist = config.getString("global_settings.world.blacklist", ""); + mob_whitelist = config.getString("head_settings.mob_heads.whitelist", ""); + mob_blacklist = config.getString("head_settings.mob_heads.blacklist", ""); + + getServer().getPluginManager().registerEvents(this, this); + + //String jarfilename = this.getFile().getAbsoluteFile().toString(); + LOGGER.debug("-<[ PLEASE INCLUDE THIS WITH ANY ISSUE REPORTS ]>-"); + LOGGER.debug("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (API version " + Bukkit.getBukkitVersion() + ")"); + LOGGER.debug("vardebug= " + debug + " debug=" + config.getString("plugin_settings.debug","error").toUpperCase() + " in " + this.getDataFolder() + File.separatorChar + "config.yml"); + LOGGER.debug("jarfilename= " + StrUtils.Right(jarfilename, jarfilename.length() - jarfilename.lastIndexOf(File.separatorChar))); + LOGGER.debug("-<[ PLEASE INCLUDE THIS WITH ANY ISSUE REPORTS ]>-"); + + /** Register EventHandler */ + String packageName = this.getServer().getClass().getPackage().getName(); + String version = packageName.substring(packageName.lastIndexOf('.') + 2); + LOGGER.debug("version=" + version); + if( version.contains("1_20_R") ){ + getServer().getPluginManager().registerEvents( new EventHandler_1_20_R1(this), this); + //getCommand("mmh").setExecutor(new EventHandler_1_17_R1(this)); + + }else{ + LOGGER.warn("Not compatible with this version of Minecraft:" + version); + getServer().getPluginManager().disablePlugin(this); + } + + namedTropicalFish.put("STRIPEY-ORANGE-GRAY", "ANEMONE"); + namedTropicalFish.put("FLOPPER-GRAY-GRAY", "BLACK_TANG"); + namedTropicalFish.put("FLOPPER-GRAY-BLUE", "BLUE_TANG"); + namedTropicalFish.put("CLAYFISH-WHITE-GRAY", "BUTTERFLYFISH"); + namedTropicalFish.put("SUNSTREAK-BLUE-GRAY", "CICHLID"); + namedTropicalFish.put("KOB-ORANGE-WHITE", "CLOWNFISH"); + namedTropicalFish.put("SPOTTY-PINK-LIGHT_BLUE", "COTTON_CANDY_BETTA"); + namedTropicalFish.put("BLOCKFISH-PURPLE-YELLOW", "DOTTYBACK"); + namedTropicalFish.put("CLAYFISH-WHITE-RED", "EMPEROR_RED_SNAPPER"); + namedTropicalFish.put("SPOTTY-WHITE-YELLOW", "GOATFISH"); + namedTropicalFish.put("GLITTER-WHITE-GRAY", "MOORISH_IDOL"); + namedTropicalFish.put("CLAYFISH-WHITE-ORANGE", "ORNATE_BUTTERFLYFISH"); + namedTropicalFish.put("DASHER-CYAN-PINK", "PARROTFISH"); + namedTropicalFish.put("BRINELY-LIME-LIGHT_BLUE", "QUEEN_ANGELFISH"); + namedTropicalFish.put("BETTY-RED-WHITE", "RED_CICHLID"); + namedTropicalFish.put("SNOOPER-GRAY-RED", "RED_LIPPED_BLENNY"); + namedTropicalFish.put("BLOCKFISH-RED-WHITE", "RED_SNAPPER"); + namedTropicalFish.put("KOB-RED-WHITE", "TOMATO_CLOWNFISH"); + namedTropicalFish.put("FLOPPER-WHITE-YELLOW", "THREADFIN"); + namedTropicalFish.put("SUNSTREAK-GRAY-WHITE", "TRIGGERFISH"); + namedTropicalFish.put("DASHER-CYAN-YELLOW", "YELLOWTAIL_PARROTFISH"); + namedTropicalFish.put("FLOPPER-YELLOW-YELLOW", "YELLOW_TANG"); + + /** Update Checker */ + if(UpdateCheck){ + LOGGER.log("Checking for updates..."); + try { + VersionChecker updater = new VersionChecker(this, projectID, githubURL); + if(updater.checkForUpdates()) { + /** Update available */ + UpdateAvailable = true; // TODO: Update Checker + UColdVers = updater.oldVersion(); + UCnewVers = updater.newVersion(); + + LOGGER.log("*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*"); + LOGGER.log("* " + get("mmh.version.message").toString().replace("", THIS_NAME) ); + LOGGER.log("* " + get("mmh.version.old_vers") + ChatColor.RED + UColdVers ); + LOGGER.log("* " + get("mmh.version.new_vers") + ChatColor.GREEN + UCnewVers ); + LOGGER.log("*"); + LOGGER.log("* " + get("mmh.version.please_update") ); + LOGGER.log("*"); + LOGGER.log("* " + get("mmh.version.download") + ": " + DownloadLink + "/files"); + LOGGER.log("* " + get("mmh.version.donate") + ": https://ko-fi.com/joelgodofwar"); + LOGGER.log("*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*"); + }else{ + /** Up to date */ + LOGGER.log("*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*"); + LOGGER.log("* " + get("mmh.version.curvers")); + LOGGER.log("* " + get("mmh.version.donate") + ": https://ko-fi.com/joelgodofwar"); + LOGGER.log("*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*"); + UpdateAvailable = false; + } + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_UPDATE_PLUGIN).error(exception)); + } + }else { + /** auto_update_check is false so nag. */ + LOGGER.log("*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*"); + LOGGER.log("* " + get("mmh.version.donate.message") + ": https://ko-fi.com/joelgodofwar"); + LOGGER.log("*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*"); + } + /** end update checker */ + + + + consoleInfo("Enabled - Loading took " + LoadTime(startTime)); + + try { + Metrics metrics = new Metrics(this, 6128); + // New chart here + // myPlugins() + metrics.addCustomChart(new AdvancedPie("my_other_plugins", new Callable>() { + @Override + public Map call() throws Exception { + Map valueMap = new HashMap<>(); + + if(getServer().getPluginManager().getPlugin("DragonDropElytra") != null){valueMap.put("DragonDropElytra", 1);} + if(getServer().getPluginManager().getPlugin("NoEndermanGrief") != null){valueMap.put("NoEndermanGrief", 1);} + if(getServer().getPluginManager().getPlugin("PortalHelper") != null){valueMap.put("PortalHelper", 1);} + if(getServer().getPluginManager().getPlugin("ShulkerRespawner") != null){valueMap.put("ShulkerRespawner", 1);} + //if(getServer().getPluginManager().getPlugin("MoreMobHeads") != null){valueMap.put("MoreMobHeads", 1);} + if(getServer().getPluginManager().getPlugin("SilenceMobs") != null){valueMap.put("SilenceMobs", 1);} + if(getServer().getPluginManager().getPlugin("SinglePlayerSleep") != null){valueMap.put("SinglePlayerSleep", 1);} + if(getServer().getPluginManager().getPlugin("VillagerWorkstationHighlights") != null){valueMap.put("VillagerWorkstationHighlights", 1);} + if(getServer().getPluginManager().getPlugin("RotationalWrench") != null){valueMap.put("RotationalWrench", 1);} + return valueMap; + } + })); + metrics.addCustomChart(new AdvancedPie("vanilla_heads", new Callable>() { + @Override + public Map call() throws Exception { + Map valueMap = new HashMap<>(); + //int varTotal = myPlugins(); + valueMap.put("CREEPER " + config.getString("head_settings.mob_heads.vanilla_heads.creeper").toUpperCase(), 1); + valueMap.put("ENDER_DRAGON " + config.getString("head_settings.mob_heads.vanilla_heads.ender_dragon").toUpperCase(), 1); + valueMap.put("SKELETON " + config.getString("head_settings.mob_heads.vanilla_heads.skeleton").toUpperCase(), 1); + valueMap.put("WITHER_SKELETON " + config.getString("head_settings.mob_heads.vanilla_heads.wither_skeleton").toUpperCase(), 1); + valueMap.put("ZOMBIE " + config.getString("head_settings.mob_heads.vanilla_heads.zombie").toUpperCase(), 1); + valueMap.put("PIGLIN " + config.getString("head_settings.mob_heads.vanilla_heads.piglin").toUpperCase(), 1); + return valueMap; + } + })); + metrics.addCustomChart(new SimplePie("auto_update_check", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("plugin_settings.auto_update_check").toUpperCase(); + } + })); + // add to site + metrics.addCustomChart(new SimplePie("var_debug", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("plugin_settings.debug").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("var_lang", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("plugin_settings.lang").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("whitelist.enforce", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("head_settings.player_heads.whitelist.enforce").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("blacklist.enforce", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("head_settings.player_heads.blacklist.enforce").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("custom_wandering_trader", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("wandering_trades.custom_wandering_trader").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("player_heads", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("wandering_trades.player_heads.enabled").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("block_heads", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("wandering_trades.block_heads.enabled").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("custom_trades", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("wandering_trades.custom_trades.enabled").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("apply_looting", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("head_settings.apply_looting").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("show_killer", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("head_settings.lore.show_killer").toUpperCase(); + } + })); + metrics.addCustomChart(new SimplePie("show_plugin_name", new Callable() { + @Override + public String call() throws Exception { + return "" + config.getString("head_settings.lore.show_plugin_name").toUpperCase(); + } + })); + } catch (Exception exception) { + // Handle the exception or log it + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_METRICS_LOAD_ERROR).error(exception)); + } + try { + // Your code here that might cause an exception + + // For testing purposes, you can throw an exception like this: + //throw new RuntimeException("This is a test exception."); + } catch (Exception exception) { + // Handle the exception or log it + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_UPDATE_PLUGIN).error(exception)); + } + + } + + @Override // TODO: onDisable + public void onDisable(){ + consoleInfo("Disabled"); + } + + public void consoleInfo(String state) { + //loading(Ansi.GREEN + "**************************************" + Ansi.RESET); + loading(ChatColor.YELLOW + " v" + THIS_VERSION + ChatColor.RESET + " is " + state + ChatColor.RESET); + //loading(Ansi.GREEN + "**************************************" + Ansi.RESET); + } + + public void loading(String string) { + if(!colorful_console) { + string = Ansi.stripAnsi(string); + string = ChatColor.stripColor(string); + } + LOGGER.log(string); + } + + public static String getMCVersion() { + String strVersion = Bukkit.getVersion(); + strVersion = strVersion.substring(strVersion.indexOf("MC: "), strVersion.length()); + strVersion = strVersion.replace("MC: ", "").replace(")", ""); + return strVersion; + } + + + public void giveMobHead(LivingEntity mob, String name){ + PlayerProfile profile = Bukkit.createPlayerProfile(name); + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + PlayerTextures textures = profile.getTextures(); + + profile.setTextures(textures); + meta.setOwnerProfile(profile); + meta.setNoteBlockSound(NamespacedKey.minecraft( "entity.player.hurt" )); + ArrayList lore = new ArrayList(); + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + THIS_NAME); + } + meta.setLore(lore); + meta.setLore(lore); + head.setItemMeta(meta); + if(mob.getEquipment().getHelmet() == null) { + mob.getEquipment().setHelmet(head); + } + head.setItemMeta(meta); + if(mob.getEquipment().getHelmet() == null) { + mob.getEquipment().setHelmet(head); + }else { + mob.getWorld().dropItemNaturally(mob.getLocation(), head); + } + + if(getServer().getPluginManager().getPlugin("WildStacker") != null){ + @Nonnull + PersistentDataContainer pdc = mob.getPersistentDataContainer(); + pdc.set(NAMETAG_KEY, PersistentDataType.STRING, "nametag"); + } + } + + public void givePlayerHead(Player player, String name){ + PlayerProfile profile = Bukkit.createPlayerProfile(name); + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + PlayerTextures textures = profile.getTextures(); + + profile.setTextures(textures); + meta.setOwnerProfile(profile); + meta.setNoteBlockSound(NamespacedKey.minecraft( "entity.player.hurt" )); + ArrayList lore = new ArrayList(); + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + THIS_NAME); + } + meta.setLore(lore); + meta.setLore(lore); + head.setItemMeta(meta); + playerGiveOrDropHead(player, head); + } + + /** + * Gives the specified player a head item if their inventory has space; otherwise, drops it naturally. + * + * @param player The player to give the head to. + * @param itemstack The head ItemStack to give or drop. + */ + public void playerGiveOrDropHead(Player player, ItemStack itemstack) { + // Check if the player or the item is null + if( (player == null) || (itemstack == null) ) { + return; + } + // Check if the player's inventory has an empty slot + if(player.getInventory().firstEmpty() != -1) { + // Add the head to the player's inventory + player.getInventory().addItem(itemstack); + }else { + // Drop the head naturally if the inventory is full + player.getWorld().dropItemNaturally(player.getLocation(), itemstack); + } + } + + public void giveBlockHead(Player player, String blockName) { + LOGGER.debug("giveBlockHead START"); + ItemStack blockStack = null; + int isBlock = isBlockHeadName(blockName); + int isBlock2 = isBlockHeadName2(blockName); + int isBlock3 = isBlockHeadName3(blockName); + int isBlock4 = isBlockHeadName4(blockName); + int isBlock5 = isBlockHeadName5(blockName); + if(isBlock != -1){ + LOGGER.debug("GBH isBlock=" + isBlock); + blockStack = blockHeads.getItemStack("blocks.block_" + isBlock + ".itemstack", new ItemStack(Material.AIR)); + }else if(isBlock2 != -1){ + LOGGER.debug("GBH isBlock2=" + isBlock2); + blockStack = blockHeads2.getItemStack("blocks.block_" + isBlock2 + ".itemstack", new ItemStack(Material.AIR)); + }else if(isBlock3 != -1){ + LOGGER.debug("GBH isBlock3=" + isBlock3); + blockStack = blockHeads3.getItemStack("blocks.block_" + isBlock3 + ".itemstack", new ItemStack(Material.AIR)); + }else if(isBlock4 != -1){ + LOGGER.debug("GBH isBlock4=" + isBlock4); + blockStack = blockHeads4.getItemStack("blocks.block_" + isBlock4 + ".itemstack", new ItemStack(Material.AIR)); + }else if(isBlock5 != -1){ + LOGGER.debug("GBH isBlock5=" + isBlock5); + blockStack = blockHeads5.getItemStack("blocks.block_" + isBlock5 + ".itemstack", new ItemStack(Material.AIR)); + }else { + /** Add translation for this line. ***************************************************************************************************** */ + player.sendMessage(THIS_NAME + " v" + THIS_VERSION + " Sorry could not find \"" + blockName + "\""); // TODO: Add translation for this line. + } + if( (blockStack != null) && (blockStack.getType() != Material.AIR) ) { + player.getWorld().dropItemNaturally(player.getLocation(), blockStack); + LOGGER.debug("GBH BlockHead given to " + player.getName()); + } + LOGGER.debug("giveBlockHead END"); + } + + /** + * Checks if the inventory of the specified player is full. + * + * @param player The player whose inventory is to be checked. + * @return `true` if the player's inventory is full; `false` otherwise. + */ + public boolean isInventoryFull(Player player) { + return !(player.getInventory().firstEmpty() == -1); + } + + @EventHandler + public void onPlayerInteractEvent(PlayerInteractEntityEvent event){// TODO: PlayerInteractEntityEvent + if(!(event.getPlayer() instanceof Player)) { + return; + } + try{ + Player player = event.getPlayer(); + if(player.hasPermission("moremobheads.nametag")){ + LOGGER.debug(" PIEE moremobheads.nametag=true"); + if(config.getBoolean("head_settings.mob_heads.nametag", false)) { + LOGGER.debug(" PIEE mob.nametag=true"); + Material material = player.getInventory().getItemInMainHand().getType(); + Material material2 = player.getInventory().getItemInOffHand().getType(); + String name = ""; + if(material.equals(Material.NAME_TAG)){ + name = player.getInventory().getItemInMainHand().getItemMeta().getDisplayName(); + LOGGER.debug(" PIEE" + player.getDisplayName() + " Main hand name=" + name); + } + if(material2.equals(Material.NAME_TAG)){ + name = player.getInventory().getItemInOffHand().getItemMeta().getDisplayName(); + LOGGER.debug("PIEE " + player.getDisplayName() + " Off hand name=" + name); + } + + //player.sendMessage("Testing"); + if(material.equals(Material.NAME_TAG)||material2.equals(Material.NAME_TAG)){ + if(getServer().getPluginManager().getPlugin("SilenceMobs") != null){ + if(name.toLowerCase().contains("silenceme")||name.toLowerCase().contains("silence me")){ + return; + } + } + //player.sendMessage("Testing 2"); + LivingEntity mob = (LivingEntity) event.getRightClicked(); + //log("mob=" + mob.getType().toString()); + LOGGER.debug("canwearhead=" + NameTag.canWearHead(mob)); + if(NameTag.canWearHead(mob)){ + // Piglin, drowned, husk, pillager, stray + + boolean enforcewhitelist = config.getBoolean("head_settings.player_heads.whitelist.enforce", false); + boolean enforceblacklist = config.getBoolean("head_settings.player_heads.blacklist.enforce", false); + boolean onwhitelist = config.getString("head_settings.player_heads.whitelist.player_head_whitelist", "").toLowerCase().contains(name.toLowerCase()); + boolean onblacklist = config.getString("head_settings.player_heads.blacklist.player_head_blacklist", "").toLowerCase().contains(name.toLowerCase()); + if(enforcewhitelist&&enforceblacklist){ + if(onwhitelist&&!(onblacklist)){ + giveMobHead(mob, name); + }else{ + event.setCancelled(true); // return; + LOGGER.debug(" PIEE - Name Error 1"); + } + }else if(enforcewhitelist&&!enforceblacklist){ + if(onwhitelist){ + giveMobHead(mob, name); + }else{ + event.setCancelled(true); // return; + LOGGER.debug(" PIEE - Name not on whitelist."); + } + }else if(!enforcewhitelist&&enforceblacklist){ + if(!onblacklist){ + giveMobHead(mob, name); + }else{ + event.setCancelled(true); // return; + LOGGER.debug(" PIEE - Name is on blacklist."); + } + }else{ + giveMobHead(mob, name); + } + } + } + } else { + LOGGER.debug("mob.nametag=false"); + } + } else { + LOGGER.debug("moremobheads.nametag=false"); + //player.sendMessage(mob.getName() + " profession= " + mob.getVillagerProfession()); + } + + //player.sendMessage(mob.getName() + " profession= " + mob.getVillagerProfession()); + } catch (Exception exception){ + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PIE_LOAD_ERROR).error(exception)); + } + + } + + /** + * Determines whether an entity should drop an item based on a chance percentage. + * + * @param event The EntityDeathEvent representing the death of the entity. + * @param chancePercent The chance percentage for the item drop. + * The drop will occur if a random value is less than or equal to this percentage. + * @return {@code true} if the item should be dropped, {@code false} otherwise. + */ + public boolean DropIt(EntityDeathEvent event, double chancePercent){// TODO: DropIt + Player player = event.getEntity().getKiller(); + ItemStack itemstack = event.getEntity().getKiller().getInventory().getItemInMainHand(); + if(isDev) {return true;} + if(itemstack != null){ + LOGGER.debug(" DI itemstack=" + itemstack.getType().toString()); + int enchantmentlevel = 0; + if(config.getBoolean("head_settings.apply_looting", true)){ + enchantmentlevel = itemstack.getEnchantmentLevel(Enchantment.LOOT_BONUS_MOBS); + } + if(chancePercent == 0){ + LOGGER.debug(" DI chancePercent == 0"); + LOGGER.debug(" DI returning=false"); + return false; + } + LOGGER.debug(" DI enchantmentlevel=" + enchantmentlevel); + // Handle lower than 1 chances + if((chancePercent < 1) && (enchantmentlevel == 0)){ + Random chanceRandom = chanceRandoms.computeIfAbsent(player, p -> new Random(p.getUniqueId().hashCode())); + double inverseChance = 1 / (chancePercent / 100); // Adjust to percentage before calculating inverse + LOGGER.debug(" DI chance=" + inverseChance); + LOGGER.debug(" DI chancePercent=" + chancePercent); + inverseChance = inverseChance - enchantmentlevel; + LOGGER.debug(" DI inverseChance - enchantmentlevel = " + inverseChance); + int randomValue = chanceRandom.nextInt((int) inverseChance); + LOGGER.debug(" DI randomValue == 0 (" + (randomValue == 0) + ")"); + if (randomValue == 0) { + LOGGER.debug(" DI returning=true"); + return true; + } + }else { // Normal 1-100 chance + Random chanceRandom = chanceRandoms.computeIfAbsent(player, p -> new Random(p.getUniqueId().hashCode())); + double chance = Double.parseDouble(String.format("%.2f", chanceRandom.nextDouble() * 100)); + LOGGER.debug(" DI chance=" + chance); + LOGGER.debug(" DI chancePercent=" + chancePercent); + chancePercent = chancePercent + enchantmentlevel; + LOGGER.debug(" DI chancePercent + enchantmentlevel=" + chancePercent); + LOGGER.debug(" DI " + chancePercent +" >= " + chance + " (" + (chancePercent >= chance) + ")"); + if ((chancePercent >= chance) || isDev){ + LOGGER.debug(" DI returning=true"); + return true; + } + } + } + LOGGER.debug(" DI returning=false"); + return false; + } + + public boolean DropIt2( double chancepercent){ + double chance = Math.random() * 100; + if(isDev) {return true;} + LOGGER.debug(" DI2 chance=" + chance); + LOGGER.debug(" DI2 chancepercent=" + chancepercent); + return chancepercent >= chance; + } + + /** + * Generates a random integer between the specified minimum and maximum values (inclusive). + * + * @param min The minimum value for the random integer (inclusive). + * @param max The maximum value for the random integer (inclusive). + * @return A random integer within the specified range [min, max]. + */ + public int randomBetween(int min, int max) { + Random r = new Random(); + int random = r.nextInt((max - min) + 1) + min; + return random; + } + + @EventHandler + public void onPlayerJoinEvent(PlayerJoinEvent event) // TODO: OnPlayerJoin + { + Player player = event.getPlayer(); + //if(p.isOp() && UpdateCheck||p.hasPermission("moremobheads.showUpdateAvailable")){ + /** Notify Ops */ + if(UpdateAvailable&&(player.isOp()||player.hasPermission("moremobheads.showUpdateAvailable"))){ + String links = "[\"\",{\"text\":\"\",\"bold\":true,\"color\":\"gold\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"/history\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"\"}},{\"text\":\" \",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"\"}},{\"text\":\"| \"},{\"text\":\"\",\"bold\":true,\"color\":\"gold\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://ko-fi.com/joelgodofwar\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"\"}},{\"text\":\" | \"},{\"text\":\"\",\"bold\":true,\"color\":\"gold\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"/updates\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"\"}}]"; + links = links.replace("", DownloadLink).replace("", get("mmh.version.download")) + .replace("", get("mmh.version.donate")).replace("", get("mmh.version.please_update")) + .replace("", get("mmh.version.donate.message")).replace("", get("mmh.version.notes")) + .replace("", get("mmh.version.notes.message")); + String versions = "" + ChatColor.GRAY + get("mmh.version.new_vers") + ": " + ChatColor.GREEN + "{nVers} | " + get("mmh.version.old_vers") + ": " + ChatColor.RED + "{oVers}"; + player.sendMessage("" + ChatColor.GRAY + get("mmh.version.message").toString().replace("", ChatColor.GOLD + THIS_NAME + ChatColor.GRAY) ); + Utils.sendJson(player, links); + player.sendMessage(versions.replace("{nVers}", UCnewVers).replace("{oVers}", UColdVers)); + } + + if(player.getDisplayName().equals("JoelYahwehOfWar")||player.getDisplayName().equals("JoelGodOfWar")){ + player.sendMessage(THIS_NAME + " " + THIS_VERSION + " Hello father!"); + } + } + + /** + * Sends a JSON-formatted message to the specified player using the `tellraw` command. + * + * @param player The player to receive the JSON message. + * @param string The JSON string representing the message to send. + */ + public void sendJson(Player player, String string) { + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw \"" + player.getName() + + "\" " + string); + } + + public void makeHead(EntityDeathEvent event, Material material){// TODO: makeHead + ItemStack itemstack = event.getEntity().getKiller().getInventory().getItemInMainHand(); + if(itemstack != null){ + LOGGER.debug("itemstack=" + itemstack.getType().toString() + " line:954"); + int enchantmentlevel = itemstack.getEnchantmentLevel(Enchantment.LOOT_BONUS_MOBS);//.containsEnchantment(Enchantment.LOOT_BONUS_MOBS); + LOGGER.debug("enchantmentlevel=" + enchantmentlevel + " line:956"); + double enchantmentlevelpercent = ((double)enchantmentlevel / 100); + LOGGER.debug("enchantmentlevelpercent=" + enchantmentlevelpercent + " line:958"); + double chance = Math.random(); + LOGGER.debug("chance=" + chance + " line:960"); + double chancepercent = 0.25; /** Set to check config.yml later*/ + LOGGER.debug("chancepercent=" + chancepercent + " line:962"); + chancepercent = chancepercent + enchantmentlevelpercent; + LOGGER.debug("chancepercent2=" + chancepercent + " line:964"); + if((chancepercent > 0.00) && (chancepercent < 0.99)){ + if (chancepercent > chance){ + event.getDrops().add(new ItemStack(material, 1)); + } + } + } + } + + public String getName(EntityType eType, Player player) { + String daType = ""; + switch(eType) { + case PLAYER: + daType = "player_heads"; + break; + default: + daType = "mob_heads"; + } + if(config.getBoolean("head_settings." + daType + ".announce_kill.displayname", true)){ + return getNickname(player); + } + return player.getName(); + } + + /** + * Retrieves the nickname of the specified player. + * + * @param player The player whose nickname is to be retrieved. + * @return The nickname of the player, or the player's name if no nickname is found. + */ + public String getNickname(Player player){ + String playerName = null; + LOGGER.debug("GN - player.getDisplayName()=" + player.getDisplayName()); + LOGGER.debug("GN - player.getName()=" + player.getName()); + LOGGER.debug("GN - head_settings.player_heads.announce_kill.displayname=" + getConfig().getBoolean("head_settings.player_heads.announce_kill.displayname")); + if(config.getBoolean("head_settings.player_heads.announce_kill.displayname", false)) { + playerName = ChatColorUtils.setColorsByCode(player.getDisplayName()); + if(getServer().getPluginManager().getPlugin("VentureChat") != null){ + MineverseChatPlayer mcp = MineverseChatAPI.getMineverseChatPlayer(player); + String nick = mcp.getNickname(); + if(nick != null){ + LOGGER.debug("GN - mcp.getNickname()=" + mcp.getNickname()); + LOGGER.debug("GN - ChatColor.translateAlternateColorCodes('&', nick)=" + ChatColor.translateAlternateColorCodes('&', nick)); + //ChatColor.translateAlternateColorCodes('&', nick); + //nick = nick.replaceAll("§", "&"); + nick = ChatColorUtils.setColorsByCode(nick); + LOGGER.debug("VentureChat ChatColorUtils.setColorsByCode(nick)=" + nick); + return nick; + } + LOGGER.debug("GN - VentureChat Nick=null using " + playerName); + return Format.color(playerName); + }else if(getServer().getPluginManager().getPlugin("Essentials") != null){ + Essentials ess = (Essentials) Bukkit.getServer().getPluginManager().getPlugin("Essentials"); + //User user = ess.getUserMap().getUser(player.getName()); + //if(debug){logDebug("Essnetials Nick=" + ess.getUserMap().getUser(player.getName()).getNickname());} + String nick = ess.getUserMap().getUser(player.getName()).getNickname(); + if(nick != null){ + LOGGER.debug("GN - Essentials Nick=" + nick); + return ChatColor.translateAlternateColorCodes('&', nick); + } + LOGGER.debug("GN - Essentials Nick=null using: " + playerName ); + return ChatColorUtils.setColorsByCode(playerName); + }else if(getServer().getPluginManager().getPlugin("HexNicks") != null){ + CompletableFuture nickFuture = HexNicks.api().getStoredNick(player); + try { + String nick = GsonComponentSerializer.gson().serialize(nickFuture.get()); + if(nick != null){ + LOGGER.debug("GN - HexNick Nick=" + nick); + if(nick.contains("[")) { + nick = nick.substring(nick.indexOf("[") + 1); + } + if(nick.contains("]")) { + nick = nick.substring(0, nick.indexOf("]")); + } + return "\"}," + ChatColor.translateAlternateColorCodes('&', nick) + ",{\"text\": \""; + } + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_GET_HEXNICK).error(exception)); + } + LOGGER.debug("GN - HexNick Nick=null using " + playerName); + return ChatColorUtils.setColorsByCode(playerName); + }else{ + LOGGER.debug("GN - No nickname found using=" + playerName); + return playerName; + } + }else { + playerName = player.getName(); + } + + return playerName; + } + + /** + * Creates a custom head ItemStack with the specified name, texture URL (as a String), + * associated entity type, and player who delivered the killing blow. + * + * @param name The name of the custom head. + * @param texture String of the Base64-encoded string or direct URL of the texture for the custom head. + * @param uuid String UUID of Mob + * @param eType The EntityType associated with the custom head. + * @param killer The player who delivered the killing blow to the entity. + * @return An ItemStack representing the custom head with the provided name, texture, sound, and lore. + */ + public ItemStack makeHead(String name, String texture,String uuid, EntityType eType, Player killer) { + // Create the PlayerProfile using UUID and name + PlayerProfile profile = Bukkit.createPlayerProfile(UUID.fromString(uuid), name); + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + PlayerTextures textures = profile.getTextures(); + URL url = null; + try { + url = convertBase64ToURL(texture); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_HEAD_URL_ERROR).error(exception)); + } + textures.setSkin(url); + profile.setTextures(textures); + meta.setOwnerProfile(profile); + meta.setNoteBlockSound(NamespacedKey.minecraft( getSoundString(meta.getDisplayName(), eType) )); + ArrayList lore = new ArrayList(); + if(config.getBoolean("head_settings.lore.show_killer", true)){ + lore.add(ChatColor.RESET + ChatColorUtils.setColors( langName.getString("killedby", "Killed By ").replace("", getName(eType, killer)) ) ); + } + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + meta.setLore(lore); + meta.setLore(lore); + meta.setDisplayName(name); + head.setItemMeta(meta); + return head; + } + + /** + * Creates a custom head ItemStack with the specified name, texture URL (as a Base64-encoded string or a direct URL), associated entity type, + * and player UUID who delivered the killing blow. + * + * @param name The name of the custom head. + * @param texture String of the Base64-encoded string or direct URL of the texture for the custom head. + * @param uuid String UUID of the player. + * @param eType The EntityType associated with the custom head. + * @param amount The number of heads to make. + * @return An ItemStack representing the custom head with the provided name, texture, sound, and lore. + */ + public ItemStack makeHeads(String name, String texture, String uuid, EntityType eType, int amount) { + // Create the PlayerProfile using UUID and name + PlayerProfile profile = Bukkit.createPlayerProfile(UUID.fromString(uuid), name); + ItemStack head = new ItemStack(Material.PLAYER_HEAD, amount); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + PlayerTextures textures = profile.getTextures(); + URL url = null; + try { + url = convertBase64ToURL(texture); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_HEAD_URL_ERROR).error(exception)); + } + textures.setSkin(url); + profile.setTextures(textures); + meta.setOwnerProfile(profile); + meta.setNoteBlockSound(NamespacedKey.minecraft( getSoundString(meta.getDisplayName(), eType) )); + ArrayList lore = new ArrayList(); + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + meta.setLore(lore); + meta.setLore(lore); + meta.setDisplayName(name); + head.setItemMeta(meta); + return head; + } + + /** + * Converts a Base64-encoded string containing a JSON structure or a URL string to a URL object + * representing the texture's URL. + * + * @param input The Base64-encoded string containing the JSON structure with a URL or a URL string itself. + * @return A URL object representing the texture's URL. If the input is a direct URL string, + * it is returned directly. If the input is a Base64-encoded string, the method + * decodes it, extracts the URL from the JSON structure, and returns the corresponding URL object. + * @throws MalformedURLException If the URL extraction or URL creation encounters a malformed URL. + */ + private static URL convertBase64ToURL(String base64) throws MalformedURLException { + try { + return new URL(base64); + } catch (MalformedURLException ignored) {} + + String jsonString = new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8); + + JSONObject jsonObject = new JSONObject(jsonString); + JSONObject jstextures = jsonObject.getJSONObject("textures"); + JSONObject skin = jstextures.getJSONObject("SKIN"); + String jsurl = skin.getString("url"); + return new URL(jsurl); + } + + /** + * Converts a Base64-encoded string containing a JSON structure or a URL string to a URL object + * representing the texture's URL. + * + * @param input The Base64-encoded string containing the JSON structure with a URL + * or a URL string itself. + * @return A URL object representing the texture's URL extracted from the input string. + * @throws MalformedURLException If the URL extraction or URL creation encounters a malformed URL. + */ + public URL getUrlFromBase64(String base64) throws MalformedURLException { + // Check if the input is already a valid URL + try { + return new URL(base64); + } catch (MalformedURLException ignored) { + // Proceed with the decoding logic if it's not a valid URL + } + String decoded = new String(Base64.getDecoder().decode(base64)); + // We simply remove the "beginning" and "ending" part of the JSON, so we're left with only the URL. You could use a proper + // JSON parser for this, but that's not worth it. The String will always start exactly with this stuff anyway + return new URL(decoded.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), decoded.length() - "\"}}}".length())); + } + + /** + * Retrieves the sound string associated with the display name and entity type. + * + * @param displayname The display name of the entity. + * @param entity The entity for which to determine the sound string. + * @return The sound string for the given entity based on its display name and type. + */ + public String getSoundString(String displayname, EntityType eType) { + //EntityType eType = entity.getType(); + String name = eType.name(); + String soundType = "ambient"; + switch (eType) { + case ALLAY: + soundType = "ambient_without_item"; + break; + case AXOLOTL: + soundType = "idle_air"; + break; + case BEE: + String bee = (displayname.contains(" ")) ? StrUtils.Left(displayname, displayname.indexOf(" ") - 1).toUpperCase() : "BEE"; + switch(bee) { + case "ANGRY": // Angry Pollinated Bee + soundType = "loop_aggressive"; + break; + case "POLLINATED": // Pollinated Bee + soundType = "pollinate"; + break; + default: // Bee + soundType = "loop"; + break; + } + break; + case CAVE_SPIDER: + case SPIDER: + name = "SPIDER"; + soundType = "step"; + break; + case CREEPER: + soundType = "primed"; + break; + case COD: + case SALMON: + case TROPICAL_FISH: + case TADPOLE: + soundType = "flop"; + break; + case IRON_GOLEM: + case PLAYER: + case SNOWMAN: + soundType = "hurt"; + break; + case GOAT: + String goat = (displayname.contains(" ")) ? StrUtils.Left(displayname, displayname.indexOf(" ") - 1).toUpperCase() : "GOAT"; + switch (goat) { + case "SCREAMING": + soundType = "screaming.ambient"; + break; + default: + soundType = "ambient"; + break; + } + break; + case MUSHROOM_COW: + name = "COW"; + soundType = "ambient"; + break; + case PANDA: + // Check If true get string left of space Default + String panda = (displayname.contains(" ")) ? StrUtils.Left(displayname, displayname.indexOf(" ") - 1).toUpperCase() : "PANDA"; + switch (panda) { + case "WORRIED": + soundType = "worried_ambient"; + break; + case "AGGRESSIVE": + soundType = "aggressive_ambient"; + break; + case "WEAK": + soundType = "sneeze"; + break; + default: + soundType = "ambient"; + break; + } + break; + case PUFFERFISH: + soundType = "blow_up"; + break; + case SLIME: + case MAGMA_CUBE: + soundType = "squish_small"; + break; + case RABBIT: + String rabbit = (displayname.contains(" ")) ? StrUtils.Left(displayname, displayname.indexOf(" ") - 1).toUpperCase() : "RABBIT"; + switch (rabbit) { + case "THE": + soundType = "attack"; + break; + default: + soundType = "ambient"; + break; + } + break; + case SNIFFER: + soundType = "scenting"; + break; + case TRADER_LLAMA: + name = "LLAMA"; + soundType = "ambient"; + break; + case TURTLE: + soundType = "ambient_land"; + break; + case VEX: + String vex = (displayname.contains(" ")) ? StrUtils.Left(displayname, displayname.indexOf(" ") - 1).toUpperCase() : "VEX"; + switch (vex) { + case "ANGRY": + soundType = "charge"; + break; + default: + soundType = "ambient"; + break; + } + break; + case WOLF: + String wolf = (displayname.contains(" ")) ? StrUtils.Left(displayname, displayname.indexOf(" ") - 1).toUpperCase() : "WOLF"; + switch (wolf) { + case "ANGRY": + soundType = "growl"; + break; + default: + soundType = "ambient"; + break; + } + break; + case WITHER: + if (displayname.contains("projectile")) { + soundType = "shoot"; + } else { + soundType = "ambient"; + } + break; + default: + soundType = "ambient"; + break; + } + return "entity." + name.toLowerCase() + "." + soundType; + } + + /** + * Creates and returns an ItemStack with the specified texture, name, and UUID, producing multiple copies. + * + * @param texture The base64 texture string for the trader's skull appearance. + * @param name The name of the custom trader's skulls. + * @param uuid The string UUID associated with the trader's GameProfile. + * @param amount The number of identical trader's skull ItemStacks to create. + * @return An ItemStack representing the custom trader's skull with the provided texture, name, UUID, and in the specified quantity. + */ + /**public ItemStack makeTraderSkull(String texture, String name, String uuid, int amount){ + ItemStack item = new ItemStack(Material.PLAYER_HEAD, amount); + if(texture == null) { + return item; + } + SkullMeta meta = (SkullMeta) item.getItemMeta(); + + GameProfile profile = new GameProfile(UUID.nameUUIDFromBytes(texture.getBytes()), texture); + profile.getProperties().put("textures", new Property("textures", texture)); + profile.getProperties().put("SkullOwner", new Property("id", uuid)); + profile.getProperties().put("display", new Property("Name", name)); + setGameProfile(meta, profile); + ArrayList lore = new ArrayList(); + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + meta.setLore(lore); + meta.setLore(lore); + + //meta.setOwningPlayer(Bukkit.getOfflinePlayer(ownerUUID)); + meta.setDisplayName(name); + item.setItemMeta(meta); + return item; + }//*/ + + /** + * Creates and returns a custom skull ItemStack with the specified texture and name. + * + * @param texture The base64 texture string for the skull's appearance. + * @param name The name of the custom skull. + * @param killer The Player object representing the killer of the mob or player that was killed. Pass null if not applicable. + * @return An ItemStack representing the custom skull with the provided texture and name. + */ + /**public ItemStack makeSkull(String texture, String name, Player killer){ + ItemStack item = new ItemStack(Material.PLAYER_HEAD); + if(texture == null) { + return item; + } + SkullMeta meta = (SkullMeta) item.getItemMeta(); + + GameProfile profile = new GameProfile(UUID.nameUUIDFromBytes(texture.getBytes()), texture); + profile.getProperties().put("textures", new Property("textures", texture)); + profile.getProperties().put("display", new Property("Name", name)); + //profile.getProperties().put("BlockEntityTag", new Property("note_block_sound", "minecraft:entity.camel.ambient")); + + setGameProfile(meta, profile); + ArrayList lore = new ArrayList(); + + if(config.getBoolean("head_settings.lore.show_killer", true)){ + lore.add(ChatColor.RESET + ChatColorUtils.setColors( langName.getString("killedby", "Killed By ").replace("", killer.getName()) ) ); + } + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + meta.setLore(lore); + meta.setLore(lore); + + //meta.setOwningPlayer(Bukkit.getOfflinePlayer(ownerUUID)); + meta.setDisplayName(name); + item.setItemMeta(meta); + return item; + }//*/ + + /** + * Creates and returns a custom skull ItemStack with the specified texture and name, producing multiple copies. + * + * @param texture The base64 texture string for the skulls' appearance. + * @param name The name of the custom skulls. + * @param amount The number of identical skull ItemStacks to create. + * @return An ItemStack of the custom skull with the provided texture and name, in the specified quantity. + */ + public ItemStack makeSkulls(String texture, String name, int amount){ + ItemStack item = new ItemStack(Material.PLAYER_HEAD, amount); + if(texture == null) { + return item; + } + SkullMeta meta = (SkullMeta) item.getItemMeta(); + + GameProfile profile = new GameProfile(UUID.nameUUIDFromBytes(texture.getBytes()), texture); + profile.getProperties().put("textures", new Property("textures", texture)); + profile.getProperties().put("display", new Property("Name", name)); + setGameProfile(meta, profile); + ArrayList lore = new ArrayList(); + + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + meta.setLore(lore); + meta.setLore(lore); + + //meta.setOwningPlayer(Bukkit.getOfflinePlayer(ownerUUID)); + meta.setDisplayName(name); + item.setItemMeta(meta); + return item; + } + + static JavaPlugin plugin = getInstance(); + private static Field fieldProfileItem; + public static void setGameProfile(SkullMeta meta, GameProfile profile) { + try{ + if(fieldProfileItem == null) { + fieldProfileItem = meta.getClass().getDeclaredField("profile"); + } + fieldProfileItem.setAccessible(true); + fieldProfileItem.set(meta, profile); + } + catch(Exception exception){ + reporter.reportDetailed(plugin, Report.newBuilder(PluginLibrary.REPORT_HEAD_URL_ERROR).error(exception)); + } + } + + public static MoreMobHeads getInstance() { + return (MoreMobHeads) Bukkit.getPluginManager().getPlugin("MoreMobHeads"); + } + + public boolean isInteger(String string){ + try{ + Integer.parseInt(string); + return true; + }catch (NumberFormatException e){ + return false; + } + } + + public String isPlayerHead(String string){ + try{ + playerFile = new File(getDataFolder() + "" + File.separatorChar + "player_heads.yml");//\ + if(!playerFile.exists()){ // checks if the yaml does not exist + return null; + } + int numOfCustomTrades = playerHeads.getInt("players.number", 0) + 1; + LOGGER.debug("iPH string=" + string); + for(int randomPlayerHead=1; randomPlayerHead= 1.16){ + if(!blockFile116.exists()){ + return null; + } + } + int numOfCustomTrades = blockHeads.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + //if(debug&&skullmeta != null){logDebug("iBH getOwner_" + randomBlockHead + "=" + skullmeta.getOwner().toString());} + if(skullmeta.getDisplayName() != null){ + if(ChatColor.stripColor(skullmeta.getDisplayName()).equals(string)){ + return itemstack.getItemMeta().getDisplayName(); + } + } + } + } + } + }catch(Exception exception){ + //stacktraceInfo(); + //e.printStackTrace(); + return null; + } + //blockHeads + return null; + } + + public String isBlockHead2(String string){ + try{ + if(!(Double.parseDouble(StrUtils.Left(getMCVersion(), 4)) >= 1.16)){ // checks if the yaml does not exist + return null; + } + blockFile1162 = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_16_2.yml"); + if(getMCVersion().startsWith("1.16")||getMCVersion().startsWith("1.17")){ + if(!blockFile1162.exists()){ + return null; + } + + } + int numOfCustomTrades = blockHeads2.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH2 string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads2.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + //if(debug&&skullmeta != null){logDebug("iBH getOwner_" + randomBlockHead + "=" + skullmeta.getOwner().toString());} + if(skullmeta.getOwner() != null){ + if(skullmeta.getOwner().toString().contains(string)){ + return itemstack.getItemMeta().getDisplayName(); + } + } + } + } + } + }catch(Exception exception){ + //stacktraceInfo(); + //e.printStackTrace(); + return null; + } + //blockHeads + return null; + } + + public String isBlockHead3(String string){ + try{ + if(!(Double.parseDouble(StrUtils.Left(getMCVersion(), 4)) >= 1.16)){ // checks if the yaml does not exist + return null; + } + blockFile117 = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_17.yml"); + if(getMCVersion().startsWith("1.16")||getMCVersion().startsWith("1.17")){ + if(!blockFile117.exists()){ + return null; + } + + } + int numOfCustomTrades = blockHeads3.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH3 string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads3.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + //if(debug&&skullmeta != null){logDebug("iBH getOwner_" + randomBlockHead + "=" + skullmeta.getOwner().toString());} + if(skullmeta.getOwner() != null){ + if(skullmeta.getOwner().toString().contains(string)){ + return itemstack.getItemMeta().getDisplayName(); + } + } + } + } + } + }catch(Exception exception){ + //stacktraceInfo(); + //e.printStackTrace(); + return null; + } + //blockHeads + return null; + } + + + public int isBlockHeadName(String string){ // TODO: isBlockHeadName + LOGGER.debug("iBHN START"); + try{ + double mcVer = Double.parseDouble(StrUtils.Left(getMCVersion(), 4)); + if(!(mcVer >= 1.16)){ + blockFile = new File(getDataFolder() + "" + File.separatorChar + "block_heads.yml");//\ + if(!blockFile.exists()){ // checks if the yaml does not exist + return -1; + } + }else if(mcVer == 1.16){ + blockFile = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_16.yml"); + }else if(mcVer >= 1.17) { + blockFile = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_17.yml"); + } + + LOGGER.debug("iBH blockFile=" + blockFile.toString()); + if(blockHeads.getInt("blocks.number", 0) == 0) { + try { + blockHeads.load(blockFile); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + } + int numOfCustomTrades = blockHeads.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH number=" + numOfCustomTrades); + LOGGER.debug("iBH string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + //if(debug&&skullmeta != null){logDebug("iBH getOwner_" + randomBlockHead + "=" + skullmeta.getOwner().toString());} + if(skullmeta.getDisplayName() != null){ + if(ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase().equals(string.toLowerCase())){ + LOGGER.debug("iBHN END Sucess!"); + return randomBlockHead; //itemstack.getItemMeta().getDisplayName(); + } + } + } + } + } + }catch(Exception exception){ + LOGGER.debug("iBHN END Failure=Exception"); + return -1; + } + //blockHeads + LOGGER.debug("iBHN END Failure!"); + return -1; + } + public int isBlockHeadName2(String string){ + LOGGER.debug("iBHN2 START"); + try{ + double mcVer = Double.parseDouble(StrUtils.Left(getMCVersion(), 4)); + if(!(mcVer >= 1.16)){ // checks if the yaml does not exist + return -1; + }else if(mcVer == 1.16) { + blockFile1162 = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_16_2.yml"); + }else if(mcVer >= 1.17) { + blockFile1162 = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_17_2.yml"); + } + + if(getMCVersion().startsWith("1.16")||getMCVersion().startsWith("1.17")){ + if(!blockFile1162.exists()){ + return -1; + } + + } + LOGGER.debug("iBH blockFile1162=" + blockFile1162.toString()); + if(blockHeads2.getInt("blocks.number", 0) == 0) { + try { + blockHeads2.load(blockFile1162); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + } + int numOfCustomTrades = blockHeads2.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH2 number=" + numOfCustomTrades); + LOGGER.debug("iBH2 string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads2.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + //if(debug&&skullmeta != null){logDebug("iBH getOwner_" + randomBlockHead + "=" + skullmeta.getOwner().toString());} + if(skullmeta.getDisplayName() != null){ + if(ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase().equals(string.toLowerCase())){ + LOGGER.debug("iBHN END Sucess!"); + return randomBlockHead; //itemstack.getItemMeta().getDisplayName(); + } + } + } + } + } + }catch(Exception exception){ + //stacktraceInfo(); + //e.printStackTrace(); + LOGGER.debug("iBHN END Failure=Exception"); + return -1; + } + //blockHeads + LOGGER.debug("iBHN2 END Failure!"); + return -1; + } + + public int isBlockHeadName3(String string){ + LOGGER.debug("iBHN3 START"); + try{ + double mcVer = Double.parseDouble(StrUtils.Left(getMCVersion(), 4)); + if(!(mcVer >= 1.16)){ // checks if the yaml does not exist + return -1; + }else if(mcVer == 1.16) { + return -1; + }else if(mcVer >= 1.17) { + blockFile117 = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_17_3.yml"); + } + + if(getMCVersion().startsWith("1.16")||getMCVersion().startsWith("1.17")){ + if(!blockFile117.exists()){ + return -1; + } + + } + LOGGER.debug("iBHN3 blockFile117=" + blockFile117.toString()); + if(blockHeads3.getInt("blocks.number", 0) == 0) { + try { + blockHeads3.load(blockFile117); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + } + int numOfCustomTrades = blockHeads3.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH3 number=" + numOfCustomTrades); + LOGGER.debug("iBH3 string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads3.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + //if(debug&&skullmeta != null){logDebug("iBH getOwner_" + randomBlockHead + "=" + skullmeta.getOwner().toString());} + if(skullmeta.getDisplayName() != null){ + if(ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase().equals(string.toLowerCase())){ + LOGGER.debug("iBHN END Sucess!"); + return randomBlockHead; //itemstack.getItemMeta().getDisplayName(); + }else { + //log(Level.INFO,"" + ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase()); + } + } + } + } + } + }catch(Exception exception){ + //stacktraceInfo(); + //e.printStackTrace(); + LOGGER.debug("iBHN3 END Failure=Exception"); + return -1; + } + //blockHeads + LOGGER.debug("iBHN3 END Failure!"); + return -1; + } + + public int isBlockHeadName4(String string){ + LOGGER.debug("iBHN4 START"); + try{ + double mcVer = Double.parseDouble(StrUtils.Left(getMCVersion(), 4)); + if(!(mcVer >= 1.16)){ // checks if the yaml does not exist + return -1; + }else if(mcVer == 1.16) { + return -1; + }else if(mcVer == 1.19) { + blockFile119 = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_19.yml"); + } + + if(getMCVersion().startsWith("1.19")){ + if(!blockFile119.exists()){ + return -1; + } + + } + LOGGER.debug("iBHN4 blockFile119=" + blockFile119.toString()); + if(blockHeads4.getInt("blocks.number", 0) == 0) { + try { + blockHeads4.load(blockFile119); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + } + int numOfCustomTrades = blockHeads4.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH4 number=" + numOfCustomTrades); + LOGGER.debug("iBH4 string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads4.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + //if(debug&&skullmeta != null){logDebug("iBH getOwner_" + randomBlockHead + "=" + skullmeta.getOwner().toString());} + if(skullmeta.getDisplayName() != null){ + if(ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase().equals(string.toLowerCase())){ + LOGGER.debug("iBHN4 END Sucess!"); + return randomBlockHead; //itemstack.getItemMeta().getDisplayName(); + }else { + //log(Level.INFO,"" + ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase()); + } + } + } + } + } + }catch(Exception exception){ + //stacktraceInfo(); + //e.printStackTrace(); + LOGGER.debug("iBHN4 END Failure=Exception"); + return -1; + } + //blockHeads + LOGGER.debug("iBHN4 END Failure!"); + return -1; + } + + public int isBlockHeadName5(String string){ + LOGGER.debug("iBHN5 START"); + try{ + double mcVer = Double.parseDouble(StrUtils.Left(getMCVersion(), 4)); + if(!(mcVer >= 1.16)){ // checks if the yaml does not exist + return -1; + }else if(mcVer == 1.16) { + return -1; + }else if(mcVer == 1.19) { + return -1; + }else if(mcVer == 1.20) { + blockFile120 = new File(getDataFolder() + "" + File.separatorChar + "block_heads_1_20.yml"); + } + + if(getMCVersion().startsWith("1.20")){ + if(!blockFile120.exists()){ + return -1; + } + + } + LOGGER.debug("iBHN5 blockFile120=" + blockFile120.toString()); + if(blockHeads5.getInt("blocks.number", 0) == 0) { + try { + blockHeads5.load(blockFile120); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + } + int numOfCustomTrades = blockHeads5.getInt("blocks.number", 0) + 1; + LOGGER.debug("iBH5 number=" + numOfCustomTrades); + LOGGER.debug("iBH5 string=" + string); + for(int randomBlockHead = 1; randomBlockHead < numOfCustomTrades; randomBlockHead++){ + ItemStack itemstack = blockHeads5.getItemStack("blocks.block_" + randomBlockHead + ".itemstack", new ItemStack(Material.AIR)); + if(itemstack != null){ + SkullMeta skullmeta = (SkullMeta) itemstack.getItemMeta(); + if(skullmeta != null){ + if(skullmeta.getDisplayName() != null){ + if(ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase().equals(string.toLowerCase())){ + LOGGER.debug("iBHN5 END Sucess!"); + return randomBlockHead; //itemstack.getItemMeta().getDisplayName(); + }else { + //log(Level.INFO,"" + ChatColor.stripColor(skullmeta.getDisplayName()).toLowerCase()); + } + } + } + } + } + }catch(Exception exception){ + LOGGER.debug("iBHN5 END Failure=Exception"); + return -1; + } + //blockHeads + LOGGER.debug("iBHN5 END Failure!"); + return -1; + } + + public static void copyFile_Java7(String origin, String destination) throws IOException { + Path FROM = Paths.get(origin); + Path TO = Paths.get(destination); + //overwrite the destination file if it exists, and copy + // the file attributes, including the rwx permissions + CopyOption[] options = new CopyOption[]{ + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.COPY_ATTRIBUTES + }; + Files.copy(FROM, TO, options); + } + + public double normalize(double value) { + // Check if the value is in the old format + if ((value >= 0.0001) && (value <= 1.00)) { + // Convert to the new format (multiply by 100) + return Math.max(value * 100.0, 0.5); + } else if ((value >= 0.5) && (value <= 100.0)) { + // Value is already in the new format + return value; + } else { + return 13.13; + } + } + + public void copyChance(String file, String file2){ + chanceConfig = new YmlConfiguration(); + oldchanceConfig = new YmlConfiguration(); + try { + chanceConfig.load(new File(file2)); + oldchanceConfig.load(new File(file)); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CHANCE_LOAD_ERROR).error(exception)); + /**stacktraceInfo(); + e.printStackTrace();//*/ + } + LOGGER.log("Copying values frome old_chance_config.yml to chance_config.yml"); + chanceConfig.set("chance_percent.player" , normalize(oldchanceConfig.getDouble("chance_percent.player", 50.0) ) ); + chanceConfig.set("chance_percent.named_mob" , normalize(oldchanceConfig.getDouble("chance_percent.named_mob", 10.0) ) ); + chanceConfig.set("chance_percent.allay" , normalize(oldchanceConfig.getDouble("chance_percent.allay", 20.0) ) ); + chanceConfig.set("chance_percent.axolotl.blue" , normalize(oldchanceConfig.getDouble("chance_percent.axolotl.blue", 100.0) ) ); + chanceConfig.set("chance_percent.axolotl.cyan" , normalize(oldchanceConfig.getDouble("chance_percent.axolotl.cyan", 20.0) ) ); + chanceConfig.set("chance_percent.axolotl.gold" , normalize(oldchanceConfig.getDouble("chance_percent.axolotl.gold", 20.0) ) ); + chanceConfig.set("chance_percent.axolotl.lucy" , normalize(oldchanceConfig.getDouble("chance_percent.axolotl.lucy", 20.0) ) ); + chanceConfig.set("chance_percent.axolotl.wild" , normalize(oldchanceConfig.getDouble("chance_percent.axolotl.wild", 20.0) ) ); + chanceConfig.set("chance_percent.bat" , normalize(oldchanceConfig.getDouble("chance_percent.bat", 10.0) ) ); + chanceConfig.set("chance_percent.bee.angry_pollinated" , normalize(oldchanceConfig.getDouble("chance_percent.bee.angry_pollinated", 20.0) ) ); + chanceConfig.set("chance_percent.bee.angry" , normalize(oldchanceConfig.getDouble("chance_percent.bee.angry", 20.0) ) ); + chanceConfig.set("chance_percent.bee.pollinated" , normalize(oldchanceConfig.getDouble("chance_percent.bee.pollinated", 20.0) ) ); + chanceConfig.set("chance_percent.bee.chance_percent" , normalize(oldchanceConfig.getDouble("chance_percent.bee.normal", 20.0) ) ); + chanceConfig.set("chance_percent.blaze" , normalize(oldchanceConfig.getDouble("chance_percent.blaze", 0.5) ) ); + chanceConfig.set("chance_percent.camel" , normalize(oldchanceConfig.getDouble("chance_percent.camel", 27.0) ) ); + chanceConfig.set("chance_percent.cat.all_black" , normalize(oldchanceConfig.getDouble("chance_percent.cat.all_black", 33.0) ) ); + chanceConfig.set("chance_percent.cat.black" , normalize(oldchanceConfig.getDouble("chance_percent.cat.black", 33.0) ) ); + chanceConfig.set("chance_percent.cat.british_shorthair" , normalize(oldchanceConfig.getDouble("chance_percent.cat.british_shorthair", 33.0) ) ); + chanceConfig.set("chance_percent.cat.calico" , normalize(oldchanceConfig.getDouble("chance_percent.cat.calico", 33.0) ) ); + chanceConfig.set("chance_percent.cat.jellie" , normalize(oldchanceConfig.getDouble("chance_percent.cat.jellie", 33.0) ) ); + chanceConfig.set("chance_percent.cat.persian" , normalize(oldchanceConfig.getDouble("chance_percent.cat.persian", 33.0) ) ); + chanceConfig.set("chance_percent.cat.ragdoll" , normalize(oldchanceConfig.getDouble("chance_percent.cat.ragdoll", 33.0) ) ); + chanceConfig.set("chance_percent.cat.red" , normalize(oldchanceConfig.getDouble("chance_percent.cat.red", 33.0) ) ); + chanceConfig.set("chance_percent.cat.siamese" , normalize(oldchanceConfig.getDouble("chance_percent.cat.siamese", 33.0) ) ); + chanceConfig.set("chance_percent.cat.tabby" , normalize(oldchanceConfig.getDouble("chance_percent.cat.tabby", 33.0) ) ); + chanceConfig.set("chance_percent.cat.white" , normalize(oldchanceConfig.getDouble("chance_percent.cat.white", 33.0) ) ); + + chanceConfig.set("chance_percent.cave_spider" , normalize(oldchanceConfig.getDouble("chance_percent.cave_spider", 0.5) ) ); + chanceConfig.set("chance_percent.chicken" , normalize(oldchanceConfig.getDouble("chance_percent.chicken", 1.0) ) ); + chanceConfig.set("chance_percent.cod" , normalize(oldchanceConfig.getDouble("chance_percent.cod", 10.0) ) ); + chanceConfig.set("chance_percent.cow" , normalize(oldchanceConfig.getDouble("chance_percent.cow", 1.0) ) ); + chanceConfig.set("chance_percent.creeper" , normalize(oldchanceConfig.getDouble("chance_percent.creeper", 50.0) ) ); + chanceConfig.set("chance_percent.creeper_charged" , normalize(oldchanceConfig.getDouble("chance_percent.creeper_charged", 100.0) ) ); + chanceConfig.set("chance_percent.dolphin" , normalize(oldchanceConfig.getDouble("chance_percent.dolphin", 33.0) ) ); + chanceConfig.set("chance_percent.donkey" , normalize(oldchanceConfig.getDouble("chance_percent.donkey", 20.0) ) ); + chanceConfig.set("chance_percent.drowned" , normalize(oldchanceConfig.getDouble("chance_percent.drowned", 5.0) ) ); + chanceConfig.set("chance_percent.elder_guardian" , normalize(oldchanceConfig.getDouble("chance_percent.elder_guardian", 100.0) ) ); + chanceConfig.set("chance_percent.ender_dragon" , normalize(oldchanceConfig.getDouble("chance_percent.ender_dragon", 100.0) ) ); + chanceConfig.set("chance_percent.enderman" , normalize(oldchanceConfig.getDouble("chance_percent.enderman", 0.5) ) ); + chanceConfig.set("chance_percent.endermite" , normalize(oldchanceConfig.getDouble("chance_percent.endermite", 10.0) ) ); + chanceConfig.set("chance_percent.evoker" , normalize(oldchanceConfig.getDouble("chance_percent.evoker", 25.0) ) ); + chanceConfig.set("chance_percent.fox.red" , normalize(oldchanceConfig.getDouble("chance_percent.fox.red", 10.0) ) ); + chanceConfig.set("chance_percent.fox.snow" , normalize(oldchanceConfig.getDouble("chance_percent.fox.snow", 20.0) ) ); + chanceConfig.set("chance_percent.frog.cold" , normalize(oldchanceConfig.getDouble("chance_percent.frog.cold", 20.0) ) ); + chanceConfig.set("chance_percent.frog.temperate" , normalize(oldchanceConfig.getDouble("chance_percent.frog.temperate", 20.0) ) ); + chanceConfig.set("chance_percent.frog.warm" , normalize(oldchanceConfig.getDouble("chance_percent.frog.warm", 20.0) ) ); + chanceConfig.set("chance_percent.ghast" , normalize(oldchanceConfig.getDouble("chance_percent.ghast", 6.25) ) ); + chanceConfig.set("chance_percent.giant" , normalize(oldchanceConfig.getDouble("chance_percent.giant", 2.5) ) ); + chanceConfig.set("chance_percent.glow_squid" , normalize(oldchanceConfig.getDouble("chance_percent.glow_squid", 5.0) ) ); + chanceConfig.set("chance_percent.goat.mormal" , normalize(oldchanceConfig.getDouble("chance_percent.goat.normal", 1.0) ) ); + chanceConfig.set("chance_percent.goat.screaming" , normalize(oldchanceConfig.getDouble("chance_percent.goat.screaming", 100.0) ) ); + chanceConfig.set("chance_percent.guardian" , normalize(oldchanceConfig.getDouble("chance_percent.guardian", 0.5) ) ); + chanceConfig.set("chance_percent.hoglin" , normalize(oldchanceConfig.getDouble("chance_percent.hoglin", 3.0) ) ); + chanceConfig.set("chance_percent.horse.black" , normalize(oldchanceConfig.getDouble("chance_percent.horse.black", 27.0) ) ); + chanceConfig.set("chance_percent.horse.brown" , normalize(oldchanceConfig.getDouble("chance_percent.horse.brown", 27.0) ) ); + chanceConfig.set("chance_percent.horse.chestnut" , normalize(oldchanceConfig.getDouble("chance_percent.horse.chestnut", 27.0) ) ); + chanceConfig.set("chance_percent.horse.creamy" , normalize(oldchanceConfig.getDouble("chance_percent.horse.creamy", 27.0) ) ); + chanceConfig.set("chance_percent.horse.dark_brown" , normalize(oldchanceConfig.getDouble("chance_percent.horse.dark_brown", 27.0) ) ); + chanceConfig.set("chance_percent.horse.gray" , normalize(oldchanceConfig.getDouble("chance_percent.horse.gray", 27.0) ) ); + chanceConfig.set("chance_percent.horse.white" , normalize(oldchanceConfig.getDouble("chance_percent.horse.white", 27.0) ) ); + chanceConfig.set("chance_percent.husk" , normalize(oldchanceConfig.getDouble("chance_percent.husk", 6.0) ) ); + chanceConfig.set("chance_percent.illusioner" , normalize(oldchanceConfig.getDouble("chance_percent.illusioner", 25.0) ) ); + chanceConfig.set("chance_percent.iron_golem" , normalize(oldchanceConfig.getDouble("chance_percent.iron_golem", 5.0) ) ); + chanceConfig.set("chance_percent.llama.brown" , normalize(oldchanceConfig.getDouble("chance_percent.llama.brown", 24.0) ) ); + chanceConfig.set("chance_percent.llama.creamy" , normalize(oldchanceConfig.getDouble("chance_percent.llama.creamy", 24.0) ) ); + chanceConfig.set("chance_percent.llama.gray" , normalize(oldchanceConfig.getDouble("chance_percent.llama.gray", 24.0) ) ); + chanceConfig.set("chance_percent.llama.white" , normalize(oldchanceConfig.getDouble("chance_percent.llama.white", 24.0) ) ); + chanceConfig.set("chance_percent.magma_cube" , normalize(oldchanceConfig.getDouble("chance_percent.magma_cube", 0.5) ) ); + chanceConfig.set("chance_percent.mule" , normalize(oldchanceConfig.getDouble("chance_percent.mule", 20.0) ) ); + chanceConfig.set("chance_percent.mushroom_cow.red" , normalize(oldchanceConfig.getDouble("chance_percent.mushroom_cow.red", 1.0) ) ); + chanceConfig.set("chance_percent.mushroom_cow.brown" , normalize(oldchanceConfig.getDouble("chance_percent.mushroom_cow.brown", 10.0) ) ); + chanceConfig.set("chance_percent.ocelot" , normalize(oldchanceConfig.getDouble("chance_percent.ocelot", 20.0) ) ); + chanceConfig.set("chance_percent.panda.aggressive" , normalize(oldchanceConfig.getDouble("chance_percent.panda.aggressive", 27.0) ) ); + chanceConfig.set("chance_percent.panda.brown" , normalize(oldchanceConfig.getDouble("chance_percent.panda.brown", 27.0) ) ); + chanceConfig.set("chance_percent.panda.lazy" , normalize(oldchanceConfig.getDouble("chance_percent.panda.lazy", 27.0) ) ); + chanceConfig.set("chance_percent.panda.normal" , normalize(oldchanceConfig.getDouble("chance_percent.panda.normal", 27.0) ) ); + chanceConfig.set("chance_percent.panda.playful" , normalize(oldchanceConfig.getDouble("chance_percent.panda.playful", 27.0) ) ); + chanceConfig.set("chance_percent.panda.weak" , normalize(oldchanceConfig.getDouble("chance_percent.panda.weak", 27.0) ) ); + chanceConfig.set("chance_percent.panda.worried" , normalize(oldchanceConfig.getDouble("chance_percent.panda.worried", 27.0) ) ); + chanceConfig.set("chance_percent.parrot.blue" , normalize(oldchanceConfig.getDouble("chance_percent.parrot.blue", 25.0) ) ); + chanceConfig.set("chance_percent.parrot.cyan" , normalize(oldchanceConfig.getDouble("chance_percent.parrot.cyan", 25.0) ) ); + chanceConfig.set("chance_percent.parrot.gray" , normalize(oldchanceConfig.getDouble("chance_percent.parrot.gray", 25.0) ) ); + chanceConfig.set("chance_percent.parrot.green" , normalize(oldchanceConfig.getDouble("chance_percent.parrot.green", 25.0) ) ); + chanceConfig.set("chance_percent.parrot.red" , normalize(oldchanceConfig.getDouble("chance_percent.parrot.red", 25.0) ) ); + chanceConfig.set("chance_percent.phantom" , normalize(oldchanceConfig.getDouble("chance_percent.phantom", 10.0) ) ); + chanceConfig.set("chance_percent.pig" , normalize(oldchanceConfig.getDouble("chance_percent.pig", 1.0) ) ); + chanceConfig.set("chance_percent.piglin" , normalize(oldchanceConfig.getDouble("chance_percent.piglin", 4.0) ) ); + chanceConfig.set("chance_percent.pig_zombie" , normalize(oldchanceConfig.getDouble("chance_percent.pig_zombie", 0.5) ) ); + chanceConfig.set("chance_percent.pillager" , normalize(oldchanceConfig.getDouble("chance_percent.pillager", 2.5) ) ); + chanceConfig.set("chance_percent.polar_bear" , normalize(oldchanceConfig.getDouble("chance_percent.polar_bear", 20.0) ) ); + chanceConfig.set("chance_percent.pufferfish" , normalize(oldchanceConfig.getDouble("chance_percent.pufferfish", 15.0) ) ); + chanceConfig.set("chance_percent.rabbit.black" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.black", 26.0) ) ); + chanceConfig.set("chance_percent.rabbit.black_and_white" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.black_and_white", 26.0) ) ); + chanceConfig.set("chance_percent.rabbit.brown" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.brown", 26.0) ) ); + chanceConfig.set("chance_percent.rabbit.gold" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.gold", 26.0) ) ); + chanceConfig.set("chance_percent.rabbit.salt_and_pepper" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.salt_and_pepper", 26.0) ) ); + chanceConfig.set("chance_percent.rabbit.the_killer_bunny" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.the_killer_bunny", 100.0) ) ); + chanceConfig.set("chance_percent.rabbit.toast" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.toast", 26.0) ) ); + chanceConfig.set("chance_percent.rabbit.white" , normalize(oldchanceConfig.getDouble("chance_percent.rabbit.white", 26.0) ) ); + chanceConfig.set("chance_percent.ravager" , normalize(oldchanceConfig.getDouble("chance_percent.ravager", 25.0) ) ); + chanceConfig.set("chance_percent.salmon" , normalize(oldchanceConfig.getDouble("chance_percent.salmon", 10.0) ) ); + chanceConfig.set("chance_percent.sheep.black" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.black", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.blue" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.blue", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.brown" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.brown", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.cyan" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.cyan", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.gray" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.gray", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.green" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.green", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.jeb_" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.jeb_", 10.0) ) ); + chanceConfig.set("chance_percent.sheep.light_blue" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.light_blue", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.light_gray" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.light_gray", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.lime" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.lime", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.magenta" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.magenta", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.orange" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.orange", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.pink" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.pink", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.purple" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.purple", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.red" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.red", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.white" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.white", 1.75) ) ); + chanceConfig.set("chance_percent.sheep.yellow" , normalize(oldchanceConfig.getDouble("chance_percent.sheep.yellow", 1.75) ) ); + chanceConfig.set("chance_percent.shulker" , normalize(oldchanceConfig.getDouble("chance_percent.shulker", 5.0) ) ); + chanceConfig.set("chance_percent.silverfish" , normalize(oldchanceConfig.getDouble("chance_percent.silverfish", 5.0) ) ); + chanceConfig.set("chance_percent.skeleton" , normalize(oldchanceConfig.getDouble("chance_percent.skeleton", 2.5) ) ); + chanceConfig.set("chance_percent.skeleton_horse" , normalize(oldchanceConfig.getDouble("chance_percent.skeleton_horse", 20.0) ) ); + chanceConfig.set("chance_percent.slime" , normalize(oldchanceConfig.getDouble("chance_percent.slime", 0.5) ) ); + chanceConfig.set("chance_percent.sniffer" , normalize(oldchanceConfig.getDouble("chance_percent.sniffer", 50.0) ) ); + chanceConfig.set("chance_percent.snowman" , normalize(oldchanceConfig.getDouble("chance_percent.snowman", 5.0) ) ); + chanceConfig.set("chance_percent.spider" , normalize(oldchanceConfig.getDouble("chance_percent.spider", 0.5) ) ); + chanceConfig.set("chance_percent.squid" , normalize(oldchanceConfig.getDouble("chance_percent.squid", 5.0) ) ); + chanceConfig.set("chance_percent.stray" , normalize(oldchanceConfig.getDouble("chance_percent.stray", 6.0) ) ); + chanceConfig.set("chance_percent.strider" , normalize(oldchanceConfig.getDouble("chance_percent.strider", 10.0) ) ); + chanceConfig.set("chance_percent.tadpole" , normalize(oldchanceConfig.getDouble("chance_percent.tadpole", 10.0) ) ); + chanceConfig.set("chance_percent.trader_llama.brown" , normalize(oldchanceConfig.getDouble("chance_percent.trader_llama.brown", 24.0) ) ); + chanceConfig.set("chance_percent.trader_llama.creamy" , normalize(oldchanceConfig.getDouble("chance_percent.trader_llama.creamy", 24.0) ) ); + chanceConfig.set("chance_percent.trader_llama.gray" , normalize(oldchanceConfig.getDouble("chance_percent.trader_llama.gray", 24.0) ) ); + chanceConfig.set("chance_percent.trader_llama.white" , normalize(oldchanceConfig.getDouble("chance_percent.trader_llama.white", 24.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.tropical_fish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.tropical_fish", 10.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.anemone" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.anemone", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.black_tang" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.black_tang", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.blue_tang" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.blue_tang", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.butterflyfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.butterflyfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.cichlid" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.cichlid", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.clownfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.clownfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.cotton_candy_betta" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.cotton_candy_betta", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.dottyback" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.dottyback", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.emperor_red_snapper" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.emperor_red_snapper", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.goatfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.goatfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.moorish_idol" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.moorish_idol", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.ornate_butterflyfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.ornate_butterflyfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.parrotfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.parrotfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.queen_angelfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.queen_angelfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.red_cichlid" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.red_cichlid", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.red_lipped_blenny" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.red_lipped_blenny", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.red_snapper" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.red_snapper", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.threadfin" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.threadfin", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.tomato_clownfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.tomato_clownfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.triggerfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.triggerfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.yellowtail_parrotfish" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.yellow_parrotfish", 50.0) ) ); + chanceConfig.set("chance_percent.tropical_fish.yellow_tang" , normalize(oldchanceConfig.getDouble("chance_percent.tropical_fish.yellow_tang", 50.0) ) ); + + chanceConfig.set("chance_percent.turtle" , normalize(oldchanceConfig.getDouble("chance_percent.turtle", 10.0) ) ); + chanceConfig.set("chance_percent.vex" , normalize(oldchanceConfig.getDouble("chance_percent.vex", 10.0) ) ); + chanceConfig.set("chance_percent.villager.desert.armorer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.armorer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.butcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.butcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.cartographer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.cartographer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.cleric" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.cleric", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.farmer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.farmer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.fisherman" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.fisherman", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.fletcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.fletcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.leatherworker" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.leatherworker", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.librarian" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.librarian", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.mason" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.mason", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.nitwit" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.nitwit", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.none" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.none", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.shepherd" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.shepherd", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.toolsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.toolsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.desert.weaponsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.desert.weaponsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.armorer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.armorer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.butcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.butcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.cartographer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.cartographer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.cleric" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.cleric", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.farmer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.farmer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.fisherman" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.fisherman", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.fletcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.fletcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.leatherworker" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.leatherworker", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.librarian" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.librarian", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.mason" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.mason", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.nitwit" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.nitwit", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.none" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.none", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.shepherd" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.shepherd", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.toolsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.toolsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.jungle.weaponsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.jungle.weaponsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.armorer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.armorer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.butcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.butcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.cartographer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.cartographer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.cleric" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.cleric", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.farmer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.farmer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.fisherman" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.fisherman", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.fletcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.fletcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.leatherworker" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.leatherworker", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.librarian" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.librarian", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.mason" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.mason", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.nitwit" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.nitwit", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.none" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.none", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.shepherd" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.shepherd", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.toolsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.toolsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.plains.weaponsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.plains.weaponsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.armorer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.armorer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.butcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.butcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.cartographer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.cartographer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.cleric" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.cleric", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.farmer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.farmer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.fisherman" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.fisherman", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.fletcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.fletcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.leatherworker" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.leatherworker", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.librarian" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.librarian", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.mason" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.mason", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.nitwit" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.nitwit", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.none" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.none", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.shepherd" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.shepherd", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.toolsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.toolsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.savanna.weaponsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.savanna.weaponsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.armorer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.armorer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.butcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.butcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.cartographer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.cartographer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.cleric" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.cleric", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.farmer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.farmer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.fisherman" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.fisherman", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.fletcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.fletcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.leatherworker" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.leatherworker", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.librarian" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.librarian", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.mason" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.mason", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.nitwit" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.nitwit", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.none" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.none", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.shepherd" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.shepherd", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.toolsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.toolsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.snow.weaponsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.snow.weaponsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.armorer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.armorer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.butcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.butcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.cartographer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.cartographer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.cleric" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.cleric", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.farmer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.farmer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.fisherman" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.fisherman", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.fletcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.fletcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.leatherworker" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.leatherworker", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.librarian" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.librarian", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.mason" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.mason", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.nitwit" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.nitwit", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.none" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.none", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.shepherd" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.shepherd", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.toolsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.toolsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.swamp.weaponsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.swamp.weaponsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.armorer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.armorer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.butcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.butcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.cartographer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.cartographer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.cleric" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.cleric", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.farmer" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.farmer", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.fisherman" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.fisherman", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.fletcher" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.fletcher", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.leatherworker" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.leatherworker", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.librarian" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.librarian", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.mason" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.mason", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.nitwit" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.nitwit", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.none" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.none", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.shepherd" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.shepherd", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.toolsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.toolsmith", 100.0) ) ); + chanceConfig.set("chance_percent.villager.taiga.weaponsmith" , normalize(oldchanceConfig.getDouble("chance_percent.villager.taiga.weaponsmith", 100.0) ) ); + chanceConfig.set("chance_percent.vindicator" , normalize(oldchanceConfig.getDouble("chance_percent.vindicator", 5.0) ) ); + chanceConfig.set("chance_percent.wandering_trader" , normalize(oldchanceConfig.getDouble("chance_percent.wandering_trader", 100.0) ) ); + chanceConfig.set("chance_percent.warden" , normalize(oldchanceConfig.getDouble("chance_percent.warden", 100.0) ) ); + chanceConfig.set("chance_percent.witch" , normalize(oldchanceConfig.getDouble("chance_percent.witch", 0.5) ) ); + chanceConfig.set("chance_percent.wither" , normalize(oldchanceConfig.getDouble("chance_percent.wither", 100.0) ) ); + chanceConfig.set("chance_percent.wither_skeleton" , normalize(oldchanceConfig.getDouble("chance_percent.wither_skeleton", 2.5) ) ); + chanceConfig.set("chance_percent.wolf" , normalize(oldchanceConfig.getDouble("chance_percent.wolf", 20.0) ) ); + chanceConfig.set("chance_percent.zoglin" , normalize(oldchanceConfig.getDouble("chance_percent.zoglin", 20.0) ) ); + chanceConfig.set("chance_percent.zombie" , normalize(oldchanceConfig.getDouble("chance_percent.zombie", 2.5) ) ); + chanceConfig.set("chance_percent.zombie_horse" , normalize(oldchanceConfig.getDouble("chance_percent.zombie_horse", 100.0) ) ); + chanceConfig.set("chance_percent.zombie_pigman" , normalize(oldchanceConfig.getDouble("chance_percent.zombie_pigman", 0.5) ) ); + chanceConfig.set("chance_percent.zombified_piglin" , normalize(oldchanceConfig.getDouble("chance_percent.zombified_piglin", 0.5) ) ); + chanceConfig.set("chance_percent.zombie_villager" , normalize(oldchanceConfig.getDouble("chance_percent.zombie_villager", 50.0) ) ); + try { + chanceConfig.save(file2); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CHANCE_SAVE_ERROR).error(exception)); + /**stacktraceInfo(); + e.printStackTrace();//*/ + } + LOGGER.log("chance_config.yml has been updated!"); + oldchanceConfig = null; + } + + public void copyConfigValues(String currentFilePath, String oldFilePath) { + // Load configurations + YmlConfiguration currentConfig = new YmlConfiguration(); + YmlConfiguration oldConfig = new YmlConfiguration(); + + try { + currentConfig.load(new File(currentFilePath)); + oldConfig.load(new File(oldFilePath)); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_LOAD_CONFIG).error(exception)); + return; + } + + // Copy values from oldConfig to currentConfig + for (String key : currentConfig.getKeys(true)) { + if (oldConfig.isSet(key)) { + currentConfig.set(key, oldConfig.get(key)); + } + } + + // Save the updated currentConfig + try { + currentConfig.save(new File(currentFilePath)); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_SAVE_CONFIG).error(exception)); + } finally { + // Release resources + currentConfig = null; + oldConfig = null; + } + } + + // Persistent Heads + private final NamespacedKey NAME_KEY = new NamespacedKey(this, "head_name"); + private final NamespacedKey LORE_KEY = new NamespacedKey(this, "head_lore"); + private final PersistentDataType LORE_PDT = new JsonDataType<>(String[].class); + //@SuppressWarnings("unused") + //private final PersistentDataType LORE_PDT2 = new JsonDataType<>(String.class); + //private final NamespacedKey DISPLAY_KEY = new NamespacedKey(this, "head_display"); + //private final NamespacedKey SKULLOWNER_KEY = new NamespacedKey(this, "head_skullowner"); + + // TODO: Persistent Heads + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockPlaceEvent(BlockPlaceEvent event) { + @Nonnull ItemStack headItem = event.getItemInHand(); + if (headItem.getType() != Material.PLAYER_HEAD) { + return; + } + ItemMeta meta = headItem.getItemMeta(); + if (meta == null) { + return; + } + @Nonnull String name = meta.getDisplayName(); + @Nullable List lore = meta.getLore(); + @Nonnull Block block = event.getBlockPlaced(); + // NOTE: Not using snapshots is broken: https://github.com/PaperMC/Paper/issues/3913 + BlockStateSnapshotResult blockStateSnapshotResult = PaperLib.getBlockState(block, true); + TileState skullState = (TileState) blockStateSnapshotResult.getState(); + @Nonnull PersistentDataContainer skullPDC = skullState.getPersistentDataContainer(); + skullPDC.set(NAME_KEY, PersistentDataType.STRING, name); + if (lore != null) { + skullPDC.set(LORE_KEY, LORE_PDT, lore.toArray(new String[0])); + } + if (blockStateSnapshotResult.isSnapshot()) { + skullState.update(); + } + String strLore = "no lore"; + if(lore != null){ strLore = lore.toString(); } + LOGGER.debug("Player " + event.getPlayer().getName() + " placed a head named \"" + name + "\" with lore=\'" + strLore + "\' at " + event.getBlockPlaced().getLocation()); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockDropItemEvent(BlockDropItemEvent event) { + @Nonnull BlockState blockState = event.getBlockState(); + Material blockType = blockState.getType(); + if ((blockType != Material.PLAYER_HEAD) && (blockType != Material.PLAYER_WALL_HEAD)) { + return; + } + TileState skullState = (TileState) blockState; + @Nonnull PersistentDataContainer skullPDC = skullState.getPersistentDataContainer(); + @Nullable String name = skullPDC.get(NAME_KEY, PersistentDataType.STRING); + @Nullable String[] lore = skullPDC.get(LORE_KEY, LORE_PDT); + if (name == null) { + return; + } + for (Item item: event.getItems()) { // Ideally should only be one... + @Nonnull ItemStack itemstack = item.getItemStack(); + if (itemstack.getType() == Material.PLAYER_HEAD) { + @Nullable ItemMeta meta = itemstack.getItemMeta(); + if (meta == null) + { + continue; // This shouldn't happen + } + meta.setDisplayName(name); + if (lore != null) { + meta.setLore(Arrays.asList(lore)); + } + itemstack.setItemMeta(meta); + } + } + LOGGER.debug("BDIE - Persistent head completed."); + } + + /** + * Prevents player from removing player-head NBT by water logging them + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) { + handleBlock(event.getBlock(), event, false); + } + + /** + * Prevents player from removing player-head NBT using running water + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onLiquidFlow(BlockFromToEvent event) { + handleBlock(event.getToBlock(), event, true); + } + + /* + * Prevents explosion from removing player-head NBT using an explosion + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockExplosion(BlockExplodeEvent event) { + handleExplosionEvent(event.blockList(), event.getYield()); + } + + /* + * Prevents entity from removing player-head NBT using an explosion + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityExplosion(EntityExplodeEvent event) { + handleExplosionEvent(event.blockList(), event.getYield()); + } + + /* + * Prevents piston extending from removing NBT data. + */ + @EventHandler(priority = EventPriority.LOWEST) + public void onPistonExtendEvent(BlockPistonExtendEvent event) { + if(!config.getBoolean("global_settings.event.piston_extend", true)) { + return; + } + List blocks = event.getBlocks(); + Iterator iter = blocks.iterator(); + try { + while (iter.hasNext()) { + Block block = iter.next(); + if (block.getState() instanceof Skull) { //if (block.getState() instanceof Skull && random.nextFloat() <= explosionYield) + handleBlock(block, null, false); + iter.remove(); + } + } + }catch(Exception ignored) { + + } + } + + @SuppressWarnings("unused") + private void handleExplosionEvent(@Nonnull final List blocksExploded, final float explosionYield) { + final Random random = ThreadLocalRandom.current(); + Iterator iter = blocksExploded.iterator(); + try { + while (iter.hasNext()) { + Block block = iter.next(); + if (block.getState() instanceof Skull) { //if (block.getState() instanceof Skull && random.nextFloat() <= explosionYield) + handleBlock(block, null, false); + iter.remove(); + } + } + }catch(Exception ignored) { + + } + } + + private void handleBlock(Block block, Cancellable event, boolean cancelEvent) { + @Nonnull BlockState blockState = block.getState(); + if ((blockState.getType() != Material.PLAYER_HEAD) && (blockState.getType() != Material.PLAYER_WALL_HEAD)) { + return; + } + Skull skullState = (Skull) blockState; + @Nonnull PersistentDataContainer skullPDC = skullState.getPersistentDataContainer(); + @Nullable String name = skullPDC.get(NAME_KEY, PersistentDataType.STRING); + @Nullable String[] lore = skullPDC.get(LORE_KEY, LORE_PDT); + if (name == null) { + return; + } + @Nonnull Optional skullStack = block.getDrops().stream().filter(is -> is.getType() == Material.PLAYER_HEAD).findAny(); + if (skullStack.isPresent()) { + if (updateDrop(block, name, lore, skullStack.get())) + { + return; // This shouldn't happen + } + if (cancelEvent) { + event.setCancelled(true); + } + } + + BlockState blockState1 = block.getWorld().getBlockAt(block.getLocation()).getState(); + blockState1.update(true, true); + LOGGER.debug("HB - Persistent head completed."); + } + + /**@EventHandler(priority = EventPriority.LOWEST) + public void onBlockExplodeEvent(BlockExplodeEvent event) { + List a = event.blockList(); + for (Block block: event.blockList()) { // Ideally should only be one... + @Nonnull BlockState blockState = block.getState(); + Material blockType = blockState.getType(); + if (blockType != Material.PLAYER_HEAD && blockType != Material.PLAYER_WALL_HEAD) return; + TileState skullState = (TileState) blockState; + @Nonnull PersistentDataContainer skullPDC = skullState.getPersistentDataContainer(); + @Nullable String name = skullPDC.get(NAME_KEY, PersistentDataType.STRING); + @Nullable String[] lore = skullPDC.get(LORE_KEY, LORE_PDT); + if (name == null) return; + block.breakNaturally(); + } + + }//*/ + + @SuppressWarnings("unused") + private void handleEvent(Supplier blockSupplier, Cancellable event, boolean cancelEvent) { + Block block = blockSupplier.get(); + @Nonnull BlockState blockState = block.getState(); + if ((blockState.getType() != Material.PLAYER_HEAD) && (blockState.getType() != Material.PLAYER_WALL_HEAD)) { + return; + } + Skull skullState = (Skull) blockState; + @Nonnull PersistentDataContainer skullPDC = skullState.getPersistentDataContainer(); + @Nullable String name = skullPDC.get(NAME_KEY, PersistentDataType.STRING); + @Nullable String[] lore = skullPDC.get(LORE_KEY, LORE_PDT); + if (name == null) { + return; + } + @Nonnull Optional skullStack = block.getDrops().stream().filter(is -> is.getType() == Material.PLAYER_HEAD).findAny(); + if (skullStack.isPresent()) { + if (updateDrop(block, name, lore, skullStack.get())) + { + return; // This shouldn't happen + } + if (cancelEvent) { + event.setCancelled(true); + } + } + + BlockState blockState1 = block.getWorld().getBlockAt(block.getLocation()).getState(); + blockState1.update(true, true); + LOGGER.debug("HE - Persistent head completed."); + } + + private boolean updateDrop(Block block, @Nullable String name, @Nullable String[] lore, @Nonnull ItemStack itemstack) { + @Nullable ItemMeta meta = itemstack.getItemMeta(); + if (meta == null) { + return true; + } + meta.setDisplayName(name); + if (lore != null) { + meta.setLore(Arrays.asList(lore)); + } + itemstack.setItemMeta(meta); + + block.getWorld().dropItemNaturally(block.getLocation(), itemstack); + block.getDrops().clear(); + block.setType(Material.AIR); + LOGGER.debug("UD - Persistent head completed."); + return false; + } + // Persistent Heads + + /**@EventHandler() + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) { + Block block = event.getBlock(); + Location loc = block.getLocation(); + @Nonnull BlockState blockState = block.getState(); + Material blockType = blockState.getType(); + if (blockType != Material.PLAYER_HEAD && blockType != Material.PLAYER_WALL_HEAD) return; + TileState skullState = (TileState) blockState; + @Nonnull PersistentDataContainer skullPDC = skullState.getPersistentDataContainer(); + @Nullable String name = skullPDC.get(NAME_KEY, PersistentDataType.STRING); + @Nullable String[] lore = skullPDC.get(LORE_KEY, LORE_PDT); + if (name == null) return; + Collection drops = block.getDrops(); + ItemStack[] stackArray = drops.toArray(new ItemStack[0]); + @Nonnull ItemStack itemstack = stackArray[0]; + if (itemstack.getType() == Material.PLAYER_HEAD) { + @Nullable ItemMeta meta = itemstack.getItemMeta(); + if (meta == null) { + logWarn("meta=null"); + return; // This shouldn't happen + } + meta.setDisplayName(name); + if (lore != null) meta.setLore(Arrays.asList(lore)); + itemstack.setItemMeta(meta); + + block.getWorld().dropItemNaturally(block.getLocation(), itemstack); + block.getDrops().clear(); + block.setType(Material.AIR); + } + + BlockState blockS = block.getWorld().getBlockAt(loc).getState(); + blockS.update(true, true); + } + + @EventHandler + public void onLiquidFlow(BlockFromToEvent event) { + Block block = event.getToBlock(); + Location loc = block.getLocation(); + + @Nonnull BlockState blockState = block.getState(); + Material blockType = blockState.getType(); + if (blockType != Material.PLAYER_HEAD && blockType != Material.PLAYER_WALL_HEAD) return; + TileState skullState = (TileState) blockState; + @Nonnull PersistentDataContainer skullPDC = skullState.getPersistentDataContainer(); + @Nullable String name = skullPDC.get(NAME_KEY, PersistentDataType.STRING); + @Nullable String[] lore = skullPDC.get(LORE_KEY, LORE_PDT); + if (name == null) return; + Collection drops = block.getDrops(); + ItemStack[] stackArray = drops.toArray(new ItemStack[0]); + @Nonnull ItemStack itemstack = stackArray[0]; + if (itemstack.getType() == Material.PLAYER_HEAD) { + @Nullable ItemMeta meta = itemstack.getItemMeta(); + if (meta == null) return; // This shouldn't happen + meta.setDisplayName(name); + if (lore != null) meta.setLore(Arrays.asList(lore)); + itemstack.setItemMeta(meta); + + block.getWorld().dropItemNaturally(block.getLocation(), itemstack); + block.getDrops().clear(); + block.setType(Material.AIR); + event.setCancelled(true); + } + + BlockState blockS = block.getWorld().getBlockAt(loc).getState(); + blockS.update(true, true); + }// */ + + @SuppressWarnings("unused") + public ItemStack fixHeadStack(ItemStack offHand, ItemStack mainHand){ + NBTItem nbti = new NBTItem(offHand); + Set SkullKeys = nbti.getKeys(); + int damage = nbti.getInteger("Damage"); + NBTCompound display = nbti.getCompound("display"); + NBTCompound SkullOwner = nbti.getCompound("SkullOwner"); + LOGGER.debug("FHS Offhand damage=" + damage); + LOGGER.debug("FHS Offhand display=" + display.toString()); + LOGGER.debug("FHS Offhand SkullOwner=" + SkullOwner.toString()); + + NBTItem nbti2 = new NBTItem(mainHand); + Set SkullKeys2 = nbti2.getKeys(); + int damage2 = nbti2.getInteger("Damage"); + NBTCompound display2 = nbti2.getCompound("display"); + NBTCompound SkullOwner2 = nbti2.getCompound("SkullOwner"); + LOGGER.debug("FHS Mainhand damage=" + damage2); + LOGGER.debug("FHS Mainhand display=" + display2.toString()); + LOGGER.debug("FHS Mainhand SkullOwner=" + SkullOwner2.toString()); + + if( display.equals(display2) && SkullOwner.equals(SkullOwner2) && (damage != damage2)){ + ItemStack is = new ItemStack(offHand); + is.setAmount(mainHand.getAmount()); + LOGGER.debug("FHS d=d2, so=so2, d!=D2 - return offhand"); + return is; + }else if( !display.equals(display2) && SkullOwner.equals(SkullOwner2) && ((damage == damage2)||(damage != damage2))){ + ItemStack is = new ItemStack(offHand); + is.setAmount(mainHand.getAmount()); + LOGGER.debug("FHS d!=d2, so=so2, d ignored - return offhand"); + return is; + }else if( display.equals(display2) && SkullOwner.equals(SkullOwner2) && (damage == damage2)){ + LOGGER.debug("FHS d=d2, so=so2, d=d2 - return mainhand"); + return mainHand; + } + return null; + } + public ItemStack fixHeadNBT(String textureValue, String displayName, ArrayList lore) { + //String textureValue = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWY1MjQxNjZmN2NlODhhNTM3MTU4NzY2YTFjNTExZTMyMmE5M2E1ZTExZGJmMzBmYTZlODVlNzhkYTg2MWQ4In19fQ=="; // Pulled from the head link, scroll to the bottom and the "Other Value" field has this texture id. + + ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1); // Creating the ItemStack, your input may vary. + NBTItem nbti = new NBTItem(head); // Creating the wrapper. + + NBTCompound disp = nbti.addCompound("display"); + disp.setString("Name", displayName); // Setting the name of the Item + if(lore.isEmpty()) { + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + } + if(!lore.isEmpty()) { + NBTList l = disp.getStringList("Lore"); + l.add(lore); // Adding a bit of lore. + } + + NBTCompound skull = nbti.addCompound("SkullOwner"); // Getting the compound, that way we can set the skin information + skull.setString("Name", displayName); // Owner's name + //skull.setString("Id", uuid); + // The UUID, note that skulls with the same UUID but different textures will misbehave and only one texture will load + // (They'll share it), if skulls have different UUIDs and same textures they won't stack. See UUID.randomUUID(); + + NBTListCompound texture = skull.addCompound("Properties").getCompoundList("textures").addCompound(); + texture.setString("Value", textureValue); + + head = nbti.getItem(); // Refresh the ItemStack + return head; + } + + public ItemStack makeHead(String textureValue, String displayName, String uuid, @Nullable ArrayList lore) { + ItemStack head = new ItemStack(Material.PLAYER_HEAD, 1); // Creating the ItemStack, your input may vary. + NBTItem nbti = new NBTItem(head); // Creating the wrapper. + + NBTCompound disp = nbti.addCompound("display"); + disp.setString("Name", displayName); // Setting the name of the Item + + if(lore.isEmpty()) { + if(config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + } + if(!lore.isEmpty()) { + NBTList l = disp.getStringList("Lore"); + l.add(lore); // Adding a bit of lore. + } + + NBTCompound skull = nbti.addCompound("SkullOwner"); // Getting the compound, that way we can set the skin information + skull.setString("Name", displayName); // Owner's name + skull.setString("Id", uuid); + + NBTListCompound texture = skull.addCompound("Properties").getCompoundList("textures").addCompound(); + texture.setString("Value", textureValue); + + head = nbti.getItem(); // Refresh the ItemStack + return head; + } + + public String getTextureFromEntity(LivingEntity entity) { + String name = entity.getName().toUpperCase(); + switch (name) { + case "CREEPER": + Creeper creeper = (Creeper) entity; + double cchance = chanceConfig.getDouble("chance_percent.creeper", defpercent); + if(creeper.isPowered()) { + name = "CREEPER_CHARGED"; + cchance = 1.00; + } + if(DropIt2(cchance)){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "ZOMBIE": + if(DropIt2(chanceConfig.getDouble("chance_percent.zombie", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "SKELETON": + if(DropIt2(chanceConfig.getDouble("chance_percent.skeleton", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "WITHER_SKELETON": + if(DropIt2(chanceConfig.getDouble("chance_percent.wither_skeleton", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "ENDER_DRAGON": + if(DropIt2(chanceConfig.getDouble("chance_percent.ender_dragon", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "TROPICAL_FISH": + TropicalFish daFish = (TropicalFish) entity; + DyeColor daFishBody = daFish.getBodyColor(); + DyeColor daFishPatternColor = daFish.getPatternColor(); + Pattern daFishPattern = daFish.getPattern(); + String daFishType = getNamedTropicalFishName(daFishPattern, daFishBody, daFishPatternColor ); + if(DropIt2(chanceConfig.getDouble("TROPICAL_FISH." + daFishType, defpercent))){ + return TropicalFishHeads.valueOf(daFishType).getTexture().toString(); + } + LOGGER.debug("Skeleton Head Dropped"); + break;//*/ + case "WITHER": + //Wither wither = (Wither) event.getEntity(); + int random = randomBetween(1, 4); + if(DropIt2(chanceConfig.getDouble("chance_percent.wither", defpercent))){ + return MobHeads.valueOf(name + "_" + random).getTexture().toString(); + } + break; + case "WOLF": + Wolf wolf = (Wolf) entity; + if(DropIt2(chanceConfig.getDouble("chance_percent." + name.toLowerCase(), defpercent))){ + if(wolf.isAngry()){ + return MobHeads.valueOf(name + "_ANGRY").getTexture().toString(); + }else{ + return MobHeads.valueOf(name).getTexture().toString(); + } + } + break; + case "FOX": + Fox dafox = (Fox) entity; + String dafoxtype = dafox.getFoxType().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.fox." + dafoxtype.toString().toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + dafoxtype).getTexture().toString(); + } + + break; + case "CAT": + Cat dacat = (Cat) entity; + String dacattype = dacat.getCatType().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.cat." + dacattype.toLowerCase(), defpercent))){ + return CatHeads.valueOf(dacattype).getTexture().toString(); + } + break; + case "OCELOT": + if(DropIt2(chanceConfig.getDouble("chance_percent." + name.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + + break; + case "BEE": + Bee daBee = (Bee) entity; + int daAnger = daBee.getAnger(); + boolean daNectar = daBee.hasNectar(); + if((daAnger >= 1)&&(daNectar == true)){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.angry_pollinated", defpercent))){ + return MobHeads.valueOf("BEE_ANGRY_POLLINATED").getTexture().toString(); + } + }else if((daAnger >= 1)&&(daNectar == false)){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.angry", defpercent))){ + return MobHeads.valueOf("BEE_ANGRY").getTexture().toString(); + } + }else if((daAnger == 0)&&(daNectar == true)){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.pollinated", defpercent))){ + return MobHeads.valueOf("BEE_POLLINATED").getTexture().toString(); + } + }else if((daAnger == 0)&&(daNectar == false)){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.chance_percent", defpercent))){ + return MobHeads.valueOf("BEE").getTexture().toString(); + } + } + break; + case "LLAMA": + Llama daLlama = (Llama) entity; + String daLlamaColor = daLlama.getColor().toString(); + //log(name + "_" + daLlamaColor); + if(DropIt2(chanceConfig.getDouble("chance_percent.llama." + daLlamaColor.toLowerCase(), defpercent))){ + return LlamaHeads.valueOf(name + "_" + daLlamaColor).getTexture().toString(); + } + break; + case "HORSE": + Horse daHorse = (Horse) entity; + String daHorseColor = daHorse.getColor().toString(); + //.replace("g", "G").replace("wh", "Wh").replace("_", " ") + " Horse Head"; + if(DropIt2(chanceConfig.getDouble("chance_percent.horse." + daHorseColor.toLowerCase(), defpercent))){ + return HorseHeads.valueOf(name + "_" + daHorseColor).getTexture().toString(); + } + break; + case "MOOSHROOM": + name = "MUSHROOM_COW"; + case "MUSHROOM_COW": + MushroomCow daMushroom = (MushroomCow) entity; + String daCowVariant = daMushroom.getVariant().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.mushroom_cow." + daCowVariant.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + daCowVariant).getTexture().toString(); + } + break; + case "PANDA": + Panda daPanda = (Panda) entity; + String daPandaGene = daPanda.getMainGene().toString(); + String daPandaName = daPandaGene.toLowerCase().replace("br", "Br").replace("ag", "Ag").replace("la", "La") + .replace("no", "No").replace("p", "P").replace("we", "We").replace("wo", "Wo") + " Panda Head"; + if(daPandaGene.equalsIgnoreCase("normal")){daPandaName.replace("normal ", "");} + if(DropIt2(chanceConfig.getDouble("chance_percent.panda." + daPandaGene.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + daPandaGene).getTexture().toString(); + } + break; + case "PARROT": + Parrot daParrot = (Parrot) entity; + String daParrotVariant = daParrot.getVariant().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.parrot." + daParrotVariant.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + daParrotVariant).getTexture().toString(); + } + break; + case "RABBIT": + String daRabbitType; + Rabbit daRabbit = (Rabbit) entity; + daRabbitType = daRabbit.getRabbitType().toString(); + if(daRabbit.getCustomName() != null){ + if(daRabbit.getCustomName().contains("Toast")){ + daRabbitType = "Toast"; + } + } + if(DropIt2(chanceConfig.getDouble("chance_percent.rabbit." + daRabbitType.toLowerCase(), defpercent))){ + return RabbitHeads.valueOf(name + "_" + daRabbitType).getTexture().toString(); + } + break; + case "VILLAGER": + Villager daVillager = (Villager) entity; // Location jobsite = daVillager.getMemory(MemoryKey.JOB_SITE); + String daVillagerType = daVillager.getVillagerType().toString(); + String daVillagerProfession = daVillager.getProfession().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.villager." + daVillagerType.toLowerCase() + "." + daVillagerProfession.toLowerCase(), defpercent))){ + return VillagerHeads.valueOf(name + "_" + daVillagerProfession + "_" + daVillagerType).getTexture().toString(); + } + break; + case "ZOMBIE_VILLAGER": + ZombieVillager daZombieVillager = (ZombieVillager) entity; + String daZombieVillagerProfession = daZombieVillager.getVillagerProfession().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.zombie_villager", defpercent))){ + return ZombieVillagerHeads.valueOf(name + "_" + daZombieVillagerProfession).getTexture().toString(); + } + break; + case "SHEEP": + Sheep daSheep = (Sheep) entity; + String daSheepColor = daSheep.getColor().toString(); + + if(daSheep.getCustomName() != null){ + if(daSheep.getCustomName().contains("jeb_")){ + daSheepColor = "jeb_"; + }else{ + daSheepColor = daSheep.getColor().toString(); + } + } + if(DropIt2(chanceConfig.getDouble("chance_percent.sheep." + daSheepColor.toLowerCase(), defpercent))){ + return SheepHeads.valueOf(name + "_" + daSheepColor).getTexture().toString(); + } + break; + /**case "STRIDER": + Strider strider = (Strider) entity; + + break;*/ + case "TRADER_LLAMA": + TraderLlama daTraderLlama = (TraderLlama) entity; + String daTraderLlamaColor = daTraderLlama.getColor().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.trader_llama." + daTraderLlamaColor.toLowerCase(), defpercent))){ + return LlamaHeads.valueOf(name + "_" + daTraderLlamaColor).getTexture().toString(); + } + break; + case "AXOLOTL": + Axolotl daAxolotl = (Axolotl) entity; + String daAxolotlVariant = daAxolotl.getVariant().toString(); + if(DropIt2(chanceConfig.getDouble("chance_percent.axolotl." + daAxolotlVariant.toLowerCase(), defpercent))){ + return MobHeads117.valueOf(name + "_" + daAxolotlVariant).getTexture().toString(); + } + break; + case "GOAT": + Goat daGoat = (Goat) entity; + String daGoatVariant; + if(daGoat.isScreaming()) { + // Giving screaming goat head + daGoatVariant = "SCREAMING"; + }else { + // give goat head + daGoatVariant = "NORMAL"; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.goat." + daGoatVariant.toLowerCase(), defpercent))){ + return MobHeads117.valueOf(name + "_" + daGoatVariant).getTexture().toString(); + } + break; + default: + //makeSkull(MobHeads.valueOf(name).getTexture(), name); + if(DropIt2(chanceConfig.getDouble("chance_percent." + name.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + } + return "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWI3YWY5ZTQ0MTEyMTdjN2RlOWM2MGFjYmQzYzNmZDY1MTk3ODMzMzJhMWIzYmM1NmZiZmNlOTA3MjFlZjM1In19fQ=="; + } + + public String getTexturefromEntityType(EntityType entityType, boolean randef) { + String name = entityType.toString(); + switch (name) { + case "CREEPER": + double cchance = chanceConfig.getDouble("chance_percent.creeper", defpercent); + if(randef) { + name = "CREEPER_CHARGED"; + cchance = 1.00; + } + if(DropIt2(cchance)){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "ZOMBIE": + if(DropIt2(chanceConfig.getDouble("chance_percent.zombie", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "SKELETON": + if(DropIt2(chanceConfig.getDouble("chance_percent.skeleton", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "WITHER_SKELETON": + if(DropIt2(chanceConfig.getDouble("chance_percent.wither_skeleton", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "ENDER_DRAGON": + if(DropIt2(chanceConfig.getDouble("chance_percent.ender_dragon", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break;//*/ + case "TROPICAL_FISH": + String[] daFishType = {"TROPICAL_FISH", "ANEMONE", "BLACK_TANG", "BLUE_TANG", "BUTTERFLYFISH", "CICHLID", "CLOWNFISH", "COTTON_CANDY_BETTA", "DOTTYBACK", "EMPEROR_RED_SNAPPER", "GOATFISH", "MOORISH_IDOL", "ORNATE_BUTTERFLYFISH", "PARROTFISH", "QUEEN_ANGELFISH", "RED_CICHLID", "RED_LIPPED_BLENNY", "RED_SNAPPER", "THREADFIN", "TRIGGERFISH", "YELLOWTAIL_PARROTFISH", "YELLOW_TANG"}; + int randomfish; + if(randef) { + randomfish = randomBetween(0, 22); + }else { + randomfish = 0; + } + if(DropIt2(chanceConfig.getDouble("TROPICAL_FISH." + daFishType, defpercent))){ + return TropicalFishHeads.valueOf(daFishType[randomfish]).getTexture().toString(); + } + break;//*/ + case "WITHER": + //Wither wither = (Wither) event.getEntity(); + int random = randomBetween(1, 4); + if(randef) { + name = name.concat("_" + random); + }else { + name = name.concat("_1"); + } + if(DropIt2(chanceConfig.getDouble("chance_percent.wither", defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + case "WOLF": + String[] dawolftype = {"","_ANGRY"}; + int randomwolf; + if(randef) { + randomwolf = randomBetween(0, 1); + }else { + randomwolf = 0; + } + if(DropIt2(chanceConfig.getDouble("chance_percent." + name.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + dawolftype[randomwolf]).getTexture().toString(); + } + break; + case "FOX": + String[] dafoxtype = {"RED","SNOW"}; + int randomfox; + if(randef) { + randomfox = randomBetween(0, 1); + }else { + randomfox = 0; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.fox." + dafoxtype[randomfox].toString().toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + dafoxtype[randomfox]).getTexture().toString(); + } + + break; + case "CAT": + int randomcat; + //String dacattype = dacat.getCatType().toString(); + String[] dacattype = {"ALL_BLACK","BLACK","BRITISH_SHORTHAIR","CALICO","JELLIE","PERSIAN","RAGDOLL","RED","SIAMESE","TABBY","WHITE"}; + if(randef) { + randomcat = randomBetween(0, 10); + }else { + randomcat = 10; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.cat." + dacattype[randomcat].toLowerCase(), defpercent))){ + return CatHeads.valueOf(dacattype[randomcat]).getTexture().toString(); + } + break; + case "OCELOT": + if(DropIt2(chanceConfig.getDouble("chance_percent." + name.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + + break; + case "BEE": + int randombee; + if(randef) { + randombee = randomBetween(1, 4); + }else { + randombee = 1; + } + if(randombee == 4){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.angry_pollinated", defpercent))){ + return MobHeads.valueOf("BEE_ANGRY_POLLINATED").getTexture().toString(); + } + }else if(randombee == 3){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.angry", defpercent))){ + return MobHeads.valueOf("BEE_ANGRY").getTexture().toString(); + } + }else if(randombee == 2){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.pollinated", defpercent))){ + return MobHeads.valueOf("BEE_POLLINATED").getTexture().toString(); + } + }else if(randombee == 1){ + if(DropIt2(chanceConfig.getDouble("chance_percent.bee.chance_percent", defpercent))){ + return MobHeads.valueOf("BEE").getTexture().toString(); + } + } + break; + case "LLAMA": + String[] daLlamaColor = {"BROWN","CREAMY","GRAY","WHITE"}; + int randomllama; + if(randef) { + randomllama = randomBetween(0, 3); + }else { + randomllama = 4; + } + //log(name + "_" + daLlamaColor); + if(DropIt2(chanceConfig.getDouble("chance_percent.llama." + daLlamaColor[randomllama].toLowerCase(), defpercent))){ + return LlamaHeads.valueOf(name + "_" + daLlamaColor[randomllama]).getTexture().toString(); + } + break; + case "TRADER_LLAMA": + String[] daTraderLlamaColor = {"BROWN","CREAMY","GRAY","WHITE"}; + int randomtllama; + if(randef) { + randomtllama = randomBetween(0, 3); + }else { + randomtllama = 4; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.trader_llama." + daTraderLlamaColor[randomtllama].toLowerCase(), defpercent))){ + return LlamaHeads.valueOf(name + "_" + daTraderLlamaColor[randomtllama]).getTexture().toString(); + } + break; + case "HORSE": + String[] daHorseColor = {"BLACK","BROWN","CHESTNUT","CREAMY","DARK_BROWN","GRAY","WHITE"}; + int randomhorse; + if(randef) { + randomhorse = randomBetween(0, 6); + }else { + randomhorse = 1; + } + //.replace("g", "G").replace("wh", "Wh").replace("_", " ") + " Horse Head"; + if(DropIt2(chanceConfig.getDouble("chance_percent.horse." + daHorseColor[randomhorse].toLowerCase(), defpercent))){ + return HorseHeads.valueOf(name + "_" + daHorseColor).getTexture().toString(); + } + break; + case "MOOSHROOM": + name = "MUSHROOM_COW"; + case "MUSHROOM_COW": + String[] daCowVariant = {"BROWN","RED"}; + int randomcow; + if(randef) { + randomcow = randomBetween(0, 6); + }else { + randomcow = 1; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.mushroom_cow." + daCowVariant[randomcow].toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + daCowVariant[randomcow]).getTexture().toString(); + } + break; + case "PANDA": + String[] daPandaGene = {"AGRESSIVE","BROWN","LAZY","NORMAL","PLAYFUL","WEAK","WORRIED"}; + int randompan; + if(randef) { + randompan = randomBetween(0, 6); + }else { + randompan = 3; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.panda." + daPandaGene[randompan].toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + daPandaGene[randompan]).getTexture().toString(); + } + break; + case "PARROT": + String[] daParrotVariant = {"BLUE","CYAN","GRAY","GREEN","RED"}; + int randompar; + if(randef) { + randompar = randomBetween(0, 4); + }else { + randompar = 3; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.parrot." + daParrotVariant[randompar].toLowerCase(), defpercent))){ + return MobHeads.valueOf(name + "_" + daParrotVariant[randompar]).getTexture().toString(); + } + break; + case "RABBIT": + String[] daRabbitType = {"BLACK","BLACK_AND_WHITE","BROWN","GOLD","SALT_AND_PEPPER","THE_KILLER_BUNNY","WHITE","Toast"}; + int randomrab; + if(randef) { + randomrab = randomBetween(0, 7); + }else { + randomrab = 3; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.rabbit." + daRabbitType[randomrab].toLowerCase(), defpercent))){ + return RabbitHeads.valueOf(name + "_" + daRabbitType[randomrab]).getTexture().toString(); + } + break; + case "VILLAGER": + String[] daVillagerType = {"DESERT","JUNGLE","PLAINS","SAVANNA","SNOW","SWAMP","TAIGA"}; + String[] daVillagerProfession = {"ARMORER","BUTCHER","CARTOGRAPHER","CLERIC","FARMER","FISHERMAN","FLETCHER" + ,"LEATHERWORKER","LIBRARIAN","MASON","NITWIT","NONE","SHEPHERD","TOOLSMITH","WEAPONSMITH"}; + int randomviltyp; + int randomvilpro; + if(randef) { + randomviltyp = randomBetween(0, 6); + randomvilpro = randomBetween(0, 14); + }else { + randomviltyp = 2; + randomvilpro = 11; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.villager." + daVillagerType[randomviltyp].toLowerCase() + "." + + daVillagerProfession[randomvilpro].toLowerCase(), defpercent))){ + return VillagerHeads.valueOf(name + "_" + daVillagerProfession[randomvilpro] + "_" + daVillagerType[randomviltyp]).getTexture().toString(); + } + break; + case "ZOMBIE_VILLAGER": + String[] daZombieVillagerProfession = {"ARMORER","BUTCHER","CARTOGRAPHER","CLERIC","FARMER","FISHERMAN","FLETCHER" + ,"LEATHERWORKER","LIBRARIAN","MASON","NITWIT","NONE","SHEPHERD","TOOLSMITH","WEAPONSMITH"}; + int randomzvpro; + if(randef) { + randomzvpro = randomBetween(0, 14); + }else { + randomzvpro = 11; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.zombie_villager", defpercent))){ + return ZombieVillagerHeads.valueOf(name + "_" + daZombieVillagerProfession[randomzvpro]).getTexture().toString(); + } + break; + case "SHEEP": + String[] daSheepColor = {"BLACK","BLUE","BROWN","CYAN","GRAY","GREEN","jeb_","LIGHT_BLUE","LIGHT_GRAY","LIME","MAGENTA","ORANGE" + ,"PINK","PURPLE","RED","WHITE","YELLOW"}; + int randomshe; + if(randef) { + randomshe = randomBetween(0, 16); + }else { + randomshe = 6; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.sheep." + daSheepColor[randomshe].toLowerCase(), defpercent))){ + return SheepHeads.valueOf(name + "_" + daSheepColor[randomshe]).getTexture().toString(); + } + break;//*/ + /**case "STRIDER": + Strider strider = (Strider) entity; + + break;*/ + case "AXOLOTL": + String[] daAxolotlVariant = {"BLUE","CYAN","GOLD","LUCY","WILD"}; + int randomaxl; + if(randef) { + randomaxl = randomBetween(0, 4); + }else { + randomaxl = 3; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.axolotl." + daAxolotlVariant[randomaxl].toLowerCase(), defpercent))){ + return MobHeads117.valueOf(name + "_" + daAxolotlVariant[randomaxl]).getTexture().toString(); + } + break; + case "GOAT": + String[] daGoatVariant = {"NORMAL","SCREAMING"}; + int randomgoat; + if(randef) { + randomgoat = randomBetween(0, 1); + }else { + randomgoat = 0; + } + if(DropIt2(chanceConfig.getDouble("chance_percent.goat." + daGoatVariant[randomgoat].toLowerCase(), defpercent))){ + return MobHeads117.valueOf(name + "_" + daGoatVariant[randomgoat]).getTexture().toString(); + } + break; + default: + //makeSkull(MobHeads.valueOf(name).getTexture(), name); + if(DropIt2(chanceConfig.getDouble("chance_percent." + name.toLowerCase(), defpercent))){ + return MobHeads.valueOf(name).getTexture().toString(); + } + break; + + } + return "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWI3YWY5ZTQ0MTEyMTdjN2RlOWM2MGFjYmQzYzNmZDY1MTk3ODMzMzJhMWIzYmM1NmZiZmNlOTA3MjFlZjM1In19fQ=="; + }//*/ + + public boolean chance25oftrue() { + //For 25% chance of true + return (random.nextInt(4) == 0) ? true : false; + } + public void consoleLog(String string) { + if(!silent_console) { + loading(string); + } + } + + /** + * Calculates and formats the elapsed time from a specified start time. + * + * @param startTime The start time in milliseconds. + * @return A formatted string representing the elapsed time. + */ + public String LoadTime(long startTime) { + long elapsedTime = System.currentTimeMillis() - startTime; + long minutes = TimeUnit.MILLISECONDS.toMinutes(elapsedTime); + long seconds = TimeUnit.MILLISECONDS.toSeconds(elapsedTime) % 60; + long milliseconds = elapsedTime % 1000; + + if (minutes > 0) { + return String.format("%d min %d s %d ms.", minutes, seconds, milliseconds); + } else if (seconds > 0) { + return String.format("%d s %d ms.", seconds, milliseconds); + } else { + return String.format("%d ms.", elapsedTime); + } + } + + + @SuppressWarnings("static-access") + public String get(String key, String... defaultValue) { + return lang2.get(key, defaultValue); + } + + public boolean isPluginRequired(String pluginName) { + String[] requiredPlugins = {"SinglePlayerSleep", "MoreMobHeads", "NoEndermanGrief", "ShulkerRespawner", "MoreMobHeads", "RotationalWrench", "SilenceMobs", "VillagerWorkstationHighlights"}; + for (String requiredPlugin : requiredPlugins) { + if ((getServer().getPluginManager().getPlugin(requiredPlugin) != null) && getServer().getPluginManager().isPluginEnabled(requiredPlugin)) { + if (requiredPlugin.equals(pluginName)) { + return true; + } else { + return false; + } + } + } + return true; + } + + public void dumpConfig(FileConfiguration config) { + for (String key : config.getKeys(true)) { + Object value = config.get(key); + if ((value != null) && value.getClass().isArray()) { + value = Arrays.asList((Object[]) value); + } + LOGGER.log(key + " = '" + value + "'"); + } + } + + public String getNamedTropicalFishName(Pattern pattern, DyeColor color1, DyeColor color2) { + String key = pattern.name() + "-" + color1.name() + "-" + color2.name(); + LOGGER.log("key=" + key); + LOGGER.log("namedTropicalFish=" + namedTropicalFish.isEmpty()); + return namedTropicalFish.getOrDefault(key, "TROPICAL_FISH"); + } + + // Used to check Minecraft version + private MinecraftVersion verifyMinecraftVersion() { + MinecraftVersion minimum = new MinecraftVersion(PluginLibrary.MINIMUM_MINECRAFT_VERSION); + MinecraftVersion maximum = new MinecraftVersion(PluginLibrary.MAXIMUM_MINECRAFT_VERSION); + + try { + MinecraftVersion current = new MinecraftVersion(this.getServer()); + + // We'll just warn the user for now + if (current.compareTo(minimum) < 0) { + LOGGER.warn("Version " + current + " is lower than the minimum " + minimum); + } + if (current.compareTo(maximum) > 0) { + LOGGER.warn("Version " + current + " has not yet been tested! Proceed with caution."); + } + + return current; + } catch (Exception exception) { + reporter.reportWarning(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(exception).messageParam(maximum)); + + // Unknown version - just assume it is the latest + return maximum; + } + } + + public String getjarfilename() { + return jarfilename; + } + + public boolean getDebug() { + return debug; + } + + +} diff --git a/common/PluginLibrary.java b/common/PluginLibrary.java new file mode 100644 index 0000000..cefe324 --- /dev/null +++ b/common/PluginLibrary.java @@ -0,0 +1,155 @@ +package com.github.joelgodofwar.mmh.common; + +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; + +import com.github.joelgodofwar.mmh.common.error.BasicErrorReporter; +import com.github.joelgodofwar.mmh.common.error.ErrorReporter; +import com.github.joelgodofwar.mmh.common.error.ReportType; +import com.google.common.collect.ImmutableList; + +public class PluginLibrary { + + /** + * The minimum version MoreMobHeads has been tested with. + */ + public static final String MINIMUM_MINECRAFT_VERSION = "1.20"; + + /** + * The maximum version MoreMobHeads has been tested with. + */ + public static final String MAXIMUM_MINECRAFT_VERSION = "1.20.4"; + + /** + * The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.4) was released. + */ + public static final String MINECRAFT_LAST_RELEASE_DATE = "2023-12-07"; + + /** + * Plugins that are currently incompatible with MoreMobHeads. + */ + public static final List INCOMPATIBLE = ImmutableList.of(""); + + private static Plugin plugin; + + private static boolean updatesDisabled; + private static boolean initialized; + private static ErrorReporter reporter = new BasicErrorReporter(); + + protected static void init(Plugin plugin, ErrorReporter reporter) { + Validate.isTrue(!initialized, "MoreMobHeads has already been initialized."); + PluginLibrary.plugin = plugin; + PluginLibrary.reporter = reporter; + + initialized = true; + } + + // Every possible error or warning report type + public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old MoreMobHeads configuration."); + public static final ReportType REPORT_CANNOT_COPY_FILE = new ReportType("Cannot copy file."); + public static final ReportType REPORT_METRICS_LOAD_ERROR = new ReportType("Cannot load bStats Metrics."); + + public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load MoreMobHeads."); + public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration"); + public static final ReportType REPORT_CANNOT_CHECK_CONFIG = new ReportType("Cannot check configuration"); + public static final ReportType REPORT_CANNOT_SAVE_CONFIG = new ReportType("Cannot save configuration"); + public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable MoreMobHeads."); + public static final ReportType REPORT_PLUGIN_UNKNOWN_ERROR = new ReportType("Unknown Error"); + + public static final ReportType REPORT_MESSAGES_LOAD_ERROR = new ReportType("Could not load messages.yml"); + public static final ReportType REPORT_MESSAGES_COPY_ERROR = new ReportType("Could not copy messages.yml to old_messages.yml"); + public static final ReportType REPORT_OLDMESSAGES_LOAD_ERROR = new ReportType("Could not load old_messages.yml"); + public static final ReportType REPORT_OLDMESSAGES_SAVE_ERROR = new ReportType("Could not save old messages to messages.yml"); + public static final ReportType REPORT_PLAYERHEAD_LOAD_ERROR = new ReportType("Could not load player_heads.yml"); + public static final ReportType REPORT_CUSTOM_LOAD_ERROR = new ReportType("Could not load custom_trades.yml"); + public static final ReportType REPORT_CHANCE_LOAD_ERROR = new ReportType("Could not load chance_config.yml"); + public static final ReportType REPORT_CHANCE_SAVE_ERROR = new ReportType("Could not save chance_config.yml"); + public static final ReportType REPORT_CHANCE_COPY_ERROR = new ReportType("Could not copy chance_config.yml to old_chance_config.yml"); + public static final ReportType REPORT_MOBNAMES_LOAD_ERROR = new ReportType("Could not load lang_mobnames file"); + + public static final ReportType REPORT_PIE_LOAD_ERROR = new ReportType("Error with naming mob."); + public static final ReportType REPORT_HEAD_URL_ERROR = new ReportType("Malformed URL Error"); + public static final ReportType REPORT_BLOCKHEAD_LOAD_ERROR = new ReportType("Cannot load BlockHeads file"); + + public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems."); + public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems."); + + public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s"); + public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions."); + public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s"); + + public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task."); + public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates."); + + public static final ReportType REPORT_CANNOT_GET_HEXNICK = new ReportType("Cannot get Nick from HexNicks."); + + public static final ReportType REPORT_PLAYER_NAMED_MOB = new ReportType("Unable to parse named mob kill."); + public static final ReportType REPORT_PLAYER_KILL_PLAYER = new ReportType("Unable to parse Player Death."); + public static final ReportType REPORT_WHITE_BLACK_LIST = new ReportType("Unable to parse global whitelist/blacklist."); + public static final ReportType REPORT_PLAYER_KILL_MOB = new ReportType("Unable to parse Mob Kill."); + + public static final ReportType REPORT_EVENT_HANDLER_LOAD = new ReportType("Error while loading EventHandler."); + public static final ReportType REPORT_CS_EVENT_ERROR = new ReportType("Error while entity spawning."); + public static final ReportType REPORT_ANNOUNCE_ERROR = new ReportType("Error announcing beheading."); + public static final ReportType REPORT_RESOLVE_DAMAGE_WEAPON = new ReportType("Error resolving the Damaging Weapon."); + public static final ReportType REPORT_GET_WEAPON_ERROR = new ReportType("Error getting weapon."); + + + public static final ReportType REPORT_COMMAND_MENU_ERROR = new ReportType("Error displaying Command Menu."); + public static final ReportType REPORT_COMMAND_HEADNBT_ERROR = new ReportType("Error executing HeadNBT command."); + public static final ReportType REPORT_COMMAND_DISPLAY_PERMS = new ReportType("Error executing Display Perms command."); + public static final ReportType REPORT_COMMAND_DISPLAY_VARS = new ReportType("Error executing Display Vars command."); + public static final ReportType REPORT_COMMAND_DISPLAY_CHANCE = new ReportType("Error executing Display Chance command."); + public static final ReportType REPORT_COMMAND_RELOAD_ERROR = new ReportType("Error executing Reload command."); + public static final ReportType REPORT_COMMAND_TOGGLE_DEBUG = new ReportType("Error executing ToggleDebug Command."); + public static final ReportType REPORT_COMMAND_CUSTOM_TRADER = new ReportType("Error executing CustomTrader Command."); + public static final ReportType REPORT_COMMAND_PLAYER_HEADS = new ReportType("Error executing PlayerHeads Command."); + public static final ReportType REPORT_COMMAND_FIX_HEAD = new ReportType("Error executing Fixhead Command."); + public static final ReportType REPORT_COMMAND_GIVE_MH = new ReportType("Error executing GiveMH Command."); + public static final ReportType REPORT_COMMAND_GIVE_PH = new ReportType("Error executing GivePH Command."); + public static final ReportType REPORT_COMMAND_GIVE_BH = new ReportType("Error executing GiveBH Command."); + public static final ReportType REPORT_COMMAND_DEV_ERROR = new ReportType("Error exuting dev Command."); + + public static final ReportType UNHANDLED_COMMAND_ERROR = new ReportType("Command had an Unhandled exception."); + public static final ReportType REPORT_TAB_COMPLETE_ERROR = new ReportType("Error parsing Tab Complete."); + + public static final ReportType UNHANDLED_ENTITYDEATHEVENT_ERROR = new ReportType("Unhandled EntityDeathEvent Exception."); + public static final ReportType UNHANDLED_ENTITYDAMAGE_EVENT_ERROR = new ReportType("Unable to parse fire damage."); + public static final ReportType UNHANDLED_ENTITYDAMAGEBYENTITY_EVENT_ERROR = new ReportType("Unable to get damager weapon."); + + + + + /** + * Gets the MoreMobHeads plugin instance. + * @return The plugin instance + */ + public static Plugin getPlugin() { + return plugin; + } + + /** + * Disables the MoreMobHeads update checker. + */ + public static void disableUpdates() { + updatesDisabled = true; + } + + /** + * Retrieve the current error reporter. + * @return Current error reporter. + */ + public static ErrorReporter getErrorReporter() { + return reporter; + } + + /** + * Whether updates are currently disabled. + * @return True if it is, false if not + */ + public static boolean updatesDisabled() { + return updatesDisabled; + } +} \ No newline at end of file diff --git a/common/PluginLogger.java b/common/PluginLogger.java new file mode 100644 index 0000000..1199293 --- /dev/null +++ b/common/PluginLogger.java @@ -0,0 +1,150 @@ +package com.github.joelgodofwar.mmh.common; + +import java.text.MessageFormat; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; + +import com.github.joelgodofwar.mmh.MoreMobHeads; +import com.github.joelgodofwar.mmh.util.MinecraftVersion; + +/** + * Custom logger for the MoreMobHeads plugin. + */ +public class PluginLogger { + private Logger logger = Logger.getLogger("Minecraft"); + private MoreMobHeads mmh; + + /** + * Constructor for the PluginLogger class. + * @param plugin The MoreMobHeads plugin instance. + */ + public PluginLogger(MoreMobHeads plugin) { + logger = plugin.getLogger(); + mmh = plugin; + } + + /** + * Logs a message to console with the INFO level. + * @param message Message to log. + */ + public void log(String message) { + log(message, ""); + } + + /** + * Logs a message to console with a given level. + * @param level Logging level + * @param message Message to log + * @param args Arguments to format in + */ + public void log(Level level, String message, Object... args) { + String pluginName = ChatColor.RESET + "[" + ChatColor.YELLOW + mmh.getName() + ChatColor.RESET + "] "; + if (!areAllArgsBlank(args)) { + //logger.log(level, applyColorFormatting(MessageFormat.format(message, args))); + Bukkit.getConsoleSender().sendMessage(pluginName + MessageFormat.format(pluginName + message, args)); + } else { + //logger.log(level, applyColorFormatting(message)); + Bukkit.getConsoleSender().sendMessage(pluginName + message); + } + } + + /** + * Logs a method to console with the INFO level. + * @param message Message to log + * @param args Arguments to format in + */ + public void log(String message, Object... args) { + log(Level.INFO, message, args); + } + + /** + * Logs a message to console with a given level and exception. + * @param level Logging level + * @param message Message to log + * @param ex Exception to log + */ + public void log(Level level, String message, Throwable ex) { + logger.log(level, message, ex); + } + + /** + * Logs a debug message to console if debugging is enabled. + * @param message Debug message to log. + */ + public void debug(String message) { + debug(message, ""); + } + + /** + * Logs a debug message to console with arguments if debugging is enabled. + * @param message Debug message to log. + * @param args Arguments to format in the debug message. + */ + public void debug(String message, Object... args) { + if (mmh.debug) { + log("[Debug] " + message, args); + } + } + + /** + * Logs a debug message to console with an exception if debugging is enabled. + * @param message Debug message to log. + * @param ex Exception to log. + */ + public void debug(String message, Throwable ex) { + if (mmh.debug) { + log(Level.WARNING, "[Debug] " + message, ex); + } + } + + /** + * Logs a warning message to console with the WARNING level. + * @param message Warning message to log. + */ + public void warn(String message) { + warn(message, ""); + } + + /** + * Logs a warning message to console with the WARNING level and arguments. + * @param message Warning message to log. + * @param args Arguments to format in the warning message. + */ + public void warn(String message, Object... args) { + log(Level.WARNING, "[Warning] " + message, args); + } + + /** + * Logs a warning message to console with the WARNING level if the Minecraft version is at or above a specified version. + * @param version The Minecraft version to compare against. + * @param message Warning message to log. + * @param args Arguments to format in the warning message. + */ + public void warnAbove(MinecraftVersion version, String message, Object... args) { + if (version.atOrAbove()) { + log(Level.WARNING, message, args); + } + } + + /** + * Checks if all arguments are null or blank. + * @param args Arguments to check + * @return true if all arguments are null or blank, false otherwise + */ + private boolean areAllArgsBlank(Object... args) { + if ((args == null) || (args.length == 0)) { + return true; // Null or empty arguments are considered blank + } + for (Object arg : args) { + if ((arg == null) || ((arg instanceof String) && ((String) arg).isEmpty())) { + continue; // Skip null or empty string + } else { + return false; // Found a non-blank argument + } + } + return true; // All arguments are null or blank + } +} \ No newline at end of file diff --git a/common/collections/ExpireHashMap.java b/common/collections/ExpireHashMap.java new file mode 100644 index 0000000..8dbcb24 --- /dev/null +++ b/common/collections/ExpireHashMap.java @@ -0,0 +1,239 @@ +package com.github.joelgodofwar.mmh.common.collections; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Ticker; +import com.google.common.collect.Maps; +import com.google.common.primitives.Longs; + +/** + * Represents a hash map where each association may expire after a given time has elapsed. + *

+ * Note that replaced key-value associations are only collected once the original expiration time has elapsed. + * + * @author Kristian Stangeland + * + * @param - type of the keys. + * @param - type of the values. + */ +public class ExpireHashMap { + private class ExpireEntry implements Comparable { + public final long expireTime; + public final K expireKey; + public final V expireValue; + + public ExpireEntry(long expireTime, K expireKey, V expireValue) { + this.expireTime = expireTime; + this.expireKey = expireKey; + this.expireValue = expireValue; + } + + @Override + public int compareTo(ExpireEntry o) { + return Longs.compare(expireTime, o.expireTime); + } + + @Override + public String toString() { + return "ExpireEntry [expireTime=" + expireTime + ", expireKey=" + expireKey + + ", expireValue=" + expireValue + "]"; + } + } + + private final Map keyLookup = new HashMap<>(); + private final PriorityQueue expireQueue = new PriorityQueue<>(); + + // View of keyLookup with direct values + private final Map valueView = Maps.transformValues(keyLookup, entry -> entry.expireValue); + + // Supplied by the constructor + private final Ticker ticker; + + /** + * Construct a new hash map where each entry may expire at a given time. + */ + public ExpireHashMap() { + this(Ticker.systemTicker()); + } + + /** + * Construct a new hash map where each entry may expire at a given time. + * @param ticker - supplier of the current time. + */ + public ExpireHashMap(Ticker ticker) { + this.ticker = ticker; + } + + /** + * Retrieve the value associated with the given key, if it has not expired. + * @param key - the key. + * @return The value, or NULL if not found or it has expired. + */ + public V get(K key) { + evictExpired(); + + ExpireEntry entry = keyLookup.get(key); + return entry != null ? entry.expireValue : null; + } + + /** + * Associate the given key with the given value, until the expire delay have elapsed. + * @param key - the key. + * @param value - the value. + * @param expireDelay - the amount of time until this association expires. Must be greater than zero. + * @param expireUnit - the unit of the expiration. + * @return Any previously unexpired association with this key, or NULL. + */ + public V put(K key, V value, long expireDelay, TimeUnit expireUnit) { + Preconditions.checkNotNull(expireUnit, "expireUnit cannot be NULL"); + Preconditions.checkState(expireDelay > 0, "expireDelay cannot be equal or less than zero."); + evictExpired(); + + ExpireEntry entry = new ExpireEntry( + ticker.read() + TimeUnit.NANOSECONDS.convert(expireDelay, expireUnit), + key, value + ); + ExpireEntry previous = keyLookup.put(key, entry); + + // We enqueue its removal + expireQueue.add(entry); + return previous != null ? previous.expireValue : null; + } + + /** + * Determine if the given key is referring to an unexpired association in the map. + * @param key - the key. + * @return TRUE if it is, FALSE otherwise. + */ + public boolean containsKey(K key) { + evictExpired(); + return keyLookup.containsKey(key); + } + + /** + * Determine if the given value is referring to an unexpired association in the map. + * @param value - the value. + * @return TRUE if it is, FALSE otherwise. + */ + public boolean containsValue(V value) { + evictExpired(); + + // Linear scan is the best we've got + for (ExpireEntry entry : keyLookup.values()) { + if (Objects.equal(value, entry.expireValue)) { + return true; + } + } + return false; + } + + /** + * Remove a key and its associated value from the map. + * @param key - the key to remove. + * @return Value of the removed association, NULL otherwise. + */ + public V removeKey(K key) { + evictExpired(); + + ExpireEntry entry = keyLookup.remove(key); + return entry != null ? entry.expireValue : null; + } + + /** + * Retrieve the number of entries in the map. + * @return The number of entries. + */ + public int size() { + evictExpired(); + return keyLookup.size(); + } + + /** + * Retrieve a view of the keys in the current map. + * @return View of the keys. + */ + public Set keySet() { + evictExpired(); + return keyLookup.keySet(); + } + + /** + * Retrieve a view of all the values in the current map. + * @return All the values. + */ + public Collection values() { + evictExpired(); + return valueView.values(); + } + + /** + * Retrieve a view of all the entries in the set. + * @return All the entries. + */ + public Set> entrySet() { + evictExpired(); + return valueView.entrySet(); + } + + /** + * Retrieve a view of this expire map as an ordinary map that does not support insertion. + * @return The map. + */ + public Map asMap() { + evictExpired(); + return valueView; + } + + /** + * Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted. + *

+ * This operation requires a linear scan of the current entries in the map. + */ + public void collect() { + // First evict what we can + evictExpired(); + + // Recreate the eviction queue - this is faster than removing entries in the old queue + expireQueue.clear(); + expireQueue.addAll(keyLookup.values()); + } + + /** + * Clear all the entries in the current map. + */ + public void clear() { + keyLookup.clear(); + expireQueue.clear(); + } + + /** + * Evict any expired entries in the map. + *

+ * This is called automatically by any of the read or write operations. + */ + protected void evictExpired() { + long currentTime = ticker.read(); + + // Remove expired entries + while ((expireQueue.size() > 0) && (expireQueue.peek().expireTime <= currentTime)) { + ExpireEntry entry = expireQueue.poll(); + + if (entry == keyLookup.get(entry.expireKey)) { + keyLookup.remove(entry.expireKey); + } + } + } + + @Override + public String toString() { + return valueView.toString(); + } +} diff --git a/common/error/BasicErrorReporter.java b/common/error/BasicErrorReporter.java new file mode 100644 index 0000000..bc9309c --- /dev/null +++ b/common/error/BasicErrorReporter.java @@ -0,0 +1,108 @@ +package com.github.joelgodofwar.mmh.common.error; + +import java.io.PrintStream; + +import org.bukkit.plugin.Plugin; + +import com.github.joelgodofwar.mmh.common.error.Report.ReportBuilder; +import com.github.joelgodofwar.mmh.common.reflect.PrettyPrinter; + +/** + * Represents a basic error reporter that prints error reports to the standard error stream. + *

+ * Note that this implementation doesn't distinguish between {@link #reportWarning(Object, Report)} + * and {@link #reportDetailed(Object, Report)} - they both have the exact same behavior. + * @author Kristian + */ +public class BasicErrorReporter implements ErrorReporter { + private final PrintStream output; + + /** + * Construct a new basic error reporter that prints directly the standard error stream. + */ + public BasicErrorReporter() { + this(System.err); + } + + /** + * Construct a error reporter that prints to the given output stream. + * @param output - the output stream. + */ + public BasicErrorReporter(PrintStream output) { + this.output = output; + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error) { + output.println("Unhandled exception occured in " + methodName + " for " + sender.getName()); + error.printStackTrace(output); + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { + reportMinimal(sender, methodName, error); + + // Also print parameters + printParameters(parameters); + } + + @Override + public void reportDebug(Object sender, Report report) { + // We just have to swallow it + } + + @Override + public void reportDebug(Object sender, ReportBuilder builder) { + // As above + } + + @Override + public void reportWarning(Object sender, Report report) { + // Basic warning + output.println("[" + sender.getClass().getSimpleName() + "] " + report.getReportMessage()); + + if (report.getException() != null) { + report.getException().printStackTrace(output); + } + printParameters(report.getCallerParameters()); + } + + @Override + public void reportWarning(Object sender, ReportBuilder reportBuilder) { + reportWarning(sender, reportBuilder.build()); + } + + @Override + public void reportDetailed(Object sender, Report report) { + // No difference from warning + reportWarning(sender, report); + } + + @Override + public void reportDetailed(Object sender, ReportBuilder reportBuilder) { + reportWarning(sender, reportBuilder); + } + + /** + * Print the given parameters to the standard error stream. + * @param parameters - the output parameters. + */ + private void printParameters(Object[] parameters) { + if ((parameters != null) && (parameters.length > 0)) { + output.println("Parameters: "); + + try { + for (Object parameter : parameters) { + if (parameter == null) { + output.println("[NULL]"); + } else { + output.println(PrettyPrinter.printObject(parameter)); + } + } + } catch (IllegalAccessException e) { + // Damn it + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/common/error/DetailedErrorReporter.java b/common/error/DetailedErrorReporter.java new file mode 100644 index 0000000..d021ee7 --- /dev/null +++ b/common/error/DetailedErrorReporter.java @@ -0,0 +1,663 @@ +package com.github.joelgodofwar.mmh.common.error; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.Plugin; + +import com.github.joelgodofwar.mmh.MoreMobHeads; +import com.github.joelgodofwar.mmh.common.collections.ExpireHashMap; +import com.github.joelgodofwar.mmh.common.error.Report.ReportBuilder; +import com.github.joelgodofwar.mmh.common.reflect.PrettyPrinter; +import com.github.joelgodofwar.mmh.util.Ansi; +import com.github.joelgodofwar.mmh.util.StrUtils; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Primitives; + +/** + * Internal class used to handle exceptions. + * + * @author Kristian + */ +public class DetailedErrorReporter implements ErrorReporter { + /** + * Report format for printing the current exception count. + */ + public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!"); + + public static final String SECOND_LEVEL_PREFIX = " "; + public static final String DEFAULT_PREFIX = " "; + public static final String DEFAULT_SUPPORT_URL = "https://discord.gg/Fq9HQ2dmtd"; + + // Users that are informed about errors in the chat + public static final String ERROR_PERMISSION = "protocol.info"; + + // We don't want to spam the server + public static final int DEFAULT_MAX_ERROR_COUNT = 20; + + // Prevent spam per plugin too + private ConcurrentMap warningCount = new ConcurrentHashMap(); + + protected String prefix; + protected String supportURL; + + protected AtomicInteger internalErrorCount = new AtomicInteger(); + + protected int maxErrorCount; + protected Logger logger; + + protected WeakReference pluginReference; + protected String pluginName; + + // Whether or not Apache Commons is not present + protected static boolean apacheCommonsMissing; + + // Whether or not detailed errror reporting is enabled + protected boolean detailedReporting; + + // Map of global objects + protected Map globalParameters = new HashMap(); + + // Reports to ignore + private ExpireHashMap rateLimited = new ExpireHashMap(); + private final Object rateLock = new Object(); + private MoreMobHeads mmh; + + /** + * Create a default error reporting system. + * @param plugin - the plugin owner. + */ + public DetailedErrorReporter(Plugin plugin) { + this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL); + } + + /** + * Create a central error reporting system. + * @param plugin - the plugin owner. + * @param prefix - default line prefix. + * @param supportURL - URL to report the error. + */ + public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) { + this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); + } + + /** + * Create a central error reporting system. + * @param plugin - the plugin owner. + * @param prefix - default line prefix. + * @param supportURL - URL to report the error. + * @param maxErrorCount - number of errors to print before giving up. + * @param LOGGER - current logger. + */ + public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be NULL."); + } + + this.pluginReference = new WeakReference(plugin); + this.pluginName = getNameSafely(plugin); + this.prefix = prefix; + this.supportURL = supportURL; + this.maxErrorCount = maxErrorCount; + this.logger = logger; + this.mmh = (MoreMobHeads) plugin; + } + + private String getNameSafely(Plugin plugin) { + try { + return plugin.getName(); + } catch (LinkageError e) { + return "MoreMobHeads"; + } + } + + // Attempt to get the logger. + private static Logger getBukkitLogger() { + try { + return Bukkit.getLogger(); + } catch (LinkageError e) { + return Logger.getLogger("Minecraft"); + } + } + + /** + * Determine if we're using detailed error reporting. + * @return TRUE if we are, FALSE otherwise. + */ + public boolean isDetailedReporting() { + return detailedReporting; + } + + /** + * Set whether or not to use detailed error reporting. + * @param detailedReporting - TRUE to enable it, FALSE otherwise. + */ + public void setDetailedReporting(boolean detailedReporting) { + this.detailedReporting = detailedReporting; + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { + if (reportMinimalNoSpam(sender, methodName, error)) { + // Print parameters, if they are given + if ((parameters != null) && (parameters.length > 0)) { + logger.log(Level.SEVERE, printParameters(parameters)); + } + } + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error) { + reportMinimalNoSpam(sender, methodName, error); + } + + /** + * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports. + * @param sender - the component that observed this exception. + * @param methodName - the method name. + * @param error - the error itself. + * @return TRUE if the error was printed, FALSE if it was suppressed. + */ + public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) { + String pluginName = sender.getName(); + AtomicInteger counter = warningCount.get(pluginName); + + // Thread safe pattern + if (counter == null) { + AtomicInteger created = new AtomicInteger(); + counter = warningCount.putIfAbsent(pluginName, created); + + if (counter == null) { + counter = created; + } + } + + final int errorCount = counter.incrementAndGet(); + + // See if we should print the full error + if (errorCount < getMaxErrorCount()) { + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occurred in " + + methodName + " for " + pluginName, error); + return true; + + } else { + // Nope - only print the error count occasionally + if (isPowerOfTwo(errorCount)) { + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occurred in " + + methodName + " for " + pluginName, error); + } + return false; + } + } + + /** + * Determine if a given number is a power of two. + *

+ * That is, if there exists an N such that 2^N = number. + * @param number - the number to check. + * @return TRUE if the given number is a power of two, FALSE otherwise. + */ + private boolean isPowerOfTwo(int number) { + return (number & (number - 1)) == 0; + } + + @Override + public void reportDebug(Object sender, ReportBuilder builder) { + reportDebug(sender, Preconditions.checkNotNull(builder, "builder cannot be NULL").build()); + } + + @Override + public void reportDebug(Object sender, Report report) { + if (logger.isLoggable(Level.FINE) && canReport(report)) { + reportLevel(Level.FINE, sender, report); + } + } + + @Override + public void reportWarning(Object sender, ReportBuilder reportBuilder) { + if (reportBuilder == null) { + throw new IllegalArgumentException("reportBuilder cannot be NULL."); + } + + reportWarning(sender, reportBuilder.build()); + } + + @Override + public void reportWarning(Object sender, Report report) { + if (logger.isLoggable(Level.WARNING) && canReport(report)) { + reportLevel(Level.WARNING, sender, report); + } + } + + /** + * Determine if we should print the given report. + *

+ * The default implementation will check for rate limits. + * @param report - the report to check. + * @return TRUE if we should print it, FALSE otherwise. + */ + protected boolean canReport(Report report) { + long rateLimit = report.getRateLimit(); + + // Check for rate limit + if (rateLimit > 0) { + synchronized (rateLock) { + if (rateLimited.containsKey(report)) { + return false; + } + rateLimited.put(report, true, rateLimit, TimeUnit.NANOSECONDS); + } + } + return true; + } + + private void reportLevel(Level level, Object sender, Report report) { + String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage(); + + // Print the main warning + if (report.getException() != null) { + logger.log(level, message, report.getException()); + } else { + logger.log(level, message); + + // Remember the call stack + if (detailedReporting) { + printCallStack(level, logger); + } + } + + // Parameters? + if (report.hasCallerParameters()) { + // Write it + logger.log(level, printParameters(report.getCallerParameters())); + } + } + + /** + * Retrieve the name of a sender class. + * @param sender - sender object. + * @return The name of the sender's class. + */ + private String getSenderName(Object sender) { + if (sender != null) { + return ReportType.getSenderClass(sender).getSimpleName(); + } else { + return "NULL"; + } + } + + @Override + public void reportDetailed(Object sender, ReportBuilder reportBuilder) { + reportDetailed(sender, reportBuilder.build()); + } + + @Override + public void reportDetailed(Object sender, Report report) { + final Plugin plugin = pluginReference.get(); + final int errorCount = internalErrorCount.incrementAndGet(); + + // Do not overtly spam the server! + if (errorCount > getMaxErrorCount()) { + // Only allow the error count at rare occations + if (isPowerOfTwo(errorCount)) { + // Permit it - but print the number of exceptions first + reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build()); + } else { + // NEVER SPAM THE CONSOLE + return; + } + } + + // Secondary rate limit + if (!canReport(report)) { + return; + } + + StringWriter text = new StringWriter(); + PrintWriter writer = new PrintWriter(text); + + // Helpful message + writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage()); + writer.println("Copy from above line down to ↓↓↓"); + writer.println("If this problem hasn't already been reported, please post"); + writer.println("at " + supportURL + " with the following data:"); + + // Data dump! + writer.println("Data Dump:"); + + // Relevant parameters + if (report.hasCallerParameters()) { + printParameters(writer, report.getCallerParameters()); + } + + // Global parameters + for (String param : globalParameters()) { + writer.println(SECOND_LEVEL_PREFIX + param + ":"); + writer.println(addPrefix(getStringDescription(getGlobalParameter(param)), + SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX)); + } + + // Now, for the sender itself + writer.println("Sender:"); + writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX)); + + // And plugin + if (plugin != null) { + writer.println("Version: " + mmh.getName() + " v" + mmh.getDescription().getVersion()); + String jarfilename = mmh.getjarfilename(); + writer.println(addPrefix("jarfilename: " + StrUtils.Right(jarfilename, jarfilename.length() - jarfilename.lastIndexOf(File.separatorChar)), SECOND_LEVEL_PREFIX)); + //writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX)); + } + + writer.println("config.yml:"); + FileConfiguration config = mmh.getConfig(); + for (String key : config.getKeys(true)) { + Object value = config.get(key); + if ((value != null) && value.getClass().isArray()) { + value = Arrays.asList((Object[]) value); + } + if ((value != null) && value.toString().contains("MemorySection")) { + value = ""; + } + writer.println(addPrefix( key + " = '" + value + "'" , SECOND_LEVEL_PREFIX)); + } + + // And java version + writer.println("Java Version: " + System.getProperty("java.version")); + //writer.println(addPrefix(System.getProperty("java.version"), SECOND_LEVEL_PREFIX)); + + // Add the server version too + if (Bukkit.getServer() != null) { + writer.println("Server: "); + writer.println(addPrefix("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")", SECOND_LEVEL_PREFIX)); + /** add list of all plugins, and their version */ + writer.println(getPluginList()); + + + // Inform of this occurrence + if (isChatWarnings()) { + Bukkit.getServer().broadcast( + String.format("Error %s (%s) occurred in %s.", report.getReportMessage(), report.getException(), sender), + ERROR_PERMISSION + ); + } + } + + // Now, let us print important exception information + writer.println("Stack Trace:"); + + if (report.getException() != null) { + report.getException().printStackTrace(writer); + } else if (detailedReporting) { + printCallStack(writer); + } + writer.println("-> -> -> -> -> -> -> -> This line <- <- <- <- <- <- <- <-"); + + // Make sure it is reported + logger.severe(addPrefix(text.toString(), prefix)); + } + + private boolean isChatWarnings() { + + return false; + } + + /** + * Print the call stack to the given logger. + * @param logger - the logger. + */ + private void printCallStack(Level level, Logger logger) { + StringWriter text = new StringWriter(); + printCallStack(new PrintWriter(text)); + + // Print the exception + logger.log(level, text.toString()); + } + + /** + * Print the current call stack. + * @param writer - the writer. + */ + private void printCallStack(PrintWriter writer) { + Exception current = new Exception("Not an error! This is the call stack."); + current.printStackTrace(writer); + } + + private String printParameters(Object... parameters) { + StringWriter writer = new StringWriter(); + + // Print and retrieve the string buffer + printParameters(new PrintWriter(writer), parameters); + return writer.toString(); + } + + private void printParameters(PrintWriter writer, Object[] parameters) { + writer.println("Parameters: "); + + // We *really* want to get as much information as possible + for (Object param : parameters) { + writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX)); + } + } + + /** + * Adds the given prefix to every line in the text. + * @param text - text to modify. + * @param prefix - prefix added to every line in the text. + * @return The modified text. + */ + protected String addPrefix(String text, String prefix) { + return text.replaceAll("(?m)^", prefix); + } + + /** + * Retrieve a string representation of the given object. + * @param value - object to convert. + * @return String representation. + */ + public static String getStringDescription(Object value) { + // We can't only rely on toString. + if (value == null) { + return "[NULL]"; + } if (isSimpleType(value) || (value instanceof Class)) { + return value.toString(); + } else { + try { + if (!apacheCommonsMissing) { + return ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null); + } + } catch (LinkageError ex) { + // Apache is probably missing + apacheCommonsMissing = true; + } catch (ThreadDeath | OutOfMemoryError e) { + throw e; + } catch (Throwable ex) { + // Don't use the error logger to log errors in error logging (that could lead to infinite loops) + getBukkitLogger().log(Level.WARNING, "Cannot convert to a String with Apache: " + ex.getMessage()); + } + + // Use our custom object printer instead + try { + return PrettyPrinter.printObject(value, value.getClass(), Object.class); + } catch (IllegalAccessException e) { + return "[Error: " + e.getMessage() + "]"; + } + } + } + + /** + * Determine if the given object is a wrapper for a primitive/simple type or not. + * @param test - the object to test. + * @return TRUE if this object is simple enough to simply be printed, FALSE othewise. + */ + protected static boolean isSimpleType(Object test) { + return (test instanceof String) || Primitives.isWrapperType(test.getClass()); + } + + /** + * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}. + * @return Number of errors printed. + */ + public int getErrorCount() { + return internalErrorCount.get(); + } + + /** + * Set the number of errors printed. + * @param errorCount - new number of errors printed. + */ + public void setErrorCount(int errorCount) { + internalErrorCount.set(errorCount); + } + + /** + * Retrieve the maximum number of errors we can print before we begin suppressing errors. + * @return Maximum number of errors. + */ + public int getMaxErrorCount() { + return maxErrorCount; + } + + /** + * Set the maximum number of errors we can print before we begin suppressing errors. + * @param maxErrorCount - new max count. + */ + public void setMaxErrorCount(int maxErrorCount) { + this.maxErrorCount = maxErrorCount; + } + + /** + * Adds the given global parameter. It will be included in every error report. + *

+ * Both key and value must be non-null. + * @param key - name of parameter. + * @param value - the global parameter itself. + */ + public void addGlobalParameter(String key, Object value) { + if (key == null) { + throw new IllegalArgumentException("key cannot be NULL."); + } + if (value == null) { + throw new IllegalArgumentException("value cannot be NULL."); + } + + globalParameters.put(key, value); + } + + /** + * Retrieve a global parameter by its key. + * @param key - key of the parameter to retrieve. + * @return The value of the global parameter, or NULL if not found. + */ + public Object getGlobalParameter(String key) { + if (key == null) { + throw new IllegalArgumentException("key cannot be NULL."); + } + + return globalParameters.get(key); + } + + /** + * Reset all global parameters. + */ + public void clearGlobalParameters() { + globalParameters.clear(); + } + + /** + * Retrieve a set of every registered global parameter. + * @return Set of all registered global parameters. + */ + public Set globalParameters() { + return globalParameters.keySet(); + } + + /** + * Retrieve the support URL that will be added to all detailed reports. + * @return Support URL. + */ + public String getSupportURL() { + return supportURL; + } + + /** + * Set the support URL that will be added to all detailed reports. + * @param supportURL - the new support URL. + */ + public void setSupportURL(String supportURL) { + this.supportURL = supportURL; + } + + /** + * Retrieve the prefix to apply to every line in the error reports. + * @return Error report prefix. + */ + public String getPrefix() { + return prefix; + } + + /** + * Set the prefix to apply to every line in the error reports. + * @param prefix - new prefix. + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + /** + * Retrieve the current logger that is used to print all reports. + * @return The current logger. + */ + public Logger getLogger() { + return logger; + } + + /** + * Set the current logger that is used to print all reports. + * @param logger - new logger. + */ + public void setLogger(Logger logger) { + this.logger = logger; + } + + private String getPluginList() { + StringBuilder pluginList = new StringBuilder(); + Plugin[] plugins = Bukkit.getPluginManager().getPlugins(); + Plugin[] var6 = plugins; + int var5 = plugins.length; + + for (int var4 = 0; var4 < var5; ++var4) { + Plugin plugin = var6[var4]; + if (pluginList.length() > 0) { + pluginList.append(Ansi.WHITE); + pluginList.append(", "); + } + + pluginList.append(plugin.isEnabled() ? Ansi.GREEN : Ansi.RED); + pluginList.append(plugin.getDescription().getName()); + if (plugin.getDescription().getProvides().size() > 0) { + pluginList.append(" (").append(String.join(", ", plugin.getDescription().getProvides())).append(")"); + } + pluginList.append(" [" + plugin.getDescription().getVersion() + "]"); + } + + return "Plugins (" + plugins.length + "): " + pluginList.toString() + Ansi.RESET; + } + +} diff --git a/common/error/ErrorReporter.java b/common/error/ErrorReporter.java new file mode 100644 index 0000000..501d5ed --- /dev/null +++ b/common/error/ErrorReporter.java @@ -0,0 +1,73 @@ +package com.github.joelgodofwar.mmh.common.error; + +import org.bukkit.plugin.Plugin; + +import com.github.joelgodofwar.mmh.common.error.Report.ReportBuilder; + +/** + * Represents an object that can forward an error {@link Report} to the display and permanent storage. + * + * @author Kristian + */ +public interface ErrorReporter { + /** + * Prints a small minimal error report regarding an exception from another plugin. + * @param sender - the other plugin. + * @param methodName - name of the caller method. + * @param error - the exception itself. + */ + void reportMinimal(Plugin sender, String methodName, Throwable error); + + /** + * Prints a small minimal error report regarding an exception from another plugin. + * @param sender - the other plugin. + * @param methodName - name of the caller method. + * @param error - the exception itself. + * @param parameters - any relevant parameters to print. + */ + void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters); + + /** + * Prints a debug message from the current sender. + *

+ * Most users will not see this message. + * @param sender - the sender. + * @param report - the report. + */ + void reportDebug(Object sender, Report report); + + /** + * Prints a debug message from the current sender. + * @param sender - the sender. + * @param builder - the report builder. + */ + void reportDebug(Object sender, ReportBuilder builder); + + /** + * Prints a warning message from the current plugin. + * @param sender - the object containing the caller method. + * @param report - an error report to include. + */ + void reportWarning(Object sender, Report report); + + /** + * Prints a warning message from the current plugin. + * @param sender - the object containing the caller method. + * @param reportBuilder - an error report builder that will be used to get the report. + */ + void reportWarning(Object sender, ReportBuilder reportBuilder); + + /** + * Prints a detailed error report about an unhandled exception. + * @param sender - the object containing the caller method. + * @param report - an error report to include. + */ + void reportDetailed(Object sender, Report report); + + /** + * Prints a detailed error report about an unhandled exception. + * @param sender - the object containing the caller method. + * @param reportBuilder - an error report builder that will be used to get the report. + */ + void reportDetailed(Object sender, ReportBuilder reportBuilder); +} \ No newline at end of file diff --git a/common/error/Report.java b/common/error/Report.java new file mode 100644 index 0000000..8b78879 --- /dev/null +++ b/common/error/Report.java @@ -0,0 +1,244 @@ +package com.github.joelgodofwar.mmh.common.error; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +/** + * Represents a error or warning report. + * + * @author Kristian + */ +public class Report { + private final ReportType type; + private final Throwable exception; + private final Object[] messageParameters; + private final Object[] callerParameters; + + // Used to rate limit reports that are similar + private final long rateLimit; + + /** + * Must be constructed through the factory method in Report. + */ + public static class ReportBuilder { + private ReportType type; + private Throwable exception; + private Object[] messageParameters; + private Object[] callerParameters; + private long rateLimit; + + private ReportBuilder() { + // Don't allow + } + + /** + * Set the current report type. Cannot be NULL. + * @param type - report type. + * @return This builder, for chaining. + */ + public ReportBuilder type(ReportType type) { + if (type == null) { + throw new IllegalArgumentException("Report type cannot be set to NULL."); + } + this.type = type; + return this; + } + + /** + * Set the current exception that occurred. + * @param exception - exception that occurred. + * @return This builder, for chaining. + */ + public ReportBuilder error(@Nullable Throwable exception) { + this.exception = exception; + return this; + } + + /** + * Set the message parameters that are used to construct a message text. + * @param messageParameters - parameters for the report type. + * @return This builder, for chaining. + */ + public ReportBuilder messageParam(@Nullable Object... messageParameters) { + this.messageParameters = messageParameters; + return this; + } + + /** + * Set the parameters in the caller method. This is optional. + * @param callerParameters - parameters of the caller method. + * @return This builder, for chaining. + */ + public ReportBuilder callerParam(@Nullable Object... callerParameters) { + this.callerParameters = callerParameters; + return this; + } + + /** + * Set the minimum number of nanoseconds to wait until a report of equal type and parameters + * is allowed to be printed again. + * @param rateLimit - number of nanoseconds, or 0 to disable. Cannot be negative. + * @return This builder, for chaining. + */ + public ReportBuilder rateLimit(long rateLimit) { + if (rateLimit < 0) { + throw new IllegalArgumentException("Rate limit cannot be less than zero."); + } + this.rateLimit = rateLimit; + return this; + } + + /** + * Set the minimum time to wait until a report of equal type and parameters is allowed to be printed again. + * @param rateLimit - the time, or 0 to disable. Cannot be negative. + * @param rateUnit - the unit of the rate limit. + * @return This builder, for chaining. + */ + public ReportBuilder rateLimit(long rateLimit, TimeUnit rateUnit) { + return rateLimit(TimeUnit.NANOSECONDS.convert(rateLimit, rateUnit)); + } + + /** + * Construct a new report with the provided input. + * @return A new report. + */ + public Report build() { + return new Report(type, exception, messageParameters, callerParameters, rateLimit); + } + } + + /** + * Construct a new report builder. + * @param type - the initial report type. + * @return Report builder. + */ + public static ReportBuilder newBuilder(ReportType type) { + return new ReportBuilder().type(type); + } + + /** + * Construct a new report with the given type and parameters. + * @param exception - exception that occured in the caller method. + * @param type - the report type that will be used to construct the message. + * @param messageParameters - parameters used to construct the report message. + * @param callerParameters - parameters from the caller method. + */ + protected Report(ReportType type, @Nullable Throwable exception, + @Nullable Object[] messageParameters, @Nullable Object[] callerParameters) { + this(type, exception, messageParameters, callerParameters, 0); + } + + /** + * Construct a new report with the given type and parameters. + * @param exception - exception that occurred in the caller method. + * @param type - the report type that will be used to construct the message. + * @param messageParameters - parameters used to construct the report message. + * @param callerParameters - parameters from the caller method. + * @param rateLimit - minimum number of nanoseconds to wait until a report of equal type and parameters is allowed to be printed again. + */ + protected Report(ReportType type, @Nullable Throwable exception, + @Nullable Object[] messageParameters, @Nullable Object[] callerParameters, long rateLimit) { + if (type == null) { + throw new IllegalArgumentException("type cannot be NULL."); + } + this.type = type; + this.exception = exception; + this.messageParameters = messageParameters; + this.callerParameters = callerParameters; + this.rateLimit = rateLimit; + } + + /** + * Format the current report type with the provided message parameters. + * @return The formated report message. + */ + public String getReportMessage() { + return type.getMessage(messageParameters); + } + + /** + * Retrieve the message parameters that will be used to construc the report message. + *

+ * This should not be confused with the method parameters of the caller method. + * @return Message parameters. + */ + public Object[] getMessageParameters() { + return messageParameters; + } + + /** + * Retrieve the parameters of the caller method. Optional - may be NULL. + * @return Parameters or the caller method. + */ + public Object[] getCallerParameters() { + return callerParameters; + } + + /** + * Retrieve the report type. + * @return Report type. + */ + public ReportType getType() { + return type; + } + + /** + * Retrieve the associated exception, or NULL if not found. + * @return Associated exception, or NULL. + */ + public Throwable getException() { + return exception; + } + + /** + * Determine if we have any message parameters. + * @return TRUE if there are any message parameters, FALSE otherwise. + */ + public boolean hasMessageParameters() { + return (messageParameters != null) && (messageParameters.length > 0); + } + + /** + * Determine if we have any caller parameters. + * @return TRUE if there are any caller parameters, FALSE otherwise. + */ + public boolean hasCallerParameters() { + return (callerParameters != null) && (callerParameters.length > 0); + } + + /** + * Retrieve desired minimum number of nanoseconds until a report of the same type and parameters should be reprinted. + *

+ * Note that this may be ignored or modified by the error reporter. Zero indicates no rate limit. + * @return The number of nanoseconds. Never negative. + */ + public long getRateLimit() { + return rateLimit; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + Arrays.hashCode(callerParameters); + result = (prime * result) + Arrays.hashCode(messageParameters); + result = (prime * result) + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Report) { + Report other = (Report) obj; + return (type == other.type) && + Arrays.equals(callerParameters, other.callerParameters) && + Arrays.equals(messageParameters, other.messageParameters); + } + return false; + } +} \ No newline at end of file diff --git a/common/error/ReportType.java b/common/error/ReportType.java new file mode 100644 index 0000000..2699dec --- /dev/null +++ b/common/error/ReportType.java @@ -0,0 +1,152 @@ +package com.github.joelgodofwar.mmh.common.error; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import com.github.joelgodofwar.mmh.common.reflect.FieldAccessException; +import com.github.joelgodofwar.mmh.common.reflect.FuzzyReflection; +import com.github.joelgodofwar.mmh.common.reflect.fuzzy.FuzzyFieldContract; + +/** + * Represents a strongly-typed report. Subclasses should be immutable. + *

+ * By convention, a report must be declared as a static field publicly accessible from the sender class. + * @author Kristian + */ +public class ReportType { + private final String errorFormat; + + // Used to store the report name + protected String reportName; + + /** + * Construct a new report type. + * @param errorFormat - string used to format the underlying report. + */ + public ReportType(String errorFormat) { + this.errorFormat = errorFormat; + } + + /** + * Convert the given report to a string, using the provided parameters. + * @param parameters - parameters to insert, or NULL to insert nothing. + * @return The full report in string format. + */ + public String getMessage(Object[] parameters) { + if ((parameters == null) || (parameters.length == 0)) { + return toString(); + } else { + return String.format(errorFormat, parameters); + } + } + + @Override + public String toString() { + return errorFormat; + } + + /** + * Retrieve the class of the given sender. + *

+ * If the sender is already a Class, we return it. + * @param sender - the sender to look up. + * @return The class of the sender. + */ + public static Class getSenderClass(Object sender) { + if (sender == null) { + throw new IllegalArgumentException("sender cannot be null."); + } else if (sender instanceof Class) { + return (Class) sender; + } else { + return sender.getClass(); + } + } + + /** + * Retrieve the full canonical name of a given report type. + *

+ * Note that the sender may be a class (for static callers), in which + * case it will be used directly instead of its getClass() method. + *

+ * It is thus not advisable for class classes to report reports. + * @param sender - the sender, or its class. + * @param type - the report type. + * @return The full canonical name. + */ + public static String getReportName(Object sender, ReportType type) { + if (sender == null) { + throw new IllegalArgumentException("sender cannot be null."); + } + return getReportName(getSenderClass(sender), type); + } + + /** + * Retrieve the full canonical name of a given report type. + *

+ * This is in the format canonical_name_of_class#report_type + * @param sender - the sender class. + * @param type - the report instance. + * @return The full canonical name. + */ + private static String getReportName(Class sender, ReportType type) { + if (sender == null) { + throw new IllegalArgumentException("sender cannot be null."); + } + + // Whether or not we need to retrieve the report name again + if (type.reportName == null) { + for (Field field : getReportFields(sender)) { + try { + field.setAccessible(true); + + if (field.get(null) == type) { + // We got the right field! + return type.reportName = field.getDeclaringClass().getCanonicalName() + "#" + field.getName(); + } + } catch (IllegalAccessException e) { + throw new FieldAccessException("Unable to read field " + field, e); + } + } + throw new IllegalArgumentException("Cannot find report name for " + type); + } + return type.reportName; + } + + /** + * Retrieve all publicly associated reports. + * @param sender - sender class. + * @return All associated reports. + */ + public static ReportType[] getReports(Class sender) { + if (sender == null) { + throw new IllegalArgumentException("sender cannot be NULL."); + } + List result = new ArrayList(); + + // Go through all the fields + for (Field field : getReportFields(sender)) { + try { + field.setAccessible(true); + result.add((ReportType) field.get(null)); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Unable to read field " + field, e); + } + } + return result.toArray(new ReportType[0]); + } + + /** + * Retrieve all publicly associated report fields. + * @param clazz - sender class. + * @return All associated report fields. + */ + private static List getReportFields(Class clazz) { + return FuzzyReflection.fromClass(clazz, true) + .getFieldList(FuzzyFieldContract.newBuilder() + .requireModifier(Modifier.STATIC) + .typeDerivedOf(ReportType.class) + .build()); + } +} \ No newline at end of file diff --git a/common/reflect/ExactReflection.java b/common/reflect/ExactReflection.java new file mode 100644 index 0000000..f347300 --- /dev/null +++ b/common/reflect/ExactReflection.java @@ -0,0 +1,214 @@ +package com.github.joelgodofwar.mmh.common.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; + +public class ExactReflection { + + private static final Joiner COMMA_SEPERATED_JOINER = Joiner.on(", "); + + // The class we're actually representing + private final Class source; + private final boolean forceAccess; + + private ExactReflection(Class source, boolean forceAccess) { + this.source = Preconditions.checkNotNull(source, "source class cannot be NULL"); + this.forceAccess = forceAccess; + } + + /** + * Retrieves an exact reflection instance from a given class. + * + * @param source - the class we'll use. + * @param forceAccess - whether to also search for members which are out of the allowed scope. + * @return A fuzzy reflection instance. + */ + public static ExactReflection fromClass(Class source, boolean forceAccess) { + return new ExactReflection(source, forceAccess); + } + + /** + * Retrieves an exact reflection instance from an object. + * + * @param reference - the object we'll use. + * @param forceAccess - whether to also search for members which are out of the allowed scope. + * @return A fuzzy reflection instance that uses the class of the given object. + */ + public static ExactReflection fromObject(Object reference, boolean forceAccess) { + return new ExactReflection(reference.getClass(), forceAccess); + } + + /** + * Retrieve the first method in the class hierarchy with the given name and parameters. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for methods which are out of the caller scope. + * + * @param methodName - the name of the method to find, NULL to only search by using the given parameters. + * @param parameters - the parameters of the method to find. + * @return the first matching method. + * @throws IllegalArgumentException if there is no method with the given name and parameter types. + */ + public Method getMethod(String methodName, Class... parameters) { + Method method = this.lookupMethod(this.source, methodName, parameters); + if (method == null) { + throw new IllegalArgumentException(String.format( + "Unable to find method %s(%s) in %s", + methodName, + COMMA_SEPERATED_JOINER.join(parameters), + this.source.getName())); + } + + return method; + } + + /** + * Finds the first method in the class hierarchy with the given name and parameters. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for methods which are out of the caller scope. + * + * @param methodName - the name of the method to find, NULL to only search by using the given parameters. + * @param parameters - the parameters of the method to find. + * @return the first matching method, NULL if no method matches. + */ + public Method findMethod(String methodName, Class... parameters) { + return this.lookupMethod(this.source, methodName, parameters); + } + + // For recursion + private Method lookupMethod(Class instanceClass, String methodName, Class... parameters) { + for (Method method : instanceClass.getDeclaredMethods()) { + if ((this.forceAccess || Modifier.isPublic(method.getModifiers())) + && ((methodName == null) || method.getName().equals(methodName)) + && Arrays.equals(method.getParameterTypes(), parameters)) { + return method; + } + } + + // Search in every superclass + if (instanceClass.getSuperclass() != null) { + return this.lookupMethod(instanceClass.getSuperclass(), methodName, parameters); + } + + return null; + } + + /** + * Retrieve a field in the class hierarchy by the given name. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for fields which are out of the caller scope. + * + * @param fieldName - the name of the field to find. + * @return the first matching field. + * @throws IllegalArgumentException if no field with the given name was found. + */ + public Field getField(String fieldName) { + Field field = this.lookupField(this.source, fieldName); + if (field == null) { + throw new IllegalArgumentException(String.format( + "Unable to find field with name %s in %s.", + fieldName, + this.source.getName())); + } + + return field; + } + + /** + * Finds a field in the class hierarchy by the given name. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for fields which are out of the caller scope. + * + * @param fieldName - the name of the field to find. + * @return the first matching field, null if no field matches. + */ + public Field findField(String fieldName) { + return this.lookupField(this.source, fieldName); + } + + // For recursion + private Field lookupField(Class instanceClass, String fieldName) { + for (Field field : instanceClass.getDeclaredFields()) { + if ((this.forceAccess || Modifier.isPublic(field.getModifiers())) && field.getName().equals(fieldName)) { + return field; + } + } + + // Recursively find the correct field + if (instanceClass.getSuperclass() != null) { + return this.lookupField(instanceClass.getSuperclass(), fieldName); + } + + return null; + } + + /** + * Retrieves the first constructor in the class hierarchy with the given parameters. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for constructors which are out of the caller scope. + * + * @param parameters - the parameters of the constructor to find. + * @return the first matching constructor. + * @throws IllegalArgumentException if no constructor with the given parameters was found. + */ + public Constructor getConstructor(Class... parameters) { + Constructor constructor = this.findConstructor(parameters); + if (constructor == null) { + throw new IllegalArgumentException(String.format( + "Unable to find constructor (%s) in %s", + COMMA_SEPERATED_JOINER.join(parameters), + this.source.getName())); + } + + return constructor; + } + + /** + * Finds the first constructor in the class hierarchy with the given parameters. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for constructors which are out of the caller scope. + * + * @param parameters - the parameters of the constructor to find. + * @return the first matching constructor, NULL if no constructor matches. + */ + public Constructor findConstructor(Class... parameters) { + try { + Constructor constructor = this.source.getDeclaredConstructor(parameters); + return this.forceAccess || Modifier.isPublic(constructor.getModifiers()) ? constructor : null; + } catch (NoSuchMethodException exception) { + return null; + } + } + + /** + * Retrieve an {@link ExactReflection} object where scope restrictions are ignored. + * + * @return A copy of the current object. + */ + public ExactReflection forceAccess() { + return new ExactReflection(this.source, true); + } + + /** + * Determine if we are overriding scope restrictions and will also find private, protected or package members. + * + * @return TRUE if we are, FALSE otherwise. + */ + public boolean isForceAccess() { + return this.forceAccess; + } + + /** + * Retrieve the source class we are searching. + * + * @return The source. + */ + public Class getSource() { + return this.source; + } +} \ No newline at end of file diff --git a/common/reflect/FieldAccessException.java b/common/reflect/FieldAccessException.java new file mode 100644 index 0000000..b710e8e --- /dev/null +++ b/common/reflect/FieldAccessException.java @@ -0,0 +1,56 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ +package com.github.joelgodofwar.mmh.common.reflect; + +/** + * Invoked when a field is inaccessible due to security limitations, or when it simply doesn't exist. + * + * @author Kristian + */ +public class FieldAccessException extends RuntimeException { + + /** + * Generated by Eclipse. + */ + private static final long serialVersionUID = 1911011681494034617L; + + public FieldAccessException() { + super(); + } + + public FieldAccessException(String message, Throwable cause) { + super(message, cause); + } + + public FieldAccessException(String message) { + super(message); + } + + public FieldAccessException(Throwable cause) { + super(cause); + } + + public static FieldAccessException fromFormat(String message, Object... params) { + return new FieldAccessException(String.format(message, params)); + } + + @Override + public String toString() { + String message = getMessage(); + return "FieldAccessException" + (message != null ? ": " + message : ""); + } +} \ No newline at end of file diff --git a/common/reflect/FuzzyReflection.java b/common/reflect/FuzzyReflection.java new file mode 100644 index 0000000..8da5485 --- /dev/null +++ b/common/reflect/FuzzyReflection.java @@ -0,0 +1,597 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ +package com.github.joelgodofwar.mmh.common.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import com.github.joelgodofwar.mmh.common.reflect.accessors.Accessors; +import com.github.joelgodofwar.mmh.common.reflect.fuzzy.AbstractFuzzyMatcher; +import com.github.joelgodofwar.mmh.common.reflect.fuzzy.FuzzyFieldContract; +import com.github.joelgodofwar.mmh.common.reflect.fuzzy.FuzzyMethodContract; +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; + +/** + * Retrieves fields and methods by signature, not just name. + * + * @author Kristian + */ +public class FuzzyReflection { + + private static final Joiner COMMA_JOINER = Joiner.on(", "); + + // The class we're actually representing + private final Class source; + private final boolean forceAccess; + + public FuzzyReflection(Class source, boolean forceAccess) { + this.source = source; + this.forceAccess = forceAccess; + } + + /** + * Retrieves a fuzzy reflection instance from a given class. + * + * @param source - the class we'll use. + * @return A fuzzy reflection instance. + */ + public static FuzzyReflection fromClass(Class source) { + return fromClass(source, false); + } + + /** + * Retrieves a fuzzy reflection instance from a given class. + * + * @param source - the class we'll use. + * @param forceAccess - whether to override scope restrictions. + * @return A fuzzy reflection instance. + */ + public static FuzzyReflection fromClass(Class source, boolean forceAccess) { + return new FuzzyReflection(source, forceAccess); + } + + /** + * Retrieves a fuzzy reflection instance from an object. + * + * @param reference - the object we'll use. + * @return A fuzzy reflection instance that uses the class of the given object. + */ + public static FuzzyReflection fromObject(Object reference) { + return new FuzzyReflection(reference.getClass(), false); + } + + /** + * Retrieves a fuzzy reflection instance from an object. + * + * @param reference - the object we'll use. + * @param forceAccess - whether to override scope restrictions. + * @return A fuzzy reflection instance that uses the class of the given object. + */ + public static FuzzyReflection fromObject(Object reference, boolean forceAccess) { + return new FuzzyReflection(reference.getClass(), forceAccess); + } + + /** + * Retrieve the value of the first field of the given type. + * + * @param Type + * @param instance - the instance to retrieve from. + * @param fieldClass - type of the field to retrieve. + * @param forceAccess - whether to look for private and protected fields. + * @return The value of that field. + * @throws IllegalArgumentException If the field cannot be found. + */ + @SuppressWarnings("unchecked") + public static T getFieldValue(Object instance, Class fieldClass, boolean forceAccess) { + return (T) Accessors.getFieldAccessor(instance.getClass(), fieldClass, forceAccess).get(instance); + } + + @SafeVarargs + public static Set combineArrays(T[]... arrays) { + Set result = new LinkedHashSet<>(); + for (T[] elements : arrays) { + if (elements != null) { + Collections.addAll(result, elements); + } + } + + return result; + } + + /** + * Retrieves the underlying class. + * + * @return The underlying class. + */ + public Class getSource() { + return this.source; + } + + /** + * Retrieves whether or not not to override any scope restrictions. + * + * @return TRUE if we override scope, FALSE otherwise. + */ + public boolean isForceAccess() { + return this.forceAccess; + } + + /** + * Retrieve the singleton instance of a class, from a method or field. + * + * @return The singleton instance. + * @throws IllegalStateException If the class has no singleton. + */ + public Object getSingleton() { + try { + // try a no-args method which is static and returns the same type as the target class + Method method = this.getMethod(FuzzyMethodContract.newBuilder() + .parameterCount(0) + .returnDerivedOf(this.source) + .requireModifier(Modifier.STATIC) + .build()); + return Accessors.getMethodAccessor(method).invoke(null); + } catch (IllegalArgumentException ignored) { + // that method doesn't exist... + } + + try { + // try a field which is static and of the same type as the target class + Field field = this.getField(FuzzyFieldContract.newBuilder() + .typeDerivedOf(this.source) + .requireModifier(Modifier.STATIC) + .build()); + return Accessors.getFieldAccessor(field).get(null); + } catch (IllegalArgumentException ignored) { + // that field doesn't exist + } + + // we're unable to find the field + throw new IllegalStateException("Unable to retrieve singleton instance of " + this.source); + } + + /** + * Retrieve the first method that matches. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level method. + * + * @param matcher - the matcher to use. + * @return The first method that satisfies the given matcher. + * @throws IllegalArgumentException If the method cannot be found. + */ + public Method getMethod(AbstractFuzzyMatcher matcher) { + List result = this.getMethodList(matcher); + if (result.size() > 0) { + return result.get(0); + } else { + throw new IllegalArgumentException("Unable to find a method that matches " + matcher); + } + } + + /** + * Retrieve a method that matches. If there are multiple methods that match, the first one with the preferred name is + * selected. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level method. + * + * @param matcher - the matcher to use. + * @param preferred - the preferred name, null for no preference. + * @return The first method that satisfies the given matcher. + * @throws IllegalArgumentException If the method cannot be found. + */ + public Method getMethod(AbstractFuzzyMatcher matcher, String preferred) { + List result = this.getMethodList(matcher); + + // if we got more than one result check for the preferred method name + if ((result.size() > 1) && (preferred != null)) { + for (Method method : result) { + if (method.getName().equals(preferred)) { + return method; + } + } + } + + if (result.size() > 0) { + return result.get(0); + } else { + throw new IllegalArgumentException("Unable to find a method that matches " + matcher); + } + } + + /** + * Retrieves a method by looking at its name. + * + * @param nameRegex - regular expression that will match method names. + * @return The first method that satisfies the regular expression. + * @throws IllegalArgumentException If the method cannot be found. + */ + public Method getMethodByName(String nameRegex) { + // compile the regex only once + Pattern match = Pattern.compile(nameRegex); + for (Method method : this.getMethods()) { + if (match.matcher(method.getName()).matches()) { + // Right - this is probably it. + return method; + } + } + + throw new IllegalArgumentException(String.format( + "Unable to find a method in %s that matches \"%s\"", + this.source, + nameRegex)); + } + + /** + * Retrieves a method by looking at the parameter types only. + * + * @param name - potential name of the method. Only used by the error mechanism. + * @param args - parameter types of the method to find. + * @return The first method that satisfies the parameter types. + * @throws IllegalArgumentException If the method cannot be found. + */ + public Method getMethodByParameters(String name, Class... args) { + // Find the correct method to call + for (Method method : this.getMethods()) { + if (Arrays.equals(method.getParameterTypes(), args)) { + return method; + } + } + + // That sucks + throw new IllegalArgumentException(String.format( + "Unable to find %s(%s) in %s", + name, + COMMA_JOINER.join(args), + this.source)); + } + + /** + * Retrieves a method by looking at the parameter types and return type only. + * + * @param name - potential name of the method. Only used by the error mechanism. + * @param returnType - return type of the method to find. + * @param args - parameter types of the method to find. + * @return The first method that satisfies the parameter types. + * @throws IllegalArgumentException If the method cannot be found. + */ + public Method getMethodByReturnTypeAndParameters(String name, Class returnType, Class... args) { + // Find the correct method to call + List methods = this.getMethodListByParameters(returnType, args); + if (methods.size() > 0) { + return methods.get(0); + } else { + // That sucks + throw new IllegalArgumentException(String.format( + "Unable to find %s(%s): %s in %s", + name, + COMMA_JOINER.join(args), + returnType, + this.source)); + } + } + + /** + * Retrieve a list of every method that matches the given matcher. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level methods. + * + * @param matcher - the matcher to apply. + * @return List of found methods. + */ + public List getMethodList(AbstractFuzzyMatcher matcher) { + // finds and adds all matching methods + List methods = new ArrayList<>(); + for (Method method : this.getMethods()) { + if (matcher.isMatch(MethodInfo.fromMethod(method), this.source)) { + methods.add(method); + } + } + + return methods; + } + + /** + * Retrieves every method that has the given parameter types and return type. + * + * @param returnType - return type of the method to find. + * @param args - parameter types of the method to find. + * @return Every method that satisfies the given constraints. + */ + public List getMethodListByParameters(Class returnType, Class... args) { + List methods = new ArrayList<>(); + // Find the correct method to call + for (Method method : this.getMethods()) { + if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) { + methods.add(method); + } + } + + return methods; + } + + /** + * Retrieve the first field that matches. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level fields. + * + * @param matcher - the matcher to use. + * @return The first method that satisfies the given matcher. + * @throws IllegalArgumentException If the method cannot be found. + */ + public Field getField(AbstractFuzzyMatcher matcher) { + List result = this.getFieldList(matcher); + if (result.size() > 0) { + return result.get(0); + } else { + throw new IllegalArgumentException("Unable to find a field that matches " + matcher); + } + } + + /** + * Retrieves a field by name. + * + * @param nameRegex - regular expression that will match a field name. + * @return The first field to match the given expression. + * @throws IllegalArgumentException If the field cannot be found. + */ + public Field getFieldByName(String nameRegex) { + // compile the pattern only once + Pattern match = Pattern.compile(nameRegex); + for (Field field : this.getFields()) { + if (match.matcher(field.getName()).matches()) { + return field; + } + } + + // Looks like we're outdated. Too bad. + throw new IllegalArgumentException(String.format( + "Unable to find a field with a name matching \"%s\" in %s", + nameRegex, + this.source)); + } + + /** + * Retrieves the first field with a type equal to or more specific to the given type. + * + * @param name - name the field probably is given. This will only be used in the error message. + * @param type - type of the field to find. + * @return The first field with a type that is an instance of the given type. + */ + public Field getFieldByType(String name, Class type) { + List fields = this.getFieldListByType(type); + if (fields.size() > 0) { + return fields.get(0); + } else { + // Looks like we're outdated. Too bad. + throw new IllegalArgumentException(String.format( + "Unable to find a field \"%s\" with the type %s in %s", + name, + type, + this.source)); + } + } + + /** + * Retrieves every field with a type equal to or more specific to the given type. + * + * @param type - type of the fields to find. + * @return Every field with a type that is an instance of the given type. + */ + public List getFieldListByType(Class type) { + // Field with a compatible type + List fields = new ArrayList<>(); + for (Field field : this.getFields()) { + if (type.isAssignableFrom(field.getType())) { + fields.add(field); + } + } + + return fields; + } + + /** + * Retrieves a field with a given type and parameters. This is most useful when dealing with Collections. + * + * @param fieldType Type of the field + * @param params Variable length array of type parameters + * @return The field + * @throws IllegalArgumentException If the field cannot be found + */ + public Field getParameterizedField(Class fieldType, Class... params) { + for (Field field : this.getFields()) { + if (field.getType().equals(fieldType)) { + Type type = field.getGenericType(); + if (type instanceof ParameterizedType) { + if (Arrays.equals(((ParameterizedType) type).getActualTypeArguments(), params)) { + return field; + } + } + } + } + + throw new IllegalArgumentException(String.format( + "Unable to find a field of type %s<%s> in %s", + fieldType, + COMMA_JOINER.join(params), + this.source)); + } + + /** + * Retrieve a list of every field that matches the given matcher. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level fields. + * + * @param matcher - the matcher to apply. + * @return List of found fields. + */ + public List getFieldList(AbstractFuzzyMatcher matcher) { + // Add all matching fields to the list + List fields = new ArrayList<>(); + for (Field field : this.getFields()) { + if (matcher.isMatch(field, this.source)) { + fields.add(field); + } + } + + return fields; + } + + /** + * Retrieves a field by type. + *

+ * Note that the type is matched using the full canonical representation, i.e.: + *

    + *
  • java.util.List
  • + *
  • net.comphenix.xp.ExperienceMod
  • + *
+ * + * @param typeRegex - regular expression that will match the field type. + * @return The first field with a type that matches the given regular expression. + * @throws IllegalArgumentException If the field cannot be found. + */ + public Field getFieldByType(String typeRegex) { + Pattern match = Pattern.compile(typeRegex); + + // Like above, only here we test the field type + for (Field field : this.getFields()) { + String name = field.getType().getName(); + if (match.matcher(name).matches()) { + return field; + } + } + + // Looks like we're outdated. Too bad. + throw new IllegalArgumentException(String.format( + "Unable to find a field with a type that matches \"%s\" in %s", + typeRegex, + this.source)); + } + + /** + * Retrieve the first constructor that matches. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level constructors. + * + * @param matcher - the matcher to use. + * @return The first constructor that satisfies the given matcher. + * @throws IllegalArgumentException If the constructor cannot be found. + */ + public Constructor getConstructor(AbstractFuzzyMatcher matcher) { + List> result = this.getConstructorList(matcher); + if (result.size() > 0) { + return result.get(0); + } else { + throw new IllegalArgumentException("Unable to find a method that matches " + matcher); + } + } + + /** + * Retrieve a list of every constructor that matches the given matcher. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level constructors. + * + * @param matcher - the matcher to apply. + * @return List of found constructors. + */ + public List> getConstructorList(AbstractFuzzyMatcher matcher) { + // Add all matching constructors to the list + List> constructors = new ArrayList<>(); + for (Constructor constructor : this.getConstructors()) { + if (matcher.isMatch(MethodInfo.fromConstructor(constructor), this.source)) { + constructors.add(constructor); + } + } + + return constructors; + } + + /** + * Retrieves all private and public fields in declared order. + *

+ * Private, protected and package fields are ignored if forceAccess is FALSE. + * + * @return Every field. + */ + public Set getFields() { + if (this.forceAccess) { + return combineArrays(this.source.getDeclaredFields(), this.source.getFields()); + } else { + return combineArrays(this.source.getFields()); + } + } + + /** + * Retrieves all private and public fields, up until a certain superclass. + * + * @param excludeClass - the class (and its superclasses) to exclude from the search. + * @return Every such declared field. + */ + public Set getDeclaredFields(Class excludeClass) { + // we only need to do this if we include inherited fields + if (this.forceAccess) { + Class current = this.source; + Set fields = Sets.newLinkedHashSet(); + + while ((current != null) && (current != excludeClass)) { + fields.addAll(Arrays.asList(current.getDeclaredFields())); + current = current.getSuperclass(); + } + + return fields; + } + + return this.getFields(); + } + + /** + * Retrieves all private and public methods in declared order (after JDK 1.5). + *

+ * Private, protected and package methods are ignored if forceAccess is FALSE. + * + * @return Every method. + */ + public Set getMethods() { + if (this.forceAccess) { + return combineArrays(this.source.getDeclaredMethods(), this.source.getMethods()); + } else { + return combineArrays(this.source.getMethods()); + } + } + + /** + * Retrieves all private and public constructors in declared order (after JDK 1.5). + *

+ * Private, protected and package constructors are ignored if forceAccess is FALSE. + * + * @return Every constructor. + */ + public Set> getConstructors() { + return combineArrays(this.forceAccess ? this.source.getDeclaredConstructors() : this.source.getConstructors()); + } +} \ No newline at end of file diff --git a/common/reflect/MethodInfo.java b/common/reflect/MethodInfo.java new file mode 100644 index 0000000..5cd9d90 --- /dev/null +++ b/common/reflect/MethodInfo.java @@ -0,0 +1,300 @@ +package com.github.joelgodofwar.mmh.common.reflect; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * Represents a method or a constructor. + * + * @author Kristian + */ +public abstract class MethodInfo implements GenericDeclaration, Member { + + /** + * Wraps a method as a MethodInfo object. + * + * @param method - the method to wrap. + * @return The wrapped method. + */ + public static MethodInfo fromMethod(final Method method) { + return new MethodInfo() { + @Override + public T getAnnotation(Class annotationClass) { + return method.getAnnotation(annotationClass); + } + + @Override + public Annotation[] getAnnotations() { + return method.getAnnotations(); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return method.getDeclaredAnnotations(); + } + + @Override + public String getName() { + return method.getName(); + } + + @Override + public Class[] getParameterTypes() { + return method.getParameterTypes(); + } + + @Override + public Class getDeclaringClass() { + return method.getDeclaringClass(); + } + + @Override + public Class getReturnType() { + return method.getReturnType(); + } + + @Override + public int getModifiers() { + return method.getModifiers(); + } + + @Override + public Class[] getExceptionTypes() { + return method.getExceptionTypes(); + } + + @Override + public TypeVariable[] getTypeParameters() { + return method.getTypeParameters(); + } + + @Override + public String toGenericString() { + return method.toGenericString(); + } + + @Override + public String toString() { + return method.toString(); + } + + @Override + public boolean isSynthetic() { + return method.isSynthetic(); + } + + @Override + public int hashCode() { + return method.hashCode(); + } + + @Override + public boolean isConstructor() { + return false; + } + }; + } + + /** + * Construct a list of method infos from a given array of methods. + * + * @param methods - array of methods. + * @return Method info list. + */ + public static Collection fromMethods(Method[] methods) { + return fromMethods(Arrays.asList(methods)); + } + + /** + * Construct a list of method infos from a given collection of methods. + * + * @param methods - list of methods. + * @return Method info list. + */ + public static List fromMethods(Collection methods) { + List infos = Lists.newArrayList(); + for (Method method : methods) { + infos.add(fromMethod(method)); + } + + return infos; + } + + /** + * Wraps a constructor as a method information object. + * + * @param constructor - the constructor to wrap. + * @return A wrapped constructor. + */ + public static MethodInfo fromConstructor(final Constructor constructor) { + return new MethodInfo() { + @Override + public T getAnnotation(Class annotationClass) { + return constructor.getAnnotation(annotationClass); + } + + @Override + public Annotation[] getAnnotations() { + return constructor.getAnnotations(); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return constructor.getDeclaredAnnotations(); + } + + @Override + public String getName() { + return constructor.getName(); + } + + @Override + public Class[] getParameterTypes() { + return constructor.getParameterTypes(); + } + + @Override + public Class getDeclaringClass() { + return constructor.getDeclaringClass(); + } + + @Override + public Class getReturnType() { + return Void.class; + } + + @Override + public int getModifiers() { + return constructor.getModifiers(); + } + + @Override + public Class[] getExceptionTypes() { + return constructor.getExceptionTypes(); + } + + @Override + public TypeVariable[] getTypeParameters() { + return constructor.getTypeParameters(); + } + + @Override + public String toGenericString() { + return constructor.toGenericString(); + } + + @Override + public String toString() { + return constructor.toString(); + } + + @Override + public boolean isSynthetic() { + return constructor.isSynthetic(); + } + + @Override + public int hashCode() { + return constructor.hashCode(); + } + + @Override + public boolean isConstructor() { + return true; + } + }; + } + + /** + * Construct a list of method infos from a given array of constructors. + * + * @param constructors - array of constructors. + * @return Method info list. + */ + public static Collection fromConstructors(Constructor[] constructors) { + return fromConstructors(Arrays.asList(constructors)); + } + + /** + * Construct a list of method infos from a given collection of constructors. + * + * @param constructors - list of constructors. + * @return Method info list. + */ + public static List fromConstructors(Collection> constructors) { + List infos = Lists.newArrayList(); + + for (Constructor constructor : constructors) { + infos.add(fromConstructor(constructor)); + } + return infos; + } + + /** + * Returns a string describing this method or constructor + * + * @return A string representation of the object. + * @see Method#toString() + * @see Constructor#toString() + */ + @Override + public String toString() { + throw new UnsupportedOperationException(); + } + + /** + * Returns a string describing this method or constructor, including type parameters. + * + * @return A string describing this Method, include type parameters + * @see Method#toGenericString() + * @see Constructor#toGenericString() + */ + public abstract String toGenericString(); + + /** + * Returns an array of Class objects that represent the types of the exceptions declared to be thrown by the + * underlying method or constructor represented by this MethodInfo object. + * + * @return The exception types declared as being thrown by the method or constructor this object represents. + * @see Method#getExceptionTypes() + * @see Constructor#getExceptionTypes() + */ + public abstract Class[] getExceptionTypes(); + + /** + * Returns a Class object that represents the formal return type of the method or constructor represented by this + * MethodInfo object. + *

+ * This is always {@link Void} for constructors. + * + * @return The return value, or Void if a constructor. + * @see Method#getReturnType() + */ + public abstract Class getReturnType(); + + /** + * Returns an array of Class objects that represent the formal parameter types, in declaration order, of the method or + * constructor represented by this MethodInfo object. + * + * @return The parameter types for the method or constructor this object represents. + * @see Method#getParameterTypes() + * @see Constructor#getParameterTypes() + */ + public abstract Class[] getParameterTypes(); + + /** + * Determine if this is a constructor or not. + * + * @return TRUE if this represents a constructor, FALSE otherwise. + */ + public abstract boolean isConstructor(); +} \ No newline at end of file diff --git a/common/reflect/PrettyPrinter.java b/common/reflect/PrettyPrinter.java new file mode 100644 index 0000000..69247c8 --- /dev/null +++ b/common/reflect/PrettyPrinter.java @@ -0,0 +1,285 @@ +package com.github.joelgodofwar.mmh.common.reflect; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.github.joelgodofwar.mmh.common.reflect.accessors.Accessors; +import com.google.common.primitives.Primitives; + +/** + * Used to print the content of an arbitrary class. + * + * @author Kristian + */ +public class PrettyPrinter { + + /** + * How far we will recurse. + */ + public static final int RECURSE_DEPTH = 3; + + /** + * Print the contents of an object. + * + * @param object - the object to serialize. + * @return String representation of the class. + * @throws IllegalAccessException If the object is null + */ + public static String printObject(Object object) throws IllegalAccessException { + if (object == null) { + throw new IllegalArgumentException("object cannot be NULL."); + } + + return printObject(object, object.getClass(), Object.class); + } + + /** + * Print the contents of an object. + * + * @param object - the object to serialize. + * @param start - class to start at. + * @param stop - superclass that will stop the process. + * @return String representation of the class + * @throws IllegalAccessException If the object is null + */ + public static String printObject(Object object, Class start, Class stop) throws IllegalAccessException { + if (object == null) { + throw new IllegalArgumentException("object cannot be NULL."); + } + + return printObject(object, start, stop, RECURSE_DEPTH); + } + + /** + * Print the contents of an object. + * + * @param object - the object to serialize. + * @param start - class to start at. + * @param stop - superclass that will stop the process. + * @param hierachyDepth - maximum recursion level. + * @return String representation of the class. + * @throws IllegalAccessException If the object is null + */ + public static String printObject(Object object, Class start, Class stop, int hierachyDepth) + throws IllegalAccessException { + return printObject(object, start, stop, hierachyDepth, ObjectPrinter.DEFAULT); + } + + /** + * Print the contents of an object. + * + * @param object - the object to serialize. + * @param start - class to start at. + * @param stop - superclass that will stop the process. + * @param hierachyDepth - maximum recursion level. + * @param printer - a generic object printer. + * @return String representation of the class. + * @throws IllegalAccessException If the object is null + */ + public static String printObject(Object object, Class start, Class stop, int hierachyDepth, + ObjectPrinter printer) throws IllegalAccessException { + if (object == null) { + throw new IllegalArgumentException("object cannot be NULL."); + } + + StringBuilder output = new StringBuilder(); + Set previous = new HashSet<>(); + + // Start and stop + output.append("{ "); + printObject(output, object, start, stop, previous, hierachyDepth, true, printer); + output.append(" }"); + + return output.toString(); + } + + @SuppressWarnings("rawtypes") + private static void printIterables(StringBuilder output, Iterable iterable, Class stop, + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + + boolean first = true; + output.append("("); + + for (Object value : iterable) { + if (first) { + first = false; + } else { + output.append(", "); + } + + // Print value + printValue(output, value, stop, previous, hierachyIndex - 1, printer); + } + + output.append(")"); + } + + /** + * Print the content of a maps entries. + * + * @param output - the output string builder. + * @param map - the map to print. + * @param current - the type of this map. + * @param stop - the class that indicates we should stop printing. + * @param previous - previous objects printed. + * @param hierachyIndex - hierachy index. + * @throws IllegalAccessException If any reflection went wrong. + */ + private static void printMap(StringBuilder output, Map map, Class current, Class stop, + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + + boolean first = true; + output.append("["); + + for (Entry entry : map.entrySet()) { + if (first) { + first = false; + } else { + output.append(", "); + } + + printValue(output, entry.getKey(), stop, previous, hierachyIndex - 1, printer); + output.append(": "); + printValue(output, entry.getValue(), stop, previous, hierachyIndex - 1, printer); + } + + output.append("]"); + } + + private static void printArray(StringBuilder output, Object array, Class current, Class stop, + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + + Class component = current.getComponentType(); + boolean first = true; + + if (!component.isArray()) { + output.append(component.getName()); + } + output.append("["); + + for (int i = 0; i < Array.getLength(array); i++) { + if (first) { + first = false; + } else { + output.append(", "); + } + + // Handle exceptions + try { + printValue(output, Array.get(array, i), component, stop, previous, hierachyIndex - 1, printer); + } catch (ArrayIndexOutOfBoundsException e) { + e.printStackTrace(); + break; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + break; + } + } + + output.append("]"); + } + + // Internal recursion method + private static void printObject(StringBuilder output, Object object, Class current, Class stop, + Set previous, int hierachyIndex, boolean first, + ObjectPrinter printer) throws IllegalAccessException { + + // See if we're supposed to skip this class + if ((current == null) || (current == Object.class) || ((stop != null) && current.equals(stop))) { + return; + } + + // Don't iterate twice + previous.add(object); + + // Hard coded limit + if (hierachyIndex < 0) { + output.append("..."); + return; + } + + for (Field field : current.getDeclaredFields()) { + int mod = field.getModifiers(); + + // Skip a good number of the fields + if (!Modifier.isTransient(mod) && !Modifier.isStatic(mod)) { + Class type = field.getType(); + Object value = Accessors.getFieldAccessor(field).get(object); + + if (first) { + first = false; + } else { + output.append(", "); + } + + output.append(field.getName()); + output.append(" = "); + printValue(output, value, type, stop, previous, hierachyIndex - 1, printer); + } + } + + // Recurse + printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex, first, printer); + } + + private static void printValue(StringBuilder output, Object value, Class stop, + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + // Handle the NULL case + printValue(output, value, value != null ? value.getClass() : null, stop, previous, hierachyIndex, printer); + } + + @SuppressWarnings("unchecked") private static void printValue(StringBuilder output, Object value, Class type, + Class stop, Set previous, int hierachyIndex, + ObjectPrinter printer) throws IllegalAccessException { + + // Just print primitive types + if (printer.print(output, value)) { + return; + } else if (value == null) { + output.append("NULL"); + } else if (type.isPrimitive() || Primitives.isWrapperType(type)) { + output.append(value); + } else if ((type == String.class) || (hierachyIndex <= 0)) { + output.append("\"").append(value).append("\""); + } else if (type.isArray()) { + printArray(output, value, type, stop, previous, hierachyIndex, printer); + } else if (Iterable.class.isAssignableFrom(type)) { + printIterables(output, (Iterable) value, stop, previous, hierachyIndex, printer); + } else if (Map.class.isAssignableFrom(type)) { + printMap(output, (Map) value, type, stop, previous, hierachyIndex, printer); + } else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) { + // Don't print previous objects + output.append("\"").append(value).append("\""); + } else { + output.append("{ "); + printObject(output, value, value.getClass(), stop, previous, hierachyIndex, true, printer); + output.append(" }"); + } + } + + /** + * Represents a generic object printer. + * + * @author Kristian + */ + public interface ObjectPrinter { + + ObjectPrinter DEFAULT = (output, value) -> false; + + /** + * Print the content of the given object. + *

+ * Return FALSE in order for let the default printer take over. + * + * @param output - where to print the output. + * @param value - the value to print, may be NULL. + * @return TRUE if we processed the value and added to the output, FALSE otherwise. + */ + boolean print(StringBuilder output, Object value); + } +} \ No newline at end of file diff --git a/common/reflect/accessors/Accessors.java b/common/reflect/accessors/Accessors.java new file mode 100644 index 0000000..38ad129 --- /dev/null +++ b/common/reflect/accessors/Accessors.java @@ -0,0 +1,170 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import com.github.joelgodofwar.mmh.common.reflect.ExactReflection; +import com.github.joelgodofwar.mmh.common.reflect.FuzzyReflection; + +// TODO: at some point we should make everything nullable to make updates easier + +public final class Accessors { + + // Seal this class + private Accessors() { + } + + /** + * Retrieve an accessor (in declared order) for every field of the givne type. + * + * @param clazz - the type of the instance to retrieve. + * @param fieldClass - type of the field(s) to retrieve. + * @param forceAccess - whether to look for private and protected fields. + * @return The accessors. + */ + public static FieldAccessor[] getFieldAccessorArray(Class clazz, Class fieldClass, boolean forceAccess) { + return FuzzyReflection.fromClass(clazz, forceAccess).getFieldListByType(fieldClass).stream() + .map(Accessors::getFieldAccessor) + .toArray(FieldAccessor[]::new); + } + + /** + * Retrieve an accessor for the first field of the given type. + * + * @param instanceClass - the type of the instance to retrieve. + * @param fieldName - name of the field to retrieve. + * @param forceAccess - whether to look for private and protected fields. + * @return The value of that field. + * @throws IllegalArgumentException If the field cannot be found. + */ + //public static FieldAccessor getFieldAccessor(Class instanceClass, String fieldName, boolean forceAccess) { + // return Accessors.getFieldAccessor(ExactReflection.fromClass(instanceClass, forceAccess).getField(fieldName)); + //} + + /** + * Retrieve an accessor for the first field of the given type. + * + * @param instanceClass - the type of the instance to retrieve. + * @param fieldClass - type of the field to retrieve. + * @param forceAccess - whether to look for private and protected fields. + * @return The field accessor. + * @throws IllegalArgumentException If the field cannot be found. + */ + public static FieldAccessor getFieldAccessor(Class instanceClass, Class fieldClass, boolean forceAccess) { + // Get a field accessor + Field field = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldByType(null, fieldClass); + return Accessors.getFieldAccessor(field); + } + + /** + * Retrieve a field accessor for a field with the given name and equivalent type, or NULL. + * + * @param clazz - the declaration class. + * @param fieldName - the field name. + * @param fieldType - assignable field type. + * @return The field accessor, or NULL if not found. + */ + public static FieldAccessor getFieldAccessorOrNull(Class clazz, String fieldName, Class fieldType) { + Field field = ExactReflection.fromClass(clazz, true).findField(fieldName); + if ((field != null) && ((fieldType == null) || fieldType.isAssignableFrom(field.getType()))) { + return Accessors.getFieldAccessor(field); + } + + // no matching field found + return null; + } + + /** + * Retrieve a field accessor from a given field that uses unchecked exceptions. + * + * @param field - the field. + * @return The field accessor. + */ + public static FieldAccessor getFieldAccessor(Field field) { + return MethodHandleHelper.getFieldAccessor(field); + } + + /** + * Retrieve a field accessor that will cache the content of the field. + *

+ * Note that we don't check if the underlying field has changed after the value has been cached, so it's best to use + * this on final fields. + * + * @param inner - the accessor. + * @return A cached field accessor. + */ + public static FieldAccessor getMemorizing(FieldAccessor inner) { + return new MemorizingFieldAccessor(inner); + } + + /** + * Retrieve a method accessor for a method with the given name and signature. + * + * @param instanceClass - the parent class. + * @param methodName - the method name. + * @param parameters - the parameters. + * @return The method accessor. + */ + public static MethodAccessor getMethodAccessor(Class instanceClass, String methodName, Class... parameters) { + Method method = ExactReflection.fromClass(instanceClass, true).getMethod(methodName, parameters); + return Accessors.getMethodAccessor(method); + } + + /** + * Retrieve a method accessor for a field with the given name and equivalent type, or NULL. + * + * @param clazz - the declaration class. + * @param methodName - the method name. + * @return The method accessor, or NULL if not found. + */ + public static MethodAccessor getMethodAccessorOrNull(Class clazz, String methodName, Class... parameters) { + Method method = ExactReflection.fromClass(clazz, true).findMethod(methodName, parameters); + return method == null ? null : Accessors.getMethodAccessor(method); + } + + /** + * Retrieve a method accessor for a particular method, avoding checked exceptions. + * + * @param method - the method to access. + * @return The method accessor. + */ + public static MethodAccessor getMethodAccessor(Method method) { + return MethodHandleHelper.getMethodAccessor(method); + } + + /** + * Retrieve a constructor accessor for a constructor with the given signature. + * + * @param instanceClass - the parent class. + * @param parameters - the parameters. + * @return The constructor accessor. + * @throws IllegalArgumentException If we cannot find this constructor. + */ + public static ConstructorAccessor getConstructorAccessor(Class instanceClass, Class... parameters) { + Constructor constructor = ExactReflection.fromClass(instanceClass, true).findConstructor(parameters); + return Accessors.getConstructorAccessor(constructor); + } + + /** + * Find a specific constructor in a class. + * + * @param clazz - the class. + * @param parameters - the signature of the constructor to find. + * @return The constructor, or NULL if not found. + */ + public static ConstructorAccessor getConstructorAccessorOrNull(Class clazz, Class... parameters) { + Constructor constructor = ExactReflection.fromClass(clazz, true).findConstructor(parameters); + return constructor == null ? null : Accessors.getConstructorAccessor(constructor); + } + + /** + * Retrieve a constructor accessor for a particular constructor, avoding checked exceptions. + * + * @param constructor - the constructor to access. + * @return The method accessor. + */ + public static ConstructorAccessor getConstructorAccessor(final Constructor constructor) { + return MethodHandleHelper.getConstructorAccessor(constructor); + } +} \ No newline at end of file diff --git a/common/reflect/accessors/ConstructorAccessor.java b/common/reflect/accessors/ConstructorAccessor.java new file mode 100644 index 0000000..5542f82 --- /dev/null +++ b/common/reflect/accessors/ConstructorAccessor.java @@ -0,0 +1,21 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.reflect.Constructor; + +public interface ConstructorAccessor { + + /** + * Invoke the underlying constructor. + * + * @param args - the arguments to pass to the method. + * @return The return value, or NULL for void methods. + */ + Object invoke(Object... args); + + /** + * Retrieve the underlying constructor. + * + * @return The method. + */ + Constructor getConstructor(); +} \ No newline at end of file diff --git a/common/reflect/accessors/DefaultConstrutorAccessor.java b/common/reflect/accessors/DefaultConstrutorAccessor.java new file mode 100644 index 0000000..ad6c9e7 --- /dev/null +++ b/common/reflect/accessors/DefaultConstrutorAccessor.java @@ -0,0 +1,29 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Constructor; + +final class DefaultConstrutorAccessor implements ConstructorAccessor { + + private final Constructor constructor; + private final MethodHandle constructorAccessor; + + public DefaultConstrutorAccessor(Constructor constructor, MethodHandle constructorAccessor) { + this.constructor = constructor; + this.constructorAccessor = constructorAccessor; + } + + @Override + public Object invoke(Object... args) { + try { + return this.constructorAccessor.invokeExact(args); + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to construct new instance using " + this.constructor, throwable); + } + } + + @Override + public Constructor getConstructor() { + return this.constructor; + } +} \ No newline at end of file diff --git a/common/reflect/accessors/DefaultFieldAccessor.java b/common/reflect/accessors/DefaultFieldAccessor.java new file mode 100644 index 0000000..7938a98 --- /dev/null +++ b/common/reflect/accessors/DefaultFieldAccessor.java @@ -0,0 +1,51 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; + +import com.google.common.base.Preconditions; + +final class DefaultFieldAccessor implements FieldAccessor { + + private final Field field; + private final boolean staticField; + + private final MethodHandle setter; + private final MethodHandle getter; + + public DefaultFieldAccessor(Field field, MethodHandle setter, MethodHandle getter, boolean staticField) { + this.field = Preconditions.checkNotNull(field, "field"); + this.setter = Preconditions.checkNotNull(setter, "setter"); + this.getter = Preconditions.checkNotNull(getter, "getter"); + this.staticField = staticField; + } + + @Override + public Object get(Object instance) { + try { + // we need this check to as the handle will treat "null" as an instance too + return this.staticField ? this.getter.invokeExact() : this.getter.invokeExact(instance); + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to read field value of " + this.field, throwable); + } + } + + @Override + public void set(Object instance, Object value) { + try { + // we need this check to as the handle will treat "null" as an instance too + if (this.staticField) { + this.setter.invokeExact(value); + } else { + this.setter.invokeExact(instance, value); + } + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to set value of field " + this.field, throwable); + } + } + + @Override + public Field getField() { + return this.field; + } +} \ No newline at end of file diff --git a/common/reflect/accessors/DefaultMethodAccessor.java b/common/reflect/accessors/DefaultMethodAccessor.java new file mode 100644 index 0000000..c7f6151 --- /dev/null +++ b/common/reflect/accessors/DefaultMethodAccessor.java @@ -0,0 +1,29 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Method; + +final class DefaultMethodAccessor implements MethodAccessor { + + private final Method method; + private final MethodHandle methodHandle; + + public DefaultMethodAccessor(Method method, MethodHandle methodHandle, boolean staticMethod) { + this.method = method; + this.methodHandle = methodHandle; + } + + @Override + public Object invoke(Object target, Object... args) { + try { + return this.methodHandle.invoke(target, args); + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to invoke method " + this.method, throwable); + } + } + + @Override + public Method getMethod() { + return this.method; + } +} \ No newline at end of file diff --git a/common/reflect/accessors/FieldAccessor.java b/common/reflect/accessors/FieldAccessor.java new file mode 100644 index 0000000..8c4fe7d --- /dev/null +++ b/common/reflect/accessors/FieldAccessor.java @@ -0,0 +1,35 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.reflect.Field; + +/** + * Represents an interface for accessing a field. + * + * @author Kristian + */ +public interface FieldAccessor { + + /** + * Retrieve the value of a field for a particular instance. + * + * @param instance - the instance, or NULL for a static field. + * @return The value of the field. + * @throws IllegalStateException If the current security context prohibits reflection. + */ + Object get(Object instance); + + /** + * Set the value of a field for a particular instance. + * + * @param instance - the instance, or NULL for a static field. + * @param value - the new value of the field. + */ + void set(Object instance, Object value); + + /** + * Retrieve the underlying field. + * + * @return The field. + */ + Field getField(); +} \ No newline at end of file diff --git a/common/reflect/accessors/MemorizingFieldAccessor.java b/common/reflect/accessors/MemorizingFieldAccessor.java new file mode 100644 index 0000000..972afaf --- /dev/null +++ b/common/reflect/accessors/MemorizingFieldAccessor.java @@ -0,0 +1,36 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.reflect.Field; + +final class MemorizingFieldAccessor implements FieldAccessor { + + // a marker object which indicates the value of the field wasn't yet read + private static final Object NIL = new Object(); + + private final FieldAccessor inner; + private volatile Object fieldValue = NIL; + + public MemorizingFieldAccessor(FieldAccessor inner) { + this.inner = inner; + } + + @Override + public Object get(Object instance) { + if (this.fieldValue == NIL) { + this.fieldValue = this.inner.get(instance); + } + + return this.fieldValue; + } + + @Override + public void set(Object instance, Object value) { + this.inner.set(instance, value); + this.fieldValue = value; + } + + @Override + public Field getField() { + return this.inner.getField(); + } +} \ No newline at end of file diff --git a/common/reflect/accessors/MethodAccessor.java b/common/reflect/accessors/MethodAccessor.java new file mode 100644 index 0000000..da5243a --- /dev/null +++ b/common/reflect/accessors/MethodAccessor.java @@ -0,0 +1,27 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.reflect.Method; + +/** + * Represents an interface for invoking a method. + * + * @author Kristian + */ +public interface MethodAccessor { + + /** + * Invoke the underlying method. + * + * @param target - the target instance, or NULL for a static method. + * @param args - the arguments to pass to the method. + * @return The return value, or NULL for void methods. + */ + Object invoke(Object target, Object... args); + + /** + * Retrieve the underlying method. + * + * @return The method. + */ + Method getMethod(); +} \ No newline at end of file diff --git a/common/reflect/accessors/MethodHandleHelper.java b/common/reflect/accessors/MethodHandleHelper.java new file mode 100644 index 0000000..89290ae --- /dev/null +++ b/common/reflect/accessors/MethodHandleHelper.java @@ -0,0 +1,135 @@ +package com.github.joelgodofwar.mmh.common.reflect.accessors; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import com.google.common.base.Preconditions; + +final class MethodHandleHelper { + + private static final Lookup LOOKUP; + + // static fields, converted as "public Object get()" and "public void set(Object value)" + private static final MethodType STATIC_FIELD_GETTER = MethodType.methodType(Object.class); + private static final MethodType STATIC_FIELD_SETTER = MethodType.methodType(void.class, Object.class); + // instance fields, converted as "public Object get(Object instance)" and "public void set(Object instance, Object value)" + private static final MethodType VIRTUAL_FIELD_GETTER = MethodType.methodType(Object.class, Object.class); + private static final MethodType VIRTUAL_FIELD_SETTER = MethodType.methodType(void.class, Object.class, Object.class); + + static { + Lookup lookup; + try { + // get the unsafe class + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + // get the unsafe instance + Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null); + // get the trusted lookup field + Field trustedLookup = Lookup.class.getDeclaredField("IMPL_LOOKUP"); + // get access to the base and offset value of it + long offset = unsafe.staticFieldOffset(trustedLookup); + Object baseValue = unsafe.staticFieldBase(trustedLookup); + // get the trusted lookup instance + lookup = (Lookup) unsafe.getObject(baseValue, offset); + } catch (Exception exception) { + //PluginLogger.log(Level.SEVERE, "Unable to retrieve trusted lookup", exception); + lookup = MethodHandles.lookup(); + } + + LOOKUP = lookup; + } + + // sealed class + private MethodHandleHelper() { + } + + public static MethodAccessor getMethodAccessor(Method method) { + Preconditions.checkNotNull(method, "method"); + + try { + MethodHandle unreflected = LOOKUP.unreflect(method); + boolean staticMethod = Modifier.isStatic(method.getModifiers()); + + MethodHandle generified = convertToGeneric(unreflected, staticMethod, false); + return new DefaultMethodAccessor(method, generified, staticMethod); + } catch (IllegalAccessException ex) { + throw new IllegalStateException("Unable to access method " + method, ex); + } + } + + public static ConstructorAccessor getConstructorAccessor(Constructor constructor) { + Preconditions.checkNotNull(constructor, "constructor"); + + try { + MethodHandle unreflected = LOOKUP.unreflectConstructor(constructor); + MethodHandle generified = convertToGeneric(unreflected, false, true); + + return new DefaultConstrutorAccessor(constructor, generified); + } catch (IllegalAccessException ex) { + throw new IllegalStateException("Unable to access constructor " + constructor, ex); + } + } + + public static FieldAccessor getFieldAccessor(Field field) { + Preconditions.checkNotNull(field, "field"); + + try { + boolean staticField = Modifier.isStatic(field.getModifiers()); + + // java hates us - unreflecting a trusted field always results in an exception, finding them doesn't... + MethodHandle getter; + MethodHandle setter; + if (staticField) { + getter = LOOKUP.findStaticGetter(field.getDeclaringClass(), field.getName(), field.getType()); + setter = LOOKUP.findStaticSetter(field.getDeclaringClass(), field.getName(), field.getType()); + } else { + getter = LOOKUP.findGetter(field.getDeclaringClass(), field.getName(), field.getType()); + setter = LOOKUP.findSetter(field.getDeclaringClass(), field.getName(), field.getType()); + } + + // generify the method type so that we don't need to worry about it when using the handles + if (staticField) { + getter = getter.asType(STATIC_FIELD_GETTER); + setter = setter.asType(STATIC_FIELD_SETTER); + } else { + getter = getter.asType(VIRTUAL_FIELD_GETTER); + setter = setter.asType(VIRTUAL_FIELD_SETTER); + } + + if (getter == null) { + throw new IllegalStateException("Unable to access field " + field + ". Could not find getter"); + } + + if (setter == null) { + throw new IllegalStateException("Unable to access field " + field + ". Could not find setter"); + } + + return new DefaultFieldAccessor(field, setter, getter, staticField); + } catch (IllegalAccessException | NoSuchFieldException ex) { + // NoSuchFieldException can never happen, the field always exists + throw new IllegalStateException("Unable to access field " + field, ex); + } + } + + private static MethodHandle convertToGeneric(MethodHandle handle, boolean staticMethod, boolean ctor) { + MethodHandle target = handle.asFixedArity(); + // special thing - we do not need the trailing array if we have 0 arguments anyway + int paramCount = handle.type().parameterCount() - (ctor || staticMethod ? 0 : 1); + MethodType methodType = MethodType.genericMethodType(ctor ? 0 : 1, true); + // spread the arguments we give into the handle + target = target.asSpreader(Object[].class, paramCount); + // adds a leading 'this' argument which we can ignore + if (staticMethod) { + target = MethodHandles.dropArguments(target, 0, Object.class); + } + // convert the type to finish + return target.asType(methodType); + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/AbstractFuzzyMatcher.java b/common/reflect/fuzzy/AbstractFuzzyMatcher.java new file mode 100644 index 0000000..746256b --- /dev/null +++ b/common/reflect/fuzzy/AbstractFuzzyMatcher.java @@ -0,0 +1,52 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +/** + * Represents a matcher for fields, methods, constructors and classes. + *

+ * This class should ideally never expose mutable state. Its round number must be immutable. + * + * @author Kristian + */ +@FunctionalInterface +public interface AbstractFuzzyMatcher { + + /** + * Determine if the given value is a match. + * + * @param value - the value to match. + * @param parent - the parent container, or NULL if this value is the root. + * @return TRUE if it is a match, FALSE otherwise. + */ + boolean isMatch(T value, Object parent); + + /** + * Create a fuzzy matcher that returns the opposite result of the current matcher. + * + * @return An inverted fuzzy matcher. + */ + default AbstractFuzzyMatcher inverted() { + return (value, parent) -> !this.isMatch(value, parent); + } + + /** + * Require that this and the given matcher be TRUE. + * + * @param other - the other fuzzy matcher. + * @return A combined fuzzy matcher. + */ + default AbstractFuzzyMatcher and(final AbstractFuzzyMatcher other) { + // They both have to be true + return (value, parent) -> this.isMatch(value, parent) && other.isMatch(value, parent); + } + + /** + * Require that either this or the other given matcher be TRUE. + * + * @param other - the other fuzzy matcher. + * @return A combined fuzzy matcher. + */ + default AbstractFuzzyMatcher or(final AbstractFuzzyMatcher other) { + // Either can be true + return (value, parent) -> this.isMatch(value, parent) || other.isMatch(value, parent); + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/AbstractFuzzyMember.java b/common/reflect/fuzzy/AbstractFuzzyMember.java new file mode 100644 index 0000000..046669a --- /dev/null +++ b/common/reflect/fuzzy/AbstractFuzzyMember.java @@ -0,0 +1,321 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + +import com.google.common.collect.Maps; + +/** + * Represents a matcher that matches members. + * + * @param - type that it matches. + * @author Kristian + */ +public abstract class AbstractFuzzyMember implements AbstractFuzzyMatcher { + + // Accessibility matchers + protected int modifiersRequired; + protected int modifiersBanned; + + protected Pattern nameRegex; + protected AbstractFuzzyMatcher> declaringMatcher = ClassTypeMatcher.MATCH_ALL; + + /** + * Whether this contract can be modified. + */ + protected transient boolean sealed; + + protected AbstractFuzzyMember() { + // Only allow construction through the builder + } + + // Clone a given contract + protected AbstractFuzzyMember(AbstractFuzzyMember other) { + this.modifiersRequired = other.modifiersRequired; + this.modifiersBanned = other.modifiersBanned; + this.nameRegex = other.nameRegex; + this.declaringMatcher = other.declaringMatcher; + this.sealed = true; + } + + private static String getBitView(int value, int bits) { + if ((bits < 0) || (bits > 31)) { + throw new IllegalArgumentException("Bits must be a value between 0 and 32"); + } + + // Extract our needed bits + int snipped = value & ((1 << bits) - 1); + return Integer.toBinaryString(snipped); + } + + /** + * Called before a builder is building a member and copying its state. + *

+ * Use this to prepare any special values. + */ + protected void prepareBuild() { + // No need to prepare anything + } + + /** + * Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that is required for the member to + * match. + * + * @return A required modifier bit field. + */ + public int getModifiersRequired() { + return this.modifiersRequired; + } + + /** + * Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that must not be present for the member + * to match. + * + * @return A banned modifier bit field. + */ + public int getModifiersBanned() { + return this.modifiersBanned; + } + + /** + * Retrieve the regular expression pattern that is used to match the name of a member. + * + * @return The regex matching a name, or NULL if everything matches. + */ + public Pattern getNameRegex() { + return this.nameRegex; + } + + /** + * Retrieve a class matcher for the declaring class of the member. + * + * @return An object matching the declaring class. + */ + public AbstractFuzzyMatcher> getDeclaringMatcher() { + return this.declaringMatcher; + } + + @Override + public boolean isMatch(T value, Object parent) { + int mods = value.getModifiers(); + + // Match accessibility and name + return ((mods & this.modifiersRequired) == this.modifiersRequired) + && ((mods & this.modifiersBanned) == 0) + && this.declaringMatcher.isMatch(value.getDeclaringClass(), value) + && this.isNameMatch(value.getName()); + } + + /** + * Determine if a given name matches the current member matcher. + * + * @param name - the name to match. + * @return TRUE if the name matches, FALSE otherwise. + */ + private boolean isNameMatch(String name) { + if (this.nameRegex == null) { + return true; + } else { + return this.nameRegex.matcher(name).matches(); + } + } + + @Override + public String toString() { + return this.getKeyValueView().toString(); + } + + /** + * Generate a view of this matcher as a key-value map. + *

+ * Used by {@link #toString()} to print a representation of this object. + * + * @return A modifiable key-value view. + */ + protected Map getKeyValueView() { + Map map = Maps.newLinkedHashMap(); + + // Build our representation + if ((this.modifiersRequired != Integer.MAX_VALUE) || (this.modifiersBanned != 0)) { + map.put("modifiers", String.format( + "[required: %s, banned: %s]", + getBitView(this.modifiersRequired, 16), + getBitView(this.modifiersBanned, 16))); + } + + if (this.nameRegex != null) { + map.put("name", this.nameRegex.pattern()); + } + + if (this.declaringMatcher != ClassTypeMatcher.MATCH_ALL) { + map.put("declaring", this.declaringMatcher); + } + + return map; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof AbstractFuzzyMember) { + AbstractFuzzyMember other = (AbstractFuzzyMember) obj; + return (this.modifiersBanned == other.modifiersBanned) + && (this.modifiersRequired == other.modifiersRequired) + && FuzzyMatchers.checkPattern(this.nameRegex, other.nameRegex) + && Objects.equals(this.declaringMatcher, other.declaringMatcher); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash( + this.modifiersBanned, + this.modifiersRequired, + this.nameRegex != null ? this.nameRegex.pattern() : null, + this.declaringMatcher); + } + + /** + * Represents a builder of a fuzzy member contract. + * + * @author Kristian + */ + public static abstract class Builder> { + + protected T member = this.initialMember(); + + /** + * Add a given bit-field of required modifiers for every matching member. + * + * @param modifier - bit-field of modifiers that are required. + * @return This builder, for chaining. + */ + public Builder requireModifier(int modifier) { + this.member.modifiersRequired |= modifier; + return this; + } + + /** + * Require that every matching member is public. + * + * @return This builder, for chaining. + */ + public Builder requirePublic() { + return this.requireModifier(Modifier.PUBLIC); + } + + /** + * Add a given bit-field of modifers that will skip or ignore members. + * + * @param modifier - bit-field of modifiers to skip or ignore. + * @return This builder, for chaining. + */ + public Builder banModifier(int modifier) { + this.member.modifiersBanned |= modifier; + return this; + } + + /** + * Set the regular expresson that matches a members name. + * + * @param regex - new regular expression of valid names. + * @return This builder, for chaining. + */ + public Builder nameRegex(String regex) { + this.member.nameRegex = Pattern.compile(regex); + return this; + } + + /** + * Set the regular expression pattern that matches a members name. + * + * @param pattern - regular expression pattern for a valid name. + * @return This builder, for chaining. + */ + public Builder nameRegex(Pattern pattern) { + this.member.nameRegex = pattern; + return this; + } + + /** + * Set the exact name of the member we are matching. + *

+ * This will overwrite the regular expression rule. + * + * @param name - exact name. + * @return This builder, for chaining. + */ + public Builder nameExact(String name) { + return this.nameRegex(Pattern.quote(name)); + } + + /** + * Require that a member is defined by this exact class. + * + * @param declaringClass - the declaring class of any matching member. + * @return This builder, for chaining. + */ + public Builder declaringClassExactType(Class declaringClass) { + this.member.declaringMatcher = FuzzyMatchers.matchExact(declaringClass); + return this; + } + + /** + * Require that a member is defined by this exact class, or any super class. + * + * @param declaringClass - the declaring class. + * @return This builder, for chaining. + */ + public Builder declaringClassSuperOf(Class declaringClass) { + this.member.declaringMatcher = FuzzyMatchers.matchSuper(declaringClass); + return this; + } + + /** + * Require that a member is defined by this exact class, or any super class. + * + * @param declaringClass - the declaring class. + * @return This builder, for chaining. + */ + public Builder declaringClassDerivedOf(Class declaringClass) { + this.member.declaringMatcher = FuzzyMatchers.matchDerived(declaringClass); + return this; + } + + /** + * Require that a member is defined by a class that matches the given matcher. + * + * @param classMatcher - class matcher. + * @return This builder, for chaining. + */ + public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { + this.member.declaringMatcher = classMatcher; + return this; + } + + /** + * Construct a new instance of the current type. + * + * @return New instance. + */ + @Nonnull + protected abstract T initialMember(); + + /** + * Build a new instance of this type. + *

+ * Builders should call {@link AbstractFuzzyMember#prepareBuild()} when constructing new objects. + * + * @return New instance of this type. + */ + public abstract T build(); + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/ClassRegexMatcher.java b/common/reflect/fuzzy/ClassRegexMatcher.java new file mode 100644 index 0000000..5668f71 --- /dev/null +++ b/common/reflect/fuzzy/ClassRegexMatcher.java @@ -0,0 +1,31 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +import java.util.regex.Pattern; + +/** + * Determine if a class matches based on its name using a regular expression. + * + * @author Kristian + */ +final class ClassRegexMatcher implements AbstractFuzzyMatcher> { + + private final Pattern regex; + + public ClassRegexMatcher(Pattern regex) { + this.regex = regex; + } + + @Override + public boolean isMatch(Class value, Object parent) { + if ((value != null) && (this.regex != null)) { + return this.regex.matcher(value.getCanonicalName()).matches(); + } else { + return false; + } + } + + @Override + public String toString() { + return "{ type matches \"" + this.regex.pattern() + "\" }"; + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/ClassSetMatcher.java b/common/reflect/fuzzy/ClassSetMatcher.java new file mode 100644 index 0000000..f730c15 --- /dev/null +++ b/common/reflect/fuzzy/ClassSetMatcher.java @@ -0,0 +1,27 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +import java.util.Set; + +/** + * Represents a class matcher that checks for equality using a given set of classes. + * + * @author Kristian + */ +final class ClassSetMatcher implements AbstractFuzzyMatcher> { + + private final Set> classes; + + public ClassSetMatcher(Set> classes) { + this.classes = classes; + } + + @Override + public boolean isMatch(Class value, Object parent) { + return this.classes.contains(value); + } + + @Override + public String toString() { + return "{ type any of " + this.classes + " }"; + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/ClassTypeMatcher.java b/common/reflect/fuzzy/ClassTypeMatcher.java new file mode 100644 index 0000000..d18ba01 --- /dev/null +++ b/common/reflect/fuzzy/ClassTypeMatcher.java @@ -0,0 +1,114 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +/** + * Used to check class equality. + * + * @author Kristian + */ +final class ClassTypeMatcher implements AbstractFuzzyMatcher> { + + /** + * Match any class. + */ + public static final ClassTypeMatcher MATCH_ALL = new ClassTypeMatcher(null, MatchVariant.MATCH_SUPER); + + private final Class matcher; + private final MatchVariant variant; + + /** + * Constructs a new class matcher. + * + * @param matcher - the matching class, or NULL to represent anything. + * @param variant - options specifying the matching rules. + */ + ClassTypeMatcher(Class matcher, MatchVariant variant) { + this.matcher = matcher; + this.variant = variant; + } + + /** + * Determine if a given class is equivalent. + *

+ * If the matcher is NULL, the result will only be TRUE if we're not matching exactly. + * + * @param input - the input class defined in the source file. + * @param parent - the container that holds a reference to this class. + * @return TRUE if input matches according to the rules in {@link #getMatchVariant()}, FALSE otherwise. + */ + @Override + public boolean isMatch(Class input, Object parent) { + if (input == null) { + throw new IllegalArgumentException("Input class cannot be NULL."); + } + + // if no type to check against is given just ensure that we're not strict checking + if (this.matcher == null) { + return this.variant != MatchVariant.MATCH_EXACT; + } + + switch (this.variant) { + case MATCH_EXACT: + return this.matcher.equals(input); + case MATCH_DERIVED: + return this.matcher.isAssignableFrom(input); + case MATCH_SUPER: + return input.isAssignableFrom(this.matcher); + default: + // unknown option? + return false; + } + } + + /** + * Retrieve the class we're comparing against. + * + * @return Class to compare against. + */ + public Class getMatcher() { + return this.matcher; + } + + /** + * The matching rules for this class matcher. + * + * @return The current matching option. + */ + public MatchVariant getMatchVariant() { + return this.variant; + } + + @Override + public String toString() { + switch (this.variant) { + case MATCH_EXACT: + return "{ type exactly " + this.matcher + " }"; + case MATCH_SUPER: + return "{ type " + this.matcher + " instanceof input }"; + case MATCH_DERIVED: + return "{ type input instanceof " + this.matcher + " }"; + default: + throw new IllegalArgumentException("Unknown match variant " + this.variant); + } + } + + /** + * Different matching rules. + */ + enum MatchVariant { + + /** + * Match classes exactly. + */ + MATCH_EXACT, + + /** + * A match if the input class is a superclass of the matcher class, or the same class. + */ + MATCH_SUPER, + + /** + * A match if the input class is a derived class of the matcher class, or the same class. + */ + MATCH_DERIVED + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/FuzzyClassContract.java b/common/reflect/fuzzy/FuzzyClassContract.java new file mode 100644 index 0000000..57183b0 --- /dev/null +++ b/common/reflect/fuzzy/FuzzyClassContract.java @@ -0,0 +1,303 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.github.joelgodofwar.mmh.common.reflect.FuzzyReflection; +import com.github.joelgodofwar.mmh.common.reflect.MethodInfo; +import com.google.common.collect.ImmutableList; + +/** + * Determine if a given class implements a given fuzzy (duck typed) contract. + * + * @author Kristian + */ +public class FuzzyClassContract implements AbstractFuzzyMatcher> { + + private final ImmutableList> fieldContracts; + private final ImmutableList> methodContracts; + private final ImmutableList> constructorContracts; + + private final ImmutableList>> baseclassContracts; + private final ImmutableList>> interfaceContracts; + + /** + * Constructs a new fuzzy class contract with the given contracts. + * + * @param builder - the builder that is constructing us. + */ + private FuzzyClassContract(Builder builder) { + this.fieldContracts = ImmutableList.copyOf(builder.fieldContracts); + this.methodContracts = ImmutableList.copyOf(builder.methodContracts); + this.constructorContracts = ImmutableList.copyOf(builder.constructorContracts); + this.baseclassContracts = ImmutableList.copyOf(builder.baseclassContracts); + this.interfaceContracts = ImmutableList.copyOf(builder.interfaceContracts); + } + + /** + * Construct a new fuzzy class contract builder. + * + * @return A new builder. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Retrieve an immutable list of every field contract. + *

+ * This list is ordered in descending order of priority. + * + * @return List of every field contract. + */ + public ImmutableList> getFieldContracts() { + return this.fieldContracts; + } + + /** + * Retrieve an immutable list of every method contract. + *

+ * This list is ordered in descending order of priority. + * + * @return List of every method contract. + */ + public ImmutableList> getMethodContracts() { + return this.methodContracts; + } + + /** + * Retrieve an immutable list of every constructor contract. + *

+ * This list is ordered in descending order of priority. + * + * @return List of every constructor contract. + */ + public ImmutableList> getConstructorContracts() { + return this.constructorContracts; + } + + /** + * Retrieve an immutable list of every baseclass contract. + *

+ * This list is ordered in descending order of priority. + * + * @return List of every baseclass contract. + */ + public ImmutableList>> getBaseclassContracts() { + return this.baseclassContracts; + } + + /** + * Retrieve an immutable list of every interface contract. + *

+ * This list is ordered in descending order of priority. + * + * @return List of every interface contract. + */ + public ImmutableList>> getInterfaceContracts() { + return this.interfaceContracts; + } + + @Override + public boolean isMatch(Class value, Object parent) { + FuzzyReflection reflection = FuzzyReflection.fromClass(value, true); + + // Make sure all the contracts are valid + return this.processValue(value.getSuperclass(), parent, this.baseclassContracts) + && this.processContracts(Arrays.asList(value.getInterfaces()), parent, this.interfaceContracts) + && this.processContracts(reflection.getFields(), value, this.fieldContracts) + && this.processContracts(MethodInfo.fromMethods(reflection.getMethods()), value, this.methodContracts) + && this.processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), value, + this.constructorContracts); + } + + private boolean processContracts(Collection values, Object parent, List> matchers) { + // they all match if no values are present + if (values.isEmpty() || matchers.isEmpty()) { + return true; + } + + // check if all contracts match the given objects + int acceptingMatchers = 0; + for (T value : values) { + if (this.processValue(value, parent, matchers)) { + acceptingMatchers++; + // if all matchers found one match we're done + if (acceptingMatchers == matchers.size()) { + return true; + } + } + } + + return false; + } + + private boolean processValue(T value, Object parent, List> matchers) { + // check if all given contracts match the given value + for (AbstractFuzzyMatcher matcher : matchers) { + if (!matcher.isMatch(value, parent)) { + return false; + } + } + + // they all match + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("FuzzyClassContract={\n"); + + // append all subcontracts + if (!this.fieldContracts.isEmpty()) { + builder.append(" fields=").append(this.fieldContracts).append("\n"); + } + + if (!this.methodContracts.isEmpty()) { + builder.append(" methods=").append(this.methodContracts).append("\n"); + } + + if (!this.constructorContracts.isEmpty()) { + builder.append(" constructors=").append(this.constructorContracts).append("\n"); + } + + if (!this.baseclassContracts.isEmpty()) { + builder.append(" baseClasses=").append(this.baseclassContracts).append("\n"); + } + + if (!this.interfaceContracts.isEmpty()) { + builder.append(" interfaceClasses=").append(this.interfaceContracts).append("\n"); + } + + // finish off + return builder.append("}").toString(); + } + + /** + * Represents a class contract builder. + * + * @author Kristian + */ + public static final class Builder { + + private final List> fieldContracts = new ArrayList<>(); + private final List> methodContracts = new ArrayList<>(); + private final List> constructorContracts = new ArrayList<>(); + + private final List>> baseclassContracts = new ArrayList<>(); + private final List>> interfaceContracts = new ArrayList<>(); + + /** + * Add a new field contract. + * + * @param matcher - new field contract. + * @return This builder, for chaining. + */ + public Builder field(AbstractFuzzyMatcher matcher) { + this.fieldContracts.add(matcher); + return this; + } + + /** + * Add a new field contract via a builder. + * + * @param builder - builder for the new field contract. + * @return This builder, for chaining. + */ + public Builder field(FuzzyFieldContract.Builder builder) { + return this.field(builder.build()); + } + + /** + * Add a new method contract. + * + * @param matcher - new method contract. + * @return This builder, for chaining. + */ + public Builder method(AbstractFuzzyMatcher matcher) { + this.methodContracts.add(matcher); + return this; + } + + /** + * Add a new method contract via a builder. + * + * @param builder - builder for the new method contract. + * @return This builder, for chaining. + */ + public Builder method(FuzzyMethodContract.Builder builder) { + return this.method(builder.build()); + } + + /** + * Add a new constructor contract. + * + * @param matcher - new constructor contract. + * @return This builder, for chaining. + */ + public Builder constructor(AbstractFuzzyMatcher matcher) { + this.constructorContracts.add(matcher); + return this; + } + + /** + * Add a new constructor contract via a builder. + * + * @param builder - builder for the new constructor contract. + * @return This builder, for chaining. + */ + public Builder constructor(FuzzyMethodContract.Builder builder) { + return this.constructor(builder.build()); + } + + /** + * Add a new base class contract. + * + * @param matcher - new base class contract. + * @return This builder, for chaining. + */ + public Builder baseclass(AbstractFuzzyMatcher> matcher) { + this.baseclassContracts.add(matcher); + return this; + } + + /** + * Add a new base class contract. + * + * @param builder - builder for the new base class contract. + * @return This builder, for chaining. + */ + public Builder baseclass(FuzzyClassContract.Builder builder) { + return this.baseclass(builder.build()); + } + + /** + * Add a new interface contract. + * + * @param matcher - new interface contract. + * @return This builder, for chaining. + */ + public Builder interfaces(AbstractFuzzyMatcher> matcher) { + this.interfaceContracts.add(matcher); + return this; + } + + /** + * Add a new interface contract. + * + * @param builder - builder for the new interface contract. + * @return This builder, for chaining. + */ + public Builder interfaces(FuzzyClassContract.Builder builder) { + return this.interfaces(builder.build()); + } + + public FuzzyClassContract build() { + // Construct a new class matcher + return new FuzzyClassContract(this); + } + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/FuzzyFieldContract.java b/common/reflect/fuzzy/FuzzyFieldContract.java new file mode 100644 index 0000000..d68a9b6 --- /dev/null +++ b/common/reflect/fuzzy/FuzzyFieldContract.java @@ -0,0 +1,177 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + +/** + * Represents a field matcher. + * + * @author Kristian + */ +public class FuzzyFieldContract extends AbstractFuzzyMember { + + private AbstractFuzzyMatcher> typeMatcher = ClassTypeMatcher.MATCH_ALL; + + private FuzzyFieldContract() { + } + + /** + * Create a new field contract from the given contract. + * + * @param other - the contract to clone. + */ + private FuzzyFieldContract(FuzzyFieldContract other) { + super(other); + this.typeMatcher = other.typeMatcher; + } + + /** + * Match a field by its type. + * + * @param matcher - the type to match. + * @return The field contract. + */ + public static FuzzyFieldContract matchType(AbstractFuzzyMatcher> matcher) { + return newBuilder().typeMatches(matcher).build(); + } + + /** + * Return a new fuzzy field contract builder. + * + * @return New fuzzy field contract builder. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Retrieve the class matcher that matches the type of a field. + * + * @return The class matcher. + */ + public AbstractFuzzyMatcher> getTypeMatcher() { + return this.typeMatcher; + } + + @Override + public boolean isMatch(Field value, Object parent) { + if (super.isMatch(value, parent)) { + return this.typeMatcher.isMatch(value.getType(), value); + } + + // No match + return false; + } + + @Override + protected Map getKeyValueView() { + Map member = super.getKeyValueView(); + if (this.typeMatcher != ClassTypeMatcher.MATCH_ALL) { + member.put("type", this.typeMatcher); + } + + return member; + } + + /** + * Represents a builder for a field matcher. + * + * @author Kristian + */ + public static class Builder extends AbstractFuzzyMember.Builder { + + @Override + public Builder requireModifier(int modifier) { + super.requireModifier(modifier); + return this; + } + + @Override + public Builder banModifier(int modifier) { + super.banModifier(modifier); + return this; + } + + @Override + public Builder requirePublic() { + super.requirePublic(); + return this; + } + + @Override + public Builder nameRegex(String regex) { + super.nameRegex(regex); + return this; + } + + @Override + public Builder nameRegex(Pattern pattern) { + super.nameRegex(pattern); + return this; + } + + @Override + public Builder nameExact(String name) { + super.nameExact(name); + return this; + } + + @Override public Builder declaringClassExactType(Class declaringClass) { + super.declaringClassExactType(declaringClass); + return this; + } + + @Override + public Builder declaringClassSuperOf(Class declaringClass) { + super.declaringClassSuperOf(declaringClass); + return this; + } + + @Override + public Builder declaringClassDerivedOf(Class declaringClass) { + super.declaringClassDerivedOf(declaringClass); + return this; + } + + @Override + public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { + super.declaringClassMatching(classMatcher); + return this; + } + + @Override + @Nonnull + protected FuzzyFieldContract initialMember() { + return new FuzzyFieldContract(); + } + + public Builder typeExact(Class type) { + this.member.typeMatcher = FuzzyMatchers.matchExact(type); + return this; + } + + public Builder typeSuperOf(Class type) { + this.member.typeMatcher = FuzzyMatchers.matchSuper(type); + return this; + } + + public Builder typeDerivedOf(Class type) { + this.member.typeMatcher = FuzzyMatchers.matchDerived(type); + return this; + } + + public Builder typeMatches(AbstractFuzzyMatcher> matcher) { + this.member.typeMatcher = matcher; + return this; + } + + @Override + public FuzzyFieldContract build() { + this.member.prepareBuild(); + return new FuzzyFieldContract(this.member); + } + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/FuzzyMatchers.java b/common/reflect/fuzzy/FuzzyMatchers.java new file mode 100644 index 0000000..ba0bda2 --- /dev/null +++ b/common/reflect/fuzzy/FuzzyMatchers.java @@ -0,0 +1,132 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +import java.util.Set; +import java.util.regex.Pattern; + +import com.github.joelgodofwar.mmh.common.reflect.fuzzy.ClassTypeMatcher.MatchVariant; +import com.google.common.collect.Sets; + +/** + * Contains factory methods for matching classes. + * + * @author Kristian + */ +public class FuzzyMatchers { + + // Constant matchers + private static final AbstractFuzzyMatcher> MATCH_ALL = (value, parent) -> true; + + private FuzzyMatchers() { + // Don't make this constructable + } + + /** + * Construct a class matcher that matches an array with a given component matcher. + * + * @param componentMatcher - the component matcher. + * @return A new array matcher. + */ + public static AbstractFuzzyMatcher> matchArray(AbstractFuzzyMatcher> componentMatcher) { + return (value, parent) -> value.isArray() && componentMatcher.isMatch(value.getComponentType(), parent); + } + + /** + * Retrieve a fuzzy matcher that will match any class. + * + * @return A class matcher. + */ + public static AbstractFuzzyMatcher> matchAll() { + return MATCH_ALL; + } + + /** + * Construct a class matcher that matches types exactly. + * + * @param matcher - the matching class. + * @return A new class matcher. + */ + public static AbstractFuzzyMatcher> matchExact(Class matcher) { + return new ClassTypeMatcher(matcher, MatchVariant.MATCH_EXACT); + } + + /** + * Construct a class matcher that matches any of the given classes exactly. + * + * @param classes - list of classes to match. + * @return A new class matcher. + */ + public static AbstractFuzzyMatcher> matchAnyOf(Class... classes) { + return matchAnyOf(Sets.newHashSet(classes)); + } + + /** + * Construct a class matcher that matches any of the given classes exactly. + * + * @param classes - set of classes to match. + * @return A new class matcher. + */ + public static AbstractFuzzyMatcher> matchAnyOf(Set> classes) { + return new ClassSetMatcher(classes); + } + + /** + * Construct a class matcher that matches super types of the given class. + * + * @param matcher - the matching type must be a super class of this type. + * @return A new class matcher. + */ + public static AbstractFuzzyMatcher> matchSuper(Class matcher) { + return new ClassTypeMatcher(matcher, MatchVariant.MATCH_SUPER); + } + + /** + * Construct a class matcher that matches derived types of the given class. + * + * @param matcher - the matching type must be a derived class of this type. + * @return A new class matcher. + */ + public static AbstractFuzzyMatcher> matchDerived(Class matcher) { + return new ClassTypeMatcher(matcher, MatchVariant.MATCH_DERIVED); + } + + /** + * Construct a class matcher based on the canonical names of classes. + * + * @param regex - regular expression pattern matching class names. + * @return A fuzzy class matcher based on name. + */ + public static AbstractFuzzyMatcher> matchRegex(final Pattern regex) { + return new ClassRegexMatcher(regex); + } + + /** + * Construct a class matcher based on the canonical names of classes. + * + * @param regex - regular expression matching class names. + * @return A fuzzy class matcher based on name. + */ + public static AbstractFuzzyMatcher> matchRegex(String regex) { + return FuzzyMatchers.matchRegex(Pattern.compile(regex)); + } + + /** + * Determine if two patterns are the same. + *

+ * Note that two patterns may be functionally the same, but nevertheless be different. + * + * @param a - the first pattern. + * @param b - the second pattern. + * @return TRUE if they are compiled from the same pattern, FALSE otherwise. + */ + static boolean checkPattern(Pattern a, Pattern b) { + if (a == null) { + return b == null; + } else if (b == null) { + return false; + } else if (a == b) { + return true; + } else { + return a.pattern().equals(b.pattern()); + } + } +} \ No newline at end of file diff --git a/common/reflect/fuzzy/FuzzyMethodContract.java b/common/reflect/fuzzy/FuzzyMethodContract.java new file mode 100644 index 0000000..c54cedb --- /dev/null +++ b/common/reflect/fuzzy/FuzzyMethodContract.java @@ -0,0 +1,564 @@ +package com.github.joelgodofwar.mmh.common.reflect.fuzzy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + +import com.github.joelgodofwar.mmh.common.reflect.MethodInfo; +import com.google.common.collect.ImmutableList; + +/** + * Represents a contract for matching methods or constructors. + * + * @author Kristian + */ +public class FuzzyMethodContract extends AbstractFuzzyMember { + + // Match return value + private AbstractFuzzyMatcher> returnMatcher = ClassTypeMatcher.MATCH_ALL; + // Handle parameters and exceptions + private List paramMatchers; + private List exceptionMatchers; + // Expected parameter count + private Integer paramCount; + + private FuzzyMethodContract() { + // Only allow construction from the builder + this.paramMatchers = new ArrayList<>(); + this.exceptionMatchers = new ArrayList<>(); + } + + private FuzzyMethodContract(FuzzyMethodContract other) { + super(other); + this.returnMatcher = other.returnMatcher; + this.paramMatchers = other.paramMatchers; + this.exceptionMatchers = other.exceptionMatchers; + this.paramCount = other.paramCount; + } + + /** + * Return a method contract builder. + * + * @return Method contract builder. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Construct a new immutable copy of the given method contract. + * + * @param other - the contract to clone. + * @return A immutable copy of the given contract. + */ + private static FuzzyMethodContract immutableCopy(FuzzyMethodContract other) { + FuzzyMethodContract copy = new FuzzyMethodContract(other); + + // Ensure that the lists are immutable + copy.paramMatchers = ImmutableList.copyOf(copy.paramMatchers); + copy.exceptionMatchers = ImmutableList.copyOf(copy.exceptionMatchers); + return copy; + } + + /** + * Retrieve the class matcher for the return type. + * + * @return Class matcher for the return type. + */ + public AbstractFuzzyMatcher> getReturnMatcher() { + return this.returnMatcher; + } + + /** + * Retrieve an immutable list of every parameter matcher for this method. + * + * @return Immutable list of every parameter matcher. + */ + public ImmutableList getParamMatchers() { + if (this.paramMatchers instanceof ImmutableList) { + return (ImmutableList) this.paramMatchers; + } else { + throw new IllegalStateException("Lists haven't been sealed yet."); + } + } + + /** + * Retrieve an immutable list of every exception matcher for this method. + * + * @return Immutable list of every exception matcher. + */ + public List getExceptionMatchers() { + if (this.exceptionMatchers instanceof ImmutableList) { + return this.exceptionMatchers; + } else { + throw new IllegalStateException("Lists haven't been sealed yet."); + } + } + + /** + * Retrieve the expected parameter count for this method. + * + * @return Expected parameter count, or NULL if anyting goes. + */ + public Integer getParamCount() { + return this.paramCount; + } + + @Override + public boolean isMatch(MethodInfo value, Object parent) { + if (super.isMatch(value, parent)) { + // check the return type first (the easiest check) + if (!this.returnMatcher.isMatch(value.getReturnType(), value)) { + return false; + } + + // check for the parameter types + Class[] params = value.getParameterTypes(); + if ((this.paramCount != null) && (this.paramCount != params.length)) { + return false; + } + + // check parameters and exceptions + return this.matchTypes(params, value, this.paramMatchers) + && this.matchTypes(value.getExceptionTypes(), value, this.exceptionMatchers); + } + + // No match + return false; + } + + private boolean matchTypes(Class[] types, MethodInfo parent, List matchers) { + if (matchers.isEmpty()) { + // no matchers - no show + return true; + } + + // the amount of matchers which are ok with the parameter types + int acceptingMatchers = 0; + for (int i = 0; i < types.length; i++) { + if (this.processValue(types[i], parent, i, matchers)) { + acceptingMatchers++; + // if all matchers accepted one type we are done + if (acceptingMatchers == matchers.size()) { + return true; + } + } + } + + return false; + } + + private boolean processValue(Class value, MethodInfo parent, int index, List matchers) { + // The order matters + for (ParameterClassMatcher matcher : matchers) { + // See if we got jackpot + if (matcher.isParameterMatch(value, parent, index)) { + return true; + } + } + + // Failure + return false; + } + + @Override + protected Map getKeyValueView() { + Map member = super.getKeyValueView(); + + // Only add fields that are actual constraints + if (this.returnMatcher != ClassTypeMatcher.MATCH_ALL) { + member.put("return", this.returnMatcher); + } + + if (!this.paramMatchers.isEmpty()) { + member.put("params", this.paramMatchers); + } + + if (!this.exceptionMatchers.isEmpty()) { + member.put("exceptions", this.exceptionMatchers); + } + + if (this.paramCount != null) { + member.put("paramCount", this.paramCount); + } + + return member; + } + + private static final class ParameterClassMatcher implements AbstractFuzzyMatcher[]> { + + /** + * The expected index. + */ + private final AbstractFuzzyMatcher> typeMatcher; + private final Integer indexMatch; + + /** + * Construct a new parameter class matcher. + * + * @param typeMatcher - class type matcher. + */ + public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher> typeMatcher) { + this(typeMatcher, null); + } + + /** + * Construct a new parameter class matcher. + * + * @param typeMatcher - class type matcher. + * @param indexMatch - parameter index to match, or NULL for anything. + */ + public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher> typeMatcher, Integer indexMatch) { + this.typeMatcher = typeMatcher; + this.indexMatch = indexMatch; + } + + /** + * See if there's a match for this matcher. + * + * @param param - the type to match. + * @param parent - the container (member) that holds a reference to this parameter. + * @param index - the index of the current parameter. + * @return TRUE if this matcher matches any of the given parameters, FALSE otherwise. + */ + public boolean isParameterMatch(Class param, MethodInfo parent, int index) { + // Make sure the index is valid (or NULL) + if ((this.indexMatch == null) || (this.indexMatch == index)) { + return this.typeMatcher.isMatch(param, parent); + } else { + return false; + } + } + + @Override + public boolean isMatch(Class[] value, Object parent) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return String.format("{ Parameter Type: %s, Index: %s }", this.typeMatcher, this.indexMatch); + } + } + + /** + * Represents a builder for a fuzzy method contract. + * + * @author Kristian + */ + public static final class Builder extends AbstractFuzzyMember.Builder { + + @Override + public Builder requireModifier(int modifier) { + super.requireModifier(modifier); + return this; + } + + @Override + public Builder requirePublic() { + super.requirePublic(); + return this; + } + + @Override + public Builder banModifier(int modifier) { + super.banModifier(modifier); + return this; + } + + @Override + public Builder nameRegex(String regex) { + super.nameRegex(regex); + return this; + } + + @Override + public Builder nameRegex(Pattern pattern) { + super.nameRegex(pattern); + return this; + } + + @Override + public Builder nameExact(String name) { + super.nameExact(name); + return this; + } + + @Override + public Builder declaringClassExactType(Class declaringClass) { + super.declaringClassExactType(declaringClass); + return this; + } + + @Override + public Builder declaringClassSuperOf(Class declaringClass) { + super.declaringClassSuperOf(declaringClass); + return this; + } + + @Override + public Builder declaringClassDerivedOf(Class declaringClass) { + super.declaringClassDerivedOf(declaringClass); + return this; + } + + @Override + public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { + super.declaringClassMatching(classMatcher); + return this; + } + + /** + * Add a new required parameter by type for any matching method. + * + * @param type - the exact type this parameter must match. + * @return This builder, for chaining. + */ + public Builder parameterExactType(Class type) { + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type))); + return this; + } + + /** + * Add a new required parameter whose type must be a superclass of the given type. + *

+ * If a method parameter is of type Number, then any derived class here (Integer, Long, etc.) will match it. + * + * @param type - a type or less derived type of the matching parameter. + * @return This builder, for chaining. + */ + public Builder parameterSuperOf(Class type) { + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type))); + return this; + } + + /** + * Add a new required parameter whose type must be a derived class of the given class. + *

+ * If the method parameter has the type Integer, then the class Number here will match it. + * + * @param type - a type or more derived type of the matching parameter. + * @return This builder, for chaining. + */ + public Builder parameterDerivedOf(Class type) { + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type))); + return this; + } + + /** + * Add a new required parameter whose type must match the given class matcher. + * + * @param classMatcher - the class matcher. + * @return This builder, for chaining. + */ + public Builder parameterMatches(AbstractFuzzyMatcher> classMatcher) { + this.member.paramMatchers.add(new ParameterClassMatcher(classMatcher)); + return this; + } + + /** + * Add a new required parameter by type and position for any matching method. + * + * @param type - the exact type this parameter must match. + * @param index - the expected position in the parameter list. + * @return This builder, for chaining. + */ + public Builder parameterExactType(Class type, int index) { + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index)); + return this; + } + + /** + * Add a new required parameters by type and order for any matching method. + * + * @param types - the types of every parameters in order. + * @return This builder, for chaining. + */ + public Builder parameterExactArray(Class... types) { + this.parameterCount(types.length); + for (int i = 0; i < types.length; i++) { + this.parameterExactType(types[i], i); + } + + return this; + } + + /** + * Add a new required parameter whose type must be a superclass of the given type. + *

+ * If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it. + * + * @param type - a type or derived type of the matching parameter. + * @param index - the expected position in the parameter list. + * @return This builder, for chaining. + */ + public Builder parameterSuperOf(Class type, int index) { + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index)); + return this; + } + + /** + * Add a new required parameter whose type must be a derived class of the given class. + *

+ * If the method parameter has the type Integer, then the class Number here will match it. + * + * @param type - a type or more derived type of the matching parameter. + * @param index - the expected position in the parameter list. + * @return This builder, for chaining. + */ + public Builder parameterDerivedOf(Class type, int index) { + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type), index)); + return this; + } + + /** + * Add a new required parameter whose type must match the given class matcher and index. + * + * @param classMatcher - the class matcher. + * @param index - the expected position in the parameter list. + * @return This builder, for chaining. + */ + public Builder parameterMatches(AbstractFuzzyMatcher> classMatcher, int index) { + this.member.paramMatchers.add(new ParameterClassMatcher(classMatcher, index)); + return this; + } + + /** + * Set the expected number of parameters in the matching method. + * + * @param expectedCount - the number of parameters to expect. + * @return This builder, for chaining. + */ + public Builder parameterCount(int expectedCount) { + this.member.paramCount = expectedCount; + return this; + } + + /** + * Require a void method. + * + * @return This builder, for chaining. + */ + public Builder returnTypeVoid() { + return this.returnTypeExact(Void.TYPE); + } + + /** + * Set the return type of a matching method exactly. + * + * @param type - the exact return type. + * @return This builder, for chaining. + */ + public Builder returnTypeExact(Class type) { + this.member.returnMatcher = FuzzyMatchers.matchExact(type); + return this; + } + + /** + * Set the expected super class of the return type for every matching method. + * + * @param type - the return type, or a super class of it. + * @return This builder, for chaining. + */ + public Builder returnDerivedOf(Class type) { + this.member.returnMatcher = FuzzyMatchers.matchDerived(type); + return this; + } + + /** + * Set a matcher that must match the return type of a matching method. + * + * @param classMatcher - the exact return type. + * @return This builder, for chaining. + */ + public Builder returnTypeMatches(AbstractFuzzyMatcher> classMatcher) { + this.member.returnMatcher = classMatcher; + return this; + } + + /** + * Add a throwable exception that must match the given type exactly. + * + * @param type - exception type. + * @return This builder, for chaining. + */ + public Builder exceptionExactType(Class type) { + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type))); + return this; + } + + /** + * Add a throwable exception that must match the given type or be derived. + * + * @param type - exception type. + * @return This builder, for chaining. + */ + public Builder exceptionSuperOf(Class type) { + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type))); + return this; + } + + /** + * Add a throwable exception that must match the given matcher, + * + * @param classMatcher - the class matcher that must match. + * @return This builder, for chaining. + */ + public Builder exceptionMatches(AbstractFuzzyMatcher> classMatcher) { + this.member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher)); + return this; + } + + /** + * Add a throwable exception that must match the given type exactly and index. + * + * @param type - exception type. + * @param index - the position in the throwable list. + * @return This builder, for chaining. + */ + public Builder exceptionExactType(Class type, int index) { + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index)); + return this; + } + + /** + * Add a throwable exception that must match the given type or be derived and index. + * + * @param type - exception type. + * @param index - the position in the throwable list. + * @return This builder, for chaining. + */ + public Builder exceptionSuperOf(Class type, int index) { + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index)); + return this; + } + + /** + * Add a throwable exception that must match the given matcher and index. + * + * @param classMatcher - the class matcher that must match. + * @param index - the position in the throwable list. + * @return This builder, for chaining. + */ + public Builder exceptionMatches(AbstractFuzzyMatcher> classMatcher, int index) { + this.member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher, index)); + return this; + } + + @Override + @Nonnull + protected FuzzyMethodContract initialMember() { + // With mutable lists + return new FuzzyMethodContract(); + } + + @Override + public FuzzyMethodContract build() { + this.member.prepareBuild(); + return immutableCopy(this.member); + } + } +} \ No newline at end of file diff --git a/handlers/EventHandler_1_20_R1.java b/handlers/EventHandler_1_20_R1.java new file mode 100644 index 0000000..4224a61 --- /dev/null +++ b/handlers/EventHandler_1_20_R1.java @@ -0,0 +1,3593 @@ +package com.github.joelgodofwar.mmh.handlers; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.UUID; + +import javax.annotation.Nonnull; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Axolotl; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Cat; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Egg; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Fox; +import org.bukkit.entity.Frog; +import org.bukkit.entity.Goat; +import org.bukkit.entity.Horse; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Llama; +import org.bukkit.entity.MushroomCow; +import org.bukkit.entity.Panda; +import org.bukkit.entity.Parrot; +import org.bukkit.entity.PigZombie; +import org.bukkit.entity.Player; +import org.bukkit.entity.Rabbit; +import org.bukkit.entity.Sheep; +import org.bukkit.entity.Skeleton; +import org.bukkit.entity.Snowball; +import org.bukkit.entity.Strider; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.entity.TraderLlama; +import org.bukkit.entity.Trident; +import org.bukkit.entity.TropicalFish; +import org.bukkit.entity.TropicalFish.Pattern; +import org.bukkit.entity.Villager; +import org.bukkit.entity.WanderingTrader; +import org.bukkit.entity.Wolf; +import org.bukkit.entity.Zombie; +import org.bukkit.entity.ZombieVillager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.StriderTemperatureChangeEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.profile.PlayerProfile; +import org.bukkit.profile.PlayerTextures; + +import com.github.joelgodofwar.mmh.MoreMobHeads; +import com.github.joelgodofwar.mmh.common.PluginLibrary; +import com.github.joelgodofwar.mmh.common.error.DetailedErrorReporter; +import com.github.joelgodofwar.mmh.common.error.Report; +import com.github.joelgodofwar.mmh.enums.CatHeads; +import com.github.joelgodofwar.mmh.enums.HorseHeads; +import com.github.joelgodofwar.mmh.enums.LlamaHeads; +import com.github.joelgodofwar.mmh.enums.MobHeads; +import com.github.joelgodofwar.mmh.enums.MobHeads117; +import com.github.joelgodofwar.mmh.enums.MobHeads119; +import com.github.joelgodofwar.mmh.enums.MobHeads120; +import com.github.joelgodofwar.mmh.enums.RabbitHeads; +import com.github.joelgodofwar.mmh.enums.SheepHeads; +import com.github.joelgodofwar.mmh.enums.TropicalFishHeads; +import com.github.joelgodofwar.mmh.enums.VillagerHeads; +import com.github.joelgodofwar.mmh.enums.ZombieVillagerHeads; +import com.github.joelgodofwar.mmh.util.ChatColorUtils; +import com.github.joelgodofwar.mmh.util.ConfigHelper; +import com.github.joelgodofwar.mmh.util.StrUtils; +import com.github.joelgodofwar.mmh.util.Utils; +import com.github.joelgodofwar.mmh.util.VerifyConfig; +import com.github.joelgodofwar.mmh.util.YmlConfiguration; + +import de.tr7zw.changeme.nbtapi.NBTItem; + +/** + * 1.8 1_8_R1 1.8.3 1_8_R2 1.8.8 1_8_R3 1.9 1_9_R1 1.9.4 1_9_R2 1.10 1_10_R1 + * 1.11 1_11_R1 1.12 1_12_R1 1.13 1_13_R1 1.13.1 1_13_R2 1.14 1_14_R1 1.15 + * 1_15_R1 1.16.1 1_16_R1 1.16.2 1_16_R2 1.17 1_17_R1 + */ + +@SuppressWarnings("deprecation") +public class EventHandler_1_20_R1 implements CommandExecutor, TabCompleter, Listener { + /** Variables */ + MoreMobHeads mmh; + double defpercent = 13.0; + String world_whitelist; + String world_blacklist; + String mob_whitelist; + String mob_blacklist; + boolean debug; + YmlConfiguration chanceConfig; + File blockFile117; + File blockFile1172; + File blockFile1173; + //File blockFile119; + File blockFile120; + public FileConfiguration blockHeads = new YamlConfiguration(); + public FileConfiguration blockHeads2 = new YamlConfiguration(); + public FileConfiguration blockHeads3 = new YamlConfiguration(); + public FileConfiguration blockHeads4 = new YamlConfiguration(); + + List playerhead_recipes = new ArrayList(); + List blockhead_recipes = new ArrayList(); + List custometrade_recipes = new ArrayList(); + int BHNum, BHNum2, BHNum3, BHNum4; + VerifyConfig verify;// = new VerifyConfig(mmh.config); + DetailedErrorReporter reporter; + + + + @SuppressWarnings({ "static-access", "unchecked", "rawtypes" }) + public EventHandler_1_20_R1(final MoreMobHeads plugin) { // TODO: Top of code + try { // REPORT_EVENT_HANDLER_LOAD "Error while loading EventHandler." + /** Set variables */ + mmh = plugin; + reporter = new DetailedErrorReporter(mmh); + verify = new VerifyConfig(mmh); + mmh.LOGGER.log("Loading 1.20 EventHandler..."); + long startTime = System.currentTimeMillis(); + mmh.getCommand("mmh").setExecutor(this); + world_whitelist = mmh.config.getString("world.whitelist", ""); + world_blacklist = mmh.config.getString("world.blacklist", ""); + mob_whitelist = mmh.config.getString("mob.whitelist", ""); + mob_blacklist = mmh.config.getString("mob.blacklist", ""); + debug = plugin.debug; + chanceConfig = plugin.chanceConfig; + blockFile117 = new File(mmh.getDataFolder() + "" + File.separatorChar + "block_heads_1_17.yml"); + blockFile1172 = new File(mmh.getDataFolder() + "" + File.separatorChar + "block_heads_1_17_2.yml"); + blockFile120 = new File(mmh.getDataFolder() + "" + File.separatorChar + "block_heads_1_20.yml"); + if (mmh.config.getBoolean("wandering_trades.custom_wandering_trader", true)) { + if (!blockFile117.exists()) { + mmh.saveResource("block_heads_1_17.yml", true); + mmh.LOGGER.log("block_heads_1_17.yml not found! Creating in " + mmh.getDataFolder() + ""); + } + if (!blockFile1172.exists()) { + mmh.saveResource("block_heads_1_17_2.yml", true); + mmh.LOGGER.log("block_heads_1_17_2.yml not found! Creating in " + mmh.getDataFolder() + ""); + } + if (!blockFile120.exists()) { + mmh.saveResource("block_heads_1_20.yml", true); + mmh.LOGGER.log("block_heads_1_20.yml not found! Creating in " + mmh.getDataFolder() + ""); + } + blockHeads = new YamlConfiguration(); + try { + mmh.LOGGER.log("Loading " + blockFile117 + "..."); + blockHeads.load(blockFile117); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + blockHeads2 = new YamlConfiguration(); + try { + mmh.LOGGER.log("Loading " + blockFile1172 + "..."); + blockHeads2.load(blockFile1172); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + if (Double.parseDouble(mmh.getMCVersion().substring(0, 4)) >= 1.20) { + blockFile1173 = blockFile120; + } + blockHeads3 = new YamlConfiguration(); + try { + mmh.LOGGER.log("Loading " + blockFile1173 + "..."); + blockHeads3.load(blockFile1173); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_BLOCKHEAD_LOAD_ERROR).error(exception)); + } + + boolean showlore = mmh.config.getBoolean("head_settings.lore.show_plugin_name", true); + ArrayList headlore = new ArrayList(); + headlore.add(ChatColor.AQUA + "" + mmh.getName()); + + mmh.LOGGER.log("Loading PlayerHead Recipes..."); + for (int i = 1; i < (mmh.playerHeads.getInt("players.number") + 1); i++) { + ItemStack price1 = mmh.playerHeads.getItemStack("players.player_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = mmh.playerHeads.getItemStack("players.player_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = mmh.playerHeads.getItemStack("players.player_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + mmh.playerHeads.getInt("players.player_" + i + ".quantity", 3)); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + playerhead_recipes.add(recipe); + } + mmh.LOGGER.log(playerhead_recipes.size() + " PlayerHead Recipes ADDED..."); + mmh.LOGGER.log("Loading BlockHead Recipes..."); + BHNum = blockHeads.getInt("blocks.number"); + // BlockHeads + mmh.LOGGER.log("BlockHeads=" + BHNum); + for (int i = 1; i < (BHNum + 1); i++) { + ItemStack price1 = blockHeads.getItemStack("blocks.block_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = blockHeads.getItemStack("blocks.block_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = blockHeads.getItemStack("blocks.block_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + blockHeads.getInt("blocks.block_" + i + ".quantity", 8)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + blockhead_recipes.add(recipe); + } + BHNum2 = blockHeads2.getInt("blocks.number"); + // blockHeads 2 + mmh.LOGGER.log("BlockHeads2=" + BHNum2); + for (int i = 1; i < (BHNum2 + 1); i++) { + ItemStack price1 = blockHeads2.getItemStack("blocks.block_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = blockHeads2.getItemStack("blocks.block_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = blockHeads2.getItemStack("blocks.block_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + blockHeads2.getInt("blocks.block_" + i + ".quantity", 8)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + blockhead_recipes.add(recipe); + } + BHNum3 = blockHeads3.getInt("blocks.number"); + // blockHeads 3 + mmh.LOGGER.log("BlockHeads3=" + BHNum3); + for (int i = 1; i < (BHNum3 + 1); i++) { + ItemStack price1 = blockHeads3.getItemStack("blocks.block_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = blockHeads3.getItemStack("blocks.block_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = blockHeads3.getItemStack("blocks.block_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + blockHeads3.getInt("blocks.block_" + i + ".quantity", 8)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + blockhead_recipes.add(recipe); + } + + mmh.LOGGER.log(blockhead_recipes.size() + " BlockHead Recipes ADDED..."); + mmh.LOGGER.log("Loading CustomTrades Recipes..."); + for (int i = 1; i < (mmh.traderCustom.getInt("custom_trades.number") + 1); i++) { + ItemStack price1 = mmh.traderCustom.getItemStack("custom_trades.trade_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = mmh.traderCustom.getItemStack("custom_trades.trade_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = mmh.traderCustom.getItemStack("custom_trades.trade_" + i + ".itemstack", + new ItemStack(Material.AIR)); + /** Code to fix missing noteblock SkullMeta */ + boolean doIt = Utils.isSupportedVersion("1.20.2.3936"); + if(doIt) { + mmh.LOGGER.log("doIt=" + doIt); + if(itemstack.getType().equals(Material.PLAYER_HEAD)) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + String sound = mmh.traderCustom.getString("custom_trades.trade_" + i + ".note_block_sound", "entity.player.hurt"); + meta.setNoteBlockSound(NamespacedKey.minecraft(sound)); + itemstack.setItemMeta(meta); + }//*/ + } + /** Code to fix missing noteblock SkullMeta */ + MerchantRecipe recipe = new MerchantRecipe(itemstack, + mmh.traderCustom.getInt("custom_trades.trade_" + i + ".quantity", 1)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + custometrade_recipes.add(recipe); + } + mmh.LOGGER.log(custometrade_recipes.size() + " CustomTrades Recipes ADDED..."); + mmh.LOGGER.log("EventHandler_1_20 took " + mmh.LoadTime(startTime) + "ms to load"); + } + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_EVENT_HANDLER_LOAD).error(exception)); + } + } + + @EventHandler + public void onEntityDamage(EntityDamageEvent event) { + try { + Entity entity = event.getEntity(); + DamageCause cause = event.getCause(); + + if(cause.equals(DamageCause.FIRE_TICK)) { + //mmh.entityDamageCause.put(entity.getUniqueId(), cause); + UUID eUUID = mmh.entityPlayers.get(entity.getUniqueId()); + mmh.playerWeapons.put(eUUID, mmh.playerWeapons.get(eUUID)); + } + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.UNHANDLED_ENTITYDAMAGE_EVENT_ERROR).error(exception)); + } + } + + @SuppressWarnings({}) + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + try { // REPORT_EDE_EVENT_ERROR "Unable to get damager weapon." + mmh.LOGGER.debug("EDEE - START"); + mmh.LOGGER.debug("EDEE - getEntity=" + event.getEntity().getName().toString()); + mmh.LOGGER.debug("EDEE - getDamager=" + event.getDamager().getName().toString()); + + Player player = null; + if (event.getDamager() instanceof Player) { + player = (Player) event.getDamager(); + if ((event.getEntity() instanceof EnderCrystal)) { + /** Is Player and Is End Crystal */ + EnderCrystal ec = (EnderCrystal) event.getEntity(); + mmh.endCrystals.put(ec.getUniqueId(), player.getUniqueId()); + } + } else if (event.getDamager() instanceof Arrow) { + Arrow arrow = (Arrow) event.getDamager(); + if ((arrow.getShooter() instanceof Player) && !(event.getEntity() instanceof EnderCrystal)) { + /** Is Player but Not End Crystal */ + player = (Player) arrow.getShooter(); + } else if ((arrow.getShooter() instanceof Player) && (event.getEntity() instanceof EnderCrystal)) { + /** Is Player and Is End Crystal */ + player = (Player) arrow.getShooter(); + EnderCrystal ec = (EnderCrystal) event.getEntity(); + mmh.endCrystals.put(ec.getUniqueId(), player.getUniqueId()); + } else { + return; // Not Player or Not End Crystal + } + } else if (event.getDamager() instanceof ThrownPotion) { + ThrownPotion potion = (ThrownPotion) event.getDamager(); + if (!(potion.getShooter() instanceof Player)) { + return; + } + player = (Player) potion.getShooter(); + } else if (event.getDamager() instanceof Snowball) { + Snowball snowball = (Snowball) event.getDamager(); + if ((snowball.getShooter() instanceof Player) && !(event.getEntity() instanceof EnderCrystal)) { + /** Is Player but Not End Crystal */ + player = (Player) snowball.getShooter(); + } else if ((snowball.getShooter() instanceof Player) && (event.getEntity() instanceof EnderCrystal)) { + /** Is Player and Is End Crystal */ + player = (Player) snowball.getShooter(); + EnderCrystal ec = (EnderCrystal) event.getEntity(); + mmh.endCrystals.put(ec.getUniqueId(), player.getUniqueId()); + } else { + return; // Not Player or Not End Crystal + } + } else if (event.getDamager() instanceof Egg) { + Egg egg = (Egg) event.getDamager(); + if ((egg.getShooter() instanceof Player) && !(event.getEntity() instanceof EnderCrystal)) { + /** Is Player but Not End Crystal */ + player = (Player) egg.getShooter(); + } else if ((egg.getShooter() instanceof Player) && (event.getEntity() instanceof EnderCrystal)) { + /** Is Player and Is End Crystal */ + player = (Player) egg.getShooter(); + EnderCrystal ec = (EnderCrystal) event.getEntity(); + mmh.endCrystals.put(ec.getUniqueId(), player.getUniqueId()); + } else { + return; // Not Player or Not End Crystal + } + } else if (event.getDamager() instanceof Trident) { + Trident trident = (Trident) event.getDamager(); + if ((trident.getShooter() instanceof Player) && !(event.getEntity() instanceof EnderCrystal)) { + /** Is Player but Not End Crystal */ + player = (Player) trident.getShooter(); + } else if ((trident.getShooter() instanceof Player) && (event.getEntity() instanceof EnderCrystal)) { + /** Is Player and Is End Crystal */ + player = (Player) trident.getShooter(); + EnderCrystal ec = (EnderCrystal) event.getEntity(); + mmh.endCrystals.put(ec.getUniqueId(), player.getUniqueId()); + } else { + return; // Not Player or Not End Crystal + } + } else if (event.getDamager() instanceof TNTPrimed) { + TNTPrimed tnt = (TNTPrimed) event.getDamager(); + if (!(tnt.getSource() instanceof Player)) { + return; + } + player = (Player) tnt.getSource(); + } else if (event.getDamager() instanceof EnderCrystal) { + EnderCrystal ec = (EnderCrystal) event.getDamager(); + UUID pUUID = mmh.endCrystals.get(ec.getUniqueId()); + player = Bukkit.getPlayer(pUUID); + mmh.playerWeapons.put(player.getUniqueId(), new ItemStack(Material.END_CRYSTAL)); + return; + } else if (event.getDamager() instanceof Creeper) { + Creeper creeper = (Creeper) event.getDamager(); + creeper.isPowered(); + return; + } else { + + return; + } + + Entity entity = event.getEntity(); + EntityDamageEvent ede = entity.getLastDamageCause(); + if(ede != null) { + DamageCause dc = ede.getCause(); + mmh.LOGGER.debug("EDEE - dc=" + dc); + } + // Store the damaging player's UUID and the damaging weapon in the map + + if(player == null) { + return; + } + mmh.LOGGER.debug("EDEE - UUID=" + player.getUniqueId()); + //mmh.LOGGER.debug("EDEE - Weapon=" + resolveDamagingWeapon(player.getInventory(), event.getCause()).orElse(null)); + mmh.entityPlayers.put(event.getEntity().getUniqueId(), player.getUniqueId()); + mmh.playerWeapons.put(player.getUniqueId(), resolveDamagingWeapon(player.getInventory(), event.getCause()).orElse(null)); + // Log Entity and Player UUIDs + // Log Player UUID and Weapon + + // if Fire_tick get Player UUID + + // Log Player UUID and Weapon stored. + + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.UNHANDLED_ENTITYDAMAGEBYENTITY_EVENT_ERROR).error(exception)); + } + } + + /** Events go here */ + @SuppressWarnings({ "unchecked", "unused", "rawtypes" }) + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityDeathEvent(EntityDeathEvent event) {// TODO: EnityDeathEvent + try { + LivingEntity entity = event.getEntity(); + // Verify that the killer is a player + if (!(entity.getKiller() instanceof Player) && !(entity.getKiller() instanceof Creeper)) { + return; + } + + World world = event.getEntity().getWorld(); + List Drops = event.getDrops(); + world_whitelist = mmh.config.getString("global_settings.world.whitelist", ""); + world_blacklist = mmh.config.getString("global_settings.world.blacklist", ""); + mob_whitelist = mmh.config.getString("head_settings.mob_heads.whitelist", ""); + mob_blacklist = mmh.config.getString("head_settings.mob_heads.blacklist", ""); + + mmh.LOGGER.debug("EDE - world_whitelist=" + world_whitelist); + mmh.LOGGER.debug("EDE - world_blacklist=" + world_blacklist); + mmh.LOGGER.debug("EDE - mob_whitelist=" + mob_whitelist); + mmh.LOGGER.debug("EDE - mob_blacklist=" + mob_blacklist); + + try { // REPORT_WHITE_BLACK_LIST "Unable to parse global whitelist/blacklist" + if ((world_whitelist != null) && !world_whitelist.isEmpty() && (world_blacklist != null) + && !world_blacklist.isEmpty()) { + if (!StrUtils.stringContains(world_whitelist, world.getName().toString()) + && StrUtils.stringContains(world_blacklist, world.getName().toString())) { + + mmh.LOGGER.debug("EDE - World - On blacklist and Not on whitelist."); + + return; + } else if (!StrUtils.stringContains(world_whitelist, world.getName().toString()) + && !StrUtils.stringContains(world_blacklist, world.getName().toString())) { + + mmh.LOGGER.debug("EDE - World - Not on whitelist."); + + return; + } else if (!StrUtils.stringContains(world_whitelist, world.getName().toString())) { + + } + } else if ((world_whitelist != null) && !world_whitelist.isEmpty()) { + if (!StrUtils.stringContains(world_whitelist, world.getName().toString())) { + + mmh.LOGGER.debug("EDE - World - Not on whitelist."); + + return; + } + } else if ((world_blacklist != null) && !world_blacklist.isEmpty()) { + if (StrUtils.stringContains(world_blacklist, world.getName().toString())) { + + mmh.LOGGER.debug("EDE - World - On blacklist."); + + return; + } + } + } catch(Exception exception){ + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_WHITE_BLACK_LIST).error(exception)); + } + + if ((entity instanceof Player) + && ((entity.getKiller() instanceof Player) || (entity.getKiller() instanceof Creeper))) { // TODO: Player Kill Player + try{ // REPORT_PLAYER_KILL_PLAYER "Unable to parse Player Death." + mmh.LOGGER.debug("EDE Entity is Player"); + if ((entity.getKiller().hasPermission("moremobheads.players") || mmh.isDev) || (entity.getKiller() instanceof Creeper)) { + + mmh.LOGGER.debug("EDE DropIt=" + mmh.DropIt(event, chanceConfig.getDouble("chance_percent.player", 50.0))); + mmh.LOGGER.debug("EDE chance_percent.player=" + chanceConfig.getDouble("chance_percent.player", 50.0)); + + if (mmh.DropIt(event, chanceConfig.getDouble("chance_percent.player", 50.0))) { + // Player daKiller = entity.getKiller(); + + mmh.LOGGER.debug("EDE Killer is Player line:1073"); + + Player victim = (Player) entity; + Player killer = entity.getKiller(); + String killerName; + String entityName; + PlayerProfile profile = victim.getPlayerProfile(); + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + PlayerTextures textures = profile.getTextures(); + + mmh.LOGGER.debug(" EDE PlayerProfile getName: " + profile.getName() ); + mmh.LOGGER.debug(" EDE PlayerProfile UUID: " + profile.getUniqueId() ); + mmh.LOGGER.debug(" EDE PlayerProfile Skin: " + profile.getTextures().getSkin() ); + + killerName = mmh.getNickname(killer); + entityName = mmh.getNickname(victim); + + head = mmh.makeHead(entityName, profile.getTextures().getSkin().toString(), profile.getUniqueId().toString(), entity.getType(), entity.getKiller()); + mmh.playerGiveOrDropHead(entity.getKiller(), head); + mmh.LOGGER.debug("EDE " + ((Player) entity).getDisplayName().toString() + " Player Head Dropped"); + if (mmh.config.getBoolean("head_settings.player_heads.announce_kill.enabled", true)) { + announceBeheading(entity, entityName, killer, + mmh.config.getBoolean("head_settings.player_heads.announce_kill.displayname", true)); + } + } + return; + } else { + mmh.LOGGER.debug("EDE Killer does not have permission \"moremobheads.players\""); + } + } catch(Exception exception){ + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLAYER_KILL_PLAYER).error(exception)); + } + + } else if (event.getEntity() instanceof LivingEntity) { + if ((entity.getKiller() instanceof Player) || (entity.getKiller() instanceof Creeper)) { + String name = event.getEntityType().toString().replace(" ", "_"); + mmh.LOGGER.debug("EDE name=" + name); + String isNametag = null; + @Nonnull + PersistentDataContainer pdc = entity.getPersistentDataContainer(); + isNametag = entity.getPersistentDataContainer().get(mmh.NAMETAG_KEY, PersistentDataType.STRING);// .getScoreboardTags();// + if ((isNametag != null)) { + mmh.LOGGER.debug("EDE isNametag=" + isNametag.toString()); + } + + if ( (entity.getKiller() instanceof Creeper) || (entity.getKiller().hasPermission("moremobheads.mobs") || mmh.isDev) ) { + if ( ((entity.getKiller() instanceof Creeper) || (entity.getKiller().hasPermission("moremobheads.nametag") || mmh.isDev) ) && (isNametag != null)) { + + try{ // REPORT_PLAYER_NAMED_MOB "Unable to parse named mob kill." + EntityDamageEvent ede = entity.getLastDamageCause(); + if(ede != null) { + DamageCause dc = ede.getCause(); + mmh.LOGGER.log("EDEE - dc=" + dc); + if(dc.equals(DamageCause.FIRE_TICK)) { + + } + } + if ((entity.getCustomName() != null) && !(entity.getCustomName().contains("jeb_")) && !(entity.getCustomName().contains("Toast"))) { + mmh.LOGGER.debug("EDE customname=" + entity.getCustomName().toString()); + if ((entity instanceof Skeleton) || (entity instanceof Zombie) || (entity instanceof PigZombie)) { + if (mmh.getServer().getPluginManager().getPlugin("SilenceMobs") != null) { + if (entity.getCustomName().toLowerCase().contains("silenceme") + || entity.getCustomName().toLowerCase().contains("silence me")) { + return; + } + } + boolean enforcewhitelist = mmh.config.getBoolean("head_settings.player_heads.whitelist.enforce", false); + boolean enforceblacklist = mmh.config.getBoolean("head_settings.player_heads.blacklist.enforce", false); + boolean onwhitelist = mmh.config.getString("head_settings.player_heads.whitelist.player_head_whitelist", "") + .toLowerCase().contains(entity.getCustomName().toLowerCase()); + boolean onblacklist = mmh.config.getString("head_settings.player_heads.blacklist.player_head_blacklist", "") + .toLowerCase().contains(entity.getCustomName().toLowerCase()); + /** */ + if (mmh.DropIt(event, chanceConfig.getDouble("named_mob", 10.0))) { + boolean isTrue = false; + if (enforcewhitelist && enforceblacklist) { + if (onwhitelist && !(onblacklist)) { + isTrue = true; + }else { + isTrue = false; + } + } else if (enforcewhitelist && !enforceblacklist) { + if (onwhitelist) { + isTrue = true; + }else { + isTrue = false; + } + } else if (!enforcewhitelist && enforceblacklist) { + if (!onblacklist) { + isTrue = true; + }else { + isTrue = false; + } + } else if (!enforcewhitelist && !enforceblacklist) { + isTrue = true; + } + if(isTrue) { + PlayerProfile profile = Bukkit.createPlayerProfile(entity.getCustomName()); + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) head.getItemMeta(); + PlayerTextures textures = profile.getTextures(); + + profile.setTextures(textures); + meta.setOwnerProfile(profile); + meta.setNoteBlockSound(NamespacedKey.minecraft( "entity.player.hurt" )); + ArrayList lore = new ArrayList(); + if(mmh.config.getBoolean("head_settings.lore.show_killer", true)){ + lore.add(ChatColor.RESET + ChatColorUtils.setColors( mmh.langName.getString("killedby", "Killed By ").replace("" + , mmh.getName(entity.getType(), entity.getKiller()) ) ) ); + } + if(mmh.config.getBoolean("head_settings.lore.show_plugin_name", true)){ + lore.add(ChatColor.AQUA + "MoreMobHeads"); + } + meta.setLore(lore); + meta.setLore(lore); + head.setItemMeta(meta); + if(entity.getEquipment().getHelmet().isSimilar(head)) { + Drops.add(head); + mmh.LOGGER.debug(" EDE " + entity.getCustomName().toString() + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, entity.getCustomName(), + entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } else if(!entity.getEquipment().getHelmet().isSimilar(head)) { + mmh.LOGGER.debug(" EDE NamedMob is not waering PlayerHead drop canceled."); + } + return; + } + } + if ((mob_whitelist != null) && !mob_whitelist.isEmpty() && (mob_blacklist != null) + && !mob_blacklist.isEmpty()) { + if (!StrUtils.stringContains(mob_whitelist, name)) {// mob_whitelist.contains(name) + mmh.LOGGER.log("EDE - Mob - Not on whitelist. Mob=" + name); + return; + } + } else if ((mob_whitelist != null) && !mob_whitelist.isEmpty()) { + if (!StrUtils.stringContains(mob_whitelist, name) + && StrUtils.stringContains(mob_blacklist, name)) {// mob_whitelist.contains(name) + mmh.LOGGER.log("EDE - Mob - Not on whitelist - Is on blacklist. Mob=" + name); + return; + } + } else if ((mob_blacklist != null) && !mob_blacklist.isEmpty()) { + if (StrUtils.stringContains(mob_blacklist, name)) { + mmh.LOGGER.log("EDE - Mob - On blacklist. Mob=" + name); + return; + } + } + } + } + } catch(Exception exception){ + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLAYER_NAMED_MOB).error(exception)); + } + } + + try { // REPORT_PLAYER_KILL_MOB "Unable to parse Mob Kill." + switch (name) { + case "CREEPER": + // ConfigHelper.Double(chanceConfig, + // "chance_percent.creeper", defpercent) + Creeper creeper = (Creeper) event.getEntity(); + double cchance = ConfigHelper.Double(chanceConfig, "chance_percent.creeper", defpercent); + if (creeper.isPowered()) { + name = "CREEPER_CHARGED"; + cchance = ConfigHelper.Double(chanceConfig, "chance_percent.creeper_charged", defpercent); + } + if (mmh.DropIt(event, cchance)) { + mmh.LOGGER.debug("EDE Creeper vanilla=" + + mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.creeper", false)); + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.creeper", false) + && (name != "CREEPER_CHARGED")) { + Drops.add(new ItemStack(Material.CREEPER_HEAD)); + } else { // mmh.langName + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + //Drops.add(MoreMobHeadsLib.addSound(mmh.makeSkull(MobHeads.valueOf(name).getTexture().toString(), mmh.langName.getString(name.toLowerCase(),MobHeads.valueOf(name).getName() + " Head"), entity.getKiller()), entity)); + } // MobHeads.valueOf(name).getName() + " Head" + mmh.LOGGER.debug("EDE Creeper Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "").replace(" Head", ""), + entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "ENDER_DRAGON": + // ConfigHelper.Double(chanceConfig, + // "chance_percent.ender_dragon", defpercent) + if (mmh.DropIt(event, + ConfigHelper.Double(chanceConfig, "chance_percent.ender_dragon", defpercent))) { + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.ender_dragon", false)) { + Drops.add(new ItemStack(Material.DRAGON_HEAD)); + } else { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + } + mmh.LOGGER.debug("EDE Ender Dragon Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity,mmh.langName.getString(name.toLowerCase(),MobHeads.valueOf(name).getName() + "").replace(" Head", "") + ,entity.getKiller(),mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname",true)); + } + } + break; + case "PIGLIN": + // ConfigHelper.Double(chanceConfig, "chance_percent.ender_dragon", defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent.piglin", defpercent))) { + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.piglin", false)) { + Drops.add(new ItemStack(Material.PIGLIN_HEAD)); + } else { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + } + mmh.LOGGER.debug("EDE Ender Dragon Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity,mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + .replace(" Head", ""),entity.getKiller(),mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "SKELETON": + // ConfigHelper.Double(chanceConfig, "chance_percent.skeleton", defpercent) + if (mmh.DropIt(event, + ConfigHelper.Double(chanceConfig, "chance_percent.skeleton", defpercent))) { + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.skeleton", false)) { + Drops.add(new ItemStack(Material.SKELETON_SKULL)); + } else { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + } + mmh.LOGGER.debug("EDE Skeleton vanilla=" + + mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.skeleton", false)); + mmh.LOGGER.debug("EDE Skeleton Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity,mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + .replace(" Head", ""),entity.getKiller(),mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "WITHER_SKELETON": + // ConfigHelper.Double(chanceConfig, "chance_percent.wither_skeleton", + // defpercent) + if (mmh.DropIt(event, + ConfigHelper.Double(chanceConfig, "chance_percent.wither_skeleton", defpercent))) { + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.wither_skeleton", false)) { + Drops.add(new ItemStack(Material.WITHER_SKELETON_SKULL)); + } else { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + } + mmh.LOGGER.debug("EDE Wither Skeleton Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "ZOMBIE": + // ConfigHelper.Double(chanceConfig, "chance_percent.zombie", defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent.zombie", defpercent))) { + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.zombie", false)) { + Drops.add(new ItemStack(Material.ZOMBIE_HEAD)); + } else { + + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + } + mmh.LOGGER.debug("EDE Zombie vanilla="+ mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.zombie", false)); + mmh.LOGGER.debug("EDE Zombie Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity,mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + .replace(" Head", ""),entity.getKiller(),mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "TROPICAL_FISH": + TropicalFish daFish = (TropicalFish) entity; + DyeColor daFishBody = daFish.getBodyColor(); + DyeColor daFishPatternColor = daFish.getPatternColor(); + Pattern daFishType = daFish.getPattern(); + mmh.LOGGER.log("bodycolor=" + daFishBody.toString() + "\nPatternColor=" + daFishPatternColor.toString() + + "\nPattern=" + daFishType.toString()); + // TropicalFishHeads daFishEnum = TropicalFishHeads.getIfPresent(name); + String daFishName = mmh.getNamedTropicalFishName(daFishType, daFishBody, daFishPatternColor); + mmh.LOGGER.log("daFishName: " + daFishName); + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent.tropical_fish." + daFishName.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daFishName.toLowerCase(), TropicalFishHeads.valueOf(name + "_" + daFishName).getName() + "") + , TropicalFishHeads.valueOf(name + "_" + daFishName).getTexture().toString(), TropicalFishHeads.valueOf(name + "_" + daFishName).getOwner(), entity.getType(), entity.getKiller() ) + //MoreMobHeadsLib.addSound(mmh.makeSkull(TropicalFishHeads.valueOf(name + "_" + daFishName).getTexture().toString() + // , mmh.langName.getString(name.toLowerCase() + "." + daFishName.toLowerCase(), TropicalFishHeads.valueOf(name + "_" + daFishName).getName() + " Head") + // , entity.getKiller()), entity) + ); + mmh.LOGGER.debug("EDE TROPICAL_FISH:" + daFishName + " head dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase() + "." + daFishName.toLowerCase(), TropicalFishHeads.valueOf(name + "_" + daFishName).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "WITHER": + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), defpercent))) { + String name2 = name + "_NORMAL"; + Drops.add( + mmh.makeHead( mmh.langName.getString(name2.toLowerCase(), MobHeads.valueOf(name2).getName() + "") + , MobHeads.valueOf(name2).getTexture().toString(), MobHeads.valueOf(name2).getOwner(), entity.getType(), entity.getKiller() ) + /**MoreMobHeadsLib.addSound(mmh.makeSkull(MobHeads.valueOf(name2).getTexture().toString(), mmh.langName.getString(name2.toLowerCase().replace("_", ".") + , MobHeads.valueOf(name2).getName() + " Head"), entity.getKiller()), entity)//*/ + ); + mmh.LOGGER.debug("EDE " + name2 + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name2.toLowerCase().replace("_", ".") + , MobHeads.valueOf(name2).getName() + "").replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + if (coinFlip()) { + name2 = name + "_PROJECTILE"; + Drops.add( + mmh.makeHead( mmh.langName.getString(name2.toLowerCase(), MobHeads.valueOf(name2).getName() + "") + , MobHeads.valueOf(name2).getTexture().toString(), MobHeads.valueOf(name2).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name2 + " Head Dropped"); + } + if (coinFlip()) { + name2 = name + "_BLUE_PROJECTILE"; + Drops.add( + mmh.makeHead( mmh.langName.getString(name2.toLowerCase(), MobHeads.valueOf(name2).getName() + "") + , MobHeads.valueOf(name2).getTexture().toString(), MobHeads.valueOf(name2).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name2 + " Head Dropped"); + } + } + break; + case "WOLF": + Wolf wolf = (Wolf) event.getEntity(); + // ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + defpercent))) { + if (wolf.isAngry()) { + String name2 = name + "_ANGRY"; + Drops.add( + mmh.makeHead( mmh.langName.getString(name2.toLowerCase(), MobHeads.valueOf(name2).getName() + "") + , MobHeads.valueOf(name2).getTexture().toString(), MobHeads.valueOf(name2).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Angry Wolf Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase() + "_angry", MobHeads.valueOf(name + "_ANGRY").getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } else { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Wolf Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + } + break; + case "FOX": + Fox dafox = (Fox) entity; + String dafoxtype = dafox.getFoxType().toString(); + mmh.LOGGER.debug("EDE dafoxtype=" + dafoxtype); + // ConfigHelper.Double(chanceConfig, "chance_percent.fox." + + // dafoxtype.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.fox." + dafoxtype.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + dafoxtype.toLowerCase(), MobHeads.valueOf(name + "_" + dafoxtype).getName() + "") + , MobHeads.valueOf(name + "_" + dafoxtype).getTexture().toString(), MobHeads.valueOf(name + "_" + dafoxtype).getOwner(), entity.getType(), entity.getKiller() ) + ); // + mmh.LOGGER.debug("EDE Fox Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + dafoxtype.toLowerCase(), MobHeads.valueOf(name + "_" + dafoxtype).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + + break; + case "CAT": + Cat dacat = (Cat) entity; + String dacattype = dacat.getCatType().toString(); + mmh.LOGGER.debug("entity cat=" + dacat.getCatType()); + // ConfigHelper.Double(chanceConfig, "chance_percent.cat." + + // dacattype.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.cat." + dacattype.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + dacattype.toLowerCase(), CatHeads.valueOf(dacattype).getName() + "") + , CatHeads.valueOf(dacattype).getTexture().toString(), CatHeads.valueOf(dacattype).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Cat Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + dacattype.toLowerCase(), CatHeads.valueOf(dacattype).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "OCELOT": + // ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(MobHeads.valueOf(name).getNameString(), + MobHeads.valueOf(name).getName() + "").replace(" Head", ""), + entity.getKiller(), + mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + mmh.LOGGER.debug("EDE " + MobHeads.valueOf(name) + " killed"); + break; + case "BEE": + Bee daBee = (Bee) entity; + int daAnger = daBee.getAnger(); + mmh.LOGGER.debug("EDE daAnger=" + daAnger); + boolean daNectar = daBee.hasNectar(); + mmh.LOGGER.debug("EDE daNectar=" + daNectar); + if ((daAnger >= 1) && (daNectar == true)) { + // ConfigHelper.Double(chanceConfig, "chance_percent.bee.angry_pollinated", + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.bee.angry_pollinated", defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString("bee.angry_pollinated", MobHeads.valueOf("BEE_ANGRY_POLLINATED").getName() + "") + , MobHeads.valueOf("BEE_ANGRY_POLLINATED").getTexture().toString(), MobHeads.valueOf("BEE_ANGRY_POLLINATED").getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Angry Pollinated Bee Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + ".angry_pollinated", MobHeads.valueOf("BEE_ANGRY_POLLINATED").getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + } else if ((daAnger >= 1) && (daNectar == false)) { + // ConfigHelper.Double(chanceConfig, "chance_percent.bee.angry", defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent.bee.angry", defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString("bee.angry", MobHeads.valueOf("BEE_ANGRY").getName() + "") + , MobHeads.valueOf("BEE_ANGRY").getTexture().toString(), MobHeads.valueOf("BEE_ANGRY").getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Angry Bee Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + ".angry", MobHeads.valueOf("BEE_ANGRY").getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + } else if ((daAnger == 0) && (daNectar == true)) { + // ConfigHelper.Double(chanceConfig, "chance_percent.bee.pollinated", + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent.bee.pollinated", defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString("bee.pollinated", MobHeads.valueOf("BEE_POLLINATED").getName() + "") + , MobHeads.valueOf("BEE_POLLINATED").getTexture().toString(), MobHeads.valueOf("BEE_POLLINATED").getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Pollinated Bee Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + ".pollinated", MobHeads.valueOf("BEE_POLLINATED").getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + } else if ((daAnger == 0) && (daNectar == false)) { + // ConfigHelper.Double(chanceConfig, "chance_percent.bee.chance_percent", + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent.bee.normal", defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString("bee.none", MobHeads.valueOf("BEE").getName() + "") + , MobHeads.valueOf("BEE").getTexture().toString(), MobHeads.valueOf("BEE").getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Bee Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + ".none", MobHeads.valueOf("BEE").getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + } + break; + case "LLAMA": + Llama daLlama = (Llama) entity; + String daLlamaColor = daLlama.getColor().toString(); + //String daLlamaName = LlamaHeads.valueOf(name + "_" + daLlamaColor).getName() + " Head";// daLlamaColor.toLowerCase().replace("b", + + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.llama." + daLlamaColor.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daLlamaColor.toLowerCase(), LlamaHeads.valueOf(name + "_" + daLlamaColor).getName() + "") + , LlamaHeads.valueOf(name + "_" + daLlamaColor).getTexture().toString(), LlamaHeads.valueOf(name + "_" + daLlamaColor).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Llama Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daLlamaColor.toLowerCase(), LlamaHeads.valueOf(name + "_" + daLlamaColor).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "HORSE": + Horse daHorse = (Horse) entity; + String daHorseColor = daHorse.getColor().toString(); + //String daHorseName = HorseHeads.valueOf(name + "_" + daHorseColor).getName() + " Head";// daHorseColor.toLowerCase().replace("b", + + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.horse." + daHorseColor.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daHorseColor.toLowerCase(), HorseHeads.valueOf(name + "_" + daHorseColor).getName() + "") + , HorseHeads.valueOf(name + "_" + daHorseColor).getTexture().toString(), HorseHeads.valueOf(name + "_" + daHorseColor).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Horse Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daHorseColor.toLowerCase(), HorseHeads.valueOf(name + "_" + daHorseColor).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "MOOSHROOM": + name = "MUSHROOM_COW"; + case "MUSHROOM_COW": + MushroomCow daMushroom = (MushroomCow) entity; + String daCowVariant = daMushroom.getVariant().toString(); + //String daCowName = daCowVariant.toLowerCase().replace("br", "Br").replace("re", "Re") + " Mooshroom Head"; + mmh.LOGGER.debug("EDE " + name + "_" + daCowVariant); + // ConfigHelper.Double(chanceConfig, "chance_percent.mushroom_cow." + + // daCowVariant.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.mushroom_cow." + daCowVariant.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daCowVariant.toLowerCase(), MobHeads.valueOf(name + "_" + daCowVariant).getName() + "") + , MobHeads.valueOf(name + "_" + daCowVariant).getTexture().toString(), MobHeads.valueOf(name + "_" + daCowVariant).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Mooshroom Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daCowVariant.toLowerCase(), MobHeads.valueOf(name + "_" + daCowVariant).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "PANDA": + Panda daPanda = (Panda) entity; + String daPandaGene = daPanda.getMainGene().toString(); + /**String daPandaName = daPandaGene.toLowerCase().replace("br", "Br").replace("ag", "Ag") + .replace("la", "La").replace("no", "No").replace("p", "P").replace("we", "We") + .replace("wo", "Wo") + " Panda Head";// + if (daPandaGene.equalsIgnoreCase("normal")) { + daPandaName.replace("normal ", ""); + }//*/ + mmh.LOGGER.debug("EDE " + name + "_" + daPandaGene); + // ConfigHelper.Double(chanceConfig, "chance_percent.panda." + + // daPandaGene.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.panda." + daPandaGene.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daPandaGene.toLowerCase(), MobHeads.valueOf(name + "_" + daPandaGene).getName() + "") + , MobHeads.valueOf(name + "_" + daPandaGene).getTexture().toString(), MobHeads.valueOf(name + "_" + daPandaGene).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Panda Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daPandaGene.toLowerCase(), MobHeads.valueOf(name + "_" + daPandaGene).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "PARROT": + Parrot daParrot = (Parrot) entity; + String daParrotVariant = daParrot.getVariant().toString(); + //String daParrotName = daParrotVariant.toLowerCase().replace("b", "B").replace("c", "C") + // .replace("g", "G").replace("red", "Red") + " Parrot Head"; + mmh.LOGGER.debug("EDE " + name + "_" + daParrotVariant); + // ConfigHelper.Double(chanceConfig, "chance_percent.parrot." + + // daParrotVariant.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.parrot." + daParrotVariant.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daParrotVariant.toLowerCase(), MobHeads.valueOf(name + "_" + daParrotVariant).getName() + "") + , MobHeads.valueOf(name + "_" + daParrotVariant).getTexture().toString(), MobHeads.valueOf(name + "_" + daParrotVariant).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Parrot Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daParrotVariant.toLowerCase(),MobHeads.valueOf(name + "_" + daParrotVariant).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "RABBIT": + String daRabbitType; + Rabbit daRabbit = (Rabbit) entity; + daRabbitType = daRabbit.getRabbitType().toString(); + if (daRabbit.getCustomName() != null) { + if (daRabbit.getCustomName().contains("Toast")) { + daRabbitType = "Toast"; + } + } + //String daRabbitName = RabbitHeads.valueOf(name + "_" + daRabbitType).getName() + " Head"; + mmh.LOGGER.debug("EDE " + name + "_" + daRabbitType); + // ConfigHelper.Double(chanceConfig, "chance_percent.rabbit." + + // daRabbitType.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.rabbit." + daRabbitType.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daRabbitType.toLowerCase(), RabbitHeads.valueOf(name + "_" + daRabbitType).getName() + "") + , RabbitHeads.valueOf(name + "_" + daRabbitType).getTexture().toString(), RabbitHeads.valueOf(name + "_" + daRabbitType).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Rabbit Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daRabbitType.toLowerCase(), RabbitHeads.valueOf(name + "_" + daRabbitType).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "VILLAGER": + Villager daVillager = (Villager) entity; // Location jobsite = + // daVillager.getMemory(MemoryKey.JOB_SITE); + String daVillagerType = daVillager.getVillagerType().toString(); + String daVillagerProfession = daVillager.getProfession().toString(); + mmh.LOGGER.debug("EDE name=" + name); + mmh.LOGGER.debug("EDE profession=" + daVillagerProfession); + mmh.LOGGER.debug("EDE type=" + daVillagerType); + String daName = name + "_" + daVillagerProfession + "_" + daVillagerType; + mmh.LOGGER.debug("EDE " + daName + " " + name + "_" + daVillagerProfession + "_" + + daVillagerType); + //String daVillagerName = VillagerHeads.valueOf(daName).getName() + " Head"; + // ConfigHelper.Double(chanceConfig, "chance_percent.villager." + + // daVillagerType.toLowerCase() + "." + daVillagerProfession.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, + ConfigHelper.Double(chanceConfig, "chance_percent.villager." + + daVillagerType.toLowerCase() + "." + daVillagerProfession.toLowerCase(), + defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(daName.toLowerCase(), VillagerHeads.valueOf(daName).getName() + "") + , VillagerHeads.valueOf(daName).getTexture().toString(), VillagerHeads.valueOf(daName).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Villager Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daVillagerType.toLowerCase() + "." + daVillagerProfession.toLowerCase(), + VillagerHeads.valueOf(name + "_" + daVillagerProfession + "_" + daVillagerType) .getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "ZOMBIE_VILLAGER": + ZombieVillager daZombieVillager = (ZombieVillager) entity; + String daZombieVillagerProfession = daZombieVillager.getVillagerProfession().toString(); + //String daZombieVillagerName = ZombieVillagerHeads.valueOf(name + "_" + daZombieVillagerProfession).getName() + " Head"; + mmh.LOGGER.debug("EDE " + name + "_" + daZombieVillagerProfession); + // ConfigHelper.Double(chanceConfig, "chance_percent.zombie_villager", + // defpercent) + if (mmh.DropIt(event, + ConfigHelper.Double(chanceConfig, "chance_percent.zombie_villager", defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daZombieVillagerProfession.toLowerCase() + , ZombieVillagerHeads.valueOf(name + "_" + daZombieVillagerProfession).getName() + "") + , ZombieVillagerHeads.valueOf(name + "_" + daZombieVillagerProfession).getTexture().toString() + , ZombieVillagerHeads.valueOf(name + "_" + daZombieVillagerProfession).getOwner(), entity.getType(), entity.getKiller() ) + + ); + mmh.LOGGER.debug("EDE Zombie Villager Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase() + "." + daZombieVillagerProfession.toLowerCase(), + ZombieVillagerHeads.valueOf(name + "_" + daZombieVillagerProfession).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "SHEEP": + Sheep daSheep = (Sheep) entity; + String daSheepColor = daSheep.getColor().toString(); + String daSheepName; + + if (daSheep.getCustomName() != null) { + if (daSheep.getCustomName().contains("jeb_")) { + daSheepColor = "jeb_"; + } else { + daSheepColor = daSheep.getColor().toString(); + } + } + //daSheepName = SheepHeads.valueOf(name + "_" + daSheepColor).getName() + " Head"; + mmh.LOGGER.debug("EDE " + daSheepColor + "_" + name); + // ConfigHelper.Double(chanceConfig, "chance_percent.sheep." + + // daSheepColor.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.sheep." + daSheepColor.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daSheepColor.toLowerCase(), SheepHeads.valueOf(name + "_" + daSheepColor).getName() + "") + , SheepHeads.valueOf(name + "_" + daSheepColor).getTexture().toString(), SheepHeads.valueOf(name + "_" + daSheepColor).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Sheep Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase() + "." + daSheepColor.toLowerCase(), SheepHeads.valueOf(name + "_" + daSheepColor).getName() + "") + .replace(" Head", ""), entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "TRADER_LLAMA": + TraderLlama daTraderLlama = (TraderLlama) entity; + String daTraderLlamaColor = daTraderLlama.getColor().toString(); + //String daTraderLlamaName = LlamaHeads.valueOf(name + "_" + daTraderLlamaColor).getName() + " Head"; + mmh.LOGGER.debug("EDE " + daTraderLlamaColor + "_" + name); + // ConfigHelper.Double(chanceConfig, "chance_percent.trader_llama." + + // daTraderLlamaColor.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.trader_llama." + daTraderLlamaColor.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daTraderLlamaColor.toLowerCase(), LlamaHeads.valueOf(name + "_" + daTraderLlamaColor).getName() + "") + , LlamaHeads.valueOf(name + "_" + daTraderLlamaColor).getTexture().toString(), LlamaHeads.valueOf(name + "_" + daTraderLlamaColor).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Trader Llama Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName + .getString(name.toLowerCase() + "." + daTraderLlamaColor.toLowerCase(), + LlamaHeads.valueOf(name + "_" + daTraderLlamaColor).getName() + "") + .replace(" Head", ""), entity.getKiller(), + mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "AXOLOTL": + Axolotl daAxolotl = (Axolotl) entity; + String daAxolotlVariant = daAxolotl.getVariant().toString(); + //String daAxolotlName = MobHeads117.valueOf(name + "_" + daAxolotlVariant).getName() + " Head"; + mmh.LOGGER.debug("EDE " + daAxolotlVariant + "_" + name); + // ConfigHelper.Double(chanceConfig, "chance_percent.axolotl." + + // daAxolotlVariant.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.axolotl." + daAxolotlVariant.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daAxolotlVariant.toLowerCase(), MobHeads117.valueOf(name + "_" + daAxolotlVariant).getName() + "") + , MobHeads117.valueOf(name + "_" + daAxolotlVariant).getTexture().toString(), MobHeads117.valueOf(name + "_" + daAxolotlVariant).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Axolotl Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName + .getString(name.toLowerCase() + "." + daAxolotlVariant.toLowerCase(), + MobHeads117.valueOf(name + "_" + daAxolotlVariant).getName() + "") + .replace(" Head", ""), entity.getKiller(), + mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "GOAT": + Goat daGoat = (Goat) entity; + String daGoatVariant; + //String daGoatName;// = MobHeads117.valueOf(name + "_" + daAxolotlVariant).getName() + " Head"; + if (daGoat.isScreaming()) { + // Giving screaming goat head + daGoatVariant = "SCREAMING"; + } else { + // give goat head + daGoatVariant = "NORMAL"; + } + mmh.LOGGER.debug("EDE " + daGoatVariant + "_" + name); + // ConfigHelper.Double(chanceConfig, "chance_percent.goat." + + // daGoatVariant.toLowerCase(), defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, + "chance_percent.goat." + daGoatVariant.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase() + "." + daGoatVariant.toLowerCase(), MobHeads117.valueOf(name + "_" + daGoatVariant).getName() + "") + , MobHeads117.valueOf(name + "_" + daGoatVariant).getTexture().toString(), MobHeads117.valueOf(name + "_" + daGoatVariant).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Goat Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName + .getString(name.toLowerCase() + "." + daGoatVariant.toLowerCase(), + MobHeads117.valueOf(name + "_" + daGoatVariant).getName() + "") + .replace(" Head", ""), + entity.getKiller(), + mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + break; + case "STRIDER": + Strider daStrider = (Strider) entity; + PersistentDataContainer pdc2 = daStrider.getPersistentDataContainer(); + boolean isShivering = Boolean.parseBoolean(daStrider.getPersistentDataContainer().get(mmh.SHIVERING_KEY, PersistentDataType.STRING)); + if (mmh.chance25oftrue()) { // chance50oftrue() + // isShivering + name = name.concat("_SHIVERING"); + // ConfigHelper.Double(chanceConfig, + // "chance_percent." + name.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase().replace("_", "."), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "").replace(" Head", ""), entity.getKiller(), + mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + } else // ConfigHelper.Double(chanceConfig, + // "chance_percent." + name.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "").replace(" Head", ""), + entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + + mmh.LOGGER.debug("EDE " + MobHeads.valueOf(name) + " killed"); + break; + case "FROG": + Frog daFrog = (Frog) entity; + String daFrogVariant = daFrog.getVariant().toString(); + name = name.concat("_" + daFrogVariant); + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.replace("_", ".").toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.replace("_", ".").toLowerCase(), MobHeads119.valueOf(name).getName() + "") + , MobHeads119.valueOf(name).getTexture().toString(), MobHeads119.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE Frog Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.replace("_", ".").toLowerCase(), MobHeads119.valueOf(name).getName() + "").replace(" Head", ""), + entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + + break; + case "TADPOLE": + case "ALLAY": + case "WARDEN": + mmh.LOGGER.debug("EDE TAW name=" + name); + mmh.LOGGER.debug("EDE TAW texture=" + MobHeads119.valueOf(name).getTexture().toString()); + mmh.LOGGER.debug("EDE TAW location=" + entity.getLocation().toString()); + mmh.LOGGER.debug("EDE TAW getName=" + event.getEntity().getName()); + mmh.LOGGER.debug("EDE TAW killer=" + entity.getKiller().toString()); + // ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads119.valueOf(name).getName() + "") + , MobHeads119.valueOf(name).getTexture().toString(), MobHeads119.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase(), MobHeads119.valueOf(name).getName() + "").replace(" Head", ""), + entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + mmh.LOGGER.debug("EDE " + MobHeads119.valueOf(name) + " killed"); + break; + case "CAMEL": + case "SNIFFER": + mmh.LOGGER.debug("EDE CS name=" + name); + mmh.LOGGER.debug("EDE CS texture=" + MobHeads120.valueOf(name).getTexture().toString()); + mmh.LOGGER.debug("EDE CS location=" + entity.getLocation().toString()); + mmh.LOGGER.debug("EDE CS getName=" + event.getEntity().getName()); + mmh.LOGGER.debug("EDE CS killer=" + entity.getKiller().toString()); + // ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads120.valueOf(name).getName() + "") + , MobHeads120.valueOf(name).getTexture().toString(), MobHeads120.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase(), MobHeads120.valueOf(name).getName() + "").replace(" Head", "") + , entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + mmh.LOGGER.debug("EDE " + MobHeads120.valueOf(name) + " killed"); + break; + case "VEX": + mmh.LOGGER.debug("EDE name=" + name); + mmh.LOGGER.debug("EDE texture=" + MobHeads.valueOf(name).getTexture().toString()); + mmh.LOGGER.debug("EDE location=" + entity.getLocation().toString()); + mmh.LOGGER.debug("EDE getName=" + event.getEntity().getName()); + mmh.LOGGER.debug("EDE killer=" + entity.getKiller().toString()); + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "").replace(" Head", ""), + entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + if (coinFlip()) { + name = name.concat("_ANGRY"); + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + } + mmh.LOGGER.debug("EDE " + MobHeads.valueOf(name) + " killed"); + } + break; + default: + // mmh.makeSkull(MobHeads.valueOf(name).getTexture(), name); + mmh.LOGGER.debug("EDE name=" + name + " line:1122"); + mmh.LOGGER.debug("EDE texture=" + MobHeads.valueOf(name).getTexture().toString() + " line:1123"); + mmh.LOGGER.debug("EDE location=" + entity.getLocation().toString() + " line:1124"); + mmh.LOGGER.debug("EDE getName=" + event.getEntity().getName() + " line:1125"); + mmh.LOGGER.debug("EDE killer=" + entity.getKiller().toString() + " line:1126"); + // ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + // defpercent) + if (mmh.DropIt(event, ConfigHelper.Double(chanceConfig, "chance_percent." + name.toLowerCase(), + defpercent))) { + Drops.add( + mmh.makeHead( mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "") + , MobHeads.valueOf(name).getTexture().toString(), MobHeads.valueOf(name).getOwner(), entity.getType(), entity.getKiller() ) + ); + mmh.LOGGER.debug("EDE " + name + " Head Dropped"); + if (mmh.config.getBoolean("head_settings.mob_heads.announce_kill.enabled", true)) { + announceBeheading(entity, + mmh.langName.getString(name.toLowerCase(), MobHeads.valueOf(name).getName() + "").replace(" Head", "") + , entity.getKiller(), mmh.config.getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + } + } + mmh.LOGGER.debug("EDE " + MobHeads.valueOf(name) + " killed"); + break; + }// End switch + }catch(Exception exception){ + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLAYER_KILL_MOB).error(exception)); + } + return; + } + } + } + }catch(Exception exception){ + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.UNHANDLED_ENTITYDEATHEVENT_ERROR).error(exception)); + } + } + + @SuppressWarnings({ "static-access", "unused" }) + @EventHandler(priority = EventPriority.LOWEST) + public void onCreatureSpawn(CreatureSpawnEvent event) { // onEntitySpawn(EntitySpawnEvent e) { // TODO: onCreatureSpawn + try { // REPORT_CS_EVENT_ERROR "Error while entity spawning." + if (mmh.config.getBoolean("wandering_trades.custom_wandering_trader", true)) { + Entity entity = event.getEntity(); + if (entity instanceof WanderingTrader) { + // traderHeads2 = YamlConfiguration.loadConfiguration(traderFile2); + mmh.LOGGER.debug("CSE WanderingTrader spawned"); + WanderingTrader trader = (WanderingTrader) entity; + List recipes = new ArrayList(); + final List oldRecipes = trader.getRecipes(); + + /** + * Player Heads + */ + if (mmh.config.getBoolean("wandering_trades.player_heads.enabled", true)) { + // Check if default max heads is larger than number of playerheads + int numOfplayerheads = (playerhead_recipes.size() - 1) >= 0 ? playerhead_recipes.size() - 1 : 0; + int phMaxDef = (3 >= numOfplayerheads) ? 3 : numOfplayerheads; + + int playerRandom = mmh.randomBetween(mmh.config.getInt("wandering_trades.player_heads.min", 0), + mmh.config.getInt("wandering_trades.player_heads.max", phMaxDef)); + mmh.LOGGER.debug("CSE.PH playerRandom=" + playerRandom); + if ((playerRandom > 0) && (numOfplayerheads > 0)) { + mmh.LOGGER.debug("CSE.PH playerRandom > 0"); + mmh.LOGGER.debug("CSE.PH numOfplayerheads=" + numOfplayerheads); + HashSet used = new HashSet(); + outerLoop: for (int i = 0; i < playerRandom; i++) { + int randomPlayerHead = mmh.randomBetween(0, numOfplayerheads); + while (used.contains(randomPlayerHead) || (randomPlayerHead > numOfplayerheads)) { // while we + // have + // already + // used + // the + // number + randomPlayerHead = mmh.randomBetween(0, numOfplayerheads); // generate a new one because + // it's already used + // infinite loop catch + if (i >= 500) { + mmh.LOGGER.debug("CSE.PH timed out"); + break outerLoop; + } + } + // by this time, add will be unique + used.add(randomPlayerHead); + recipes.add(playerhead_recipes.get(randomPlayerHead)); + } + used.clear(); + } + } + /** + * Block Heads + */ + if (mmh.config.getBoolean("wandering_trades.block_heads.enabled", true)) { + // check if default max block heads is larger than number of blockheads + int numOfblockheads = BHNum >= 0 ? BHNum : 0; + int bhMaxDef = (5 >= numOfblockheads) ? 5 : numOfblockheads; + + int min = mmh.config.getInt("wandering_trades.block_heads.pre_116.min", 0); + int max; + if (Double.parseDouble(mmh.getMCVersion().substring(0, 4)) >= 1.16) { + max = mmh.config.getInt("wandering_trades.block_heads.pre_116.max", bhMaxDef); + } else { + max = mmh.config.getInt("wandering_trades.block_heads.pre_116.max", bhMaxDef); + } + mmh.LOGGER.debug("CSE BH1 min=" + min + " max=" + max); + int blockRandom = mmh.randomBetween(min, max); + mmh.LOGGER.debug("CSE blockRandom=" + blockRandom); + if (blockRandom > 0) { + mmh.LOGGER.debug("CSE blockRandom > 0"); + mmh.LOGGER.debug("CSE numOfblockheads=" + numOfblockheads); + HashSet used = new HashSet(); + outerLoop: for (int i = 0; i < blockRandom; i++) { + mmh.LOGGER.debug("CSE i=" + i); + int randomBlockHead = mmh.randomBetween(0, numOfblockheads); + while (used.contains(randomBlockHead)) { + // while we have already used the number + randomBlockHead = mmh.randomBetween(0, numOfblockheads); + // generate a new one because it's already used + if (i >= 500) { + mmh.LOGGER.debug("CSE.BH1 timed out"); + break outerLoop; + } + } + // by this time, add will be unique + used.add(randomBlockHead); + recipes.add(blockhead_recipes.get(randomBlockHead)); + } + used.clear(); + } + } + + /** + * Block Heads 2 + */ + if (mmh.config.getBoolean("wandering_trades.block_heads.enabled", true)) { + if (Double.parseDouble(mmh.getMCVersion().substring(0, 4)) >= 1.16) { + // check if default max BH is larger than number of block heads + int numOfblockheads = ((BHNum + BHNum2) - 1) >= 0 ? (BHNum + BHNum2) - 1 : 0; + int bhMaxDef = (5 >= numOfblockheads) ? 5 : numOfblockheads; + + int min = mmh.config.getInt("wandering_trades.block_heads.is_116.min", 0); + int max = mmh.config.getInt("wandering_trades.block_heads.is_116.max", bhMaxDef); + mmh.LOGGER.debug("CSE BH2 min=" + min + " max=" + max); + int blockRandom = mmh.randomBetween(min, max); + mmh.LOGGER.debug("CSE blockRandom=" + blockRandom); + if (blockRandom > 0) { + mmh.LOGGER.debug("CSE blockRandom > 0"); + mmh.LOGGER.debug("CSE numOfblockheads=" + numOfblockheads); + HashSet used = new HashSet(); + outerLoop: for (int i = 0; i < blockRandom; i++) { + mmh.LOGGER.debug("CSE i=" + i); + int randomBlockHead = mmh.randomBetween(BHNum - 1, numOfblockheads); + while (used.contains(randomBlockHead)) { + // while we have already used the number + randomBlockHead = mmh.randomBetween(BHNum - 1, numOfblockheads); + // generate a new one because it's already used + if (i >= 500) { + mmh.LOGGER.debug("CSE.BH2 timed out"); + break outerLoop; + } + } + // by this time, add will be unique + used.add(randomBlockHead); + recipes.add(blockhead_recipes.get(randomBlockHead)); + } + used.clear(); + } + } + if (Double.parseDouble(mmh.getMCVersion().substring(0, 4)) >= 1.17) { + // check if default max BH is larger than number of blockheads + int numOfblockheads = ((BHNum + BHNum2 + BHNum3) - 1) >= 0 ? (BHNum + BHNum2 + BHNum3) - 1 : 0; + int bhMaxDef = (5 >= numOfblockheads) ? 5 : numOfblockheads; + + int min1 = mmh.config.getInt("wandering_trades.block_heads.is_117.min", 0); + // int max1 = mmh.config.getInt("wandering_trades.block_heads.is_117.max", + // 5) / 2; + int max1 = mmh.config.getInt("wandering_trades.block_heads.is_117.max", 5); + mmh.LOGGER.debug("CSE BH2 min=" + min1 + " max=" + max1); + int blockRandom1 = mmh.randomBetween(min1, max1); + mmh.LOGGER.debug("CSE blockRandom=" + blockRandom1); + if (blockRandom1 > 0) { + mmh.LOGGER.debug("CSE blockRandom > 0"); + mmh.LOGGER.debug("CSE numOfblockheads=" + numOfblockheads); + HashSet used = new HashSet(); + outerLoop: for (int i = 0; i < blockRandom1; i++) { + mmh.LOGGER.debug("CSE i=" + i); + int randomBlockHead = mmh.randomBetween((BHNum + BHNum2) - 1, numOfblockheads); + while (used.contains(randomBlockHead) || (randomBlockHead > numOfblockheads)) { // while + // we have already used the number + randomBlockHead = mmh.randomBetween((BHNum + BHNum2) - 1, numOfblockheads); + // generate a new one because it's already used + if (i >= 500) { + mmh.LOGGER.debug("CSE.BH3 timed out"); + break outerLoop; + } + } + // by this time, add will be unique + used.add(randomBlockHead); + recipes.add(blockhead_recipes.get(randomBlockHead)); + } + used.clear(); + } + } + + } + + /** + * Custom Trades + */ + if (mmh.config.getBoolean("wandering_trades.custom_trades.enabled", false)) { + int numOfCustomTrades = (custometrade_recipes.size() - 1) >= 0 ? custometrade_recipes.size() - 1 + : 0; + numOfCustomTrades = numOfCustomTrades - 1; + int ctMaxDef = (5 >= numOfCustomTrades) ? 5 : numOfCustomTrades; + + int customRandom = mmh.randomBetween( + mmh.config.getInt("wandering_trades.custom_trades.min", 0), + mmh.config.getInt("wandering_trades.custom_trades.max", ctMaxDef)); + + // if(debug){mmh.TEST.debug("CSE numOfCustomTrades=" + numOfCustomTrades);} + // int customRandom = + // randomBetween(getConfig().getInt("wandering_trades.min_custom_trades", 0), + // mmh.config.getInt("wandering_trades.max_custom_trades", 3)); + mmh.LOGGER.debug("CSE customRandom=" + customRandom); + if (customRandom > 0) { + mmh.LOGGER.debug("CSE customRandom > 0"); + // for(int randomCustomTrade=1; randomCustomTrade used = new HashSet(); + for (int i = 0; i < customRandom; i++) { + + double chance = Math.random(); + mmh.LOGGER.debug("CSE chance=" + chance + " line:1540"); + if (mmh.traderCustom.getDouble("custom_trades.trade_" + i + ".chance", 0.002) > chance) { + recipes.add(custometrade_recipes.get(i)); + } + if (i >= 500) { + mmh.LOGGER.debug("CSE.CT timed out"); + break; + } + } + used.clear(); + } + } + + if (mmh.config.getBoolean("wandering_trades.keep_default_trades", true)) { + recipes.addAll(oldRecipes); + } + trader.setRecipes(recipes); + /** }});// */ + } + + } + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CS_EVENT_ERROR).error(exception)); + } + + } + + @Override + @SuppressWarnings({ "unused", "static-access", "rawtypes", "unchecked" }) + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { // TODO: Commands + try { + // log("command=" + cmd.getName() + " args=" + args[0] + args[1]); + if (cmd.getName().equalsIgnoreCase("MMH")) { + try { // REPORT_COMMAND_MENU_ERROR "Error displaying Command Menu." + if (args.length == 0) { + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + sender.sendMessage(ChatColor.WHITE + " ");// https://ko-fi.com/joelgodofwar + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.version.donate") + ": https://ko-fi.com/joelgodofwar");// https://ko-fi.com/joelgodofwar + sender.sendMessage(ChatColor.WHITE + " ");// https://ko-fi.com/joelgodofwar + sender.sendMessage( + ChatColor.WHITE + " /mmh reload - " + mmh.get("mmh.command.reload", "Reloads this plugin."));// subject + // to + // server + // admin + // approval"); + sender.sendMessage(ChatColor.WHITE + " /mmh toggledebug - " + + mmh.get("mmh.command.debuguse", "Temporarily toggles debug."));// Cancels SinglePlayerSleep"); + if (mmh.config.getBoolean("wandering_trades.custom_wandering_trader", true)) { + sender.sendMessage(ChatColor.WHITE + " /mmh playerheads - " + + mmh.get("mmh.command.playerheads", "Shows how to use the playerheads commands")); + // sender.sendMessage(ChatColor.WHITE + " /mmh blockheads - " + + // mmh.lang.get("blockheads", "Shows how to use the blockheads commands")); + sender.sendMessage(ChatColor.WHITE + " /mmh customtrader - " + + mmh.get("mmh.command.customtrader", "Shows how to use the customtrader commands")); + } + sender.sendMessage(ChatColor.WHITE + " /mmh fixhead - " + mmh.get("mmh.command.headfix")); + sender.sendMessage(ChatColor.WHITE + " /mmh givemh - " + mmh.get("mmh.command.give.mobhead")); + sender.sendMessage(ChatColor.WHITE + " /mmh giveph - " + mmh.get("mmh.command.give.playerhead")); + sender.sendMessage(ChatColor.WHITE + " /mmh givebh - " + mmh.get("mmh.command.give.blockhead")); + sender.sendMessage( + ChatColor.WHITE + " /mmh display perms/vars - " + mmh.get("mmh.command.display.help")); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + return true; + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_MENU_ERROR).error(exception)); + } + if (args[0].equalsIgnoreCase("headNBT")) { + try { // REPORT_COMMAND_HEADNBT_ERROR "Error executing HeadNBT command." + if (!(sender instanceof Player)) { + return true; + } + Player player = (Player) sender; + ItemStack mainHand = player.getInventory().getItemInMainHand(); + ItemStack offHand = player.getInventory().getItemInOffHand(); + if ((mainHand != null) && mainHand.getType().equals(Material.PLAYER_HEAD)) { + NBTItem item = new NBTItem(mainHand); + mmh.LOGGER.log("" + item); + player.sendMessage("" + item); + } else if ((offHand != null) && offHand.getType().equals(Material.PLAYER_HEAD)) { + NBTItem item = new NBTItem(offHand); + player.sendMessage("" + item); + mmh.LOGGER.log("" + item); + } else { + // log(Level.INFO,"You do not have a head in either hand."); + player.sendMessage("" + mmh.get("mmh.command.headnbt")); + } + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_HEADNBT_ERROR).error(exception)); + } + } + // /mmh display permvar playername + // / 0 1 2 + if (args[0].equalsIgnoreCase("display")) { + if (args[1].equalsIgnoreCase("perms") || args[1].equalsIgnoreCase("permissions")) { + try { // REPORT_COMMAND_DISPLAY_PERMS "Error executing Display Perms command." + if (sender instanceof Player) { + Player player = (Player) sender; + sender.sendMessage( + "" + mmh.get("mmh.command.display.you").replace("", player.getDisplayName())); + sender.sendMessage("moremobheads.players=" + player.hasPermission("moremobheads.players")); + sender.sendMessage("moremobheads.mobs=" + player.hasPermission("moremobheads.mobs")); + sender.sendMessage("moremobheads.nametag=" + player.hasPermission("moremobheads.nametag")); + sender.sendMessage("moremobheads.reload=" + player.hasPermission("moremobheads.reload")); + sender.sendMessage( + "moremobheads.toggledebug=" + player.hasPermission("moremobheads.toggledebug")); + sender.sendMessage("moremobheads.showUpdateAvailable=" + + player.hasPermission("moremobheads.showUpdateAvailable")); + sender.sendMessage( + "moremobheads.customtrader=" + player.hasPermission("moremobheads.customtrader")); + sender.sendMessage( + "moremobheads.playerheads=" + player.hasPermission("moremobheads.playerheads")); + sender.sendMessage( + "moremobheads.blockheads=" + player.hasPermission("moremobheads.blockheads")); + sender.sendMessage("moremobheads.fixhead=" + player.hasPermission("moremobheads.fixhead")); + sender.sendMessage("moremobheads.give=" + player.hasPermission("moremobheads.give")); + sender.sendMessage( + "" + mmh.getName() + " " + mmh.getDescription().getVersion() + " display perms end"); + } else if (args.length >= 2) { + Player player = sender.getServer().getPlayer(args[2]); + sender.sendMessage("" + + mmh.get("mmh.command.display.them").replace("", player.getDisplayName())); + sender.sendMessage("moremobheads.players=" + player.hasPermission("moremobheads.players")); + sender.sendMessage("moremobheads.mobs=" + player.hasPermission("moremobheads.mobs")); + sender.sendMessage("moremobheads.nametag=" + player.hasPermission("moremobheads.nametag")); + sender.sendMessage("moremobheads.reload=" + player.hasPermission("moremobheads.reload")); + sender.sendMessage( + "moremobheads.toggledebug=" + player.hasPermission("moremobheads.toggledebug")); + sender.sendMessage("moremobheads.showUpdateAvailable=" + + player.hasPermission("moremobheads.showUpdateAvailable")); + sender.sendMessage( + "moremobheads.customtrader=" + player.hasPermission("moremobheads.customtrader")); + sender.sendMessage( + "moremobheads.playerheads=" + player.hasPermission("moremobheads.playerheads")); + sender.sendMessage( + "moremobheads.blockheads=" + player.hasPermission("moremobheads.blockheads")); + sender.sendMessage("moremobheads.fixhead=" + player.hasPermission("moremobheads.fixhead")); + sender.sendMessage("moremobheads.give=" + player.hasPermission("moremobheads.give")); + sender.sendMessage("" + mmh.getName() + " " + mmh.getDescription().getVersion() + + " display perms end"); + } else { + sender.sendMessage("Console can only check permissions of Players."); + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_DISPLAY_PERMS).error(exception)); + } + } else if (args[1].equalsIgnoreCase("vars") || args[1].equalsIgnoreCase("variables")) { + try { // REPORT_COMMAND_DISPLAY_VARS "Error executing Display Vars command." + sender.sendMessage( + "" + mmh.getName() + " " + mmh.getDescription().getVersion() + " display varss start"); + sender.sendMessage("debug=" + debug); + sender.sendMessage("daLang=" + mmh.daLang); + + world_whitelist = mmh.config.getString("global_settings.world.whitelist", ""); + world_blacklist = mmh.config.getString("global_settings.world.blacklist", ""); + mob_whitelist = mmh.config.getString("head_settings.mob_heads.whitelist", ""); + mob_blacklist = mmh.config.getString("head_settings.mob_heads.blacklist", ""); + + sender.sendMessage("world_whitelist=" + world_whitelist); + sender.sendMessage("world_blacklist=" + world_blacklist); + sender.sendMessage("mob_whitelist=" + mob_whitelist); + sender.sendMessage("mob_blacklist=" + mob_blacklist); + sender.sendMessage("" + mmh.getName() + " " + mmh.getDescription().getVersion() + " display varss end"); + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_DISPLAY_VARS).error(exception)); + } + } else if (args[1].equalsIgnoreCase("chance") || args[1].equalsIgnoreCase("chance_percent")) { + try { // REPORT_COMMAND_DISPLAY_CHANCE "Error executing Display Chance command." + ConfigurationSection cs = chanceConfig.getConfigurationSection("chance_percent"); + List daSet = new ArrayList(); + // log(Level.INFO, "args.lngth=" + args.length); + if (args.length == 3) { + for (String key : cs.getKeys(true)) { + if (key.contains(args[2])) { + sender.sendMessage("" + key + "=" + cs.get(key)); + daSet.add("" + key + "=" + cs.get(key)); + } + } + + if (!daSet.isEmpty()) { + File chanceFile = new File( + mmh.getDataFolder() + "" + File.separatorChar + "logs" + File.separatorChar, + "chance_dump" + ".log"); + PrintWriter pw = null; + try { + pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(chanceFile), "UTF-8")); + for (String s : daSet) { + pw.println(s); + } + pw.flush(); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + } finally { + pw.close(); + } + sender.sendMessage("chance_config.yml has been dumped into " + chanceFile.toString()); + mmh.LOGGER.log("chance_config.yml has been dumped into " + chanceFile.toString()); + } else { + sender.sendMessage("" + args[2] + " was not found in chance_percent.yml"); + } + } else { + for (String key : cs.getKeys(true)) { + if (!cs.get(key).toString().contains("MemorySection")) { + sender.sendMessage("" + key + "=" + cs.get(key).toString()); + daSet.add("" + key + "=" + cs.get(key).toString()); + } + } + if (!daSet.isEmpty()) { + File chanceFile = new File( + mmh.getDataFolder() + "" + File.separatorChar + "logs" + File.separatorChar, + "chance_dump" + ".log"); + PrintWriter pw = null; + try { + pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(chanceFile), "UTF-8")); + for (String s : daSet) { + pw.println(s); + } + pw.flush(); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + } finally { + pw.close(); + } + sender.sendMessage("chance_config.yml has been dumped into " + chanceFile.toString()); + mmh.LOGGER.log("chance_config.yml has been dumped into " + chanceFile.toString()); + } else { + sender.sendMessage("Error dumping chance_percent.yml"); + } + } + // chanceConfig + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_DISPLAY_CHANCE).error(exception)); + } + } else if ( args[1].equalsIgnoreCase("config") ) { + mmh.LOGGER.debug("Config.yml DUMP - INCLUDE THIS WITH ANY ISSUE REPORT VVV"); + mmh.dumpConfig(mmh.getConfig()); + mmh.LOGGER.debug("Config.yml DUMP - INCLUDE THIS WITH ANY ISSUE REPORT ^^^"); + } + } + if (args[0].equalsIgnoreCase("reload")) { //TODO: Reload command + try { // REPORT_COMMAND_RELOAD_ERROR "Error executing Reload command." + String perm = "moremobheads.reload"; + boolean hasPerm = sender.hasPermission(perm) || !(sender instanceof Player) ? true : false; + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if ((hasPerm || mmh.isDev) || !(sender instanceof Player) ) { + //mmh.configReload(); + mmh.LOGGER.log("Reloading 1.20 EventHandler..."); + long startTime = System.currentTimeMillis(); + try { + mmh.config.load(new File(mmh.getDataFolder(), "config.yml")); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_CANNOT_LOAD_CONFIG).error(exception)); + } + world_whitelist = mmh.config.getString("global_settings.world.whitelist", ""); + world_blacklist = mmh.config.getString("global_settings.world.blacklist", ""); + mob_whitelist = mmh.config.getString("head_settings.mob_heads.whitelist", ""); + mob_blacklist = mmh.config.getString("head_settings.mob_heads.blacklist", ""); + debug = mmh.debug; + chanceConfig = mmh.chanceConfig; + blockFile117 = new File(mmh.getDataFolder() + "" + File.separatorChar + "block_heads_1_17.yml"); + blockFile1172 = new File(mmh.getDataFolder() + "" + File.separatorChar + "block_heads_1_17_2.yml"); + blockFile120 = new File(mmh.getDataFolder() + "" + File.separatorChar + "block_heads_1_20.yml"); + + if (mmh.config.getBoolean("wandering_trades.custom_wandering_trader", true)) { + if (!blockFile117.exists()) { + mmh.saveResource("block_heads_1_17.yml", true); + mmh.LOGGER.log("block_heads_1_17.yml not found! Creating in " + mmh.getDataFolder() + ""); + } + if (!blockFile1172.exists()) { + mmh.saveResource("block_heads_1_17_2.yml", true); + mmh.LOGGER.log("block_heads_1_17_2.yml not found! Creating in " + mmh.getDataFolder() + ""); + } + if (!blockFile120.exists()) { + mmh.saveResource("block_heads_1_20.yml", true); + mmh.LOGGER.log("block_heads_1_20.yml not found! Creating in " + mmh.getDataFolder() + ""); + } + blockHeads = new YamlConfiguration(); + try { + mmh.LOGGER.log("Loading " + blockFile117 + "..."); + blockHeads.load(blockFile117); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + } + blockHeads2 = new YamlConfiguration(); + try { + mmh.LOGGER.log("Loading " + blockFile1172 + "..."); + blockHeads2.load(blockFile1172); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + } + blockHeads3 = new YamlConfiguration(); + try { + mmh.LOGGER.log("Loading " + blockFile120 + "..."); + blockHeads3.load(blockFile120); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + } + + boolean showlore = mmh.config.getBoolean("head_settings.lore.show_plugin_name", true); + ArrayList headlore = new ArrayList(); + headlore.add(ChatColor.AQUA + "" + mmh.getName()); + + if(!mmh.playerFile.exists()){ + mmh.saveResource("player_heads.yml", true); + mmh.LOGGER.log("player_heads.yml not found! copied player_heads.yml to " + mmh.getDataFolder() + ""); + } + mmh.LOGGER.log("Loading player_heads file..."); + mmh.playerHeads = new YamlConfiguration(); + try { + mmh.playerHeads.load(mmh.playerFile); + } catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLAYERHEAD_LOAD_ERROR).error(exception)); + } + + mmh.LOGGER.log("Loading PlayerHead Recipes..."); + for (int i = 1; i < (mmh.playerHeads.getInt("players.number") + 1); i++) { + ItemStack price1 = mmh.playerHeads.getItemStack("players.player_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = mmh.playerHeads.getItemStack("players.player_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = mmh.playerHeads.getItemStack("players.player_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + mmh.playerHeads.getInt("players.player_" + i + ".quantity", 3)); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + playerhead_recipes.add(recipe); + } + mmh.LOGGER.log(playerhead_recipes.size() + " PlayerHead Recipes ADDED..."); + mmh.LOGGER.log("Loading BlockHead Recipes..."); + BHNum = blockHeads.getInt("blocks.number"); + // BlockHeads + mmh.LOGGER.log("BlockHeads=" + BHNum); + for (int i = 1; i < (BHNum + 1); i++) { + ItemStack price1 = blockHeads.getItemStack("blocks.block_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = blockHeads.getItemStack("blocks.block_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = blockHeads.getItemStack("blocks.block_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + blockHeads.getInt("blocks.block_" + i + ".quantity", 8)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + blockhead_recipes.add(recipe); + } + BHNum2 = blockHeads2.getInt("blocks.number"); + // blockHeads 2 + mmh.LOGGER.log("BlockHeads2=" + BHNum2); + for (int i = 1; i < (BHNum2 + 1); i++) { + ItemStack price1 = blockHeads2.getItemStack("blocks.block_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = blockHeads2.getItemStack("blocks.block_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = blockHeads2.getItemStack("blocks.block_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + blockHeads2.getInt("blocks.block_" + i + ".quantity", 8)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + blockhead_recipes.add(recipe); + } + BHNum3 = blockHeads3.getInt("blocks.number"); + // blockHeads 3 + mmh.LOGGER.log("BlockHeads3=" + BHNum3); + for (int i = 1; i < (BHNum3 + 1); i++) { + ItemStack price1 = blockHeads3.getItemStack("blocks.block_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = blockHeads3.getItemStack("blocks.block_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = blockHeads3.getItemStack("blocks.block_" + i + ".itemstack", + new ItemStack(Material.AIR)); + if (showlore) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + meta.setLore(headlore); + itemstack.setItemMeta(meta); + itemstack.setItemMeta(meta); + } + MerchantRecipe recipe = new MerchantRecipe(itemstack, + blockHeads3.getInt("blocks.block_" + i + ".quantity", 8)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + blockhead_recipes.add(recipe); + } + + mmh.LOGGER.log(blockhead_recipes.size() + " BlockHead Recipes ADDED..."); + mmh.LOGGER.log("Loading CustomTrades Recipes..."); + for (int i = 1; i < (mmh.traderCustom.getInt("custom_trades.number") + 1); i++) { + ItemStack price1 = mmh.traderCustom.getItemStack("custom_trades.trade_" + i + ".price_1", + new ItemStack(Material.AIR)); + ItemStack price2 = mmh.traderCustom.getItemStack("custom_trades.trade_" + i + ".price_2", + new ItemStack(Material.AIR)); + ItemStack itemstack = mmh.traderCustom.getItemStack("custom_trades.trade_" + i + ".itemstack", + new ItemStack(Material.AIR)); + /** Code to fix missing noteblock SkullMeta */ + boolean doIt = Utils.isSupportedVersion("1.20.2.3936"); + if(doIt) { + mmh.LOGGER.log("doIt=" + doIt); + if(itemstack.getType().equals(Material.PLAYER_HEAD)) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + String sound = mmh.traderCustom.getString("custom_trades.trade_" + i + ".note_block_sound", "entity.player.hurt"); + meta.setNoteBlockSound(NamespacedKey.minecraft(sound)); + itemstack.setItemMeta(meta); + }//*/ + } + /** Code to fix missing noteblock SkullMeta */ + MerchantRecipe recipe = new MerchantRecipe(itemstack, + mmh.traderCustom.getInt("custom_trades.trade_" + i + ".quantity", 1)); + recipe.setExperienceReward(true); + recipe.addIngredient(price1); + recipe.addIngredient(price2); + custometrade_recipes.add(recipe); + } + mmh.LOGGER.log(custometrade_recipes.size() + " CustomTrades Recipes ADDED..."); + mmh.LOGGER.log("EventHandler_1_20 took " + mmh.LoadTime(startTime) + "ms to load"); + } + sender.sendMessage( + ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + mmh.get("mmh.message.reloaded")); + + return true; + } else if (!hasPerm) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.noperm").toString().replace("", perm)); + return false; + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_RELOAD_ERROR).error(exception)); + } + } + if (args[0].equalsIgnoreCase("toggledebug") || args[0].equalsIgnoreCase("td")) { + try { // REPORT_COMMAND_TOGGLE_DEBUG "Error executing ToggleDebug Command." + String perm = "moremobheads.toggledebug"; + boolean hasPerm = sender.hasPermission(perm) || !(sender instanceof Player) ? true : false; + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if (sender.isOp() || (hasPerm || mmh.isDev) || !(sender instanceof Player) ) { + debug = !debug; + mmh.debug = debug; + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.debugtrue").toString().replace("boolean", "" + debug)); + return true; + } else if (!hasPerm) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.noperm").toString().replace("", perm)); + return false; + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_TOGGLE_DEBUG).error(exception)); + + } + } + if (args[0].equalsIgnoreCase("customtrader") || args[0].equalsIgnoreCase("ct")) { + try { // REPORT_COMMAND_CUSTOM_TRADER "Error executing CustomTrader Command." + String perm = "moremobheads.customtrader"; + boolean hasPerm = sender.hasPermission(perm); + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if ( (hasPerm || mmh.isDev) && (sender instanceof Player) + && mmh.config.getBoolean("wandering_trades.custom_wandering_trader", true)) { + mmh.LOGGER.log("has permission"); + Player player = (Player) sender; + if (!(args.length >= 2)) { + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.WHITE + " /mmh ct - " + mmh.get("mmh.command.ct.help")); + sender.sendMessage(ChatColor.WHITE + " /mmh ct add - " + mmh.get("mmh.command.ct.add") + + "custom_trades.yml"); + sender.sendMessage(ChatColor.WHITE + " /mmh ct remove # - " + mmh.get("mmh.command.ct.remove")); + sender.sendMessage(ChatColor.WHITE + " /mmh ct replace # - " + + mmh.get("mmh.command.ct.replace").replace("", "#")); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + return true; + } else if (args[1].equalsIgnoreCase("add")) { + mmh.LOGGER.debug("CMD CT ADD Start -----"); + ItemStack itemstack = player.getInventory().getItemInOffHand(); + ItemStack price1 = player.getInventory().getItem(0); + ItemStack price2 = player.getInventory().getItem(1); + if (price1 == null) { + price1 = new ItemStack(Material.AIR); + } + if (price2 == null) { + price2 = new ItemStack(Material.AIR); + } + // Material price1 = item1.getType(); + // Material price2 = item2.getType(); + + if ((itemstack.getType() == Material.AIR) || (price1 == null) || (price1.getType() == Material.AIR)) { + mmh.LOGGER.log("error air"); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line1") + "custom_trades.yml"); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line2")); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line3")); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line4") + "/mmh ct add"); + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line5") + "custom trade."); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + mmh.LOGGER.debug("CMD CT ADD End Error -----"); + return false; + } + int tradeNumber = (int) mmh.traderCustom.get("custom_trades.number", 1); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber + 1) + ".price_1", price1); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber + 1) + ".price_2", price2); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber + 1) + ".itemstack", itemstack); + /** Code to fix missing noteblock SkullMeta */ + boolean doIt = Utils.isSupportedVersion("1.20.2.3936"); + if(doIt) { + mmh.LOGGER.log("CT A doIt=" + doIt); + if(itemstack.getType().equals(Material.PLAYER_HEAD)) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + NamespacedKey sound = meta.getNoteBlockSound(); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber + 1) + ".note_block_sound", sound.getKey()); + mmh.LOGGER.debug("sound.getKey()=" + sound.getNamespace()); + mmh.LOGGER.debug("sound.getKey()=" + sound.getKey()); + + } + } + /** Code to fix missing noteblock SkullMeta */ + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber + 1) + ".quantity", + itemstack.getAmount()); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber + 1) + ".chance", 0.002); + mmh.traderCustom.set("custom_trades.number", (tradeNumber + 1)); + mmh.LOGGER.debug("CMD CT ADD price1=" + price1.getType()); + mmh.LOGGER.debug("CMD CT ADD price2=" + price2.getType()); + mmh.LOGGER.debug("CMD CT ADD itemstack=" + itemstack.getType()); + if (itemstack.getType() == Material.PLAYER_HEAD) { + ItemMeta skullMeta = itemstack.getItemMeta(); + mmh.LOGGER.debug("CMD CT ADD IS DisplayName=" + skullMeta.getDisplayName()); + if (skullMeta.hasLore()) { + mmh.LOGGER.debug("CMD CT ADD IS lore=" + String.join(",", skullMeta.getLore())); + + } + } + mmh.LOGGER.debug("CMD CT ADD quantity=" + itemstack.getAmount()); + mmh.LOGGER.debug("CMD CT ADD chance=0.002"); + // log("customFile=" + customFile); + try { + mmh.traderCustom.save(mmh.customFile); + mmh.traderCustom.load(mmh.customFile); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + } + mmh.LOGGER.debug("CMD CT ADD End -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.WHITE + " trade_" + + (tradeNumber + 1) + " " + mmh.get("mmh.message.ct.successadd")); + return true; + } else if (args[1].equalsIgnoreCase("remove")) { + mmh.LOGGER.debug("CMD CT Remove Start -----"); + if (!(args.length >= 3)) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.argument")); + return false; + } else if (mmh.isInteger(args[2])) { + mmh.traderCustom.set("custom_trades.trade_" + args[2] + ".price_1", ""); + mmh.traderCustom.set("custom_trades.trade_" + args[2] + ".price_2", ""); + mmh.traderCustom.set("custom_trades.trade_" + args[2] + ".itemstack", ""); + mmh.traderCustom.set("custom_trades.trade_" + args[2] + ".quantity", ""); + mmh.traderCustom.set("custom_trades.trade_" + args[2] + ".chance", ""); + mmh.LOGGER.debug("customFile=" + mmh.customFile); + try { + mmh.traderCustom.save(mmh.customFile); + mmh.traderCustom.load(mmh.customFile); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + + mmh.LOGGER.debug("CMD CT Remove End Exception -----"); + + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.error")); + return false; + // e.printStackTrace(); + } + mmh.LOGGER.debug("CMD CT Remove End -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.WHITE + " trade_" + + args[2] + " " + mmh.get("mmh.message.ct.successrem")); + return true; + } else { + mmh.LOGGER.debug("CMD CT Remove End 2 -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.numberreq")); + return false; + } + } else if (args[1].equalsIgnoreCase("replace")) { + mmh.LOGGER.debug("CMD CT Replace Start -----"); + if (args.length != 3) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.argument")); + return false; + } else if (mmh.isInteger(args[2])) { + ItemStack itemstack = player.getInventory().getItemInOffHand(); + ItemStack price1 = player.getInventory().getItem(0); + ItemStack price2 = player.getInventory().getItem(1); + if (price1 == null) { + price1 = new ItemStack(Material.AIR); + } + if (price2 == null) { + price2 = new ItemStack(Material.AIR); + } + if ((itemstack.getType() == Material.AIR) || (price1 == null) + || (price1.getType() == Material.AIR)) { + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + + mmh.getName() + ChatColor.GREEN + "]===============[]"); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line1") + + "custom_trades.yml"); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line2")); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line3")); + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line4") + "/mmh ct add"); + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line5") + "custom trade."); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + + mmh.getName() + ChatColor.GREEN + "]===============[]"); + mmh.LOGGER.debug("CMD CT Replace End Error -----"); + return false; + } + int tradeNumber = Integer.parseInt(args[2]); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber) + ".price_1", price1); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber) + ".price_2", price2); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber) + ".itemstack", itemstack); + /** Code to fix missing noteblock SkullMeta */ + boolean doIt = Utils.isSupportedVersion("1.20.2.3936"); + if(doIt) { + mmh.LOGGER.log("CT A doIt=" + doIt); + if(itemstack.getType().equals(Material.PLAYER_HEAD)) { + SkullMeta meta = (SkullMeta) itemstack.getItemMeta(); + NamespacedKey sound = meta.getNoteBlockSound(); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber + 1) + ".note_block_sound", sound.getKey()); + mmh.LOGGER.debug("sound.getKey()=" + sound.getNamespace()); + mmh.LOGGER.debug("sound.getKey()=" + sound.getKey()); + } + } + /** Code to fix missing noteblock SkullMeta */ + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber) + ".quantity", + itemstack.getAmount()); + mmh.traderCustom.set("custom_trades.trade_" + (tradeNumber) + ".chance", 0.002); + mmh.LOGGER.debug("CMD CT Replace price1=" + price1.getType()); + mmh.LOGGER.debug("CMD CT Replace price2=" + price2.getType()); + mmh.LOGGER.debug("CMD CT Replace itemstack=" + itemstack.getType()); + if (itemstack.getType() == Material.PLAYER_HEAD) { + ItemMeta skullMeta = itemstack.getItemMeta(); + mmh.LOGGER.debug("CMD CT Replace IS DisplayName=" + skullMeta.getDisplayName()); + if (skullMeta.hasLore()) { + mmh.LOGGER.debug("CMD CT Replace IS lore=" + String.join(",", skullMeta.getLore())); + } + mmh.LOGGER.debug("CMD CT Replace quantity=" + itemstack.getAmount()); + mmh.LOGGER.debug("CMD CT Replace chance=0.002"); + // log("customFile=" + customFile); + try { + mmh.traderCustom.save(mmh.customFile); + mmh.traderCustom.load(mmh.customFile); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + mmh.LOGGER.debug("CMD CT Replace End Exception -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.error")); + return false; + // e.printStackTrace(); + } + mmh.LOGGER.debug("CMD CT Replace End -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.WHITE + " trade_" + + args[2] + " " + mmh.get("mmh.message.ct.successrep")); + return true; + } else { + mmh.LOGGER.debug("CMD CT Replace End 2 -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.numberreq")); + return false; + } + } + } + } else if (!(sender instanceof Player)) { + mmh.LOGGER.debug("CMD CT Replace End Console -----"); + sender.sendMessage( + ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + mmh.get("mmh.message.noconsole")); + return false; + } else if (!hasPerm) { + mmh.LOGGER.debug("CMD CT Replace End !Perm -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.nopermordisabled").toString().replace("", perm)); + return false; + } + + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_CUSTOM_TRADER).error(exception)); + } + } + if (args[0].equalsIgnoreCase("playerheads") || args[0].equalsIgnoreCase("ph")) { + try { // REPORT_COMMAND_PLAYER_HEADS "Error executing PlayerHeads Command." + String perm = "moremobheads.playerheads"; + boolean hasPerm = sender.hasPermission(perm); + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if ( (hasPerm || mmh.isDev) && (sender instanceof Player) + && mmh.config.getBoolean("wandering_trades.custom_wandering_trader", true)) { + Player player = (Player) sender; + if (!(args.length >= 2)) { + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.WHITE + " /mmh ph - " + mmh.get("mmh.command.ct.help")); + sender.sendMessage(ChatColor.WHITE + " /mmh ph add - " + mmh.get("mmh.command.ct.add") + + "player_heads.yml"); + sender.sendMessage(ChatColor.WHITE + " /mmh ph remove # - " + + mmh.get("mmh.command.ct.remove").replace("custom_trades", "playerheads")); + sender.sendMessage(ChatColor.WHITE + " /mmh ph replace # - " + mmh.get("mmh.command.ct.replace") + .replace("", "#").replace("custom trade", "pleayerhead")); + // sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + return true; + } else if (args[1].equalsIgnoreCase("add")) { + mmh.LOGGER.debug("CMD PH ADD Start -----"); + ItemStack itemstack = player.getInventory().getItemInOffHand(); + ItemStack price1 = player.getInventory().getItem(0); + ItemStack price2 = player.getInventory().getItem(1); + if (price1 == null) { + price1 = new ItemStack(Material.AIR); + } + if (price2 == null) { + price2 = new ItemStack(Material.AIR); + } + // Material price1 = item1.getType(); + // Material price2 = item2.getType(); + + if ((itemstack.getType() == Material.AIR) || (price1 == null) || (price1.getType() == Material.AIR) + || (itemstack.getType() != Material.PLAYER_HEAD)) { + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + sender.sendMessage(ChatColor.WHITE + " "); + if (itemstack.getType() != Material.PLAYER_HEAD) { + sender.sendMessage(ChatColor.RED + " MUST BE PLAYERHEAD"); + sender.sendMessage(ChatColor.WHITE + " "); + } + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line1") + "player_heads.yml"); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line2")); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line3")); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line4") + "/mmh ph add"); + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line5") + "player head."); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + mmh.getName() + + ChatColor.GREEN + "]===============[]"); + mmh.LOGGER.debug("CMD PH ADD End Error -----"); + return false; + } + int tradeNumber = (int) mmh.playerHeads.get("players.number", 1); + mmh.playerHeads.set("players.player_" + (tradeNumber + 1) + ".price_1", price1); + mmh.playerHeads.set("players.player_" + (tradeNumber + 1) + ".price_2", price2); + mmh.playerHeads.set("players.player_" + (tradeNumber + 1) + ".itemstack", itemstack); + mmh.playerHeads.set("players.player_" + (tradeNumber + 1) + ".quantity", itemstack.getAmount()); + mmh.LOGGER.debug("CMD PH ADD price1=" + price1.getType()); + mmh.LOGGER.debug("CMD PH ADD price2=" + price2.getType()); + mmh.LOGGER.debug("CMD PH ADD itemstack=" + itemstack.getType()); + if (itemstack.getType() == Material.PLAYER_HEAD) { + ItemMeta skullMeta = itemstack.getItemMeta(); + mmh.LOGGER.debug("CMD PH ADD IS DisplayName=" + skullMeta.getDisplayName()); + if (skullMeta.hasLore()) { + mmh.LOGGER.debug("CMD PH ADD IS lore=" + String.join(",", skullMeta.getLore())); + + } + } + mmh.LOGGER.debug("CMD PH ADD quantity=" + itemstack.getAmount()); + // playerHeads.set("players.player_" + (tradeNumber + 1) + ".chance", 0.002); + mmh.playerHeads.set("players.number", (tradeNumber + 1)); + // log("customFile=" + customFile); + try { + mmh.playerHeads.save(mmh.playerFile); + mmh.playerHeads.load(mmh.playerFile); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + mmh.LOGGER.debug("CMD PH ADD End Exception -----"); + } + mmh.LOGGER.debug("CMD PH ADD End -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.WHITE + " player_" + + (tradeNumber + 1) + " " + mmh.get("mmh.message.ct.successadd")); + return true; + } else if (args[1].equalsIgnoreCase("remove")) { + mmh.LOGGER.debug("CMD PH Remove Start -----"); + if (!(args.length >= 3)) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.argument")); + return false; + } else if (mmh.isInteger(args[2])) { + mmh.playerHeads.set("players.player_" + args[2] + ".price_1", ""); + mmh.playerHeads.set("players.player_" + args[2] + ".price_2", ""); + mmh.playerHeads.set("players.player_" + args[2] + ".itemstack", ""); + mmh.playerHeads.set("players.player_" + args[2] + ".quantity", ""); + // playerHeads.set("custom_trades.trade_" + args[2] + ".chance", ""); + mmh.LOGGER.debug("playerFile=" + mmh.playerFile); + try { + mmh.playerHeads.save(mmh.playerFile); + mmh.playerHeads.load(mmh.playerFile); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + + mmh.LOGGER.debug("CMD PH Remove End Exception -----"); + + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.error") + "custom_trades.yml!"); + return false; + // e.printStackTrace(); + } + mmh.LOGGER.debug("CMD PH Remove End -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.WHITE + " player_" + + args[2] + " " + mmh.get("mmh.message.ct.successrem")); + return true; + } else { + mmh.LOGGER.debug("CMD PH Remove End 2 -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.numberreq")); + return false; + } + } else if (args[1].equalsIgnoreCase("replace")) { + mmh.LOGGER.debug("CMD PH Replace Start -----"); + if (args.length != 3) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.argument")); + return false; + } else if (mmh.isInteger(args[2])) { + ItemStack itemstack = player.getInventory().getItemInOffHand(); + ItemStack price1 = player.getInventory().getItem(0); + ItemStack price2 = player.getInventory().getItem(1); + if (price1 == null) { + price1 = new ItemStack(Material.AIR); + } + if (price2 == null) { + price2 = new ItemStack(Material.AIR); + } + if ((itemstack.getType() == Material.AIR) || (price1 == null) + || (price1.getType() == Material.AIR) + || (itemstack.getType() != Material.PLAYER_HEAD)) { + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + + mmh.getName() + ChatColor.GREEN + "]===============[]"); + sender.sendMessage(ChatColor.WHITE + " "); + if (itemstack.getType() != Material.PLAYER_HEAD) { + sender.sendMessage(ChatColor.RED + " " + mmh.get("mmh.command.playerhead.msg")); + sender.sendMessage(ChatColor.WHITE + " "); + } + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line1") + + "player_heads.yml"); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line2")); + sender.sendMessage(ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line3")); + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line4") + "/mmh ph add"); + sender.sendMessage( + ChatColor.WHITE + " " + mmh.get("mmh.command.ct.line5") + "player head."); + sender.sendMessage(ChatColor.WHITE + " "); + sender.sendMessage(ChatColor.GREEN + "[]===============[" + ChatColor.YELLOW + + mmh.getName() + ChatColor.GREEN + "]===============[]"); + mmh.LOGGER.debug("CMD PH Replace End Error -----"); + return false; + } + int tradeNumber = Integer.parseInt(args[2]); + mmh.playerHeads.set("players.player_" + (tradeNumber) + ".price_1", price1); + mmh.playerHeads.set("players.player_" + (tradeNumber) + ".price_2", price2); + mmh.playerHeads.set("players.player_" + (tradeNumber) + ".itemstack", itemstack); + mmh.playerHeads.set("players.player_" + (tradeNumber) + ".quantity", + itemstack.getAmount()); + mmh.LOGGER.debug("CMD PH Replace price1=" + price1.getType()); + mmh.LOGGER.debug("CMD PH Replace price2=" + price2.getType()); + mmh.LOGGER.debug("CMD PH Replace itemstack=" + itemstack.getType()); + if (itemstack.getType() == Material.PLAYER_HEAD) { + ItemMeta skullMeta = itemstack.getItemMeta(); + mmh.LOGGER.debug("CMD PH Replace IS DisplayName=" + skullMeta.getDisplayName()); + if (skullMeta.hasLore()) { + mmh.LOGGER.debug("CMD PH Replace IS lore=" + String.join(",", skullMeta.getLore())); + } + mmh.LOGGER.debug("CMD PH Replace quantity=" + itemstack.getAmount()); + // playerHeads.set("players.player_" + (tradeNumber + 1) + ".chance", 0.002); + // log("customFile=" + customFile); + try { + mmh.playerHeads.save(mmh.playerFile); + mmh.playerHeads.load(mmh.playerFile); + } catch (Exception exception) { + mmh.reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_PLUGIN_UNKNOWN_ERROR).error(exception)); + mmh.LOGGER.debug("CMD PH Replace End Exception -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.error") + "player_heads.yml!"); + return false; + // e.printStackTrace(); + } + mmh.LOGGER.debug("CMD PH Replace End -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.WHITE + " player_" + + args[2] + " " + mmh.get("mmh.message.ct.successrep")); + return true; + } else { + mmh.LOGGER.debug("CMD PH Replace End 2 -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.command.ct.numberreq")); + return false; + } + } + } + } else if (!(sender instanceof Player)) { + mmh.LOGGER.debug("CMD PH Replace End Console -----"); + sender.sendMessage( + ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + mmh.get("mmh.message.noconsole")); + return false; + } else if (!hasPerm) { + mmh.LOGGER.debug("CMD PH Replace End !Perm -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.nopermordisabled").toString().replace("", perm)); + return false; + } + + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_PLAYER_HEADS).error(exception)); + } + } + if (args[0].equalsIgnoreCase("fixhead") || args[0].equalsIgnoreCase("fh")) { + try { // REPORT_COMMAND_FIX_HEAD "Error executing Fixhead Command." + String perm = "moremobheads.fixhead"; + boolean hasPerm = sender.hasPermission(perm); + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if (sender instanceof Player) { + Player player = (Player) sender; + if (hasPerm || mmh.isDev) { + if (!args[1].isEmpty()) { + if (args[1].equalsIgnoreCase("name")) { + mmh.LOGGER.debug("CMD FH Name Start -----"); + // FixHead NBT + ItemStack mainHand = player.getInventory().getItemInMainHand(); + if (mainHand != null) { + if (mainHand.getType().equals(Material.PLAYER_HEAD)) { + String texture = mainHand.getItemMeta().getDisplayName(); + + SkullMeta skullname = (SkullMeta) mainHand.getItemMeta(); + if (skullname.getOwner() != null) { + String name = skullname.getOwner().toString(); + mmh.LOGGER.debug("EPIE name=" + name); + mmh.LOGGER.debug("EPIE lore=" + skullname.getLore()); + if (skullname.getOwner().toString().length() >= 40) { + mmh.LOGGER.debug("EPIE ownerName.lngth >= 40"); + ItemStack itmStack = mainHand; + // SkullMeta skullname = (SkullMeta) + // e.getItem().getItemStack().getItemMeta(); + String daMobName = "null"; + if (skullname != null) { + String isCat = CatHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isHorse = HorseHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isLlama = LlamaHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isMobHead = MobHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isRabbit = RabbitHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isSheep = SheepHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isVillager = VillagerHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isZombieVillager = ZombieVillagerHeads + .getNameFromTexture(skullname.getOwner().toString()); + String isplayerhead = mmh + .isPlayerHead(skullname.getOwner().toString()); + String isblockhead = mmh + .isBlockHead(skullname.getOwner().toString()); + String isblockhead2 = mmh + .isBlockHead2(skullname.getOwner().toString()); + String isblockhead3 = mmh + .isBlockHead3(skullname.getOwner().toString()); + if (isCat != null) { + daMobName = isCat; + } + if (isHorse != null) { + daMobName = isHorse; + } + if (isLlama != null) { + daMobName = isLlama; + } + if (isMobHead != null) { + daMobName = isMobHead; + } + if (isRabbit != null) { + daMobName = isRabbit; + } + if (isSheep != null) { + daMobName = isSheep; + } + if (isVillager != null) { + daMobName = isVillager; + } + if (isZombieVillager != null) { + daMobName = isZombieVillager; + } + if (daMobName == null) { + if (blockHeads != null) { + if (isblockhead != null) { + daMobName = isblockhead; + } + } + if (blockHeads2 != null) { + if (isblockhead2 != null) { + daMobName = isblockhead2; + } + } + if (blockHeads3 != null) { + if (isblockhead3 != null) { + daMobName = isblockhead3; + } + } + if (mmh.playerHeads != null) { + if (isplayerhead != null) { + daMobName = isplayerhead; + } + } + } + ArrayList lore = new ArrayList(); + // log("" + meta.getOwner().toString()); + // String name = + // LlamaHeads.getNameFromTexture(meta.getOwner().toString()); + mmh.LOGGER.debug("EPIE mobname from texture=" + daMobName); + List skullLore = skullname.getLore(); + if (skullLore != null) { + if (skullLore.toString().contains( ChatColorUtils.setColors( mmh.langName.getString("killedby", "Killed By ") ) ) ) { + lore.addAll(skullname.getLore()); + } + } + if ((skullLore == null) || !skullname.getLore().toString() + .contains(mmh.getName())) { + if (mmh.config.getBoolean("head_settings.lore.show_plugin_name", true)) { + lore.add(ChatColor.AQUA + "" + mmh.getName()); + } + } + if (daMobName != "null") { + daMobName = mmh.langName.getString( + daMobName.toLowerCase().replace(" ", "."), daMobName); + } else { + daMobName = mmh.langName.getString( + daMobName.toLowerCase().replace(" ", "."), + "404 Name Not Found"); + } + skullname.setLore(lore); + skullname.setDisplayName(daMobName); + itmStack.setItemMeta(skullname); + // fixHeadNBT(skullname.getOwner(), daMobName, lore); + mmh.LOGGER.debug("CMD FH Name End -----"); + sender.sendMessage("" + mmh.get("mmh.command.fixhead.name")); + // if(debug){mmh.TEST.debug("test3a");} + return true; + } else { + mmh.LOGGER.debug("CMD FH Name End Meta Null -----"); + return false; + } + } + } + } else { + mmh.LOGGER.debug("CMD FH Name End Error -----"); + sender.sendMessage("An Error occured."); + return false; + } + } + } + + if (args[1].equalsIgnoreCase("stack")) { + mmh.LOGGER.debug("CMD FH Stack Start -----"); + // FixHead Stack + ItemStack mainHand = player.getInventory().getItemInMainHand(); + ItemStack offHand = player.getInventory().getItemInOffHand(); + if ((mainHand != null) && (offHand != null)) { + if (mainHand.getType().equals(Material.PLAYER_HEAD) + && offHand.getType().equals(Material.PLAYER_HEAD)) { + ItemStack is = mmh.fixHeadStack(offHand, mainHand); + // is.setAmount(mainHand.getAmount()); + if (is != mainHand) { + player.getInventory().setItemInMainHand(is); + mmh.LOGGER.debug("is=" + is.getType()); + mmh.LOGGER.debug("CMD FH Stack End -----"); + sender.sendMessage("" + mmh.get("mmh.command.fixhead.stack.success")); + return true; + } else { + mmh.LOGGER.debug("CMD FH Stack End Error -----"); + sender.sendMessage("" + mmh.get("mmh.command.fixhead.stack.error")); + return false; + } + } else if (!mainHand.getType().equals(Material.PLAYER_HEAD) + && !offHand.getType().equals(Material.PLAYER_HEAD)) { + mmh.LOGGER.debug("CMD FH Stack End Error Main Off -----"); + sender.sendMessage("" + mmh.get("mmh.command.fixhead.stack.notph")); + return false; + } else if (!mainHand.getType().equals(Material.PLAYER_HEAD) + && offHand.getType().equals(Material.PLAYER_HEAD)) { + mmh.LOGGER.debug("CMD FH Stack End Error Main -----"); + sender.sendMessage("" + mmh.get("mmh.command.fixhead.stack.main")); + return false; + } else if (mainHand.getType().equals(Material.PLAYER_HEAD) + && !offHand.getType().equals(Material.PLAYER_HEAD)) { + mmh.LOGGER.debug("CMD FH Stack End Error Off -----"); + sender.sendMessage("" + mmh.get("mmh.command.fixhead.stack.off")); + return false; + } + } + } + } + + } else if (!hasPerm) { + mmh.LOGGER.debug("CMD FH Stack End !Perm -----"); + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.noperm").toString().replace("", perm)); + return false; + } + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_FIX_HEAD).error(exception)); + } + } + if (args[0].equalsIgnoreCase("giveMH")) { + try { // REPORT_COMMAND_GIVE_MH "Error executing GiveMH Command." + // /mmh giveMH player mob qty + // cmd 0 1 2 3 + if (args.length == 4) { + String perm = "moremobheads.give"; + boolean hasPerm = sender.hasPermission(perm) || !(sender instanceof Player) ? true : false; + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if (hasPerm || mmh.isDev) { + Player player = Bukkit.getPlayer(args[1]); + if (!args[2].isEmpty()) { + String mob = args[2].toLowerCase(); + mmh.LOGGER.log("mob=" + mob); + if (!args[3].isEmpty()) { + int number = Integer.parseInt(args[3]); + String[] splitmob = mob.split("\\."); + mmh.LOGGER.debug("CMD GMH splitmob[0]=" + splitmob[0]); + switch (splitmob[0]) { + case "creeper_charged": + mmh.playerGiveOrDropHead( player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.CREEPER, number) + ); + break; + case "creeper": + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.creeper", false)) { + mmh.playerGiveOrDropHead(player, new ItemStack(Material.CREEPER_HEAD)); + } else { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.CREEPER, number) + ); + } + break; + case "mushroom_cow": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads( + mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), MobHeads.valueOf((splitmob[0] + "." + splitmob[1]).toUpperCase()).getName()), + MobHeads.valueOf((splitmob[0] + "." + splitmob[1]).toUpperCase()).getTexture(), MobHeads.valueOf((splitmob[0] + "." + splitmob[1]).toUpperCase()).getOwner(), EntityType.COW, number) + ); + break; + case "strider_shivering": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.STRIDER, number) + ); + break; + case "zombie": + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.zombie", false)) { + mmh.playerGiveOrDropHead(player, new ItemStack(Material.ZOMBIE_HEAD)); + } else { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.ZOMBIE, number) + ); + } + break; + + case "piglin": + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.piglin", false)) { + mmh.playerGiveOrDropHead(player, new ItemStack(Material.PIGLIN_HEAD)); + } else { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.PIGLIN, number) + ); + } + break; + case "skeleton": + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.skeleton", false)) { + mmh.playerGiveOrDropHead(player, new ItemStack(Material.SKELETON_SKULL)); + } else { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.SKELETON, number) + ); + } + break; + case "wither_skeleton": + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.wither_skeleton", false)) { + mmh.playerGiveOrDropHead(player, new ItemStack(Material.WITHER_SKELETON_SKULL)); + } else { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.WITHER_SKELETON, number) + ); + } + break; + case "ender_dragon": + if (mmh.config.getBoolean("head_settings.mob_heads.vanilla_heads.ender_dragon", false)) { + mmh.playerGiveOrDropHead(player, new ItemStack(Material.DRAGON_HEAD)); + } else { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase(), MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.ENDER_DRAGON, number) + ); + } + break; + case "cat": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads( + mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), CatHeads.valueOf(splitmob[1].toUpperCase()).getName()), + CatHeads.valueOf(splitmob[1].toUpperCase()).getTexture(), CatHeads.valueOf(splitmob[1].toUpperCase()).getOwner(), EntityType.CAT, number) + ); + break; + case "bee": + mmh.LOGGER.log("splitmob.length=" + splitmob.length); + if (splitmob.length == 1) { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(splitmob[0].toLowerCase() + ".none", MobHeads.valueOf(splitmob[0].toUpperCase()).getName()), + MobHeads.valueOf(splitmob[0].toUpperCase()).getTexture(), MobHeads.valueOf(splitmob[0].toUpperCase()).getOwner(), EntityType.BEE, number) + ); + } else { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(mob.replace(".", "_").toLowerCase(), MobHeads.valueOf(mob.replace(".", "_").toUpperCase()).getName()), + MobHeads.valueOf(mob.replace(".", "_").toUpperCase()).getTexture(), MobHeads.valueOf(mob.replace(".", "_").toUpperCase()).getOwner(), EntityType.BEE, number) + ); + } + break; + case "villager": // villager type profession, + // villager profession type + // name = splitmob[0], type = splitmob[1], + // profession = splitmob[2] + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1] + "." + splitmob[2]).toLowerCase(), + VillagerHeads.valueOf((splitmob[0] + "_" + splitmob[2] + "_" + splitmob[1]).toUpperCase()).getName()), + VillagerHeads.valueOf((splitmob[0] + "_" + splitmob[2] + "_" + splitmob[1]).toUpperCase()).getTexture(), + VillagerHeads.valueOf((splitmob[0] + "_" + splitmob[2] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.VILLAGER, number) + ); + break; + case "zombie_villager": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + ZombieVillagerHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getName()), + ZombieVillagerHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getTexture(), + ZombieVillagerHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.ZOMBIE_VILLAGER, number) + ); + break; + case "llama": + case "trader_llama": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + LlamaHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getName()), + LlamaHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getTexture(), + LlamaHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.LLAMA, number) + ); + break; + case "horse": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + HorseHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getName()), + HorseHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getTexture(), + HorseHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.HORSE, number) + ); + break; + case "rabbit": + if( splitmob[1].equalsIgnoreCase("Toast") ) { + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + RabbitHeads.valueOf((splitmob[0] + "_").toUpperCase().concat( StrUtils.toTitleCase(splitmob[1]) )).getName()), + RabbitHeads.valueOf((splitmob[0] + "_").toUpperCase().concat( StrUtils.toTitleCase(splitmob[1]) )).getTexture(), + RabbitHeads.valueOf((splitmob[0] + "_").toUpperCase().concat( StrUtils.toTitleCase(splitmob[1]) )).getOwner(), EntityType.RABBIT, number) + ); + break; + } + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + RabbitHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getName()), + RabbitHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getTexture(), + RabbitHeads.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.RABBIT, number) + ); + break; + case "sheep": + String sheeptype; + if (splitmob[1].equalsIgnoreCase("jeb_")) { + sheeptype = "jeb_"; + } else { + sheeptype = splitmob[1].toUpperCase(); + } + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + sheeptype).toLowerCase(), + SheepHeads.valueOf((splitmob[0] + "_").toUpperCase().concat(sheeptype)).getName()), + SheepHeads.valueOf((splitmob[0] + "_").toUpperCase().concat(sheeptype)).getTexture(), + SheepHeads.valueOf((splitmob[0] + "_").toUpperCase().concat(sheeptype)).getOwner(), EntityType.SHEEP, number) + ); + break; + case "goat": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + MobHeads117.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getName()), + MobHeads117.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getTexture(), + MobHeads117.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.GOAT, number) + ); + break; + case "axolotl": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + MobHeads117.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getName()), + MobHeads117.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getTexture(), + MobHeads117.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.AXOLOTL, number) + ); + break; + case "frog": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + MobHeads119.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getName()), + MobHeads119.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getTexture(), + MobHeads119.valueOf((splitmob[0] + "_" + splitmob[1]).toUpperCase()).getOwner(), EntityType.FROG, number) + ); + break; + case "camel": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0]).toLowerCase(), + MobHeads120.valueOf((splitmob[0]).toUpperCase()).getName()), + MobHeads120.valueOf((splitmob[0]).toUpperCase()).getTexture(), + MobHeads120.valueOf((splitmob[0]).toUpperCase()).getOwner(), EntityType.CAMEL, number) + ); + break; + case "sniffer": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0]).toLowerCase(), + MobHeads120.valueOf((splitmob[0]).toUpperCase()).getName()), + MobHeads120.valueOf((splitmob[0]).toUpperCase()).getTexture(), + MobHeads120.valueOf((splitmob[0]).toUpperCase()).getOwner(), EntityType.SNIFFER, number) + ); + break; + case "allay": + case "tadpole": + case "warden": + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(mob.toLowerCase(), + MobHeads119.valueOf(mob.replace(".", "_").toUpperCase()).getName()), + MobHeads119.valueOf(mob.replace(".", "_").toUpperCase()).getTexture(), + MobHeads119.valueOf(mob.replace(".", "_").toUpperCase()).getOwner(), EntityType.fromName(splitmob[0].toString()), number) + ); + break; + case "tropical_fish":// TropicalFishHeads + String fishType = splitmob[1].toUpperCase(); + mmh.LOGGER.log("splitmob[0]=" + splitmob[0]); + mmh.LOGGER.log("splitmob[1]=" + splitmob[1]); + mmh.LOGGER.log("fishType=" + fishType); + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString((splitmob[0] + "." + splitmob[1]).toLowerCase(), + TropicalFishHeads.valueOf((splitmob[0] + "." + splitmob[1]).toUpperCase()).getName()), + TropicalFishHeads.valueOf((splitmob[0] + "." + splitmob[1]).toUpperCase()).getTexture(), + TropicalFishHeads.valueOf((splitmob[0] + "." + splitmob[1]).toUpperCase()).getOwner(), EntityType.TROPICAL_FISH, number) + ); + break; + default: + mmh.playerGiveOrDropHead(player, + mmh.makeHeads(mmh.langName.getString(mob.toLowerCase(), + MobHeads.valueOf(mob.replace(".", "_").toUpperCase()).getName()), + MobHeads.valueOf(mob.replace(".", "_").toUpperCase()).getTexture(), + MobHeads.valueOf(mob.replace(".", "_").toUpperCase()).getOwner(), EntityType.fromName(splitmob[0].toString()), number) + ); + break; + } + } + } + } else if (!hasPerm) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.noperm").toString().replace("", perm)); + return false; + } + } else { + sender.sendMessage("" + mmh.get("mmh.command.usage") + ", /mmh givemh playername mobname 1"); + return false; + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_GIVE_MH).error(exception)); + } + } + // /mmh giveph player + // /mmh giveph player player + // 0 1 2 3 + if (args[0].equalsIgnoreCase("givePH")) { + try { // REPORT_COMMAND_GIVE_PH "Error executing GivePH Command." + if (args.length >= 2) { + String perm = "moremobheads.give"; + boolean hasPerm = sender.hasPermission(perm) || !(sender instanceof Player) ? true : false; + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if (hasPerm || mmh.isDev) { + mmh.LOGGER.debug("CMD GPH args.length=" + args.length); + if ((args.length == 2) && (sender instanceof Player)) { + mmh.givePlayerHead((Player) sender, args[1]); + mmh.LOGGER.debug("CMD GPH args1=" + args[1]); + return true; + } else if (args.length == 3) { + Player player = Bukkit.getPlayer(args[1]); + mmh.givePlayerHead(player, args[2]); + mmh.LOGGER.debug("CMD GPH args1=" + args[1] + ", args2=" + args[2]); + return true; + } else if ((args.length == 2) && !(sender instanceof Player)) { + sender.sendMessage( + "" + mmh.get("mmh.command.give.console") + "" + mmh.get("mmh.command.usage") + ":"); + sender.sendMessage( + "\"/mmh giveph playername 1\" - " + mmh.get("mmh.command.give.playerhead.you")); + sender.sendMessage("\"/mmh giveph playername playername 1\" - " + + mmh.get("mmh.command.give.playerhead.them")); + return false; + } + } else if (!hasPerm) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.noperm").toString().replace("", perm)); + return false; + } + } else { + sender.sendMessage("" + mmh.get("mmh.command.usage") + ":"); + sender.sendMessage("\"/mmh giveph playername 1\" - " + mmh.get("mmh.command.give.playerhead.you")); + sender.sendMessage( + "\"/mmh giveph playername playername 1\" - " + mmh.get("mmh.command.give.playerhead.them")); + return false; + } + return false; + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_GIVE_PH).error(exception)); + } + } + // /mmh givebh block + // /mmh givebh player block + // 0 1 2 3 + if (args[0].equalsIgnoreCase("giveBH")) { + try { // REPORT_COMMAND_GIVE_BH "Error executing GiveBH Command." + mmh.LOGGER.debug("Start GiveBH"); + mmh.LOGGER.debug("Command=" + cmd.getName() + ", arguments=" + Arrays.toString(args)); + if (args.length >= 2) { + String perm = "moremobheads.give"; + boolean hasPerm = sender.hasPermission(perm) || !(sender instanceof Player) ? true : false; + mmh.LOGGER.debug(sender.getName() + " has the permission " + perm + "=" + hasPerm); + if (hasPerm || mmh.isDev) { + mmh.LOGGER.debug("CMD GBH args.length=" + args.length); + if ((args.length == 2) && (sender instanceof Player)) { + mmh.giveBlockHead((Player) sender, args[1].replace("_", " ")); + mmh.LOGGER.debug("CMD GBH args1=" + args[1]); + mmh.LOGGER.debug("End GiveBH True 1"); + return true; + } else if (args.length == 3) { + Player player = Bukkit.getPlayer(args[1]); + mmh.giveBlockHead(player, args[2].replace("_", " ")); + mmh.LOGGER.debug("CMD GBH args1=" + args[1] + ", args2=" + args[2]); + mmh.LOGGER.debug("End GiveBH True 2"); + return true; + } else if ((args.length == 2) && !(sender instanceof Player)) { + sender.sendMessage( + "" + mmh.get("mmh.command.give.console") + " mmh giveBh "); + mmh.LOGGER.debug("End GiveBH False 1"); + return false; + } + } else if (!hasPerm) { + sender.sendMessage(ChatColor.YELLOW + mmh.getName() + ChatColor.RED + " " + + mmh.get("mmh.message.noperm").toString().replace("", perm)); + mmh.LOGGER.debug("End GiveBH False 2"); + return false; + } + } else { + sender.sendMessage("" + mmh.get("mmh.command.usage") + ":"); + sender.sendMessage("\"/mmh givebh \" - " + mmh.get("mmh.command.give.blockhead.you")); + sender.sendMessage( + "\"/mmh giveph playername \" - " + mmh.get("mmh.command.give.blockhead.them")); + mmh.LOGGER.debug("End GiveBH False 3"); + return false; + } + mmh.LOGGER.debug("End GiveBH False 4"); + return false; + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_GIVE_BH).error(exception)); + } + } + if (args[0].equalsIgnoreCase("dev")) { + try { // REPORT_COMMAND_DEV_ERROR "Error exuting dev Command." + if (sender instanceof Player) { + Player player = (Player) sender; + if (player.getName().equalsIgnoreCase("JoelGodOfWar") + || player.getName().equalsIgnoreCase("JoelYahwehOfWar")) { + mmh.isDev = !mmh.isDev; + player.sendMessage("You have toggled isDev to " + mmh.isDev); + return true; + } else { + player.sendMessage("You are not the developer."); + return false; + } + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_COMMAND_DEV_ERROR).error(exception)); + } + } + } + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.UNHANDLED_COMMAND_ERROR).error(exception)); + // ERROR_RUNNING_DRAGON_DEATH_COMMAND "Error running command after dragon death." + } + return false; + } + + @Override + @SuppressWarnings("static-access") + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { // TODO: Tab Complete + try { // REPORT_TAB_COMPLETE_ERROR "Error parsing Tab Complete." + if (command.getName().equalsIgnoreCase("mmh")) { + List autoCompletes = new ArrayList<>(); // create a new string list for tab completion + if (args.length == 1) { // reload, toggledebug, playerheads, customtrader, headfix + autoCompletes.add("reload"); + autoCompletes.add("toggledebug"); + autoCompletes.add("playerheads"); + autoCompletes.add("customtrader"); + autoCompletes.add("fixhead"); + autoCompletes.add("givemh"); + autoCompletes.add("giveph"); + autoCompletes.add("givebh"); + autoCompletes.add("display"); + return autoCompletes; // then return the list + } + if (args.length > 1) { + if (args[0].equalsIgnoreCase("display") && args[1].isEmpty()) { + autoCompletes.add("permissions"); + autoCompletes.add("variables"); + return autoCompletes; // then return the list + } else if (args[0].equalsIgnoreCase("display") && args[1].equalsIgnoreCase("permissions")) { + if (args[1].equalsIgnoreCase("permissions")) { + return null; + } + } + if (args[0].equalsIgnoreCase("fixhead") || (args[0].equalsIgnoreCase("fh") && args[1].isEmpty())) { + autoCompletes.add("name"); + autoCompletes.add("stack"); + return autoCompletes; // then return the list + } + if (args[0].equalsIgnoreCase("playerheads") || (args[0].equalsIgnoreCase("ph") && args[1].isEmpty())) { + autoCompletes.add("add"); + autoCompletes.add("remove"); + autoCompletes.add("replace"); + return autoCompletes; // then return the list + } else if ((args[0].equalsIgnoreCase("playerheads") || args[0].equalsIgnoreCase("ph")) + && (args[1].equalsIgnoreCase("remove") || args[1].equalsIgnoreCase("replace"))) { + if (args[1].equalsIgnoreCase("remove")) { + autoCompletes.add("0"); + return autoCompletes; // then return the list + } + if (args[1].equalsIgnoreCase("replace")) { + autoCompletes.add("0"); + return autoCompletes; // then return the list + } + } + if (args[0].equalsIgnoreCase("customtrader") || (args[0].equalsIgnoreCase("ct") && args[1].isEmpty())) { + autoCompletes.add("add"); + autoCompletes.add("remove"); + autoCompletes.add("replace"); + return autoCompletes; // then return the list + } else if ((args[0].equalsIgnoreCase("customtrader") || args[0].equalsIgnoreCase("ct")) + && (args[1].equalsIgnoreCase("remove") || args[1].equalsIgnoreCase("replace"))) { + if (args[1].equalsIgnoreCase("remove")) { + autoCompletes.add("0"); + return autoCompletes; // then return the list + } + if (args[1].equalsIgnoreCase("replace")) { + autoCompletes.add("0"); + return autoCompletes; // then return the list + } + } + if (args[0].equalsIgnoreCase("givebh")) { + if (args.length < 2) { + // /mmh giveph block + // /mmh giveph @p block + // /cmd 0 1 2 + // return null to list all players. + return null; + } + if (args.length > 2) { + for (int i = 1; i < blockHeads.getInt("blocks.number"); ++i) { + ItemStack stack = blockHeads.getItemStack("blocks.block_" + i + ".itemstack"); + String name = stack.getItemMeta().getDisplayName().replace(" ", "_"); + autoCompletes.add(ChatColor.stripColor(name)); + } + if (Double.parseDouble(StrUtils.Left(mmh.getMCVersion(), 4)) >= 1.16) { + for (int i = 1; i < blockHeads2.getInt("blocks.number"); ++i) { + ItemStack stack = blockHeads2.getItemStack("blocks.block_" + i + ".itemstack"); + String name = stack.getItemMeta().getDisplayName().replace(" ", "_"); + autoCompletes.add(ChatColor.stripColor(name)); + } + } + if (Double.parseDouble(StrUtils.Left(mmh.getMCVersion(), 4)) >= 1.17) { + for (int i = 1; i < blockHeads3.getInt("blocks.number"); ++i) { + ItemStack stack = blockHeads3.getItemStack("blocks.block_" + i + ".itemstack"); + String name = stack.getItemMeta().getDisplayName().replace(" ", "_"); + autoCompletes.add(ChatColor.stripColor(name)); + } + } + + return autoCompletes; + } + } + if (args[0].equalsIgnoreCase("giveph")) { + // return null; + if (args.length < 2) { + // /mmh giveph @p @P + // /cmd 0 1 2 + // return null to list all players. + return null; + } + if (args.length == 2) { + // /mmh giveph @p @P + // /cmd 0 1 2 + // return null to list all players. + return null; + } + + } + if (args[0].equalsIgnoreCase("givemh")) { + if (args.length < 2) { + // /mmh give @p + // /cmd 0 1 + // return null to list all players. + return null; + } else if (args.length > 2) { + mmh.LOGGER.debug("TC arg1!null args.length=" + args.length); + } + if (args.length == 3) { + + // /mmh give @p moblist # + // /cmd 0 1 2 3 + for (String key : chanceConfig.getConfigurationSection("chance_percent").getKeys(true)) { + // System.out.println(key); + autoCompletes.add(key); + // System.out.println(key); + if (key.equalsIgnoreCase("wolf")) { + autoCompletes.add("wolf.angry"); + } else if (key.equalsIgnoreCase("wither")) { + autoCompletes.add("wither.normal"); + autoCompletes.add("wither.projectile"); + autoCompletes.add("wither.blue_projectile"); + autoCompletes.remove(autoCompletes.indexOf("wither")); + } else if (key.equalsIgnoreCase("zombie_villager")) { + autoCompletes.add("zombie_villager.armorer"); + autoCompletes.add("zombie_villager.butcher"); + autoCompletes.add("zombie_villager.cartographer"); + autoCompletes.add("zombie_villager.cleric"); + autoCompletes.add("zombie_villager.farmer"); + autoCompletes.add("zombie_villager.fisherman"); + autoCompletes.add("zombie_villager.fletcher"); + autoCompletes.add("zombie_villager.leatherworker"); + autoCompletes.add("zombie_villager.librarian"); + autoCompletes.add("zombie_villager.mason"); + autoCompletes.add("zombie_villager.nitwit"); + autoCompletes.add("zombie_villager.none"); + autoCompletes.add("zombie_villager.shepherd"); + autoCompletes.add("zombie_villager.toolsmith"); + autoCompletes.add("zombie_villager.weaponsmith"); + autoCompletes.remove(autoCompletes.indexOf("zombie_villager")); + } else if (key.equalsIgnoreCase("vex")) { + autoCompletes.add("vex.angry"); + } + } + autoCompletes.remove(autoCompletes.indexOf("axolotl")); + autoCompletes.remove(autoCompletes.indexOf("bee")); + autoCompletes.remove(autoCompletes.indexOf("cat")); + autoCompletes.remove(autoCompletes.indexOf("fox")); + autoCompletes.remove(autoCompletes.indexOf("goat")); + autoCompletes.remove(autoCompletes.indexOf("horse")); + autoCompletes.remove(autoCompletes.indexOf("llama")); + autoCompletes.remove(autoCompletes.indexOf("panda")); + autoCompletes.remove(autoCompletes.indexOf("parrot")); + autoCompletes.remove(autoCompletes.indexOf("rabbit")); + autoCompletes.remove(autoCompletes.indexOf("sheep")); + autoCompletes.remove(autoCompletes.indexOf("trader_llama")); + autoCompletes.remove(autoCompletes.indexOf("mushroom_cow")); + autoCompletes.remove(autoCompletes.indexOf("tropical_fish")); + autoCompletes.remove(autoCompletes.indexOf("villager")); + autoCompletes.remove(autoCompletes.indexOf("villager.desert")); + autoCompletes.remove(autoCompletes.indexOf("villager.jungle")); + autoCompletes.remove(autoCompletes.indexOf("villager.plains")); + autoCompletes.remove(autoCompletes.indexOf("villager.savanna")); + autoCompletes.remove(autoCompletes.indexOf("villager.snow")); + autoCompletes.remove(autoCompletes.indexOf("villager.swamp")); + autoCompletes.remove(autoCompletes.indexOf("villager.taiga")); + autoCompletes.remove(autoCompletes.indexOf("frog")); + + return autoCompletes; + } else if (args.length == 4) { + autoCompletes.add("1"); + return autoCompletes; + } + } + } + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_TAB_COMPLETE_ERROR).error(exception)); + } + return null; + + } + + /** + * Simulates a coin flip, returning `true` for heads and `false` for tails. + * + * @return `true` if the coin flip results in heads; `false` for tails. + */ + public boolean coinFlip() { + Random random = new Random(); + if(mmh.isDev) {return true;} + return random.nextDouble() < 0.5; + } + + @EventHandler + public void onStriderShiver(StriderTemperatureChangeEvent event) { + Strider strider = event.getEntity(); + PersistentDataContainer pdc = strider.getPersistentDataContainer(); + if (event.isShivering()) { + pdc.set(mmh.SHIVERING_KEY, PersistentDataType.STRING, "true"); + } else { + pdc.set(mmh.SHIVERING_KEY, PersistentDataType.STRING, "false"); + } + } + + public void announceBeheading(Entity entity, String entityName2, Player player, boolean display) { + try { // REPORT_ANNOUNCE_ERROR "Error announcing beheading." + UUID damagingPlayerUUID = player != null ? player.getUniqueId() : null; + ItemStack damagingWeapon = mmh.playerWeapons.get(damagingPlayerUUID); + String killerName = player.getDisplayName(); + String entityName = entityName2; + if (display) { + killerName = player.getDisplayName(); + entityName = entityName2; + } else { + killerName = player.getName(); + entityName = entity.getName(); + } + + if ((damagingPlayerUUID != null) && (damagingWeapon != null)) { + Player damagingPlayer = Bukkit.getPlayer(damagingPlayerUUID); + if (damagingPlayer != null) { + String weaponName = damagingWeapon.getItemMeta().getDisplayName(); + if (!damagingWeapon.getItemMeta().hasDisplayName()) { + weaponName = damagingWeapon.getType().name(); + } + int randomIndex = (int) (Math.random() * mmh.beheadingMessages.getConfigurationSection("messages").getKeys(false).size()) + 1; + String announcement = mmh.beheadingMessages.getString("messages.message_" + randomIndex, "%killerName% beheaded %entityName% with %weaponName%.") + .replace("%killerName%", killerName).replace("%entityName%", entityName).replace("%weaponName%", weaponName); + + Bukkit.broadcastMessage(ChatColorUtils.setColors(announcement)); + } + } else if ((damagingPlayerUUID != null) && (damagingWeapon == null)) { // Bare Hands? + String weaponName = "Bare Hands"; + int randomIndex = (int) (Math.random() * mmh.beheadingMessages.getConfigurationSection("messages").getKeys(false).size()) + 1; + String announcement = mmh.beheadingMessages.getString("messages.message_" + randomIndex, "%killerName% beheaded %entityName% with %weaponName%.") + .replace("%killerName%", killerName).replace("%entityName%", entityName).replace("%weaponName%", weaponName); + + Bukkit.broadcastMessage(ChatColorUtils.setColors(announcement)); + + } + + // Remove entity from map since it's dead. + UUID entityUUID = entity.getUniqueId(); + if (mmh.entityPlayers.containsKey(entityUUID)) { + mmh.entityPlayers.remove(entityUUID); + } + mmh.playerWeapons.remove(damagingPlayerUUID); + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_ANNOUNCE_ERROR).error(exception)); + } + } + + // public class EquipmentSlotResolver { + + /** + * Resolves the damaging weapon used based on the specified DamageCause in a player's inventory. + * + * @param playerInventory The player's inventory to search for the damaging weapon. + * @param damageCause The DamageCause that triggered the damage event. + * @return An Optional containing the damaging weapon item if found, or an empty Optional if not found. + */ + public Optional resolveDamagingWeapon(PlayerInventory playerInventory, DamageCause damageCause) { + try { // REPORT_RESOLVE_DAMAGE_WEAPON "Error resolving the Damaging Weapon." + mmh.LOGGER.debug("RDW - DamageCause=" + damageCause.toString()); + switch (damageCause) { + case ENTITY_ATTACK: + // Check if the player is holding any item in the main hand + ItemStack mainHandItem = playerInventory.getItemInMainHand(); + mmh.LOGGER.debug("RDW - mainHandItem=" + mainHandItem.getType().toString()); + if (!mainHandItem.getType().equals(Material.AIR)) { + return Optional.of(mainHandItem); + } + break; + case PROJECTILE: + // Ranged damage + Optional bowItem = getWeaponItem(playerInventory, Material.BOW); + if (bowItem.isPresent()) { + return bowItem; + } + Optional crossbowItem = getWeaponItem(playerInventory, Material.CROSSBOW); + if (crossbowItem.isPresent()) { + return crossbowItem; + } + Optional tridentItem = getWeaponItem(playerInventory, Material.TRIDENT); + if (tridentItem.isPresent()) { + return tridentItem; + } + Optional snowItem = getWeaponItem(playerInventory, Material.SNOWBALL); + if (snowItem.isPresent()) { + return snowItem; + } + Optional eggItem = getWeaponItem(playerInventory, Material.EGG); + if (eggItem.isPresent()) { + return eggItem; + } + break; + case MAGIC: + // Potion + return getWeaponItem(playerInventory, Material.SPLASH_POTION); + case ENTITY_EXPLOSION: + return Optional.of(new ItemStack(Material.TNT)); + default: + break; + } + }catch (Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_RESOLVE_DAMAGE_WEAPON).error(exception)); + } + return Optional.empty(); + } + + /** + * Retrieves the player's weapon item of the specified material, if it exists in either the main hand or off hand. + * + * @param playerInventory The player's inventory to search. + * @param material The material type of the weapon to look for. + * @return An Optional containing the weapon item if found, or an empty Optional if not found. + */ + private Optional getWeaponItem(PlayerInventory playerInventory, Material material) { + try { // REPORT_GET_WEAPON_ERROR "Error getting weapon." + ItemStack mainHandItem = playerInventory.getItemInMainHand(); + ItemStack offHandItem = playerInventory.getItemInOffHand(); + + if (mainHandItem.getType() == material) { + return Optional.of(mainHandItem); + } else if (offHandItem.getType() == material) { + return Optional.of(offHandItem); + } + }catch(Exception exception) { + reporter.reportDetailed(this, Report.newBuilder(PluginLibrary.REPORT_GET_WEAPON_ERROR).error(exception)); + } + return Optional.empty(); + } + + + + + +} \ No newline at end of file diff --git a/util/Ansi.java b/util/Ansi.java new file mode 100644 index 0000000..81cefde --- /dev/null +++ b/util/Ansi.java @@ -0,0 +1,72 @@ +package com.github.joelgodofwar.mmh.util; + +//import org.bukkit.ChatColor; + +public class Ansi { + public static final String RESET = "\u001B[0m"; + + public static final String BOLD = "\u001B[1m"; + public static final String NORMAL = "\u001B[2m"; + + public static final String ITALIC = "\u001B[3m"; + public static final String UNDERLINE = "\u001B[4m"; + public static final String BLINK = "\u001B[5m"; + public static final String RAPID_BLINK = "\u001B[6m"; + + public static final String BLACK = "\u001B[30m"; + public static final String RED = "\u001B[31m"; + public static final String GREEN = "\u001B[32m"; + public static final String YELLOW = "\u001B[33m"; + public static final String BLUE = "\u001B[34m"; + public static final String MAGENTA = "\u001B[35m"; + public static final String CYAN = "\u001B[36m"; + public static final String WHITE = "\u001B[37m"; + + + public static final String LIGHT_BLACK = "\u001B[90m"; + public static final String LIGHT_RED = "\u001B[91m"; + public static final String LIGHT_GREEN = "\u001B[92m"; + public static final String LIGHT_YELLOW = "\u001B[93m"; + public static final String LIGHT_BLUE = "\u001B[94m"; + public static final String LIGHT_MAGENTA = "\u001B[95m"; + public static final String LIGHT_CYAN = "\u001B[96m"; + public static final String LIGHT_WHITE = "\u001B[97m"; + + public static String stripAnsi(String string) { + string = string.replace("" + Ansi.BLACK , "").replace("" + Ansi.BLINK , "").replace("" + Ansi.BLUE , "").replace("" + Ansi.BOLD , "") + .replace("" + Ansi.CYAN , "").replace("" + Ansi.GREEN , "").replace("" + Ansi.ITALIC , "").replace("" + Ansi.LIGHT_BLACK , "") + .replace("" + Ansi.LIGHT_BLUE , "").replace("" + Ansi.LIGHT_CYAN , "").replace("" + Ansi.LIGHT_GREEN , "").replace("" + Ansi.LIGHT_MAGENTA , "") + .replace("" + Ansi.LIGHT_RED , "").replace("" + Ansi.LIGHT_WHITE , "").replace("" + Ansi.LIGHT_YELLOW , "").replace("" + Ansi.MAGENTA , "") + .replace("" + Ansi.NORMAL , "").replace("" + Ansi.RAPID_BLINK , "").replace("" + Ansi.RED , "").replace("" + Ansi.RESET , "").replace("" + Ansi.UNDERLINE , "") + .replace("" + Ansi.WHITE , "").replace("" + Ansi.YELLOW , ""); + return string; + } + + + /**public static final ChatColor AQUA = ChatColor.AQUA; + public static final ChatColor BLACK = ChatColor.BLACK; + public static final ChatColor BLUE = ChatColor.BLUE; + public static final ChatColor DARK_AQUA = ChatColor.DARK_AQUA; + public static final ChatColor DARK_BLUE = ChatColor.DARK_BLUE; + public static final ChatColor DARK_GRAY = ChatColor.DARK_GRAY; + public static final ChatColor DARK_GREEN = ChatColor.DARK_GREEN; + public static final ChatColor DARK_PURPLE = ChatColor.DARK_PURPLE; + public static final ChatColor DARK_RED = ChatColor.DARK_RED; + public static final ChatColor GOLD = ChatColor.GOLD; + public static final ChatColor GRAY = ChatColor.GRAY; + public static final ChatColor GREEN = ChatColor.GREEN; + public static final ChatColor MAGENTA = ChatColor.LIGHT_PURPLE; + public static final ChatColor RED = ChatColor.RED; + public static final ChatColor WHITE = ChatColor.WHITE; + public static final ChatColor YELLOW = ChatColor.YELLOW; + + public static final ChatColor BOLD = ChatColor.BOLD; + public static final ChatColor ITALIC = ChatColor.ITALIC; + public static final ChatColor MAGIC = ChatColor.MAGIC; + public static final ChatColor RESET = ChatColor.RESET; + public static final ChatColor STRIKETHROUGH = ChatColor.STRIKETHROUGH; + public static final ChatColor UNDERLINE = ChatColor.UNDERLINE;//*/ + + + +} \ No newline at end of file diff --git a/util/ChatColorUtils.java b/util/ChatColorUtils.java new file mode 100644 index 0000000..ba46753 --- /dev/null +++ b/util/ChatColorUtils.java @@ -0,0 +1,89 @@ +package com.github.joelgodofwar.mmh.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bukkit.ChatColor; + +public class ChatColorUtils +{ + public static String setNametoRGB(String s){ + String prefix = "§x"; + s = s.replace("", prefix + "000000").replace("", prefix + "0000AA") + .replace("", prefix + "00AA00").replace("", prefix + "00AAAA") + .replace("", prefix + "AA0000").replace("", prefix + "AA00AA") + .replace("", prefix + "FFAA00").replace("", prefix + "AAAAAA") + .replace("", prefix + "555555").replace("", prefix + "5555FF") + .replace("", prefix + "55FF55").replace("", prefix + "55FFFF") + .replace("", prefix + "FF5555").replace("", prefix + "FF55FF") + .replace("", prefix + "FFFF55").replace("", prefix + "FFFFFF"); + return s; + } + + public static String setColorsByCode(String s) + { + s = s.replace("&0", "" + ChatColor.BLACK).replace("&1", "" + ChatColor.DARK_BLUE) + .replace("&2", "" + ChatColor.DARK_GREEN).replace("&3", "" + ChatColor.DARK_AQUA) + .replace("&4", "" + ChatColor.DARK_RED).replace("&5", "" + ChatColor.DARK_PURPLE) + .replace("&6", "" + ChatColor.GOLD).replace("&7", "" + ChatColor.GRAY) + .replace("&8", "" + ChatColor.DARK_GRAY).replace("&9", "" + ChatColor.BLUE) + .replace("&a", "" + ChatColor.GREEN).replace("&b", "" + ChatColor.AQUA) + .replace("&c", "" + ChatColor.RED).replace("&d", "" + ChatColor.LIGHT_PURPLE) + .replace("&e", "" + ChatColor.YELLOW).replace("&f", "" + ChatColor.WHITE) + .replace("&k", "" + ChatColor.MAGIC).replace("&l", "" + ChatColor.BOLD) + .replace("&m", "" + ChatColor.STRIKETHROUGH).replace("&n", "" + ChatColor.UNDERLINE) + .replace("&o", "" + ChatColor.ITALIC).replace("&r", "" + ChatColor.RESET) + .replace("&A", "" + ChatColor.GREEN).replace("&B", "" + ChatColor.AQUA) + .replace("&C", "" + ChatColor.RED).replace("&D", "" + ChatColor.LIGHT_PURPLE) + .replace("&E", "" + ChatColor.YELLOW).replace("&F", "" + ChatColor.WHITE) + .replace("&K", "" + ChatColor.MAGIC).replace("&L", "" + ChatColor.BOLD) + .replace("&M", "" + ChatColor.STRIKETHROUGH).replace("&N", "" + ChatColor.UNDERLINE) + .replace("&O", "" + ChatColor.ITALIC).replace("&R", "" + ChatColor.RESET); + return s; + } + public static String setColorsByName(String s){ + s = s.replace("", "" + ChatColor.BLACK).replace("", "" + ChatColor.DARK_BLUE) + .replace("", "" + ChatColor.DARK_GREEN).replace("", "" + ChatColor.DARK_AQUA) + .replace("", "" + ChatColor.DARK_RED).replace("", "" + ChatColor.DARK_PURPLE) + .replace("", "" + ChatColor.GOLD).replace("", "" + ChatColor.GRAY) + .replace("", "" + ChatColor.DARK_GRAY).replace("", "" + ChatColor.BLUE) + .replace("", "" + ChatColor.GREEN).replace("", "" + ChatColor.AQUA) + .replace("", "" + ChatColor.RED).replace("", "" + ChatColor.LIGHT_PURPLE) + .replace("", "" + ChatColor.YELLOW).replace("", "" + ChatColor.WHITE) + .replace("", "" + ChatColor.MAGIC).replace("", "" + ChatColor.BOLD) + .replace("", "" + ChatColor.STRIKETHROUGH).replace("", "" + ChatColor.UNDERLINE) + .replace("", "" + ChatColor.ITALIC).replace("", "" + ChatColor.RESET); + return s; + } + public static String setColors(String string) { + // Replace color placeholders while ignoring case + Pattern pattern = Pattern.compile("<(\\w+)>", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(string); + while (matcher.find()) { + String colorPlaceholder = matcher.group(1); + String color = setColors2("<" + colorPlaceholder.toUpperCase() + ">"); + if (color != null) { + string = string.replaceFirst(Pattern.quote(matcher.group()), color); + } + } + + // Apply color formatting + string = ChatColor.translateAlternateColorCodes('&', string); + + return string; + } + public static String setColors2(String string) { + string = string.replace("", "&0" ) .replace("", "&1" ) + .replace("", "&2" ) .replace("", "&3" ) + .replace("", "&4" ) .replace("", "&5" ) + .replace("", "&6" ) .replace("", "&7" ) + .replace("", "&8" ) .replace("", "&9" ) + .replace("", "&a" ) .replace("", "&b" ) + .replace("", "&c" ) .replace("", "&d" ) + .replace("", "&e" ) .replace("", "&f" ) + .replace("", "&k" ) .replace("", "&l" ) + .replace("", "&m" ) .replace("", "&n" ) + .replace("", "&o" ) .replace("", "&r" ); + return string; + } +} \ No newline at end of file diff --git a/util/ConfigAPI.java b/util/ConfigAPI.java new file mode 100644 index 0000000..b4be609 --- /dev/null +++ b/util/ConfigAPI.java @@ -0,0 +1,60 @@ +package com.github.joelgodofwar.mmh.util; + +import java.io.File; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; + +import com.github.joelgodofwar.mmh.MoreMobHeads; + +public class ConfigAPI { + + public final static Logger logger = Logger.getLogger("Minecraft"); + + public void CheckForConfig(MoreMobHeads plugin){ + try{ + if(!plugin.getDataFolder().exists()){ + log(": Data Folder doesn't exist"); + log(": Creating Data Folder"); + plugin.getDataFolder().mkdirs(); + log(": Data Folder Created at " + plugin.getDataFolder()); + } + File file = new File(plugin.getDataFolder(), "config.yml"); + plugin.getLogger().info("" + file); + if(!file.exists()){ + log("config.yml not found, creating!"); + plugin.saveDefaultConfig(); + FileConfiguration config = plugin.getConfig(); + + config.options().copyDefaults(true); + plugin.saveConfig(); + } + }catch(Exception e){ + e.printStackTrace(); + } + } + + public static void Reloadconfig(MoreMobHeads plugin){ + // Load config. + FileConfiguration config = plugin.getConfig(); + String daString = config.getString("debug").replace("'", "") + ","; + + if(daString.contains("true")){ + plugin.debug = true; + }else{ + plugin.debug = false; + } + String daString2 = config.getString("auto_update_check").replace("'", "") + ","; + if(daString2.contains("true")){ + plugin.UpdateCheck = true; + }else{ + plugin.UpdateCheck = false; + } + + if(plugin.debug){log("UpdateCheck = " + plugin.UpdateCheck);} //TODO: Logger + } + public static void log(String dalog){ + Bukkit.getLogger().info(dalog); + } +} diff --git a/util/ConfigHelper.java b/util/ConfigHelper.java new file mode 100644 index 0000000..4a4f5ed --- /dev/null +++ b/util/ConfigHelper.java @@ -0,0 +1,30 @@ +package com.github.joelgodofwar.mmh.util; + +public class ConfigHelper { + + public static Double Double(YmlConfiguration config, String get, Double def) { + String str = config.getString(get); + if (str == null || str.isEmpty()) { + if(def != null) { + return def; + }else { + return 0.00; + } + } + if(str.indexOf('\'') >= 0) { + str = removeQuotes(str); + return Double.parseDouble(str); + } + return Double.parseDouble(str); + } + + public static String removeQuotes(String string) { + if (string.length() >= 2 && string.charAt(0) == '"' && string.charAt(string.length() - 1) == '"'){ + string = string.substring(1, string.length() - 1); + } + if (string.length() >= 2 && string.charAt(0) == '\'' && string.charAt(string.length() - 1) == '\''){ + string = string.substring(1, string.length() - 1); + } + return string; + } +} diff --git a/util/EquipmentSlotResolver.java b/util/EquipmentSlotResolver.java new file mode 100644 index 0000000..809e2ff --- /dev/null +++ b/util/EquipmentSlotResolver.java @@ -0,0 +1,76 @@ +package com.github.joelgodofwar.mmh.util; + +import java.util.Optional; + +import org.bukkit.Material; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +/** +* Utility for resolving the {@link EquipmentSlot} containing a player's weapon for an +* {@link EntityDamageByEntityEvent}. This is used since that event type doesn't provide a +* way for finding out the equipment slot. +*/ +public class EquipmentSlotResolver { + + public static Optional resolveDamagingWeapon(PlayerInventory playerInventory, DamageCause damageCause) { + //System.out.println("damageCause=" + damageCause.toString()); + switch (damageCause) { + case ENTITY_ATTACK: + // Check if the player is holding any item in the main hand + ItemStack mainHandItem = playerInventory.getItemInMainHand(); + //System.out.println("mainHandItem=" + mainHandItem.getType().toString()); + if (!mainHandItem.getType().equals(Material.AIR)) { + return Optional.of(mainHandItem); + } + break; + case PROJECTILE: + // Ranged damage + Optional bowItem = getWeaponItem(playerInventory, Material.BOW); + if (bowItem.isPresent()) { + return bowItem; + } + Optional crossbowItem = getWeaponItem(playerInventory, Material.CROSSBOW); + if (crossbowItem.isPresent()) { + return crossbowItem; + } + Optional tridentItem = getWeaponItem(playerInventory, Material.TRIDENT); + if (tridentItem.isPresent()) { + return tridentItem; + } + Optional snowItem = getWeaponItem(playerInventory, Material.SNOWBALL); + if (snowItem.isPresent()) { + return snowItem; + } + Optional eggItem = getWeaponItem(playerInventory, Material.EGG); + if (eggItem.isPresent()) { + return eggItem; + } + break; + case MAGIC: + // Potion + return getWeaponItem(playerInventory, Material.SPLASH_POTION); + + default: + break; + } + + return Optional.empty(); + } + + private static Optional getWeaponItem(PlayerInventory playerInventory, Material material) { + ItemStack mainHandItem = playerInventory.getItemInMainHand(); + ItemStack offHandItem = playerInventory.getItemInOffHand(); + + if (mainHandItem.getType() == material) { + return Optional.of(mainHandItem); + } else if (offHandItem.getType() == material) { + return Optional.of(offHandItem); + } + + return Optional.empty(); + } +} diff --git a/util/Format.java b/util/Format.java new file mode 100644 index 0000000..f9e6895 --- /dev/null +++ b/util/Format.java @@ -0,0 +1,558 @@ +package com.github.joelgodofwar.mmh.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bukkit.ChatColor; + +//This class is where all formatting methods are stored. +@SuppressWarnings("unused") +public class Format { + //private static MineverseChat plugin = MineverseChat.getInstance(); + + public static final int LEGACY_COLOR_CODE_LENGTH = 2; + public static final int HEX_COLOR_CODE_LENGTH = 14; + public static final String DEFAULT_COLOR_CODE = String.valueOf(ChatColor.WHITE); + public static final String HEX_COLOR_CODE_PREFIX = "#"; + public static final String BUKKIT_COLOR_CODE_PREFIX = "�"; + public static final String BUKKIT_HEX_COLOR_CODE_PREFIX = "x"; + + /**public static String convertToJson(MineverseChatPlayer sender, String format, String chat) { + JsonFormat JSONformat = MineverseChat.jfInfo.getJsonFormat(sender.getJsonFormat()); + String f = format.replace("\\", "\\\\").replace("\"", "\\\""); + String c = chat.replace("\\", "\\\\").replace("\"", "\\\""); + String json = "[\"\",{\"text\":\"\",\"extra\":["; + String prefix = ""; + String suffix = ""; + try { + prefix = FormatStringAll(MineverseChat.chat.getPlayerPrefix(sender.getPlayer())); + suffix = FormatStringAll(MineverseChat.chat.getPlayerSuffix(sender.getPlayer())); + if(suffix.equals("")) { + suffix = "venturechat_no_suffix_code"; + } + if(prefix.equals("")) { + prefix = "venturechat_no_prefix_code"; + } + } + catch(Exception e) { + System.out.println("Exception?" + e.getLocalizedMessage()); + if(plugin.getConfig().getString("loglevel", "info").equals("debug")) { + Bukkit.getConsoleSender().sendMessage(Format.FormatStringAll("&8[&eVentureChat&8]&e - Prefix and / or suffix don't exist, setting to nothing.")); + } + suffix = "venturechat_no_suffix_code"; + prefix = "venturechat_no_prefix_code"; + } + String nickname = ""; + if(sender.getPlayer() != null) { + nickname = FormatStringAll(sender.getPlayer().getDisplayName()); + } + json += convertPlaceholders(f, JSONformat, prefix, nickname, suffix, sender); + json += "]}"; + json += "," + convertLinks(c); + json += "]"; + if(plugin.getConfig().getString("loglevel", "info").equals("debug")) { + System.out.println(json); + System.out.println("END OF JSON"); + System.out.println("END OF JSON"); + System.out.println("END OF JSON"); + System.out.println("END OF JSON"); + System.out.println("END OF JSON"); + } + return json; + }*/ + + /**private static String convertPlaceholders(String s, JsonFormat format, String prefix, String nickname, String suffix, MineverseChatPlayer icp) { + String remaining = s; + String temp = ""; + int indexStart = -1; + int indexEnd = -1; + String placeholder = ""; + String lastCode = "�f"; + do { + Pattern pattern = Pattern.compile("(" + escapeAllRegex(prefix) + "|" + escapeAllRegex(nickname) + "|" + escapeAllRegex(suffix) + ")"); + Matcher matcher = pattern.matcher(remaining); + if(matcher.find()) { + indexStart = matcher.start(); + indexEnd = matcher.end(); + placeholder = remaining.substring(indexStart, indexEnd); + temp += convertToJsonColors(lastCode + remaining.substring(0, indexStart)) + ","; + lastCode = getLastCode(lastCode + remaining.substring(0, indexStart)); + String action = ""; + String text = ""; + String hover = ""; + if(placeholder.contains(prefix)) { + action = format.getClickPrefix(); + text = PlaceholderAPI.setBracketPlaceholders(icp.getPlayer(), format.getClickPrefixText()); + for(String st : format.getHoverTextPrefix()) { + hover += Format.FormatStringAll(st) + "\n"; + } + } + if(placeholder.contains(nickname)) { + action = format.getClickName(); + text = PlaceholderAPI.setBracketPlaceholders(icp.getPlayer(), format.getClickNameText()); + for(String st : format.getHoverTextName()) { + hover += Format.FormatStringAll(st) + "\n"; + } + } + if(placeholder.contains(suffix)) { + action = format.getClickSuffix(); + text = PlaceholderAPI.setBracketPlaceholders(icp.getPlayer(), format.getClickSuffixText()); + for(String st : format.getHoverTextSuffix()) { + hover += Format.FormatStringAll(st) + "\n"; + } + } + hover = PlaceholderAPI.setBracketPlaceholders(icp.getPlayer(), hover.substring(0, hover.length() - 1)); + temp += convertToJsonColors(lastCode + placeholder, ",\"clickEvent\":{\"action\":\"" + action + "\",\"value\":\"" + text + "\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"\",\"extra\":[" + convertToJsonColors(hover) + "]}}") + ","; + lastCode = getLastCode(lastCode + placeholder); + remaining = remaining.substring(indexEnd); + } + else { + temp += convertToJsonColors(lastCode + remaining); + break; + } + } + while(true); + return temp; + }*/ + + /**private static String convertLinks(String s) { + String remaining = s; + String temp = ""; + int indexLink = -1; + int indexLinkEnd = -1; + String link = ""; + String lastCode = "�f"; + do { + Pattern pattern = Pattern.compile("([a-zA-Z0-9�\\-:/]+\\.[a-zA-Z/0-9�\\-:_#]+(\\.[a-zA-Z/0-9.�\\-:#\\?\\+=_]+)?)"); + Matcher matcher = pattern.matcher(remaining); + if(matcher.find()) { + indexLink = matcher.start(); + indexLinkEnd = matcher.end(); + link = remaining.substring(indexLink, indexLinkEnd); + temp += convertToJsonColors(lastCode + remaining.substring(0, indexLink)) + ","; + lastCode = getLastCode(lastCode + remaining.substring(0, indexLink)); + String https = ""; + if(ChatColor.stripColor(link).contains("https://")) + https = "s"; + temp += convertToJsonColors(lastCode + link, ",\"underlined\":\"" + underlineURLs() + "\",\"clickEvent\":{\"action\":\"open_url\",\"value\":\"http" + https + "://" + ChatColor.stripColor(link.replace("http://", "").replace("https://", "")) + "\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"\",\"extra\":[" + convertToJsonColors(lastCode + link) + "]}}") + ","; + lastCode = getLastCode(lastCode + link); + remaining = remaining.substring(indexLinkEnd); + } + else { + temp += convertToJsonColors(lastCode + remaining); + break; + } + } + while(true); + return temp; + }*/ + + public static String getLastCode(String s) { + String ts = ""; + char[] ch = s.toCharArray(); + for(int a = 0; a < s.length() - 1; a ++) { + if(String.valueOf(ch[a + 1]).matches("[lkomnLKOMN]") && ch[a] == '�') { + ts += String.valueOf(ch[a]) + ch[a + 1]; + a ++; + } + else if(String.valueOf(ch[a + 1]).matches("[0123456789abcdefrABCDEFR]") && ch[a] == '�') { + ts = String.valueOf(ch[a]) + ch[a + 1]; + a ++; + } + else if(ch[a + 1] == 'x' && ch[a] == '�') { + if(ch.length > a + 13) { + if(String.valueOf(ch[a + 3]).matches("[0123456789abcdefABCDEF]") && String.valueOf(ch[a + 5]).matches("[0123456789abcdefABCDEF]") && String.valueOf(ch[a + 7]).matches("[0123456789abcdefABCDEF]") && String.valueOf(ch[a + 9]).matches("[0123456789abcdefABCDEF]") && String.valueOf(ch[a + 11]).matches("[0123456789abcdefABCDEF]") && String.valueOf(ch[a + 13]).matches("[0123456789abcdefABCDEF]") && ch[a + 2] == '�' && ch[a + 4] == '�' && ch[a + 6] == '�' && ch[a + 8] == '�' && ch[a + 10] == '�' && ch[a + 12] == '�') { + ts = String.valueOf(ch[a]) + ch[a + 1] + ch[a + 2] + ch[a + 3] + ch[a + 4] + ch[a + 5] + ch[a + 6] + ch[a + 7] + ch[a + 8] + ch[a + 9] + ch[a + 10] + ch[a + 11] + ch[a + 12] + ch[a + 13]; + a += 13; + } + } + } + } + return ts; + } + + + private static String convertToJsonColors(String s) { + return convertToJsonColors(s, ""); + } + + private static String convertToJsonColors(String s, String extensions) { + String remaining = s; + String temp = ""; + int indexColor = -1; + int indexNextColor = -1; + String color = ""; + String modifier = ""; + boolean bold = false; + boolean obfuscated = false; + boolean italic = false; + boolean strikethrough = false; + boolean underlined = false; + String previousColor = ""; + int colorLength = LEGACY_COLOR_CODE_LENGTH; + do { + if(remaining.length() < LEGACY_COLOR_CODE_LENGTH) { + temp = "{\"text\":\"" + remaining + "\"},"; + break; + } + modifier = ""; + indexColor = remaining.indexOf(BUKKIT_COLOR_CODE_PREFIX); + previousColor = color; + + color = remaining.substring(1, indexColor + LEGACY_COLOR_CODE_LENGTH); + if(color.equals(BUKKIT_HEX_COLOR_CODE_PREFIX)) { + if(remaining.length() >= HEX_COLOR_CODE_LENGTH) { + color = HEX_COLOR_CODE_PREFIX + remaining.substring(LEGACY_COLOR_CODE_LENGTH, indexColor + HEX_COLOR_CODE_LENGTH).replace(BUKKIT_COLOR_CODE_PREFIX, ""); + colorLength = HEX_COLOR_CODE_LENGTH; + bold = false; + obfuscated = false; + italic = false; + strikethrough = false; + underlined = false; + } + } + else if(!color.matches("[0123456789abcdefABCDEF]")) { + switch(color) { + case "l": + case "L": { + bold = true; + break; + } + case "k": + case "K": { + obfuscated = true; + break; + } + case "o": + case "O": { + italic = true; + break; + } + case "m": + case "M": { + strikethrough = true; + break; + } + case "n": + case "N": { + underlined = true; + break; + } + case "r": + case "R": { + bold = false; + obfuscated = false; + italic = false; + strikethrough = false; + underlined = false; + color = "f"; + break; + } + } + if(!color.equals("f")) + color = previousColor; + if(color.length() == 0) + color = "f"; + } + else { + bold = false; + obfuscated = false; + italic = false; + strikethrough = false; + underlined = false; + } + if(bold) + modifier += ",\"bold\":\"true\""; + if(obfuscated) + modifier += ",\"obfuscated\":\"true\""; + if(italic) + modifier += ",\"italic\":\"true\""; + if(underlined) + modifier += ",\"underlined\":\"true\""; + if(strikethrough) + modifier += ",\"strikethrough\":\"true\""; + remaining = remaining.substring(colorLength); + colorLength = LEGACY_COLOR_CODE_LENGTH; + indexNextColor = remaining.indexOf(BUKKIT_COLOR_CODE_PREFIX); + if(indexNextColor == -1) { + indexNextColor = remaining.length(); + } + temp += "{\"text\":\"" + remaining.substring(0, indexNextColor) + "\",\"color\":\"" + hexidecimalToJsonColorRGB(color) + "\"" + modifier + extensions + "},"; + remaining = remaining.substring(indexNextColor); + } + while(remaining.length() > 1 && indexColor != -1); + if(temp.length() > 1) + temp = temp.substring(0, temp.length() - 1); + return temp; + } + + private static String hexidecimalToJsonColorRGB(String c) { + if(c.length() == 1) { + switch(c) { + case "0": return "black"; + case "1": return "dark_blue"; + case "2": return "dark_green"; + case "3": return "dark_aqua"; + case "4": return "dark_red"; + case "5": return "dark_purple"; + case "6": return "gold"; + case "7": return "gray"; + case "8": return "dark_gray"; + case "9": return "blue"; + case "a": + case "A": return "green"; + case "b": + case "B": return "aqua"; + case "c": + case "C": return "red"; + case "d": + case "D": return "light_purple"; + case "e": + case "E": return "yellow"; + case "f": + case "F": return "white"; + default: return "white"; + } + } + if(isValidHexColor(c)) { + return c; + } + return "white"; + } + + /**public static String convertPlainTextToJson(String s, boolean convertURL) { + if(convertURL) { + return "[" + Format.convertLinks(s) + "]"; + } + else { + return "[" + convertToJsonColors("�f" + s) + "]"; + } + } + + /**public static String formatModerationGUI(String json, Player player, String sender, String channelName, int hash) { + if(player.hasPermission("venturechat.gui")) { + json = json.substring(0, json.length() - 1); + json += "," + Format.convertToJsonColors(Format.FormatStringAll(plugin.getConfig().getString("guiicon")), ",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/vchatgui " + sender + " " + channelName + " " + hash +"\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"\",\"extra\":[" + Format.convertToJsonColors(Format.FormatStringAll(plugin.getConfig().getString("guitext"))) + "]}}") + "]"; + } + return json; + }*/ + + /**public static PacketContainer createPacketPlayOutChat(String json) { + WrappedChatComponent component = WrappedChatComponent.fromJson(json); + PacketContainer container = new PacketContainer(PacketType.Play.Server.CHAT); + container.getModifier().writeDefaults(); + container.getChatComponents().write(0, component); + return container; + } + + public static PacketContainer createPacketPlayOutChat(WrappedChatComponent component) { + PacketContainer container = new PacketContainer(PacketType.Play.Server.CHAT); + container.getModifier().writeDefaults(); + container.getChatComponents().write(0, component); + return container; + } + + public static void sendPacketPlayOutChat(Player player, PacketContainer packet) { + try { + ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); + } + catch(Exception e) { + e.printStackTrace(); + } + } + + public static String toPlainText(Object o, Class c) { + List finalList = new ArrayList<>(); + StringBuilder stringbuilder = new StringBuilder(); + try { + splitComponents(finalList, o, c); + for(Object component : finalList) { + if(VersionHandler.is1_7_10()) { + stringbuilder.append((String) component.getClass().getMethod("e").invoke(component)); + } + else { + stringbuilder.append((String) component.getClass().getMethod("getText").invoke(component)); + } + } + } + catch(Exception e) { + e.printStackTrace(); + } + if(plugin.getConfig().getString("loglevel", "info").equals("debug")) { + System.out.println("my string"); + System.out.println("my string"); + System.out.println("my string"); + System.out.println("my string"); + System.out.println("my string"); + System.out.println(stringbuilder.toString()); + } + return stringbuilder.toString(); + } + + private static void splitComponents(List finalList, Object o, Class c) throws Exception { + if(plugin.getConfig().getString("loglevel", "info").equals("debug")) { + for(Method m : c.getMethods()) { + System.out.println(m.getName()); + } + } + if(VersionHandler.is1_7() || VersionHandler.is1_8() || VersionHandler.is1_9() || VersionHandler.is1_10() || VersionHandler.is1_11() || VersionHandler.is1_12() || VersionHandler.is1_13() || (VersionHandler.is1_14() && !VersionHandler.is1_14_4())) { + ArrayList list = (ArrayList) c.getMethod("a").invoke(o, new Object[0]); + for(Object component : list) { + ArrayList innerList = (ArrayList) c.getMethod("a").invoke(component, new Object[0]); + if(innerList.size() > 0) { + splitComponents(finalList, component, c); + } + else { + finalList.add(component); + } + } + } + else { + ArrayList list = (ArrayList) c.getMethod("getSiblings").invoke(o, new Object[0]); + for(Object component : list) { + ArrayList innerList = (ArrayList) c.getMethod("getSiblings").invoke(component, new Object[0]); + if(innerList.size() > 0) { + splitComponents(finalList, component, c); + } + else { + finalList.add(component); + } + } + } + }*/ + + protected static Pattern chatHexColorPattern = Pattern.compile("(?i)&([X])"); + protected static Pattern chatColorPattern = Pattern.compile("(?i)&([0-9A-F])"); + protected static Pattern chatMagicPattern = Pattern.compile("(?i)&([K])"); + protected static Pattern chatBoldPattern = Pattern.compile("(?i)&([L])"); + protected static Pattern chatStrikethroughPattern = Pattern.compile("(?i)&([M])"); + protected static Pattern chatUnderlinePattern = Pattern.compile("(?i)&([N])"); + protected static Pattern chatItalicPattern = Pattern.compile("(?i)&([O])"); + protected static Pattern chatResetPattern = Pattern.compile("(?i)&([R])"); + + public static String FormatStringColor(String string) { + String allFormated = string; + allFormated = chatColorPattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = chatHexColorPattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = allFormated.replaceAll("%", "\\%"); + + allFormated = convertHexColorCodeStringToBukkitColorCodeString(allFormated); + return allFormated; + } + + public static String FormatString(String string) { + String allFormated = string; + allFormated = chatMagicPattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = chatBoldPattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = chatStrikethroughPattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = chatUnderlinePattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = chatItalicPattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = chatResetPattern.matcher(allFormated).replaceAll("\u00A7$1"); + allFormated = allFormated.replaceAll("%", "\\%"); + return allFormated; + } + + public static String FormatStringAll(String string) { + String allFormated = Format.FormatString(string); + allFormated = Format.FormatStringColor(allFormated); + return allFormated; + } + + public static String FormatPlayerName(String playerPrefix, String playerDisplayName, String playerSuffix) { + playerPrefix = chatColorPattern.matcher(playerPrefix).replaceAll("\u00A7$1"); + playerPrefix = chatMagicPattern.matcher(playerPrefix).replaceAll("\u00A7$1"); + playerPrefix = chatBoldPattern.matcher(playerPrefix).replaceAll("\u00A7$1"); + playerPrefix = chatStrikethroughPattern.matcher(playerPrefix).replaceAll("\u00A7$1"); + playerPrefix = chatUnderlinePattern.matcher(playerPrefix).replaceAll("\u00A7$1"); + playerPrefix = chatItalicPattern.matcher(playerPrefix).replaceAll("\u00A7$1"); + playerPrefix = chatResetPattern.matcher(playerPrefix).replaceAll("\u00A7$1"); + + playerSuffix = chatColorPattern.matcher(playerSuffix).replaceAll("\u00A7$1"); + playerSuffix = chatMagicPattern.matcher(playerSuffix).replaceAll("\u00A7$1"); + playerSuffix = chatBoldPattern.matcher(playerSuffix).replaceAll("\u00A7$1"); + playerSuffix = chatStrikethroughPattern.matcher(playerSuffix).replaceAll("\u00A7$1"); + playerSuffix = chatUnderlinePattern.matcher(playerSuffix).replaceAll("\u00A7$1"); + playerSuffix = chatItalicPattern.matcher(playerSuffix).replaceAll("\u00A7$1"); + playerSuffix = chatResetPattern.matcher(playerSuffix).replaceAll("\u00A7$1"); + return playerPrefix + playerDisplayName.trim() + playerSuffix; + } + + /**public static String FilterChat(String msg) { + int t = 0; + List filters = plugin.getConfig().getStringList("filters"); + for(String s : filters) { + t = 0; + String[] pparse = new String[2]; + pparse[0] = " "; + pparse[1] = " "; + StringTokenizer st = new StringTokenizer(s, ","); + while(st.hasMoreTokens()) { + if(t < 2) { + pparse[t++] = st.nextToken(); + } + } + msg = msg.replaceAll("(?i)" + pparse[0], pparse[1]); + } + return msg; + }*/ + + public static boolean isValidColor(String color) { + Boolean bFound = false; + for(ChatColor bkColors : ChatColor.values()) { + if(color.equalsIgnoreCase(bkColors.name())) { + bFound = true; + } + } + return bFound; + } + + public static boolean isValidHexColor(String color) { + Pattern pattern = Pattern.compile("(^#[0-9a-fA-F]{6}\\b)"); + Matcher matcher = pattern.matcher(color); + return matcher.find(); + } + + public static String convertHexColorCodeToBukkitColorCode(String color) { + StringBuilder bukkitColorCode = new StringBuilder(BUKKIT_COLOR_CODE_PREFIX + BUKKIT_HEX_COLOR_CODE_PREFIX); + for(int a = 1; a < color.length(); a++) { + bukkitColorCode.append(BUKKIT_COLOR_CODE_PREFIX + color.charAt(a)); + } + return bukkitColorCode.toString(); + } + + public static String convertHexColorCodeStringToBukkitColorCodeString(String string) { + Pattern pattern = Pattern.compile("(#[0-9a-fA-F]{6})"); + Matcher matcher = pattern.matcher(string); + while(matcher.find()) { + int indexStart = matcher.start(); + int indexEnd = matcher.end(); + String hexColor = string.substring(indexStart, indexEnd); + String bukkitColor = convertHexColorCodeToBukkitColorCode(hexColor); + string = string.replaceAll(hexColor, bukkitColor); + matcher.reset(string); + } + return string; + } + + public static String escapeAllRegex(String input) { + return input.replace("[", "\\[").replace("]", "\\]").replace("{", "\\{").replace("}", "\\}").replace("(", "\\(").replace(")", "\\)").replace("|", "\\|").replace("+", "\\+").replace("*", "\\*"); + } + + /**public static boolean underlineURLs() { + return plugin.getConfig().getBoolean("underlineurls", true); + }*/ + public static final char colorChar = '\u00a7'; + //code taken from bukkit, so it can work on bungee too + public static String color(String textToTranslate){ + if (textToTranslate == null) return null; + if (!textToTranslate.contains("&")) return textToTranslate; + char[] b = textToTranslate.toCharArray(); + for (int i = 0; i < b.length - 1; i++) { + if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[(i + 1)]) > -1)){ + b[i] = colorChar; + b[(i + 1)] = Character.toLowerCase(b[(i + 1)]); + } + } + return new String(b); + } +} \ No newline at end of file diff --git a/util/GetMCVersion.java b/util/GetMCVersion.java new file mode 100644 index 0000000..2917118 --- /dev/null +++ b/util/GetMCVersion.java @@ -0,0 +1,36 @@ +package com.github.joelgodofwar.mmh.util; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.Bukkit; + +public class GetMCVersion { + private String Version; + private int Major; + private int Minor; + private int Patch; + private int Build; + public GetMCVersion(){ + Version = Bukkit.getVersion(); + Version = Version.substring(Version.indexOf("MC: "), Version.length()).replace("MC: ", "").replace(")", ""); + String[] strArray = Version.split("."); + Major = NumberUtils.toInt(strArray[0]); + Minor = NumberUtils.toInt(strArray[1]); + Patch = NumberUtils.toInt(strArray[2], 0); + } + + public String VERSION(){ + return Version; + } + public int MAJOR(){ + return Major; + } + public int MINOR(){ + return Minor; + } + public int PATCH(){ + return Patch; + } + public int BUILD(){ + return Build; + } +} diff --git a/util/Logging.java b/util/Logging.java new file mode 100644 index 0000000..aa73435 --- /dev/null +++ b/util/Logging.java @@ -0,0 +1,94 @@ +package com.github.joelgodofwar.mmh.util; + +/* + * Copyright (C) 2012 p000ison + * + * This work is licensed under the Creative Commons + * Attribution-NonCommercial-NoDerivs 3.0 Unported License. To view a copy of + * this license, visit http://creativecommons.org/licenses/by-nc-nd/3.0/ or send + * a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, + * California, 94105, USA. + * + */ + +import java.io.File; +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; +import org.bukkit.plugin.Plugin; + +/** + * This should be get initialised when the plugin loads (It contains the Logging + * system) + * + * @author Max + */ +public class Logging +{ + + private static Logger log; + private static final Logger debugLogger = Logger.getLogger("Logging"); + private FileHandler debugFileHandler; + + /** + * Gets the logger for your plugin + * + * @param plugin The plugin to apply + */ + public Logging(Plugin plugin) + { + + log = plugin.getLogger(); + + try { + + debugFileHandler = new FileHandler(plugin.getDataFolder().getAbsolutePath() + File.pathSeparator + plugin.getName() + ".log", true); + SimpleFormatter formatter = new SimpleFormatter(); + + debugLogger.addHandler(debugFileHandler); + debugFileHandler.setFormatter(formatter); + + } catch (IOException ex) { + debug(null, ex, false); + } catch (SecurityException ex) { + debug(null, ex, false); + } + } + + /** + * Logs a message + * + * @param level The level to log + * @param msg The message to log + * @param toFile log to own log? + */ + public static void debug(Level level, String msg, boolean toFile){ + if (toFile) { + if (debugLogger != null) { + debugLogger.log(level, msg); + } + } + log.log(level, msg); + } + + /** + * Logs a Exception + * + * @param msg The message to log + * @param exception the exception + * @param toFile log to own log? + */ + public static void debug(String msg, Throwable exception, boolean toFile){ + if (toFile) { + if (debugLogger != null) { + debugLogger.log(Level.SEVERE, msg, exception); + } + } + log.log(Level.SEVERE, msg, exception); + } +} + + + diff --git a/util/Metrics.java b/util/Metrics.java new file mode 100644 index 0000000..0eac7f5 --- /dev/null +++ b/util/Metrics.java @@ -0,0 +1,848 @@ +package com.github.joelgodofwar.mmh.util; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +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; +import javax.net.ssl.HttpsURLConnection; +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; + +public class Metrics { + + 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/util/Metrics2.java b/util/Metrics2.java new file mode 100644 index 0000000..8772f6f --- /dev/null +++ b/util/Metrics2.java @@ -0,0 +1,732 @@ +package com.github.joelgodofwar.mmh.util; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +import javax.net.ssl.HttpsURLConnection; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * bStats collects some data for plugin authors. + *

+ * Check out https://bStats.org/ to learn more about bStats! + */ +//@SuppressWarnings({"WeakerAccess", "unused"}) +public class Metrics2 { + + static { + // 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', '.', 'b', 'u', 'k', 'k', 'i', 't'}); + final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure nobody just copy & pastes the example and use the wrong package names + if (Metrics2.class.getPackage().getName().equals(defaultPackage) || Metrics2.class.getPackage().getName().equals(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + + // Is bStats enabled on this server? + private boolean enabled; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // Should the sent data be logged? + private static boolean logSentData; + + // Should the response text be logged? + private static boolean logResponseStatusText; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final Plugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + */ + public Metrics2(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + 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); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + // Should the sent data be logged? + config.addDefault("logSentData", false); + // Should the response text be logged? + config.addDefault("logResponseStatusText", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { } + } + + // Load the data + enabled = config.getBoolean("enabled", true); + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + logSentData = config.getBoolean("logSentData", false); + logResponseStatusText = config.getBoolean("logResponseStatusText", false); + + if (enabled) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { } + } + // Register our service + Bukkit.getServicesManager().register(Metrics2.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Checks if bStats is enabled. + * + * @return Whether bStats is enabled or not. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, () -> submitData()); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JsonObject getPluginData() { + JsonObject data = new JsonObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.addProperty("pluginName", pluginName); // Append the name of the plugin + data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin + JsonArray customCharts = new JsonArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JsonObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.add("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JsonObject getServerData() { + // Minecraft specific data + int playerAmount; + try { + // Around MC 1.8 the return type was changed to a collection from an array, + // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = Bukkit.getVersion(); + String bukkitName = Bukkit.getName(); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JsonObject data = new JsonObject(); + + data.addProperty("serverUUID", serverUUID); + + data.addProperty("playerAmount", playerAmount); + data.addProperty("onlineMode", onlineMode); + data.addProperty("bukkitVersion", bukkitVersion); + data.addProperty("bukkitName", bukkitName); + + data.addProperty("javaVersion", javaVersion); + data.addProperty("osName", osName); + data.addProperty("osArch", osArch); + data.addProperty("osVersion", osVersion); + data.addProperty("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JsonObject data = getServerData(); + + JsonArray pluginData = new JsonArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); + if (plugin instanceof JsonObject) { + pluginData.add((JsonObject) plugin); + } else { // old bstats version compatibility + try { + Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); + if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { + Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); + jsonStringGetter.setAccessible(true); + String jsonString = (String) jsonStringGetter.invoke(plugin); + JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); + pluginData.add(object); + } + } catch (ClassNotFoundException e) { + // minecraft version 1.14+ + if (logFailedRequests) { + this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e); + } + continue; // continue looping since we cannot do any other thing. + } + } + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } + } + } catch (NoSuchFieldException ignored) { } + } + + data.add("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + } + + /** + * Sends the data to the bStats server. + * + * @param plugin Any plugin. It's just used to get a logger instance. + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(Plugin plugin, JsonObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + if (logSentData) { + plugin.getLogger().info("Sending data to bStats: " + data.toString()); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + InputStream inputStream = connection.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + + StringBuilder builder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + bufferedReader.close(); + if (logResponseStatusText) { + plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString()); + } + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + private JsonObject getRequestJsonObject() { + JsonObject chart = new JsonObject(); + chart.addProperty("chartId", chartId); + try { + JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.add("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JsonObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + 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 JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.addProperty("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + 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 JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + 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) { + continue; // Skip this invalid + } + allSkipped = false; + values.addProperty(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + 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 JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObject value = new JsonObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.addProperty(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.add(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + 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 JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.addProperty("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + 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 JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + 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) { + continue; // Skip this invalid + } + allSkipped = false; + values.addProperty(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + 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 JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JsonArray categoryValues = new JsonArray(); + categoryValues.add(entry.getValue()); + values.add(entry.getKey(), categoryValues); + } + data.add("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + 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 JsonObject getChartData() throws Exception { + JsonObject data = new JsonObject(); + JsonObject values = new JsonObject(); + 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) { + continue; // Skip this invalid + } + allSkipped = false; + JsonArray categoryValues = new JsonArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.add(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.add("values", values); + return data; + } + } + +} \ No newline at end of file diff --git a/util/MinecraftVersion.java b/util/MinecraftVersion.java new file mode 100644 index 0000000..2238fab --- /dev/null +++ b/util/MinecraftVersion.java @@ -0,0 +1,445 @@ +package com.github.joelgodofwar.mmh.util; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bukkit.Bukkit; +import org.bukkit.Server; + +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; + +/** + * Determine the current Minecraft version. + * + * @author Kristian + */ +public final class MinecraftVersion implements Comparable, Serializable { + /** + * The minimum version ProtocolLib has been tested with. + */ + public static final String MINIMUM_MINECRAFT_VERSION = "1.20"; + + /** + * The maximum version ProtocolLib has been tested with. + */ + public static final String MAXIMUM_MINECRAFT_VERSION = "1.20.4"; + + /** + * The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.4) was released. + */ + public static final String MINECRAFT_LAST_RELEASE_DATE = "2023-12-07"; + /** + * Version 1.20.4 - the decorated pot update + */ + public static final MinecraftVersion v1_20_4 = new MinecraftVersion("1.20.4"); + + /** + * Version 1.20.2 - the update that added the configuration protocol phase. + */ + public static final MinecraftVersion CONFIG_PHASE_PROTOCOL_UPDATE = new MinecraftVersion("1.20.2"); + /** + * Version 1.20 - the trails and tails update + */ + public static final MinecraftVersion TRAILS_AND_TAILS = new MinecraftVersion("1.20"); + + /** + * Version 1.19.4 - the rest of the feature preview + */ + public static final MinecraftVersion FEATURE_PREVIEW_2 = new MinecraftVersion("1.19.4"); + + /** + * Version 1.19.3 - introducing feature preview + */ + public static final MinecraftVersion FEATURE_PREVIEW_UPDATE = new MinecraftVersion("1.19.3"); + /** + * Version 1.19 - the wild update + */ + public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19"); + /** + * Version 1.18 - caves and cliffs part 2 + */ + public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18"); + /** + * Version 1.17 - caves and cliffs part 1 + */ + public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17"); + /** + * Version 1.16.4 + */ + public static final MinecraftVersion NETHER_UPDATE_4 = new MinecraftVersion("1.16.4"); + /** + * Version 1.16.2 - breaking change to the nether update + */ + public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2"); + /** + * Version 1.16.0 - the nether update + */ + public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16"); + /** + * Version 1.15 - the bee update + */ + public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15"); + /** + * Version 1.14 - village and pillage update. + */ + public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14"); + /** + * Version 1.13 - update aquatic. + */ + public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13"); + /** + * Version 1.12 - the world of color update. + */ + public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12"); + /** + * Version 1.11 - the exploration update. + */ + public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11"); + /** + * Version 1.10 - the frostburn update. + */ + public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10"); + /** + * Version 1.9 - the combat update. + */ + public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9"); + /** + * Version 1.8 - the "bountiful" update. + */ + public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8"); + /** + * Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise) + */ + public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8"); + /** + * Version 1.7.2 - the update that changed the world. + */ + public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2"); + /** + * Version 1.6.1 - the horse update. + */ + public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1"); + /** + * Version 1.5.0 - the redstone update. + */ + public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0"); + /** + * Version 1.4.2 - the scary update (Wither Boss). + */ + public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2"); + + /** + * The latest release version of minecraft. + */ + public static final MinecraftVersion LATEST = v1_20_4; + + // used when serializing + private static final long serialVersionUID = -8695133558996459770L; + + /** + * Regular expression used to parse version strings. + */ + private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*"); + + /** + * The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion() + */ + private static MinecraftVersion currentVersion; + + private final int major; + private final int minor; + private final int build; + // The development stage + private final String development; + + // Snapshot? + private final SnapshotVersion snapshot; + private volatile Boolean atCurrentOrAbove; + + /** + * Determine the current Minecraft version. + * + * @param server - the Bukkit server that will be used to examine the MC version. + */ + public MinecraftVersion(Server server) { + this(extractVersion(server.getVersion())); + } + + /** + * Construct a version object from the format major.minor.build, or the snapshot format. + * + * @param versionOnly - the version in text form. + */ + public MinecraftVersion(String versionOnly) { + this(versionOnly, true); + } + + /** + * Construct a version format from the standard release version or the snapshot verison. + * + * @param versionOnly - the version. + * @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise. + */ + private MinecraftVersion(String versionOnly, boolean parseSnapshot) { + String[] section = versionOnly.split("-"); + SnapshotVersion snapshot = null; + int[] numbers = new int[3]; + + try { + numbers = this.parseVersion(section[0]); + } catch (NumberFormatException cause) { + // Skip snapshot parsing + if (!parseSnapshot) { + throw cause; + } + + try { + // Determine if the snapshot is newer than the current release version + snapshot = new SnapshotVersion(section[0]); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + + MinecraftVersion latest = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION, false); + boolean newer = snapshot.getSnapshotDate().compareTo( + format.parse(MINECRAFT_LAST_RELEASE_DATE)) > 0; + + numbers[0] = latest.getMajor(); + numbers[1] = latest.getMinor() + (newer ? 1 : -1); + } catch (Exception e) { + throw new IllegalStateException("Cannot parse " + section[0], e); + } + } + + this.major = numbers[0]; + this.minor = numbers[1]; + this.build = numbers[2]; + this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null); + this.snapshot = snapshot; + } + + /** + * Construct a version object directly. + * + * @param major - major version number. + * @param minor - minor version number. + * @param build - build version number. + */ + public MinecraftVersion(int major, int minor, int build) { + this(major, minor, build, null); + } + + /** + * Construct a version object directly. + * + * @param major - major version number. + * @param minor - minor version number. + * @param build - build version number. + * @param development - development stage. + */ + public MinecraftVersion(int major, int minor, int build, String development) { + this.major = major; + this.minor = minor; + this.build = build; + this.development = development; + this.snapshot = null; + } + + /** + * Extract the Minecraft version from CraftBukkit itself. + * + * @param text - the server version in text form. + * @return The underlying MC version. + * @throws IllegalStateException If we could not parse the version string. + */ + public static String extractVersion(String text) { + Matcher version = VERSION_PATTERN.matcher(text); + + if (version.matches() && (version.group(1) != null)) { + return version.group(1); + } else { + throw new IllegalStateException("Cannot parse version String '" + text + "'"); + } + } + + /** + * Parse the given server version into a Minecraft version. + * + * @param serverVersion - the server version. + * @return The resulting Minecraft version. + */ + public static MinecraftVersion fromServerVersion(String serverVersion) { + return new MinecraftVersion(extractVersion(serverVersion)); + } + + public static MinecraftVersion getCurrentVersion() { + if (currentVersion == null) { + currentVersion = fromServerVersion(Bukkit.getVersion()); + } + + return currentVersion; + } + + public static void setCurrentVersion(MinecraftVersion version) { + currentVersion = version; + } + + private static boolean atOrAbove(MinecraftVersion version) { + return getCurrentVersion().isAtLeast(version); + } + + private int[] parseVersion(String version) { + String[] elements = version.split("\\."); + int[] numbers = new int[3]; + + // Make sure it's even a valid version + if (elements.length < 1) { + throw new IllegalStateException("Corrupt MC version: " + version); + } + + // The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively. + for (int i = 0; i < Math.min(numbers.length, elements.length); i++) { + numbers[i] = Integer.parseInt(elements[i].trim()); + } + return numbers; + } + + /** + * Major version number + * + * @return Current major version number. + */ + public int getMajor() { + return this.major; + } + + /** + * Minor version number + * + * @return Current minor version number. + */ + public int getMinor() { + return this.minor; + } + + /** + * Build version number + * + * @return Current build version number. + */ + public int getBuild() { + return this.build; + } + + /** + * Retrieve the development stage. + * + * @return Development stage, or NULL if this is a release. + */ + public String getDevelopmentStage() { + return this.development; + } + + /** + * Retrieve the snapshot version, or NULL if this is a release. + * + * @return The snapshot version. + */ + public SnapshotVersion getSnapshot() { + return this.snapshot; + } + + /** + * Determine if this version is a snapshot. + * + * @return The snapshot version. + */ + public boolean isSnapshot() { + return this.snapshot != null; + } + + /** + * Checks if this version is at or above the current version the server is running. + * + * @return true if this version is equal or newer than the server version, false otherwise. + */ + public boolean atOrAbove() { + if (this.atCurrentOrAbove == null) { + this.atCurrentOrAbove = atOrAbove(this); + } + + return this.atCurrentOrAbove; + } + + /** + * Retrieve the version String (major.minor.build) only. + * + * @return A normal version string. + */ + public String getVersion() { + if (this.getDevelopmentStage() == null) { + return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild()); + } else { + return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(), + this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : ""); + } + } + + @Override + public int compareTo(MinecraftVersion o) { + if (o == null) { + return 1; + } + + return ComparisonChain.start() + .compare(this.getMajor(), o.getMajor()) + .compare(this.getMinor(), o.getMinor()) + .compare(this.getBuild(), o.getBuild()) + .compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast()) + .compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst()) + .result(); + } + + public boolean isAtLeast(MinecraftVersion other) { + if (other == null) { + return false; + } + + return this.compareTo(other) >= 0; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + + if (obj instanceof MinecraftVersion) { + MinecraftVersion other = (MinecraftVersion) obj; + + return (this.getMajor() == other.getMajor()) && + (this.getMinor() == other.getMinor()) && + (this.getBuild() == other.getBuild()) && + Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage()); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild()); + } + + @Override + public String toString() { + // Convert to a String that we can parse back again + return String.format("(MC: %s)", this.getVersion()); + } +} \ No newline at end of file diff --git a/util/SnapshotVersion.java b/util/SnapshotVersion.java new file mode 100644 index 0000000..17d6581 --- /dev/null +++ b/util/SnapshotVersion.java @@ -0,0 +1,130 @@ +package com.github.joelgodofwar.mmh.util; +import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.collect.ComparisonChain; + +/** + * Used to parse a snapshot version. + * + * @author Kristian + */ +public class SnapshotVersion implements Comparable, Serializable { + + private static final long serialVersionUID = 2778655372579322310L; + private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(\\d{2}w\\d{2})([a-z])"); + + private final Date snapshotDate; + private final int snapshotWeekVersion; + + private transient String rawString; + + public SnapshotVersion(String version) { + Matcher matcher = SNAPSHOT_PATTERN.matcher(version.trim()); + + if (matcher.matches()) { + try { + this.snapshotDate = getDateFormat().parse(matcher.group(1)); + this.snapshotWeekVersion = matcher.group(2).charAt(0) - 'a'; + this.rawString = version; + } catch (ParseException e) { + throw new IllegalArgumentException("Date implied by snapshot version is invalid.", e); + } + } else { + throw new IllegalArgumentException("Cannot parse " + version + " as a snapshot version."); + } + } + + /** + * Retrieve the snapshot date parser. + *

+ * We have to create a new instance of SimpleDateFormat every time as it is not thread safe. + * + * @return The date formatter. + */ + private static SimpleDateFormat getDateFormat() { + SimpleDateFormat format = new SimpleDateFormat("yy'w'ww", Locale.US); + format.setLenient(false); + return format; + } + + /** + * Retrieve the snapshot version within a week, starting at zero. + * + * @return The weekly version + */ + public int getSnapshotWeekVersion() { + return this.snapshotWeekVersion; + } + + /** + * Retrieve the week this snapshot was released. + * + * @return The week. + */ + public Date getSnapshotDate() { + return this.snapshotDate; + } + + /** + * Retrieve the raw snapshot string (yy'w'ww[a-z]). + * + * @return The snapshot string. + */ + public String getSnapshotString() { + if (this.rawString == null) { + // It's essential that we use the same locale + Calendar current = Calendar.getInstance(Locale.US); + current.setTime(this.snapshotDate); + this.rawString = String.format("%02dw%02d%s", + current.get(Calendar.YEAR) % 100, + current.get(Calendar.WEEK_OF_YEAR), + (char) ('a' + this.snapshotWeekVersion)); + } + return this.rawString; + } + + @Override + public int compareTo(SnapshotVersion o) { + if (o == null) { + return 1; + } + + return ComparisonChain.start() + .compare(this.snapshotDate, o.getSnapshotDate()) + .compare(this.snapshotWeekVersion, o.getSnapshotWeekVersion()) + .result(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof SnapshotVersion) { + SnapshotVersion other = (SnapshotVersion) obj; + return Objects.equals(this.snapshotDate, other.getSnapshotDate()) + && (this.snapshotWeekVersion == other.getSnapshotWeekVersion()); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(this.snapshotDate, this.snapshotWeekVersion); + } + + @Override + public String toString() { + return this.getSnapshotString(); + } +} \ No newline at end of file diff --git a/util/StrUtils.java b/util/StrUtils.java new file mode 100644 index 0000000..6cf08bb --- /dev/null +++ b/util/StrUtils.java @@ -0,0 +1,88 @@ +package com.github.joelgodofwar.mmh.util; + +import org.bukkit.ChatColor; + +public class StrUtils { + /** + * Returns the rightmost characters of a string up to the specified length. + * + * @param input The input string. + * @param chars The number of characters to retrieve from the right. + * @return The rightmost characters of the input string, or the full string if its length is less than or equal to the specified length. + */ + public static String Right(String input, int chars){ + if (input.length() > chars){ + //System.out.println("Right input=" + input); + return input.substring(input.length() - chars); + } + else{ + return input; + } + } + + /** + * Returns the leftmost characters of a string up to the specified length. + * + * @param input The input string. + * @param chars The number of characters to retrieve from the left. + * @return The leftmost characters of the input string, or the full string if its length is less than or equal to the specified length. + */ + public static String Left(String input, int chars){ + if (input.length() > chars){ + //System.out.println("Left input=" + input); + //System.out.println("Left chars=" + chars); + return input.substring(0, chars); + } + else{ + return input; + } + } + + /** + * Checks if a string contains a specified substring, ignoring case. + * + * @param string The input string to search within (comma-separated values). + * @param string2 The substring to check for. + * @return True if the substring is found in the input string; otherwise, false. + */ + public static boolean stringContains(String string, String string2){ + String[] string3 = string.toUpperCase().split(", "); + for(int i = 0; i < string3.length; i++){ + if(string3[i].equals(string2.toUpperCase())){ + return true; + } + } + return false; + } + + /** + * Compares two strings for equality after removing color codes. + * + * @param string1 The first string to compare. + * @param string2 The second string to compare. + * @return True if the strings are equal (ignoring color codes); otherwise, false. + */ + public static boolean stringEquals(String string1, String string2) { + //string1 = ChatColor.stripColor(string1); + //string2 = ChatColor.stripColor(string2); + if(ChatColor.stripColor(string1).equals(ChatColor.stripColor(string2))) { + return true; + } + return false; + } + + /** + * Converts the first character of a string to uppercase and the remaining characters to lowercase, + * creating a title case format. + * + * @param input The input string to convert to title case. + * @return The input string in Title Case. + */ + public static String toTitleCase(String input) { + if ((input == null) || input.isEmpty()) { + return input; + } + + return input.substring(0, 1).toUpperCase() + input.substring(1).toLowerCase(); + } +} diff --git a/util/UpdateChecker.java b/util/UpdateChecker.java new file mode 100644 index 0000000..c432c52 --- /dev/null +++ b/util/UpdateChecker.java @@ -0,0 +1,128 @@ +package com.github.joelgodofwar.mmh.util; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +public class UpdateChecker { + private static int project; + private URL checkURL; + private String newVersion; + private String newMinVers; + private String oldVersion; + private String oldMinVers; + private JavaPlugin plugin; + String[] strVersionNew; // [0]=1.14 [1]=1.0.0.? + String[] strVersionCurrent; // [0]=1.14 [1]=1.0.0.? + + + public UpdateChecker(JavaPlugin plugin, int projectID, String verURL) { + this.plugin = plugin; + project = projectID; + newVersion = plugin.getDescription().getVersion(); + try { + checkURL = new URL(verURL.replace("{vers}", newVersion.substring(0, 4))); + }catch(MalformedURLException e) { + Bukkit.getLogger().warning(Ansi.RED + "Could not connect to update server."); + //Bukkit.getPluginManager().disablePlugin(plugin); + } + } + public UpdateChecker(String plugin, int projectID, String verURL) { + newVersion = plugin; + try { + checkURL = new URL(verURL.replace("{vers}", newVersion.substring(0, 4))); + }catch(MalformedURLException e) { + Bukkit.getLogger().warning(Ansi.RED + "Could not connect to update server."); + //Bukkit.getPluginManager().disablePlugin(plugin); + } + } + public static String getResourceUrl() {return "https://spigotmc.org/resources/" + project;} + public boolean checkForUpdates() throws Exception { + boolean isOutdated = false; + + URLConnection con = checkURL.openConnection(); + con.setConnectTimeout(30000); + con.setReadTimeout(15000); + newVersion = new BufferedReader(new InputStreamReader(con.getInputStream())).readLine(); + strVersionNew = newVersion.split("_"); // Split into minimum MC version and version + + newMinVers = strVersionNew[0]; + newVersion = strVersionNew[1]; + //System.out.println("newVersion=" + newVersion); + Version newVers = new Version(newVersion); + + strVersionCurrent = plugin.getDescription().getVersion().split("_"); + + oldMinVers = strVersionCurrent[0]; + oldVersion = strVersionCurrent[1]; + //System.out.println("oldVersion=" + oldVersion); + Version oldVers = new Version(oldVersion); + + if(oldVers.isDev()){ /** If currently on Dev version, ignore build -1 from Patch */ + if(newVers.Major() > oldVers.Major()){isOutdated = true;} + if(newVers.Major() == oldVers.Major()){/** same do nothing */} + if(newVers.Minor() > oldVers.Minor()){isOutdated = true;} + else if(newVers.Minor() == oldVers.Minor()){/** same do nothing */} + if(newVers.Patch() > (oldVers.Patch() - 1)){isOutdated = true;} + else if(newVers.Patch() == (oldVers.Patch() - 1)){/** same do nothing */} + /**if(newVers.Build() > oldVers.Build()){isOutdated = true;} + else if(newVers.Build() == oldVers.Build()){/** same do nothing }*/ + }else { + if(newVers.Major() > oldVers.Major()){isOutdated = true;} + if(newVers.Major() == oldVers.Major()){/** same do nothing */} + if(newVers.Minor() > oldVers.Minor()){isOutdated = true;} + else if(newVers.Minor() == oldVers.Minor()){/** same do nothing */} + if(newVers.Patch() > oldVers.Patch()){isOutdated = true;} + else if(newVers.Patch() == oldVers.Patch()){/** same do nothing */} + if(newVers.Build() > oldVers.Build()){isOutdated = true;} + else if(newVers.Build() == oldVers.Build()){/** same do nothing */} + } + return isOutdated; //plugin.getDescription().getVersion().equals(newVersion); TODO: + } + public String newVersion(){ + return newMinVers + "_" + newVersion; + } + public String oldVersion(){ + return oldMinVers + "_" + oldVersion; + } + + public class Version{ + private int Major; // 1 + private int Minor; // 16 + private int Patch; // 1 + private int Build; // ? + private boolean isDev = false; + private String[] string2 = {"0","0","0","0"}; + public Version(String string){ + //System.out.println("string=" + string); + string2 = string.split("\\."); + //for (String a : string2) + //System.out.println(a); + //System.out.println("string2=" + string2.toString()); + //System.out.println("string2.length=" + string2.length); + this.Major = NumberUtils.toInt(string2[0]); + this.Minor = NumberUtils.toInt(string2[1]); + this.Patch = NumberUtils.toInt(string2[2]); + if(string2.length >= 4){ + if(string2[3].toUpperCase().contains("D")){ + isDev = true; + string2[3] = string2[3].toUpperCase().replace("D", ""); + } + this.Build = NumberUtils.toInt(string2[3]); + }else{ + this.Build = 0; + } + } + public int Major(){return Major;} + public int Minor(){return Minor;} + public int Patch(){return Patch;} + public int Build(){return Build;} + public boolean isDev(){return isDev;} + } +} \ No newline at end of file diff --git a/util/Utils.java b/util/Utils.java new file mode 100644 index 0000000..dba54f1 --- /dev/null +++ b/util/Utils.java @@ -0,0 +1,115 @@ +package com.github.joelgodofwar.mmh.util; + +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import me.clip.placeholderapi.PlaceholderAPI; + +public class Utils { + + public static void sendJson(CommandSender player, String string){ + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw \"" + player.getName() + "\" " + string); + } + + public static void sendJson(Player player, String string){ + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw \"" + player.getName() + "\" " + string); + } + + public String parsePAPI(String string) { + if(Bukkit.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null){ + return PlaceholderAPI.setPlaceholders(null, string); + }else { + return string; + } + } + + public String parsePAPI(Player player, String string) { + if(Bukkit.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null){ + return PlaceholderAPI.setPlaceholders(player, string); + }else { + return string; + } + } + + public String parsePAPI(OfflinePlayer player, String string) { + if(Bukkit.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null){ + return PlaceholderAPI.setPlaceholders(player, string); + }else { + return string; + } + } + + /** + * Checks if the server version is supported based on minimum Minecraft version and build number. + * + * @param minMCVersion Minimum Minecraft version in the format "Major.Minor.BugFix.Build" (e.g., "1.20.2.3936"). + * @return True if the server version is not supported; otherwise, false. + */ + public static boolean isSupportedVersion(String minMCVersion) { + // 3936-Spigot-dba3cdc-5a2d905 (MC: 1.20.2) + // 0 1 2 + //String[] minVersion = minMCVersion.split("\\."); + Version minVersion = new Version(minMCVersion); + + String serverVersion = Bukkit.getVersion(); // Get the server version string + String[] versionParts = serverVersion.split(" "); // Split by space to get the MC version and build number + String daVersion = versionParts[2].replace(")", ".").concat(serverVersion.split("-")[0]); + /**System.out.println("Bukkit.getVersion()=" + Bukkit.getVersion()); + System.out.println("versionParts.length=" + versionParts.length); + System.out.println("versionParts.0=" + versionParts[0]); + System.out.println("versionParts.1=" + versionParts[1]); + System.out.println("versionParts.1=" + versionParts[2]);//*/ + Version checkVersion = new Version(daVersion); + + if(minVersion.Major() > checkVersion.Major()){return true;} + if(minVersion.Major() == checkVersion.Major()){/** same do nothing */} + if(minVersion.Minor() > checkVersion.Minor()){return true;} + else if(minVersion.Minor() == checkVersion.Minor()){/** same do nothing */} + if(minVersion.Patch() > checkVersion.Patch()){return true;} + else if(minVersion.Patch() == checkVersion.Patch()){/** same do nothing */} + if(minVersion.Build() > checkVersion.Build()){return true;} + else if(minVersion.Build() == checkVersion.Build()){/** same do nothing */} + + return true; // Server version is not supported + } + + public static class Version{ + private int Major; // 1 + private int Minor; // 16 + private int Patch; // 1 + private int Build; // ? + private boolean isDev = false; + private String[] string2 = {"0","0","0","0"}; + public Version(String string){ + //System.out.println("string=" + string); + string2 = string.split("\\."); + /**for (String a : string2) { + System.out.println(a); + System.out.println("string2=" + string2.toString()); + System.out.println("string2.length=" + string2.length); + }//*/ + this.Major = NumberUtils.toInt(string2[0]); + this.Minor = NumberUtils.toInt(string2[1]); + this.Patch = NumberUtils.toInt(string2[2]); + if(string2.length >= 4){ + if(string2[3].toUpperCase().contains("D")){ + isDev = true; + string2[3] = string2[3].toUpperCase().replace("D", ""); + } + this.Build = NumberUtils.toInt(string2[3]); + }else{ + this.Build = 0; + } + } + public int Major(){return Major;} + public int Minor(){return Minor;} + public int Patch(){return Patch;} + public int Build(){return Build;} + public boolean isDev(){return isDev;} + } + + +} diff --git a/util/VerifyConfig.java b/util/VerifyConfig.java new file mode 100644 index 0000000..496d8d1 --- /dev/null +++ b/util/VerifyConfig.java @@ -0,0 +1,143 @@ +package com.github.joelgodofwar.mmh.util; + +import com.github.joelgodofwar.mmh.MoreMobHeads; + +/** + * Utility class for verifying and retrieving values from a FileConfiguration. + */ +public class VerifyConfig { + MoreMobHeads mmh; + + /** + * Constructs a VerifyConfig object with the given FileConfiguration. + * + * @param config The FileConfiguration to be verified. + */ + public VerifyConfig(final MoreMobHeads plugin) { + this.mmh = plugin; + } + + /** + * Retrieves a string value from the configuration for the specified path. + * If the value is null, the specified defaultValue is returned. + * + * @param path The path to the configuration value. + * @param defaultValue The default value to be returned if the specified path is null. + * @return The retrieved string value or the defaultValue if null. + */ + public String getString(String path, String defaultValue ) { + try { + String stringValue = mmh.getConfig().getString(path, null); + if (stringValue == null) { + throw new NullPointerException("Could not read value of: " + path); + } + return stringValue; + }catch (NullPointerException e) { + e.printStackTrace(); + } + return defaultValue; + } + + /** + * Retrieves a boolean value from the configuration for the specified path. + * If the value is null, the specified defaultValue is returned. + * + * @param path The path to the configuration value. + * @param defaultValue The default value to be returned if the specified path is null. + * @return The retrieved boolean value or the defaultValue if null. + */ + public boolean getBoolean(String path, boolean defaultValue ) { + try { + String stringValue = mmh.getConfig().getString(path, null); + if (stringValue == null) { + throw new NullPointerException("Could not read value of: " + path); + } + return Boolean.parseBoolean(stringValue); + }catch (NullPointerException e) { + e.printStackTrace(); + } + return defaultValue; + } + + /** + * Retrieves an integer value from the configuration for the specified path. + * If the value is null, the specified defaultValue is returned. + * + * @param path The path to the configuration value. + * @param defaultValue The default value to be returned if the specified path is null. + * @return The retrieved integer value or the defaultValue if null. + */ + public int getInt(String path, int defaultValue ) { + try { + String stringValue = mmh.getConfig().getString(path, null); + if (stringValue == null) { + throw new NullPointerException("Could not read value of: " + path); + } + return Integer.parseInt(stringValue); + }catch (NullPointerException | NumberFormatException e) { + e.printStackTrace(); + } + return defaultValue; + } + + public boolean checkString(String path){ + try { + String stringValue = mmh.getConfig().getString(path, null); + if(stringValue != null) { + return true; + }else { + return false; + } + }catch(Exception e) { + return false; + } + } + + public void verifyValues() { + mmh.LOGGER.log("plugin_settings.auto_update_check " + getBoolean("plugin_settings.auto_update_check", true)); + mmh.LOGGER.log("plugin_settings.debug " + getBoolean("plugin_settings.debug", false)); + mmh.LOGGER.log("plugin_settings.lang " + getString("plugin_settings.lang", "en_US")); + mmh.LOGGER.log("global_settings.console.colorful_console " + getBoolean("global_settings.console.colorful_console", true)); + mmh.LOGGER.log("global_settings.console.silent_console " + getBoolean("global_settings.console.silent_console", true)); + mmh.LOGGER.log("global_settings.console.longpluginname " + getBoolean("global_settings.console.longpluginname", true)); + mmh.LOGGER.log("global_settings.world.whitelist " + getString("global_settings.world.whitelist", "")); + mmh.LOGGER.log("global_settings.world.blacklist " + getString("global_settings.world.blacklist", "")); + mmh.LOGGER.log("global_settings.event.piston_extend " + getBoolean("global_settings.event.piston_extend", true)); + mmh.LOGGER.log("head_settings.apply_looting " + getBoolean("head_settings.apply_looting", true)); + mmh.LOGGER.log("head_settings.lore.show_killer " + getBoolean("head_settings.lore.show_killer", true)); + mmh.LOGGER.log("head_settings.lore.show_plugin_name " + getBoolean("head_settings.lore.show_plugin_name", true)); + mmh.LOGGER.log("head_settings.player_heads.announce_kill.enabled" + getBoolean("head_settings.player_heads.announce_kill.enabled", false)); + mmh.LOGGER.log("head_settings.player_heads.announce_kill.displayname " + getBoolean("head_settings.player_heads.announce_kill.displayname", true)); + mmh.LOGGER.log("head_settings.player_heads.whitelist.enforce " + getBoolean("head_settings.player_heads.whitelist.enforce", true)); + mmh.LOGGER.log("head_settings.player_heads.whitelist.player_head_whitelist " + getString("head_settings.player_heads.whitelist.player_head_whitelist", "")); + mmh.LOGGER.log("head_settings.player_heads.blacklist.enforce " + getBoolean("head_settings.player_heads.blacklist.enforce", true)); + mmh.LOGGER.log("head_settings.player_heads.blacklist.player_head_blacklist " + getString("head_settings.player_heads.blacklist.player_head_blacklist", "")); + mmh.LOGGER.log("head_settings.mob_heads.announce_kill.enabled " + getBoolean("head_settings.mob_heads.announce_kill.enabled", false)); + mmh.LOGGER.log("head_settings.mob_heads.announce_kill.displayname " + getBoolean("head_settings.mob_heads.announce_kill.displayname", true)); + mmh.LOGGER.log("head_settings.mob_heads.whitelist " + getString("head_settings.mob_heads.whitelist", "")); + mmh.LOGGER.log("head_settings.mob_heads.blacklist " + getString("head_settings.mob_heads.blacklist", "")); + mmh.LOGGER.log("head_settings.mob_heads.nametag " + getBoolean("head_settings.mob_heads.nametag", false)); + mmh.LOGGER.log("head_settings.mob_heads.vanilla_heads.creepers " + getBoolean("head_settings.mob_heads.vanilla_heads.creepers", true)); + mmh.LOGGER.log("head_settings.mob_heads.vanilla_heads.ender_dragon " + getBoolean("head_settings.mob_heads.vanilla_heads.ender_dragon", true)); + mmh.LOGGER.log("head_settings.mob_heads.vanilla_heads.piglin " + getBoolean("head_settings.mob_heads.vanilla_heads.piglin", true)); + mmh.LOGGER.log("head_settings.mob_heads.vanilla_heads.skeleton " + getBoolean("head_settings.mob_heads.vanilla_heads.skeleton", true)); + mmh.LOGGER.log("head_settings.mob_heads.vanilla_heads.wither_skeleto " + getBoolean("head_settings.mob_heads.vanilla_heads.wither_skeleton", true)); + mmh.LOGGER.log("head_settings.mob_heads.vanilla_heads.zombie " + getBoolean("head_settings.mob_heads.vanilla_heads.zombie", true)); + mmh.LOGGER.log("wandering_trades.custom_wandering_trader " + getBoolean("wandering_trades.custom_wandering_trader", true)); + mmh.LOGGER.log("wandering_trades.keep_default_trades " + getBoolean("wandering_trades.keep_default_trades", true)); + mmh.LOGGER.log("wandering_trades.player_heads.enabled " + getBoolean("wandering_trades.player_heads.enabled", true)); + mmh.LOGGER.log("wandering_trades.player_heads.min " + getInt("wandering_trades.player_heads.min", 0)); + mmh.LOGGER.log("wandering_trades.player_heads.max " + getInt("wandering_trades.player_heads.max", 5)); + mmh.LOGGER.log("wandering_trades.block_heads.enabled " + getBoolean("wandering_trades.block_heads.enabled", true)); + mmh.LOGGER.log("wandering_trades.block_heads.pre_116.min" + getInt("wandering_trades.block_heads.pre_116.min", 0)); + mmh.LOGGER.log("wandering_trades.block_heads.pre_116.max " + getInt("wandering_trades.block_heads.pre_116.max", 5)); + mmh.LOGGER.log("wandering_trades.block_heads.is_116.min" + getInt("wandering_trades.block_heads.is_116.min", 0)); + mmh.LOGGER.log("wandering_trades.block_heads.is_116.max " + getInt("wandering_trades.block_heads.is_116.max", 5)); + mmh.LOGGER.log("wandering_trades.block_heads.is_117.min " + getInt("wandering_trades.block_heads.is_117.min", 0)); + mmh.LOGGER.log("wandering_trades.block_heads.is_117.max " + getInt("wandering_trades.block_heads.is_117.max", 5)); + mmh.LOGGER.log("wandering_trades.custom_trades.enabled " + getBoolean("wandering_trades.custom_trades.enabled", true)); + mmh.LOGGER.log("wandering_trades.custom_trades.min " + getInt("wandering_trades.custom_trades.min", 0)); + mmh.LOGGER.log("wandering_trades.custom_trades.max " + getInt("wandering_trades.custom_trades.max", 5)); + } + +} diff --git a/util/VersionChecker.java b/util/VersionChecker.java new file mode 100644 index 0000000..96b700f --- /dev/null +++ b/util/VersionChecker.java @@ -0,0 +1,222 @@ +package com.github.joelgodofwar.mmh.util; + +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.bukkit.ChatColor; +import org.bukkit.plugin.java.JavaPlugin; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import com.github.joelgodofwar.mmh.MoreMobHeads; + +@SuppressWarnings("unused") +public class VersionChecker { + private String pluginName; + private JavaPlugin plugin; + private int projectID; + private String currentVersion; + private String githubURL; // = "https://github.com/JoelGodOfwar/MoreMobHeads/raw/master/versioncheck/1.15/versions.xml"; + String versionType; + private List releaseList = new ArrayList<>(); + private List developerList = new ArrayList<>(); + private String recommendedVersion = "uptodate"; + private MoreMobHeads mmh; + + public VersionChecker(MoreMobHeads plugin, int projectID, String githubURL) { + this.mmh = plugin; + this.projectID = projectID; + this.currentVersion = plugin.getDescription().getVersion(); + this.githubURL = githubURL; + } + + public VersionChecker(String plugin, int projectID, String githubURL) { + this.currentVersion = plugin; + this.projectID = projectID; + this.githubURL = githubURL; + } + + public String getReleaseUrl() {return "https://dev.bukkit.org/projects/moremobheads2";} + + public boolean checkForUpdates() throws Exception { + URL url = new URL(githubURL); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + connection.setRequestProperty("Cache-Control", "no-cache, no-store, must-revalidate"); + Document doc = db.parse(connection.getInputStream()); + doc.getDocumentElement().normalize(); + NodeList releaseNodes = doc.getElementsByTagName("release"); + for (int i = 0; i < releaseNodes.getLength(); i++) { + Node node = releaseNodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + releaseList.add(element.getElementsByTagName("version").item(0).getTextContent().replace("", "").replace("", "")); + releaseList.add(element.getElementsByTagName("notes").item(0).getTextContent().replace("", "").replace("", "")); + releaseList.add(element.getElementsByTagName("link").item(0).getTextContent().replace("", "").replace("", "")); + } + } + NodeList developerNodes = doc.getElementsByTagName("developer"); + for (int i = 0; i < developerNodes.getLength(); i++) { + Node node = developerNodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + developerList.add(element.getElementsByTagName("version").item(0).getTextContent().replace("", "").replace("", "")); + developerList.add(element.getElementsByTagName("notes").item(0).getTextContent().replace("", "").replace("", "")); + developerList.add(element.getElementsByTagName("link").item(0).getTextContent().replace("", "").replace("", "")); + } + } + + if (connection != null) { + connection.getInputStream().close(); + } + String releaseVersion = releaseList.get(0); + String developerVersion = developerList.get(0); + mmh.LOGGER.debug(ChatColor.RED + "currentVersion=" + currentVersion); + mmh.LOGGER.debug(ChatColor.RED + "releaseVersion=" + releaseVersion); + mmh.LOGGER.debug(ChatColor.RED + "developerVersion=" + developerVersion); + if (currentVersion.compareTo(releaseVersion) < 0) { + recommendedVersion = "release"; + return true; + } else if (currentVersion.equals(releaseVersion)) { + recommendedVersion = "uptodate"; + return false; + } else if (currentVersion.contains(".D")) { + String[] splitCurrentVersion = currentVersion.split("\\.D"); + String currentReleaseVersion = splitCurrentVersion[0]; + String[] splitDeveloperVersion = developerVersion.split("\\.D"); + String developerReleaseVersion = splitDeveloperVersion[0]; + if (currentReleaseVersion.equals(releaseVersion) && (developerReleaseVersion.compareTo(currentReleaseVersion) <= 0)) { + recommendedVersion = "release"; + return true; + } else if ((developerReleaseVersion.compareTo(releaseVersion) < 0) && developerReleaseVersion.equals(currentReleaseVersion)) { + recommendedVersion = "You are Up To Date"; + return false; + } else if (developerVersion.compareTo(currentVersion) > 0) { + recommendedVersion = "developer"; + return true; + } + } + + return false; + } + public List getReleaseList() { + return releaseList; + } + public List getDeveloperList() { + return developerList; + } + public String getRecommendedVersion() { + return recommendedVersion; + } + public String oldVersion() { + return currentVersion; + } + public String newVersion() { + if(recommendedVersion.equalsIgnoreCase("release")) { + return releaseList.get(0); + }else if(recommendedVersion.equalsIgnoreCase("developer")) { + return developerList.get(0); + }else { + return "UpToDate"; + } + } + public String newVersionNotes() { + if (recommendedVersion.equalsIgnoreCase("release")) { + return releaseList.get(1); + } else if (recommendedVersion.equalsIgnoreCase("developer")) { + return developerList.get(1); + } else { + return "UpToDate"; + } + } + public String getDownloadLink() { + if (recommendedVersion.equalsIgnoreCase("release")) { + return releaseList.get(2);//"https://www.spigotmc.org/resources/no-enderman-grief2.71236/history"; + } else if (recommendedVersion.equalsIgnoreCase("developer")) { + return developerList.get(2);//"https://discord.com/channels/654087250665144321/654444482372173875"; + } else { + return "UpToDate"; + } + } + + /**public boolean checkForUpdates2() { + if(currentVersion.contains(".D")){ + versionType = "developer"; + }else{ + versionType = "release"; + } + String[] versionArray = getLatestVersion(versionType); + + if (versionArray == null) { + Bukkit.getLogger().warning(Ansi.RED + "Could not connect to update server."); + return false; + } + + String latestVersion = versionArray[0]; + String latestNotes = versionArray[1]; + + if (latestVersion.equals(currentVersion)) { + return false; + } else { + System.out.println("There is a new version of " + pluginName + " available!"); + System.out.println("Your version: " + currentVersion); + System.out.println("Latest version: " + latestVersion); + System.out.println("Update notes: " + latestNotes); + System.out.println("Please update to the newest version."); + System.out.println("Download: https://www.spigotmc.org/resources/" + pluginName + ".75749/history"); + System.out.println("Donate: "); + return true; + } + }//*/ + + /**private String[] getLatestVersion(String versionType) { + try { + URL url = new URL(githubURL); + InputStream stream = url.openStream(); + Scanner scanner = new Scanner(stream); + scanner.useDelimiter("\\Z"); + String xmlContent = scanner.next(); + scanner.close(); + stream.close(); + + String versionTag = "<" + versionType + ">"; + String notesTag = "<" + versionType + "-notes>"; + String endTag = ""; + + int versionStartIndex = xmlContent.indexOf(versionTag); + int notesStartIndex = xmlContent.indexOf(notesTag); + int versionEndIndex = xmlContent.indexOf(endTag); + + if (versionStartIndex == -1 || notesStartIndex == -1 || versionEndIndex == -1) { + return null; + } + + String versionNumber = xmlContent.substring(versionStartIndex + versionTag.length(), versionEndIndex); + String notes = xmlContent.substring(notesStartIndex + notesTag.length(), xmlContent.indexOf(endTag + "-notes>")); + String[] versionArray = new String[2]; + versionArray[0] = versionNumber; + versionArray[1] = notes; + return versionArray; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + }//*/ + + /**public static void main(String[] args) { + //String pluginName = "MyPlugin"; + String currentVersion = "1.14_1.0.4"; + String githubURL = "https://github.com/JoelGodOfwar/NoEndermanGrief/raw/master/versions/1.14/versions.xml"; + VersionChecker checker = new VersionChecker(currentVersion, 71236, githubURL); + checker.checkForUpdates(); + }//*/ +} diff --git a/util/YmlConfiguration.java b/util/YmlConfiguration.java new file mode 100644 index 0000000..2dd254d --- /dev/null +++ b/util/YmlConfiguration.java @@ -0,0 +1,114 @@ +package com.github.joelgodofwar.mmh.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.file.YamlConstructor; +import org.bukkit.configuration.file.YamlRepresenter; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.representer.Representer; + +import com.google.common.base.Charsets; + +import jdk.internal.joptsimple.internal.Strings; + +/* + * @author: Aoife (Josh) + * @date: 2020-04-25 + * @project: WarpConverter + */ +@SuppressWarnings("deprecation") +public class YmlConfiguration extends YamlConfiguration { + + private final DumperOptions yamlOptions = new DumperOptions(); + private final Representer yamlRepresenter = new YamlRepresenter(); + private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions); + private Map commentContainer = new HashMap<>(); + + @Override public void save(File file) throws IOException { + Validate.notNull(file); + try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) { + writer.write(saveToString()); + } + } + + public static void saveConfig(File file, YmlConfiguration config) { + try { + config.save(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public String saveToString(){ + yamlOptions.setIndent(options().indent()); + yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + String header = "";//buildHeader(); + String dump = yaml.dump(getValues(false)); + + if (dump.equals(BLANK_CONFIG)) { + dump = Strings.EMPTY; + } else { + StringBuilder sb = new StringBuilder(); + int line = 0; + for(String s : dump.split("\n")) { + line++; + while (commentContainer.containsKey(line)) { + sb.append(commentContainer.get(line)).append("\n"); + line++; + } + sb.append(s).append("\n"); + } + + dump = sb.toString(); + } + return header.length() > 0 ? header + dump : dump; + } + + @Override + public void load(Reader reader) throws IOException, InvalidConfigurationException { + BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + StringBuilder builder = new StringBuilder(); + try { + String line; + int count = 0; + while ((line = input.readLine()) != null) { + count++; + if (line.contains(COMMENT_PREFIX)|| line.isEmpty()) { + commentContainer.put(count, line); + } + builder.append(line).append('\n'); + } + } finally { + input.close(); + } + loadFromString(builder.toString()); + } + + public static YamlConfiguration loadConfiguration(File file, YmlConfiguration config) { + Validate.notNull(file, "File cannot be null"); + try { + config.load(file); + } catch (FileNotFoundException ex) { + } catch (IOException | InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); + } + return config; + } +} \ No newline at end of file diff --git a/util/datatypes/JsonDataType.java b/util/datatypes/JsonDataType.java new file mode 100644 index 0000000..578c379 --- /dev/null +++ b/util/datatypes/JsonDataType.java @@ -0,0 +1,44 @@ +package com.github.joelgodofwar.mmh.util.datatypes; + +import javax.annotation.Nonnull; + +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataType; + +import com.google.gson.Gson; + +/** + * Lets you store arbitrary data in PDC. + * @param + */ +public class JsonDataType implements PersistentDataType { + + private static final Gson gson = new Gson(); + private final Class typeClass; + + public JsonDataType(Class typeClass) { + this.typeClass = typeClass; + } + + @Override + public @Nonnull Class getPrimitiveType() { + return String.class; + } + + @Override + public @Nonnull Class getComplexType() { + return typeClass; + } + + @Nonnull + @Override + public String toPrimitive(@Nonnull T complex, @Nonnull PersistentDataAdapterContext persistentDataAdapterContext) { + return gson.toJson(complex); + } + + @Nonnull + @Override + public T fromPrimitive(@Nonnull String primitive, @Nonnull PersistentDataAdapterContext context) { + return gson.fromJson(primitive, getComplexType()); + } +} \ No newline at end of file diff --git a/util/mob/NameTag.java b/util/mob/NameTag.java new file mode 100644 index 0000000..c2dfeb7 --- /dev/null +++ b/util/mob/NameTag.java @@ -0,0 +1,44 @@ +package com.github.joelgodofwar.mmh.util.mob; + +import org.bukkit.entity.Drowned; +import org.bukkit.entity.Evoker; +import org.bukkit.entity.Husk; +import org.bukkit.entity.Illusioner; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.PigZombie; +import org.bukkit.entity.Pillager; +import org.bukkit.entity.Skeleton; +import org.bukkit.entity.Stray; +import org.bukkit.entity.Vex; +import org.bukkit.entity.Villager; +import org.bukkit.entity.Vindicator; +import org.bukkit.entity.WanderingTrader; +import org.bukkit.entity.WitherSkeleton; +import org.bukkit.entity.Zombie; +import org.bukkit.entity.ZombieVillager; + +import com.github.joelgodofwar.mmh.MoreMobHeads; + +public class NameTag { + + public static boolean canWearHead(LivingEntity mob) { + if((mob instanceof Skeleton)||(mob instanceof Zombie)||(mob instanceof PigZombie)||(mob instanceof Villager)|| + (mob instanceof Drowned)||(mob instanceof Stray)||(mob instanceof Husk)||(mob instanceof WitherSkeleton)|| + (mob instanceof ZombieVillager)||(mob instanceof Evoker)||(mob instanceof WanderingTrader)||(mob instanceof Vex)|| + (mob instanceof Illusioner)||(mob instanceof Pillager)||(mob instanceof Vindicator)){ + return true; + } + double vers = Double.parseDouble( MoreMobHeads.getMCVersion().substring(2, 4) ); // 1.16.2 + if (vers >= 16) { + if(NameTag_1_16.canWearHead(mob)) { + return true; + } + if (vers >= 16.2) { + if(NameTag_1_16_2.canWearHead(mob)) { + return true; + } + } + } + return false; + } +} diff --git a/util/mob/NameTag_1_16.java b/util/mob/NameTag_1_16.java new file mode 100644 index 0000000..f12dddc --- /dev/null +++ b/util/mob/NameTag_1_16.java @@ -0,0 +1,16 @@ +package com.github.joelgodofwar.mmh.util.mob; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Piglin; + +public class NameTag_1_16 { + + public static boolean canWearHead(LivingEntity mob) { + if(mob instanceof Piglin) { + return true; + } + + return false; + } + +} diff --git a/util/mob/NameTag_1_16_2.java b/util/mob/NameTag_1_16_2.java new file mode 100644 index 0000000..fd526d1 --- /dev/null +++ b/util/mob/NameTag_1_16_2.java @@ -0,0 +1,16 @@ +package com.github.joelgodofwar.mmh.util.mob; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.PiglinBrute; + +public class NameTag_1_16_2 { + + public static boolean canWearHead(LivingEntity mob) { + if(mob instanceof PiglinBrute) { + return true; + } + + return false; + } + +}