Removed Birthday Code and ...

Removed Birthday Code and added better error reporting and logging.
This commit is contained in:
JoelStoner authored and GitHub committed 2024-04-16 14:11:15 -05:00
1 parent 7a7288a5bf
commit d874ddeb7e
52 files changed
+12489 -5832

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -0,0 +1,147 @@
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<String> 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_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_EDBE_EVENT_ERROR = new ReportType("Unable to get damager weapon.");
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_TAB_COMPLETE_ERROR = new ReportType("Error parsing Tab Complete.");
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.");
/**
* 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;
}
}
@@ -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
}
}
@@ -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.
* <p>
* Note that replaced key-value associations are only collected once the original expiration time has elapsed.
*
* @author Kristian Stangeland
*
* @param <K> - type of the keys.
* @param <V> - type of the values.
*/
public class ExpireHashMap<K, V> {
private class ExpireEntry implements Comparable<ExpireEntry> {
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<K, ExpireEntry> keyLookup = new HashMap<>();
private final PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<>();
// View of keyLookup with direct values
private final Map<K, V> 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<K> keySet() {
evictExpired();
return keyLookup.keySet();
}
/**
* Retrieve a view of all the values in the current map.
* @return All the values.
*/
public Collection<V> values() {
evictExpired();
return valueView.values();
}
/**
* Retrieve a view of all the entries in the set.
* @return All the entries.
*/
public Set<Entry<K, V>> 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<K, V> asMap() {
evictExpired();
return valueView;
}
/**
* Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted.
* <p>
* 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.
* <p>
* 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();
}
}
@@ -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.
* <p>
* 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();
}
}
}
}
@@ -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<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
protected String prefix;
protected String supportURL;
protected AtomicInteger internalErrorCount = new AtomicInteger();
protected int maxErrorCount;
protected Logger logger;
protected WeakReference<Plugin> 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<String, Object> globalParameters = new HashMap<String, Object>();
// Reports to ignore
private ExpireHashMap<Report, Boolean> rateLimited = new ExpireHashMap<Report, Boolean>();
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>(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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<String> 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;
}
}
@@ -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.
* <p>
* 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);
}
@@ -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.
* <p>
* 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.
* <p>
* 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;
}
}
@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* Note that the sender may be a class (for static callers), in which
* case it will be used directly instead of its getClass() method.
* <p>
* 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.
* <p>
* This is in the format <i>canonical_name_of_class#report_type</i>
* @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<ReportType> result = new ArrayList<ReportType>();
// 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<Field> getReportFields(Class<?> clazz) {
return FuzzyReflection.fromClass(clazz, true)
.getFieldList(FuzzyFieldContract.newBuilder()
.requireModifier(Modifier.STATIC)
.typeDerivedOf(ReportType.class)
.build());
}
}
@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}
@@ -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 : "");
}
}
@@ -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 <T> 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> T getFieldValue(Object instance, Class<T> fieldClass, boolean forceAccess) {
return (T) Accessors.getFieldAccessor(instance.getClass(), fieldClass, forceAccess).get(instance);
}
@SafeVarargs
public static <T> Set<T> combineArrays(T[]... arrays) {
Set<T> 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.
* <p>
* 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<MethodInfo> matcher) {
List<Method> 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.
* <p>
* 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<MethodInfo> matcher, String preferred) {
List<Method> 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<Method> 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.
* <p>
* 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<Method> getMethodList(AbstractFuzzyMatcher<MethodInfo> matcher) {
// finds and adds all matching methods
List<Method> 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<Method> getMethodListByParameters(Class<?> returnType, Class<?>... args) {
List<Method> 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.
* <p>
* 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<Field> matcher) {
List<Field> 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<Field> 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<Field> getFieldListByType(Class<?> type) {
// Field with a compatible type
List<Field> 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.
* <p>
* 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<Field> getFieldList(AbstractFuzzyMatcher<Field> matcher) {
// Add all matching fields to the list
List<Field> fields = new ArrayList<>();
for (Field field : this.getFields()) {
if (matcher.isMatch(field, this.source)) {
fields.add(field);
}
}
return fields;
}
/**
* Retrieves a field by type.
* <p>
* Note that the type is matched using the full canonical representation, i.e.:
* <ul>
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
*
* @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.
* <p>
* 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<MethodInfo> matcher) {
List<Constructor<?>> 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.
* <p>
* 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<Constructor<?>> getConstructorList(AbstractFuzzyMatcher<MethodInfo> matcher) {
// Add all matching constructors to the list
List<Constructor<?>> 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.
* <p>
* Private, protected and package fields are ignored if forceAccess is FALSE.
*
* @return Every field.
*/
public Set<Field> 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<Field> getDeclaredFields(Class<?> excludeClass) {
// we only need to do this if we include inherited fields
if (this.forceAccess) {
Class<?> current = this.source;
Set<Field> 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).
* <p>
* Private, protected and package methods are ignored if forceAccess is FALSE.
*
* @return Every method.
*/
public Set<Method> 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).
* <p>
* Private, protected and package constructors are ignored if forceAccess is FALSE.
*
* @return Every constructor.
*/
public Set<Constructor<?>> getConstructors() {
return combineArrays(this.forceAccess ? this.source.getDeclaredConstructors() : this.source.getConstructors());
}
}
@@ -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 extends Annotation> T getAnnotation(Class<T> 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<MethodInfo> 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<MethodInfo> fromMethods(Collection<Method> methods) {
List<MethodInfo> 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 extends Annotation> T getAnnotation(Class<T> 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<MethodInfo> 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<MethodInfo> fromConstructors(Collection<Constructor<?>> constructors) {
List<MethodInfo> 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.
* <p>
* 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();
}
@@ -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<Object> 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<Object> 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<Object, Object> map, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException {
boolean first = true;
output.append("[");
for (Entry<Object, Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object, Object>) 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.
* <p>
* 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);
}
}
@@ -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.
* <p>
* 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);
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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();
}
}
@@ -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();
}
@@ -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);
}
}
@@ -0,0 +1,52 @@
package com.github.joelgodofwar.mmh.common.reflect.fuzzy;
/**
* Represents a matcher for fields, methods, constructors and classes.
* <p>
* This class should ideally never expose mutable state. Its round number must be immutable.
*
* @author Kristian
*/
@FunctionalInterface
public interface AbstractFuzzyMatcher<T> {
/**
* 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<T> 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<T> and(final AbstractFuzzyMatcher<T> 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<T> or(final AbstractFuzzyMatcher<T> other) {
// Either can be true
return (value, parent) -> this.isMatch(value, parent) || other.isMatch(value, parent);
}
}
@@ -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 <T> - type that it matches.
* @author Kristian
*/
public abstract class AbstractFuzzyMember<T extends Member> implements AbstractFuzzyMatcher<T> {
// Accessibility matchers
protected int modifiersRequired;
protected int modifiersBanned;
protected Pattern nameRegex;
protected AbstractFuzzyMatcher<Class<?>> 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<T> 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.
* <p>
* 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<Class<?>> 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.
* <p>
* Used by {@link #toString()} to print a representation of this object.
*
* @return A modifiable key-value view.
*/
protected Map<String, Object> getKeyValueView() {
Map<String, Object> 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<T extends AbstractFuzzyMember<?>> {
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<T> requireModifier(int modifier) {
this.member.modifiersRequired |= modifier;
return this;
}
/**
* Require that every matching member is public.
*
* @return This builder, for chaining.
*/
public Builder<T> 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<T> 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<T> 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<T> nameRegex(Pattern pattern) {
this.member.nameRegex = pattern;
return this;
}
/**
* Set the exact name of the member we are matching.
* <p>
* This will overwrite the regular expression rule.
*
* @param name - exact name.
* @return This builder, for chaining.
*/
public Builder<T> 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<T> 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<T> 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<T> 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<T> declaringClassMatching(AbstractFuzzyMatcher<Class<?>> 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.
* <p>
* Builders should call {@link AbstractFuzzyMember#prepareBuild()} when constructing new objects.
*
* @return New instance of this type.
*/
public abstract T build();
}
}
@@ -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<Class<?>> {
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() + "\" }";
}
}
@@ -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<Class<?>> {
private final Set<Class<?>> classes;
public ClassSetMatcher(Set<Class<?>> 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 + " }";
}
}
@@ -0,0 +1,114 @@
package com.github.joelgodofwar.mmh.common.reflect.fuzzy;
/**
* Used to check class equality.
*
* @author Kristian
*/
final class ClassTypeMatcher implements AbstractFuzzyMatcher<Class<?>> {
/**
* 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.
* <p>
* 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
}
}
@@ -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<Class<?>> {
private final ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts;
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts;
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts;
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> baseclassContracts;
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> 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.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every field contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Field>> getFieldContracts() {
return this.fieldContracts;
}
/**
* Retrieve an immutable list of every method contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every method contract.
*/
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getMethodContracts() {
return this.methodContracts;
}
/**
* Retrieve an immutable list of every constructor contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every constructor contract.
*/
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getConstructorContracts() {
return this.constructorContracts;
}
/**
* Retrieve an immutable list of every baseclass contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every baseclass contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Class<?>>> getBaseclassContracts() {
return this.baseclassContracts;
}
/**
* Retrieve an immutable list of every interface contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every interface contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Class<?>>> 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 <T> boolean processContracts(Collection<T> values, Object parent, List<AbstractFuzzyMatcher<T>> 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 <T> boolean processValue(T value, Object parent, List<AbstractFuzzyMatcher<T>> matchers) {
// check if all given contracts match the given value
for (AbstractFuzzyMatcher<T> 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<AbstractFuzzyMatcher<Field>> fieldContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<MethodInfo>> constructorContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<Class<?>>> baseclassContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<Class<?>>> interfaceContracts = new ArrayList<>();
/**
* Add a new field contract.
*
* @param matcher - new field contract.
* @return This builder, for chaining.
*/
public Builder field(AbstractFuzzyMatcher<Field> 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<MethodInfo> 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<MethodInfo> 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<Class<?>> 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<Class<?>> 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);
}
}
}
@@ -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<Field> {
private AbstractFuzzyMatcher<Class<?>> 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<Class<?>> 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<Class<?>> 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<String, Object> getKeyValueView() {
Map<String, Object> 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<FuzzyFieldContract> {
@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<Class<?>> 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<Class<?>> matcher) {
this.member.typeMatcher = matcher;
return this;
}
@Override
public FuzzyFieldContract build() {
this.member.prepareBuild();
return new FuzzyFieldContract(this.member);
}
}
}
@@ -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<Class<?>> 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<Class<?>> matchArray(AbstractFuzzyMatcher<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> matchAnyOf(Set<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> matchRegex(String regex) {
return FuzzyMatchers.matchRegex(Pattern.compile(regex));
}
/**
* Determine if two patterns are the same.
* <p>
* 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());
}
}
}
@@ -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<MethodInfo> {
// Match return value
private AbstractFuzzyMatcher<Class<?>> returnMatcher = ClassTypeMatcher.MATCH_ALL;
// Handle parameters and exceptions
private List<ParameterClassMatcher> paramMatchers;
private List<ParameterClassMatcher> 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<Class<?>> getReturnMatcher() {
return this.returnMatcher;
}
/**
* Retrieve an immutable list of every parameter matcher for this method.
*
* @return Immutable list of every parameter matcher.
*/
public ImmutableList<ParameterClassMatcher> getParamMatchers() {
if (this.paramMatchers instanceof ImmutableList) {
return (ImmutableList<ParameterClassMatcher>) 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<ParameterClassMatcher> 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<ParameterClassMatcher> 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<ParameterClassMatcher> 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<String, Object> getKeyValueView() {
Map<String, Object> 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<Class<?>[]> {
/**
* The expected index.
*/
private final AbstractFuzzyMatcher<Class<?>> typeMatcher;
private final Integer indexMatch;
/**
* Construct a new parameter class matcher.
*
* @param typeMatcher - class type matcher.
*/
public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher<Class<?>> 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<Class<?>> 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<FuzzyMethodContract> {
@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<Class<?>> 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.
* <p>
* 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.
* <p>
* 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<Class<?>> 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.
* <p>
* 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.
* <p>
* 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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);
}
}
}
@@ -1,90 +1,89 @@
package com.github.joelgodofwar.mmh.enums;
import java.util.UUID;
public enum CatHeads {
ALL_BLACK(
"Black Cat", "cat.all_black",
"f89934e4-99a0-4dab-9151-7b63831e5fd1", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTA2NGYzN2IxYjVhZDRhMjQ5M2Q4ZmM5ZWJmYzYxYzNkMzUxNTBlNjkxNzc5NzZjNjJjM2U1YjhjNzg0YmY5NiJ9fX0="
),
"f89934e4-99a0-4dab-9151-7b63831e5fd1", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTA2NGYzN2IxYjVhZDRhMjQ5M2Q4ZmM5ZWJmYzYxYzNkMzUxNTBlNjkxNzc5NzZjNjJjM2U1YjhjNzg0YmY5NiJ9fX0="
),
BLACK(//used for unknown player heads
"Tuxedo Cat", "cat.black",
"f0db2cac-dde4-47de-9c27-c001aee27712", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTk2NmI5MDVmODFlZjk1MmI2NjEwZjNhMGJiZDY1NDdiMmYyY2QzNDQ3ZTFmYzgyZDkwNDI5OWJkNjI5NGY4YSJ9fX0="
),
BRITISH_SHORTHAIR(//used for unknown player heads
"f0db2cac-dde4-47de-9c27-c001aee27712", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTk2NmI5MDVmODFlZjk1MmI2NjEwZjNhMGJiZDY1NDdiMmYyY2QzNDQ3ZTFmYzgyZDkwNDI5OWJkNjI5NGY4YSJ9fX0="
),
BRITISH_SHORTHAIR(//used for unknown player heads
"British Shorthair Cat", "cat.british_shorthair",
"4332ff48-8a0e-4164-ae55-2d16caf68190", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjNiYjEzNzhlM2ExMDA5ZGViMWIyYTc2YThkNmYwMDljNDZkMGEwMDgwZWVjYjBjM2QyYjRmNjE3Nzc5YzRhYyJ9fX0="
),
CALICO(//used for unknown player heads
"4332ff48-8a0e-4164-ae55-2d16caf68190", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjNiYjEzNzhlM2ExMDA5ZGViMWIyYTc2YThkNmYwMDljNDZkMGEwMDgwZWVjYjBjM2QyYjRmNjE3Nzc5YzRhYyJ9fX0="
),
CALICO(//used for unknown player heads
"Calico Cat", "cat.calico",
"024560fb-84a5-40cf-b6a1-c8f9d9db2fe9", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWRhNTI3MTVjMDZmNTQ2ZmRkMTZlZjdhMjllZTc5YzBjZjIxZjEwYzI5ODU4NTgxZDVmYWJlMmY0MzQ5M2E2MSJ9fX0="
),
JELLIE(//used for unknown player heads
"024560fb-84a5-40cf-b6a1-c8f9d9db2fe9", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWRhNTI3MTVjMDZmNTQ2ZmRkMTZlZjdhMjllZTc5YzBjZjIxZjEwYzI5ODU4NTgxZDVmYWJlMmY0MzQ5M2E2MSJ9fX0="
),
JELLIE(//used for unknown player heads
"Jellie Cat", "cat.jellie",
"f0aaa05b-0283-4663-9b57-52dbf2ca2750", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2I3MWE3MjYxNTc2NmQ3YzQ2YTliMzYwNTc0NWIxZmIzZjkxYjg0NzBmYmJlYmI2YjNmNmY0OTYyYTk5YzRkNCJ9fX0="
),
PERSIAN(//used for unknown player heads
"f0aaa05b-0283-4663-9b57-52dbf2ca2750", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2I3MWE3MjYxNTc2NmQ3YzQ2YTliMzYwNTc0NWIxZmIzZjkxYjg0NzBmYmJlYmI2YjNmNmY0OTYyYTk5YzRkNCJ9fX0="
),
PERSIAN(//used for unknown player heads
"Persian Cat", "cat.persian",
"701fa2a8-ef2b-46cd-b9d3-6cd16be17bb4", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWVkMDI5YTRjMDI3MDIwZDJmZjQ3NTQ0ZTQwYTQ0OTkyM2QyODljZjQ0ZjIyOWRjNzM2ZDNhMTQ1YmMzYTE3NyJ9fX0="
),
RAGDOLL(//used for unknown player heads
"701fa2a8-ef2b-46cd-b9d3-6cd16be17bb4", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWVkMDI5YTRjMDI3MDIwZDJmZjQ3NTQ0ZTQwYTQ0OTkyM2QyODljZjQ0ZjIyOWRjNzM2ZDNhMTQ1YmMzYTE3NyJ9fX0="
),
RAGDOLL(//used for unknown player heads
"Ragdoll Cat", "cat.ragdoll",
"b65e722b-5a35-4561-a8df-db9c7a52041f", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmY3NDM1YzIwZWJjNDE0N2YyNmQzYzY1NjQxOTU5YzljYWI0ZGFjOTZiNzMwZjlmNTQyNTVlNzgxY2VlNTMxYSJ9fX0="
),
RED(//used for unknown player heads
"b65e722b-5a35-4561-a8df-db9c7a52041f", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmY3NDM1YzIwZWJjNDE0N2YyNmQzYzY1NjQxOTU5YzljYWI0ZGFjOTZiNzMwZjlmNTQyNTVlNzgxY2VlNTMxYSJ9fX0="
),
RED(//used for unknown player heads
"Red Cat", "cat.red",
"11d2442b-0bc1-4475-a499-f07dcc2aa40d", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjM3ZmEwYWJiMjY0ZmRlY2FmN2U5ODQ4MmNkNzgyNGU4ODlkNmQ2NjdlYjc1NDlmMmNhNDk1NDQyZmNlNGEwOCJ9fX0="
),
SIAMESE(//used for unknown player heads
"11d2442b-0bc1-4475-a499-f07dcc2aa40d", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjM3ZmEwYWJiMjY0ZmRlY2FmN2U5ODQ4MmNkNzgyNGU4ODlkNmQ2NjdlYjc1NDlmMmNhNDk1NDQyZmNlNGEwOCJ9fX0="
),
SIAMESE(//used for unknown player heads
"Siamese Cat", "cat.siamese",
"7d487214-5276-49af-bbb1-019b49384d69", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2U0OTBiNjEwY2RkZmUyMDA0ODM3ZTFhNzFjOTkxZGQ5NGQ1ZGIxNWZkZTk5ZGVkNWY5MDM3YTdkZWZkMGQwZSJ9fX0="
),
TABBY(//used for unknown player heads
"7d487214-5276-49af-bbb1-019b49384d69", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2U0OTBiNjEwY2RkZmUyMDA0ODM3ZTFhNzFjOTkxZGQ5NGQ1ZGIxNWZkZTk5ZGVkNWY5MDM3YTdkZWZkMGQwZSJ9fX0="
),
TABBY(//used for unknown player heads
"Tabby Cat", "cat.tabby",
"18d071ee-a17c-46eb-866c-304a4823ac05", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTgyOTEzMGEyOTM1ZTM5N2M5ZjIxZWRmZWRjNjg5NzNjNzljNGE2Yjc4Njg0ZjQyYjhmNGU2NmUwNGY1NzIyMyJ9fX0="
),
WHITE(//used for unknown player heads
"18d071ee-a17c-46eb-866c-304a4823ac05", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTgyOTEzMGEyOTM1ZTM5N2M5ZjIxZWRmZWRjNjg5NzNjNzljNGE2Yjc4Njg0ZjQyYjhmNGU2NmUwNGY1NzIyMyJ9fX0="
),
WHITE(//used for unknown player heads
"White Cat", "cat.white",
"db9474c0-f11e-47d3-a6dc-2ebcdd5f37e0", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGRiOTE2YTA3NTg0MDRkMTIzZjNlZTE3YTZiNzUxNmIxMzg3N2YyODFkOTEwNmM0NDA1NWY0MmY0OTUxOTY2MSJ9fX0="
),
;
private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
CatHeads(String name, String nameString, String ownerUUID, String texture){
this.name = name;
this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(CatHeads cathead : CatHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(cathead.getTexture().contains(texture) )
return cathead.getNameString() ;
}
"db9474c0-f11e-47d3-a6dc-2ebcdd5f37e0", // UUID
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGRiOTE2YTA3NTg0MDRkMTIzZjNlZTE3YTZiNzUxNmIxMzg3N2YyODFkOTEwNmM0NDA1NWY0MmY0OTUxOTY2MSJ9fX0="
),
;
private final String owner;
private final String texture;
private final String name;
private final String nameString;
CatHeads(String name, String nameString, String ownerUUID, String texture){
this.name = name;
this.owner = ownerUUID;
this.texture = texture;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(CatHeads cathead : CatHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(cathead.getTexture().contains(texture) ) {
return cathead.getNameString() ;
}
}
return null;
}
return null;
}
/**
* @return the name
*/
@@ -95,7 +94,7 @@ public enum CatHeads {
/**
* @return the owner
*/
public UUID getOwner() {
public String getOwner() {
return owner;
}
@@ -105,7 +104,7 @@ public enum CatHeads {
public String getTexture() {
return texture;
}
/**
* @return the name
*/
@@ -1,80 +1,79 @@
package com.github.joelgodofwar.mmh.enums;
import java.util.UUID;
public enum HorseHeads {
HORSE(
"Horse", "horse",
"dc1293f0-c0cb-4a1e-973f-bd36d70a3de9",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWRlMTYwNDQ0ODVkMzQwOWE2ZTg4MmJjOWM5MDRjNDQyNWI5Mjg4YWFjZTYwMzg4NWQ1NGM1YWRjNWM3ZWVjYyJ9fX0="
),
HORSE_BLACK(
"Black Horse", "horse.black",
"4083cd58-1325-4bfa-9efb-5b8b93a02493",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjIxZjE0ZjU4ZmRmMWM0OGU0YzI4YWZiZWI0YzY4YmE4MGJhMTc1MDVlNTYwYjUwNTk0NTdjN2I3MWMyM2EzZiJ9fX0="
),
HORSE_BROWN(
"Brown Horse", "horse.brown",
"2dcb4f55-ab85-48ba-b7d2-7b57d3ec7bfa",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjI1OWUwOWRjYzMwMTg3OTY1YWQxZmZkODRhM2ZjMWQ2ODZhMGQxMTI0NDFiMjA4NWZlNGIxNjMxMWY4OTE1NCJ9fX0="
),
HORSE_CHESTNUT(
"Chestnut Horse", "horse.chestnut",
"5a2546e1-2213-4f2a-8cbe-5ffddf3e7269",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTExMzFhZTlmNmNmNzk1NjBlMDgzMjY4Mjc1MTE0NTdjOTljMjg2NmJmYTI1ZDY4Mjg0ZDRhMTY3OWMzZTYyNiJ9fX0="
),
HORSE_CREAMY(
"Creamy Horse", "horse.creamy",
"65e8bd32-6f48-4b92-ab48-fe1add6b67d1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDhjNjRlMTQ5Mzg5Y2JmNTliZTIzNzkyZjE4YWMzOTkxNDI2NThmN2QyZTY2NGY3NTE0N2NhN2FjMjlkODE5OCJ9fX0="
),
HORSE_DARK_BROWN(
"Dark Brown Horse", "horse.dark_brown",
"c6abc94e-a5ff-45fe-a0d7-4e479f290a6f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTVkZjI4MGJmYjI0YmFjZDA2YzJlZjU3OWMxNDlhYTVmZDNkNzIzZWY2NWZhNmE5M2I4MzM0YjdmMjkxMzU5YSJ9fX0="
),
HORSE_GRAY(
"Gray Horse", "horse.gray",
"b600f9c3-9c3f-4e3c-828c-3ebb6414eaa7",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWNlZGNmOTNmY2ZhYWEwZWQ1OThjNTA5YmZhNmY2ODFjYTIyYzc4MGVmYTFmNDA3ZjBlYTlmOTkxNzE4YWVjMiJ9fX0="
),
HORSE_WHITE(
"White Horse", "horse.white",
"d941cb68-8842-4f78-bdf8-5f1d3c6e7ac0",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGE3MWFiNTMwZGY4ZGZjZmQ2MjljODFkMDFlNjU0YTEyZTcyN2VmYjY1YzU1YzkwNjg1ODliOTE2MDgzNGU1ZCJ9fX0="
),
;
private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
HorseHeads(String name, String nameString, String ownerUUID, String texture){
this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(HorseHeads verbosity : HorseHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) )
return verbosity.getNameString() ;
}
"dc1293f0-c0cb-4a1e-973f-bd36d70a3de9",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWRlMTYwNDQ0ODVkMzQwOWE2ZTg4MmJjOWM5MDRjNDQyNWI5Mjg4YWFjZTYwMzg4NWQ1NGM1YWRjNWM3ZWVjYyJ9fX0="
),
HORSE_BLACK(
"Black Horse", "horse.black",
"4083cd58-1325-4bfa-9efb-5b8b93a02493",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjIxZjE0ZjU4ZmRmMWM0OGU0YzI4YWZiZWI0YzY4YmE4MGJhMTc1MDVlNTYwYjUwNTk0NTdjN2I3MWMyM2EzZiJ9fX0="
),
HORSE_BROWN(
"Brown Horse", "horse.brown",
"2dcb4f55-ab85-48ba-b7d2-7b57d3ec7bfa",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjI1OWUwOWRjYzMwMTg3OTY1YWQxZmZkODRhM2ZjMWQ2ODZhMGQxMTI0NDFiMjA4NWZlNGIxNjMxMWY4OTE1NCJ9fX0="
),
HORSE_CHESTNUT(
"Chestnut Horse", "horse.chestnut",
"5a2546e1-2213-4f2a-8cbe-5ffddf3e7269",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTExMzFhZTlmNmNmNzk1NjBlMDgzMjY4Mjc1MTE0NTdjOTljMjg2NmJmYTI1ZDY4Mjg0ZDRhMTY3OWMzZTYyNiJ9fX0="
),
HORSE_CREAMY(
"Creamy Horse", "horse.creamy",
"65e8bd32-6f48-4b92-ab48-fe1add6b67d1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDhjNjRlMTQ5Mzg5Y2JmNTliZTIzNzkyZjE4YWMzOTkxNDI2NThmN2QyZTY2NGY3NTE0N2NhN2FjMjlkODE5OCJ9fX0="
),
HORSE_DARK_BROWN(
"Dark Brown Horse", "horse.dark_brown",
"c6abc94e-a5ff-45fe-a0d7-4e479f290a6f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTVkZjI4MGJmYjI0YmFjZDA2YzJlZjU3OWMxNDlhYTVmZDNkNzIzZWY2NWZhNmE5M2I4MzM0YjdmMjkxMzU5YSJ9fX0="
),
HORSE_GRAY(
"Gray Horse", "horse.gray",
"b600f9c3-9c3f-4e3c-828c-3ebb6414eaa7",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWNlZGNmOTNmY2ZhYWEwZWQ1OThjNTA5YmZhNmY2ODFjYTIyYzc4MGVmYTFmNDA3ZjBlYTlmOTkxNzE4YWVjMiJ9fX0="
),
HORSE_WHITE(
"White Horse", "horse.white",
"d941cb68-8842-4f78-bdf8-5f1d3c6e7ac0",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGE3MWFiNTMwZGY4ZGZjZmQ2MjljODFkMDFlNjU0YTEyZTcyN2VmYjY1YzU1YzkwNjg1ODliOTE2MDgzNGU1ZCJ9fX0="
),
;
return null;
}
/**
private final String owner;
private final String texture;
private final String name;
private final String nameString;
HorseHeads(String name, String nameString, String ownerUUID, String texture){
this.owner = ownerUUID;
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(HorseHeads verbosity : HorseHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) ) {
return verbosity.getNameString() ;
}
}
return null;
}
/**
* @return the owner
*/
public UUID getOwner() {
public String getOwner() {
return owner;
}
@@ -91,7 +90,7 @@ public enum HorseHeads {
public String getName() {
return name;
}
/**
* @return the name
*/
@@ -1,100 +1,99 @@
package com.github.joelgodofwar.mmh.enums;
import java.util.UUID;
public enum LlamaHeads {
LLAMA_BROWN(
"Brown Llama", "llama.brown",
"75fb08e5-2419-46fa-bf09-57362138f234",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjgxNmVmOWRiNzUzNDg4ODNhZTgwMWI5Mzg2YzhlZDM2N2YzYjhkMGMzYmEwMTY3NjYyZGNhZjliMGU4MzkxMiJ9fX0="
),
LLAMA_CREAMY(
"Creamy Llama", "llama.creamy",
"dd0a3919-e919-428c-9298-6dcc416fec9d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTA3ZDAyYjgyNWQ0YTQ3NmQ3OGFmNjA3ODU0MWJkZDBmY2I2ODJiMGI0MDk4YjkxYTMxNGUxYjFjNGUzYzRlMSJ9fX0="
),
LLAMA_GRAY(
"Gray Llama", "llama.gray",
"edca7a0d-770f-43d6-8ffc-f6a00e94e477",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTYxMjNiMzA0NzE0ZDIzZDExNGRmZTY3Nzk0NmRjYmU0ZmVlODYzNWMwZDAyMTdiODg0NmQxOTM3ODM1ZTZjYiJ9fX0="
),
LLAMA_WHITE(
"White Llama", "llama.white",
"60d7893f-b634-48b8-8d6e-f07fa14f5115",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWZhNTgzNmQ5MTk3YTQwNGJiNzJkN2ZiYmVlY2NmOWE0MTQzYzQ1MWVjODQ0NTUzNTUyMTU5ZWQzNjdhNTYxMCJ9fX0="
),
TRADER_LLAMA_BROWN(
"Brown Trader Llama", "trader_llama.brown",
"a957be18-324a-4984-a81b-f556a793a64a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODQyNDc4MGIzYzVjNTM1MWNmNDlmYjViZjQxZmNiMjg5NDkxZGY2YzQzMDY4M2M4NGQ3ODQ2MTg4ZGI0Zjg0ZCJ9fX0="
),
TRADER_LLAMA_CREAMY(
"Creamy Trader Llama", "trader_llama.creamy",
"75fb08e5-2419-46fa-bf09-57362138f234",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjgxNmVmOWRiNzUzNDg4ODNhZTgwMWI5Mzg2YzhlZDM2N2YzYjhkMGMzYmEwMTY3NjYyZGNhZjliMGU4MzkxMiJ9fX0="
),
LLAMA_CREAMY(
"Creamy Llama", "llama.creamy",
"dd0a3919-e919-428c-9298-6dcc416fec9d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTA3ZDAyYjgyNWQ0YTQ3NmQ3OGFmNjA3ODU0MWJkZDBmY2I2ODJiMGI0MDk4YjkxYTMxNGUxYjFjNGUzYzRlMSJ9fX0="
),
LLAMA_GRAY(
"Gray Llama", "llama.gray",
"edca7a0d-770f-43d6-8ffc-f6a00e94e477",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTYxMjNiMzA0NzE0ZDIzZDExNGRmZTY3Nzk0NmRjYmU0ZmVlODYzNWMwZDAyMTdiODg0NmQxOTM3ODM1ZTZjYiJ9fX0="
),
LLAMA_WHITE(
"White Llama", "llama.white",
"60d7893f-b634-48b8-8d6e-f07fa14f5115",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWZhNTgzNmQ5MTk3YTQwNGJiNzJkN2ZiYmVlY2NmOWE0MTQzYzQ1MWVjODQ0NTUzNTUyMTU5ZWQzNjdhNTYxMCJ9fX0="
),
TRADER_LLAMA_BROWN(
"Brown Trader Llama", "trader_llama.brown",
"a957be18-324a-4984-a81b-f556a793a64a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODQyNDc4MGIzYzVjNTM1MWNmNDlmYjViZjQxZmNiMjg5NDkxZGY2YzQzMDY4M2M4NGQ3ODQ2MTg4ZGI0Zjg0ZCJ9fX0="
),
TRADER_LLAMA_CREAMY(
"Creamy Trader Llama", "trader_llama.creamy",
"b8e21edd-c25b-4673-9602-6671007f5088",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTg5YTJlYjE3NzA1ZmU3MTU0YWIwNDFlNWM3NmEwOGQ0MTU0NmEzMWJhMjBlYTMwNjBlM2VjOGVkYzEwNDEyYyJ9fX0="
),
TRADER_LLAMA_GRAY(
"Gray Trader Llama", "trader_llama.gray",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTg5YTJlYjE3NzA1ZmU3MTU0YWIwNDFlNWM3NmEwOGQ0MTU0NmEzMWJhMjBlYTMwNjBlM2VjOGVkYzEwNDEyYyJ9fX0="
),
TRADER_LLAMA_GRAY(
"Gray Trader Llama", "trader_llama.gray",
"34bfbc2b-6c59-47df-8cf6-7457ad15165a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmU0ZDhhMGJjMTVmMjM5OTIxZWZkOGJlMzQ4MGJhNzdhOThlZTdkOWNlMDA3MjhjMGQ3MzNmMGEyZDYxNGQxNiJ9fX0="
),
TRADER_LLAMA_WHITE(
"White Trader Llama", "trader_llama.white",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmU0ZDhhMGJjMTVmMjM5OTIxZWZkOGJlMzQ4MGJhNzdhOThlZTdkOWNlMDA3MjhjMGQ3MzNmMGEyZDYxNGQxNiJ9fX0="
),
TRADER_LLAMA_WHITE(
"White Trader Llama", "trader_llama.white",
"47dbdab5-105f-42bc-9580-c61cee9231f3",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzA4N2E1NTZkNGZmYTk1ZWNkMjg0NGYzNTBkYzQzZTI1NGU1ZDUzNWZhNTk2ZjU0MGQ3ZTc3ZmE2N2RmNDY5NiJ9fX0="
),
;
private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
LlamaHeads(String name, String nameString, String ownerUUID, String texture){
//this.material = material;
this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(LlamaHeads verbosity : LlamaHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) )
return verbosity.getNameString() ;
}
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzA4N2E1NTZkNGZmYTk1ZWNkMjg0NGYzNTBkYzQzZTI1NGU1ZDUzNWZhNTk2ZjU0MGQ3ZTc3ZmE2N2RmNDY5NiJ9fX0="
),
;
return null;
}
private final String owner;
private final String texture;
private final String name;
private final String nameString;
/**
LlamaHeads(String name, String nameString, String ownerUUID, String texture){
//this.material = material;
this.owner = ownerUUID;
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(LlamaHeads verbosity : LlamaHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) ) {
return verbosity.getNameString() ;
}
}
return null;
}
/**
* @return the owner
*/
public UUID getOwner() {
public String getOwner() {
return owner;
}
/**
* @return the texture
*/
public String getTexture() {
return texture;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the name
*/
@@ -118,7 +118,7 @@ public enum MobHeads {
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTJiYTM0NDE2NjcwNDU0YjFhMjA0OTZmODBiOTM5ODUyOWY0OTAwM2ZjNjEzZWI5MzAyNDhlYTliNWQxYTM5MSJ9fX0="
),
HOGLIN(
"Hoglin", "hoglin", "",
"Hoglin", "hoglin", "6ba8c18c-881a-4829-86e6-d641611ad18e",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGE3ZTI4OGUzNTQ0NWI1MjhjNDAxZWJjNzkwZjU1MWY2ZGM3NDkxYzBkZTY2YjYzNjI2YzUxNzU5MWJkNTViNCJ9fX0="
),
HUSK(
@@ -158,7 +158,7 @@ public enum MobHeads {
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTU0NmU0MzZkMTY2YjE3ZjA1MjFiZDg1MzhlYTEzY2Q2ZWUzYjVkZjEwMmViMzJlM2U0MjVjYjI4NWQ0NDA2MyJ9fX0="
),
PANDA_BROWN(
"Drown Panda", "panda.brown", "e19e8aae-3bf4-4c7a-83bc-9a8c9f6ad544",
"Brown Panda", "panda.brown", "e19e8aae-3bf4-4c7a-83bc-9a8c9f6ad544",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWQ1ZjZkNjEyNjcyODY3MWI0NGMxYzc3NWY5OTYxNzQyNGUzMzYxMWI1ZDMxYWQyYWNmZjI4MDRlYjk2ZWIwNiJ9fX0="
),
PANDA_LAZY(
@@ -278,11 +278,11 @@ public enum MobHeads {
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTkyYjU1OTcwODVlMzVkYjUzZDliZGEwMDhjYWU3MmIyZjAwY2Q3ZDRjZDhkYzY5ZmYxNzRhNTViNjg5ZTZlIn19fQ=="
),
STRIDER(
"Strider", "strider", "",
"Strider", "strider", "9fde30af-3e79-433b-b4af-ae9cc52b0c96",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGQ2OWVlODMxMmNjN2IxY2ZiZDk1ZTQxZTg5NzFiMmIzNjAyY2FmYTExMDJjMWNjODhhZGQ2MTdmMjhjZmM3ZCJ9fX0="
),
STRIDER_SHIVERING(
"Shivering Strider", "strider_shivering", "",
"Shivering Strider", "strider_shivering", "d843d702-9a8c-419a-80d5-da6c51a2c006",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjkwYTEyMDI4YjkwYzBkYTFkNGQxN2U2ZmUxYmY1YmNhZjMzZGNmY2Y0ZTVkNTQ5MzkxMDZiMTg2Y2UyMWQ3NiJ9fX0="
),
TROPICAL_FISH(
@@ -342,7 +342,7 @@ public enum MobHeads {
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGQxYWE3ZTNiOTU2NGIzODQ2ZjFkZWExNGYxYjFjY2JmMzk5YmJiMjNiOTUyZGJkN2VlYzQxODAyYTI4OWM5NiJ9fX0="
),
ZOGLIN(
"Zoglin", "zoglin", "",
"Zoglin", "zoglin", "746fe686-6785-4584-9fdb-c76ae015af4e",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzY0Y2ZjNjFhYzUyOTUzMzhkZDJmNGZlNjdmYWI2MjhjZWZkOTQ5MjY5OTIzZjE5YTBjNzcxNjRmOGYwMzRiZiJ9fX0="
),
ZOMBIE(
@@ -1,80 +1,79 @@
package com.github.joelgodofwar.mmh.enums;
import java.util.UUID;
public enum RabbitHeads {
RABBIT_BLACK(
"Black Rabbit", "rabbit.black",
"827f5a8f-1cd7-4148-872f-d3c7f424882f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjJiNDI1ZmYyYTIzNmFiMTljYzkzOTcxOTVkYjQwZjhmMTg1YjE5MWM0MGJmNDRiMjZlOTVlYWM5ZmI1ZWZhMyJ9fX0="
),
RABBIT_BLACK_AND_WHITE(
"Black and White Rabbit", "rabbit.black_and_white",
"6bafd710-5fa3-4f4d-99cf-ad47fa436e3f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzVmNzJhMjE5NWViZjQxMTdjNTA1NmNmZTJiNzM1N2VjNWJmODMyZWRlMTg1NmE3NzczZWU0MmEwZDBmYjNmMCJ9fX0="
),
RABBIT_BROWN(
"Brown Rabbit", "rabbit.brown",
"2186bdc6-55b1-4b44-8a46-3c8a11d40f3d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2ZkNGY4NmNmNzQ3M2ZiYWU5M2IxZTA5MDQ4OWI2NGMwYmUxMjZjN2JiMTZmZmM4OGMwMDI0NDdkNWM3Mjc5NSJ9fX0="
),
RABBIT_GOLD(
"Gold Rabbit", "rabbit.gold",
"dacb90db-f547-4b25-b9fd-c2aefc0b07fa",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzY3YjcyMjY1NmZkZWVjMzk5NzRkMzM5NWM1ZTE4YjQ3YzVlMjM3YmNlNWJiY2VkOWI3NTUzYWExNGI1NDU4NyJ9fX0="
),
RABBIT_SALT_AND_PEPPER(
"Salt and Pepper Rabbit", "rabbit.salt_and_pepper",
"02703b0c-573f-4042-a91b-659a3981b508",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTIzODUxOWZmMzk4MTViMTZjNDA2MjgyM2U0MzE2MWZmYWFjOTY4OTRmZTA4OGIwMThlNmEyNGMyNmUxODFlYyJ9fX0="
),
RABBIT_THE_KILLER_BUNNY(
"The Killer Bunny", "rabbit.the_killer_bunny",
"38b35a02-6ac6-4cbc-b7d6-6a7043640695",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzFkZDc2NzkyOWVmMmZkMmQ0M2U4NmU4NzQ0YzRiMGQ4MTA4NTM0NzEyMDFmMmRmYTE4Zjk2YTY3ZGU1NmUyZiJ9fX0="
),
RABBIT_WHITE(
"White Rabbit", "rabbit.white",
"1288e126-41dd-4608-b304-86d84464d5e4",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTU0MmQ3MTYwOTg3MTQ4YTVkOGUyMGU0NjliZDliM2MyYTM5NDZjN2ZiNTkyM2Y1NWI5YmVhZTk5MTg1ZiJ9fX0="
),
RABBIT_Toast(
"Toast", "rabbit.toast",
"4e03f384-b31f-4803-8d74-801ecdf78869",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTFhNTdjM2QwYTliMTBlMTNmNjZkZjc0MjAwY2I4YTZkNDg0YzY3MjIyNjgxMmQ3NGUyNWY2YzAyNzQxMDYxNiJ9fX0="
),
;
private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
RabbitHeads(String name, String nameString, String ownerUUID, String texture){
this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(RabbitHeads verbosity : RabbitHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) )
return verbosity.getNameString() ;
}
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjJiNDI1ZmYyYTIzNmFiMTljYzkzOTcxOTVkYjQwZjhmMTg1YjE5MWM0MGJmNDRiMjZlOTVlYWM5ZmI1ZWZhMyJ9fX0="
),
RABBIT_BLACK_AND_WHITE(
"Black and White Rabbit", "rabbit.black_and_white",
"6bafd710-5fa3-4f4d-99cf-ad47fa436e3f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzVmNzJhMjE5NWViZjQxMTdjNTA1NmNmZTJiNzM1N2VjNWJmODMyZWRlMTg1NmE3NzczZWU0MmEwZDBmYjNmMCJ9fX0="
),
RABBIT_BROWN(
"Brown Rabbit", "rabbit.brown",
"2186bdc6-55b1-4b44-8a46-3c8a11d40f3d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2ZkNGY4NmNmNzQ3M2ZiYWU5M2IxZTA5MDQ4OWI2NGMwYmUxMjZjN2JiMTZmZmM4OGMwMDI0NDdkNWM3Mjc5NSJ9fX0="
),
RABBIT_GOLD(
"Gold Rabbit", "rabbit.gold",
"dacb90db-f547-4b25-b9fd-c2aefc0b07fa",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzY3YjcyMjY1NmZkZWVjMzk5NzRkMzM5NWM1ZTE4YjQ3YzVlMjM3YmNlNWJiY2VkOWI3NTUzYWExNGI1NDU4NyJ9fX0="
),
RABBIT_SALT_AND_PEPPER(
"Salt and Pepper Rabbit", "rabbit.salt_and_pepper",
"02703b0c-573f-4042-a91b-659a3981b508",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTIzODUxOWZmMzk4MTViMTZjNDA2MjgyM2U0MzE2MWZmYWFjOTY4OTRmZTA4OGIwMThlNmEyNGMyNmUxODFlYyJ9fX0="
),
RABBIT_THE_KILLER_BUNNY(
"The Killer Bunny", "rabbit.the_killer_bunny",
"38b35a02-6ac6-4cbc-b7d6-6a7043640695",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzFkZDc2NzkyOWVmMmZkMmQ0M2U4NmU4NzQ0YzRiMGQ4MTA4NTM0NzEyMDFmMmRmYTE4Zjk2YTY3ZGU1NmUyZiJ9fX0="
),
RABBIT_WHITE(
"White Rabbit", "rabbit.white",
"1288e126-41dd-4608-b304-86d84464d5e4",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTU0MmQ3MTYwOTg3MTQ4YTVkOGUyMGU0NjliZDliM2MyYTM5NDZjN2ZiNTkyM2Y1NWI5YmVhZTk5MTg1ZiJ9fX0="
),
RABBIT_Toast(
"Toast", "rabbit.toast",
"4e03f384-b31f-4803-8d74-801ecdf78869",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTFhNTdjM2QwYTliMTBlMTNmNjZkZjc0MjAwY2I4YTZkNDg0YzY3MjIyNjgxMmQ3NGUyNWY2YzAyNzQxMDYxNiJ9fX0="
),
;
return null;
}
/**
private final String owner;
private final String texture;
private final String name;
private final String nameString;
RabbitHeads(String name, String nameString, String ownerUUID, String texture){
this.owner = ownerUUID;
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(RabbitHeads verbosity : RabbitHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) ) {
return verbosity.getNameString() ;
}
}
return null;
}
/**
* @return the owner
*/
public UUID getOwner() {
public String getOwner() {
return owner;
}
@@ -91,7 +90,7 @@ public enum RabbitHeads {
public String getName() {
return name;
}
/**
* @return the name
*/
@@ -1,125 +1,124 @@
package com.github.joelgodofwar.mmh.enums;
import java.util.UUID;
public enum SheepHeads {
SHEEP_BLACK(
"Black Sheep", "sheep.black",
"f6801465-fd07-47f9-92a1-3ae921c3ef05",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTMzMzVlODA2NWM3YjVkZmVhNThkM2RmNzQ3NGYzOTZhZjRmYTBhMmJhNTJhM2M5YjdmYmE2ODMxOTI3MWM5MSJ9fX0="
),
SHEEP_BLUE(
"Blue Sheep", "sheep.blue",
"dc501290-d6f2-4fc2-a4c3-b4de68fbb395",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzQwZTI3N2RhNmMzOThiNzQ5YTMyZjlkMDgwZjFjZjRjNGVmM2YxZjIwZGQ5ZTVmNDIyNTA5ZTdmZjU5M2MwIn19fQ=="
),
SHEEP_BROWN(
"Brown Sheep", "sheep.brown",
"68f33306-b8e2-472b-984e-6a05ecb32d63",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzEyOGQwODZiYzgxNjY5ZmMyMjU1YmIyMmNhZGM2NmEwZjVlZDcwODg1ZTg0YzMyZDM3YzFiNDg0ZGIzNTkwMSJ9fX0="
),
SHEEP_CYAN(
"Cyan Sheep", "sheep.cyan",
"6484f6d0-03c5-4c88-b121-c776dd24e6b4",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWQ0MmZjYmNhZjlkNDhmNzNmZmIwYzNjMzZmMzRiNDY0MzI5NWY2ZGFhNmNjNzRhYjlkMjQyZWQ1YWE1NjM2In19fQ=="
),
SHEEP_GRAY(
"Gray Sheep", "sheep.gray",
"1ad6a380-fd48-43e0-a7ea-5685072f37b6",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2ZhZmVjZjA2MDNiMmRjZDc5ODRkMjUyNTg2MDY5ODk1ZGI5YWE3OGUxODQxYmQ1NTRiMTk1MDhkY2Y5NjdhMSJ9fX0="
),
SHEEP_GREEN(
"Green Sheep", "sheep.green",
"710d1cc5-d61c-415b-b4a6-658d1e5652e8",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWVhODg3ZWFlNGIwNzYzNmU5ZTJmOTA2NjA5YjAwYWI4ZDliODZiNzQ3MjhiODE5ZmY2ZjM3NjU4M2VhMTM5In19fQ=="
),
SHEEP_jeb_(
"Jeb_ Sheep", "sheep.jeb_",
"9bb1e075-7a09-4bac-a8a4-441ee9184303",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGQxYjAxZmJlOTllOWY3MDA3OTA1YTU3OGYwYTkyMzVmNzM5NjU2NTg2Yzc5ZjFkYmQyMTExZjlhMzliZGMxZCJ9fX0="
),
SHEEP_LIGHT_BLUE(
"Light Blue Sheep", "sheep.light_blue",
"f6ad6acf-aabe-45ac-94bb-d67b9dfdcc63",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWJmMjNhZjg3MTljNDM3YjNlZTg0MDE5YmEzYzllNjljYTg1NGQzYThhZmQ1Y2JhNmQ5Njk2YzA1M2I0ODYxNCJ9fX0="
),
SHEEP_LIGHT_GRAY(
"Light Gray Sheep", "sheep.light_gray",
"97331797-cf17-4127-b250-4ec2e8cc0608",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWQyZTJlOTNhMTQyYmZkNDNmMjQwZDM3ZGU4ZjliMDk3NmU3NmU2NWIyMjY1MTkwODI1OWU0NmRiNzcwZSJ9fX0="
),
SHEEP_LIME(
"Lime Sheep", "sheep.lime",
"2d1afbe7-9651-45f7-bf6e-fa7647ed6a42",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmJlYWQwMzQyYWU4OWI4ZGZkM2Q3MTFhNjBhZGQ2NWUyYzJiZmVhOGQwYmQyNzRhNzU4N2RlZWQ3YTMxODkyZSJ9fX0="
),
SHEEP_MAGENTA(
"Magenta Sheep", "sheep.magenta",
"df42f9f2-86ef-488e-874a-7f8438f56d56",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYThlMWYwNWYwZGFjY2E2M2E3MzE4NzRmOTBhNjkzZmZlMjFmZjgzMmUyYjFlMWQwN2I2NWM4NzY0NTI2ZjA4OSJ9fX0="
),
SHEEP_ORANGE(
"Orange Sheep", "sheep.orange",
"c1cde30f-88ad-4d05-a278-6dd2bae9a500",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjY4NGQwNGZhODBhYTU5ZGExNDUzNWRlYWQzODgzZDA5N2ZiYmE0MDA2MjU2NTlmNTI1OTk2NDgwNmJhNjZmMCJ9fX0="
),
SHEEP_PINK(
"Pink Sheep", "sheep.pink",
"b7df2ea8-b576-4386-8026-7df2af1051f1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjM2M2U4YTkzZDI4N2E4NGU2NDAzMDlhZTgzY2ExZGUwYTBiMjU3NTA1YTIwZWM1NWIzMzQ5ZDQwYTQ0ODU0In19fQ=="
),
SHEEP_PURPLE(
"Purple Sheep", "sheep.purple",
"ff5c1932-8096-4751-bbcf-7e0d8837b8c1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzQ0OWQwODI5MWRhZTQ1YTI0NjczNjE5NjAyZjQzNWI1N2Y0Y2Q0ZTllOThkMmUwZmJlYzRmMTgxNDQ3ODFkMyJ9fX0="
),
SHEEP_RED(
"Red Sheep", "sheep.red",
"b2386bba-3c78-456c-bc90-a9716cec93bd",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTQ3OGUwNTcxNThkZTZmNDVlMjU0MWNkMTc3ODhlNjQwY2NiNTk3MjNkZTU5YzI1NGU4MmFiNTcxMWYzZmMyNyJ9fX0="
),
SHEEP_WHITE(
"White Sheep", "sheep.white",
"fa234925-9dbe-4b8f-a544-7c70fb6b6ac5",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmRmZTdjYzQ2ZDc0OWIxNTMyNjFjMWRjMTFhYmJmMmEzMTA4ZWExYmEwYjI2NTAyODBlZWQxNTkyZGNmYzc1YiJ9fX0="
),
SHEEP_YELLOW(
"Yellow Sheep", "sheep.yellow",
"f4b4f889-4cdb-458e-8c86-50d91c4c1c89",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTRiMjhmMDM1NzM1OTA2ZjgyZmZjNGRiYTk5YzlmMGI1NTI0MGU0MjZjZDFjNTI1YTlhYTc3MTgwZWVjNDkzNCJ9fX0="
),
;
private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
SheepHeads(String name, String nameString, String ownerUUID, String texture){
this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(SheepHeads verbosity : SheepHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) )
return verbosity.getNameString() ;
}
"Black Sheep", "sheep.black",
"f6801465-fd07-47f9-92a1-3ae921c3ef05",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTMzMzVlODA2NWM3YjVkZmVhNThkM2RmNzQ3NGYzOTZhZjRmYTBhMmJhNTJhM2M5YjdmYmE2ODMxOTI3MWM5MSJ9fX0="
),
SHEEP_BLUE(
"Blue Sheep", "sheep.blue",
"dc501290-d6f2-4fc2-a4c3-b4de68fbb395",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzQwZTI3N2RhNmMzOThiNzQ5YTMyZjlkMDgwZjFjZjRjNGVmM2YxZjIwZGQ5ZTVmNDIyNTA5ZTdmZjU5M2MwIn19fQ=="
),
SHEEP_BROWN(
"Brown Sheep", "sheep.brown",
"68f33306-b8e2-472b-984e-6a05ecb32d63",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzEyOGQwODZiYzgxNjY5ZmMyMjU1YmIyMmNhZGM2NmEwZjVlZDcwODg1ZTg0YzMyZDM3YzFiNDg0ZGIzNTkwMSJ9fX0="
),
SHEEP_CYAN(
"Cyan Sheep", "sheep.cyan",
"6484f6d0-03c5-4c88-b121-c776dd24e6b4",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWQ0MmZjYmNhZjlkNDhmNzNmZmIwYzNjMzZmMzRiNDY0MzI5NWY2ZGFhNmNjNzRhYjlkMjQyZWQ1YWE1NjM2In19fQ=="
),
SHEEP_GRAY(
"Gray Sheep", "sheep.gray",
"1ad6a380-fd48-43e0-a7ea-5685072f37b6",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2ZhZmVjZjA2MDNiMmRjZDc5ODRkMjUyNTg2MDY5ODk1ZGI5YWE3OGUxODQxYmQ1NTRiMTk1MDhkY2Y5NjdhMSJ9fX0="
),
SHEEP_GREEN(
"Green Sheep", "sheep.green",
"710d1cc5-d61c-415b-b4a6-658d1e5652e8",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWVhODg3ZWFlNGIwNzYzNmU5ZTJmOTA2NjA5YjAwYWI4ZDliODZiNzQ3MjhiODE5ZmY2ZjM3NjU4M2VhMTM5In19fQ=="
),
SHEEP_jeb_(
"Jeb_ Sheep", "sheep.jeb_",
"9bb1e075-7a09-4bac-a8a4-441ee9184303",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGQxYjAxZmJlOTllOWY3MDA3OTA1YTU3OGYwYTkyMzVmNzM5NjU2NTg2Yzc5ZjFkYmQyMTExZjlhMzliZGMxZCJ9fX0="
),
SHEEP_LIGHT_BLUE(
"Light Blue Sheep", "sheep.light_blue",
"f6ad6acf-aabe-45ac-94bb-d67b9dfdcc63",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWJmMjNhZjg3MTljNDM3YjNlZTg0MDE5YmEzYzllNjljYTg1NGQzYThhZmQ1Y2JhNmQ5Njk2YzA1M2I0ODYxNCJ9fX0="
),
SHEEP_LIGHT_GRAY(
"Light Gray Sheep", "sheep.light_gray",
"97331797-cf17-4127-b250-4ec2e8cc0608",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWQyZTJlOTNhMTQyYmZkNDNmMjQwZDM3ZGU4ZjliMDk3NmU3NmU2NWIyMjY1MTkwODI1OWU0NmRiNzcwZSJ9fX0="
),
SHEEP_LIME(
"Lime Sheep", "sheep.lime",
"2d1afbe7-9651-45f7-bf6e-fa7647ed6a42",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmJlYWQwMzQyYWU4OWI4ZGZkM2Q3MTFhNjBhZGQ2NWUyYzJiZmVhOGQwYmQyNzRhNzU4N2RlZWQ3YTMxODkyZSJ9fX0="
),
SHEEP_MAGENTA(
"Magenta Sheep", "sheep.magenta",
"df42f9f2-86ef-488e-874a-7f8438f56d56",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYThlMWYwNWYwZGFjY2E2M2E3MzE4NzRmOTBhNjkzZmZlMjFmZjgzMmUyYjFlMWQwN2I2NWM4NzY0NTI2ZjA4OSJ9fX0="
),
SHEEP_ORANGE(
"Orange Sheep", "sheep.orange",
"c1cde30f-88ad-4d05-a278-6dd2bae9a500",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjY4NGQwNGZhODBhYTU5ZGExNDUzNWRlYWQzODgzZDA5N2ZiYmE0MDA2MjU2NTlmNTI1OTk2NDgwNmJhNjZmMCJ9fX0="
),
SHEEP_PINK(
"Pink Sheep", "sheep.pink",
"b7df2ea8-b576-4386-8026-7df2af1051f1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjM2M2U4YTkzZDI4N2E4NGU2NDAzMDlhZTgzY2ExZGUwYTBiMjU3NTA1YTIwZWM1NWIzMzQ5ZDQwYTQ0ODU0In19fQ=="
),
SHEEP_PURPLE(
"Purple Sheep", "sheep.purple",
"ff5c1932-8096-4751-bbcf-7e0d8837b8c1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzQ0OWQwODI5MWRhZTQ1YTI0NjczNjE5NjAyZjQzNWI1N2Y0Y2Q0ZTllOThkMmUwZmJlYzRmMTgxNDQ3ODFkMyJ9fX0="
),
SHEEP_RED(
"Red Sheep", "sheep.red",
"b2386bba-3c78-456c-bc90-a9716cec93bd",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTQ3OGUwNTcxNThkZTZmNDVlMjU0MWNkMTc3ODhlNjQwY2NiNTk3MjNkZTU5YzI1NGU4MmFiNTcxMWYzZmMyNyJ9fX0="
),
SHEEP_WHITE(
"White Sheep", "sheep.white",
"fa234925-9dbe-4b8f-a544-7c70fb6b6ac5",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmRmZTdjYzQ2ZDc0OWIxNTMyNjFjMWRjMTFhYmJmMmEzMTA4ZWExYmEwYjI2NTAyODBlZWQxNTkyZGNmYzc1YiJ9fX0="
),
SHEEP_YELLOW(
"Yellow Sheep", "sheep.yellow",
"f4b4f889-4cdb-458e-8c86-50d91c4c1c89",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTRiMjhmMDM1NzM1OTA2ZjgyZmZjNGRiYTk5YzlmMGI1NTI0MGU0MjZjZDFjNTI1YTlhYTc3MTgwZWVjNDkzNCJ9fX0="
),
;
return null;
}
/**
private final String owner;
private final String texture;
private final String name;
private final String nameString;
SheepHeads(String name, String nameString, String ownerUUID, String texture){
this.owner = ownerUUID;
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(SheepHeads verbosity : SheepHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) ) {
return verbosity.getNameString() ;
}
}
return null;
}
/**
* @return the owner
*/
public UUID getOwner() {
public String getOwner() {
return owner;
}
@@ -136,7 +135,7 @@ public enum SheepHeads {
public String getName() {
return name;
}
/**
* @return the name
*/
@@ -5,177 +5,180 @@ public enum TropicalFishHeads {
"Anemone", "tropical_fish.anemone",
"00a90199-6ce0-4564-975e-8290216c8366",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZThkMjc1NThiYTYzYmI2ZTE0MTFmY2ZiYWRjMjMzNzk1N2RjMDBjNjIzYmFlZDEwMWZmOTFlYjVlOGQ3Mzc3ZSJ9fX0="
),
),
TROPICAL_FISH_BLACK_TANG(
"Black Tang", "tropical_fish.black_tang",
"e871a0d2-dbd4-4828-b10a-31e81c1a532c",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWM5MzIzYzk2Y2JkYjhmNzJmYWYyZjMzOTZhZmUxNDc5MjQ2ODUyNTZkZTZlZmZjNDgxOTg5NzhiM2YwMThmYiJ9fX0="
),
),
TROPICAL_FISH_BLUE_DORY(
"Blue dory", "tropical_fish.blue_dory",
"80e99073-7638-4828-8758-ce72259aa1f3",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2IwNDlhNjlhZDJiNGQ4YTVkYWFiODM3MmVlOTEyYzcyOGFjODA5ZjhmZDQxMGNiZTdhMGI4MDU0NmRlOThjYiJ9fX0="
),
),
TROPICAL_FISH_BLUE_TANG(
"Blue Tang", "tropical_fish.blue_tang",
"8efbc759-f464-4439-b5aa-a1556e35473c",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmZkYzlkOTEwODM0NDYxNjkyNTAwMjBkNzU4YWUxMjY5YzMxOTFlY2YwMDIxNWQxYzY2ZTgyOTEzNDY3YTA4MiJ9fX0="
),
),
TROPICAL_FISH_BUTTERFLYFISH(
"Butterflyfish", "tropical_fish.butterflyfish",
"bfc37de1-b659-4ba8-8a54-42c7bcf1e585",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGM1Zjc3ZDkxN2FjZmQzNzEzYzUzNTUyY2FkYmEwNjdmNWI2ZGY3MjM5M2IxYjMwYTJkYzEwODMzZDkxM2NlMiJ9fX0="
),
),
TROPICAL_FISH_CICHLID(
"Cichlid", "tropical_fish.cichlid",
"633754f1-c11b-4f7c-b14e-b544d2317cab",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGNjYWU0MzY2NWYxMjNhODRjZWUzN2Y1MGVlMjQxODc3ZTA1ZGJjZmEyODA5ZWM1OWFjNTdkMGNlZDZmZTc5OSJ9fX0="
),
),
TROPICAL_FISH_CLOWNFISH(
"Clownfish", "tropical_fish.clownfish",
"df0eaa76-2936-452c-bdd8-d1eed124206a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzYzODlhY2Q3YzgyODBkMmM4MDg1ZTZhNmE5MWUxODI0NjUzNDdjYzg5OGRiOGMyZDliYjE0OGUwMjcxYzNlNSJ9fX0="
),
),
TROPICAL_FISH_COTTON_CANDY_BETTA(
"Cotton Candy Betta‌", "tropical_fish.cotton_candy_betta",
"e843d41d-5a3f-4183-bc9e-9021b98e3569",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTNkNzc2N2RlN2FjZDlmZTllMDM4MzI1NzRiMjQ4MzQ0ZjBhZTA2ZjRjMWE3YzZjYjVjNGIwOTIzYWVmZTU2ZCJ9fX0="
),
),
TROPICAL_FISH_DOTTYBACK(
"Dottyback", "tropical_fish.dottyback",
"55dcc35d-d6cb-4e91-b110-9f985b5627e9",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWZhMWY1NWFlMWEwMWM1ZGJkMGM2NGIzMTg4OTFjZWJlNmVmMTVjODk2MmNkZTZlMDRjM2E5Njg0ZTg1MjI0NyJ9fX0="
),
),
TROPICAL_FISH_EMPEROR_RED_SNAPPER(
"Emperor Red Snapper", "tropical_fish.emperor_red_snapper",
"f35b0c65-4042-4b5e-8b86-7308518ac531",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjhkNDRiMzNlYWY5YTY4OGYxYzVmMWUwOTM0ZDFmYjQwMzM3NzkxNjk4N2UyMWMzZjNhMDg1ZGI2NmU1ZWYzNyJ9fX0="
),
),
TROPICAL_FISH_GOATFISH(
"Goatfish", "tropical_fish.goatfish",
"a3331d69-5d09-471b-b9c6-50b1cdc6e98b",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWRjNDdiNDBhYzMxMDM0YTZhYzAyOGNlNWU5NmQ2YWUyNzBiNzM5Zjc0MmIzNTU3MmE5OTk0N2I5MzA2YmQ3In19fQ=="
),
),
TROPICAL_FISH_MOORISH_IDOL(
"Moorish Idol", "tropical_fish.moorish_idol",
"995ff86a-934c-4055-a4e6-1674e0a4fdf4",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2UzNDU1Y2RlZTNjODRjYzhiZWY3OWM5MmI1MzZjMjg4OWVhZjhmOTQxMjYxNjFmNmJiZjlkNjNiZTc1OGE1NCJ9fX0="
),
),
TROPICAL_FISH_ORNATE_BUTTERFLYFISH(
"Ornate Butterflyfish‌", "tropical_fish.ornate_butterflyfish",
"ae3034b3-4dc1-4c25-8b46-81b234cf5a0d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWE0MzQ5ZWIxMmIxMDE5MWUwYzA0Y2EzNDlmZTI2ZjI0MTlkYWNjNWJjMDk1NjllZTlmNjk4ZDAyYWEwNGI4ZSJ9fX0="
),
),
TROPICAL_FISH_PARROTFISH(
"Parrotfish", "tropical_fish.parrotfish",
"02d9385b-a486-4975-8d5b-ada5a7ea6c01",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzViYTM5M2I5MjZlZTY5YTE0ZDRkODUzYjI1ZDI4NWE4NjhiMTZjNTZjOTE5ZDJiODI0NDEzZTY1NjZkNGU1NSJ9fX0="
),
),
TROPICAL_FISH_QUEEN_ANGELFISH(
"Queen Angelfish", "tropical_fish.queen_angelfish",
"7f37ffc4-3ba4-4905-bc6f-2617fa3ba8f9",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWRmYjRkN2RmZjU0NTZkZTcwNDk2NDE0ZTg3NGM2NjUwYTFlODM5NTYzY2M3NWQ0Yjk5MzgwYjIxNmNmZGJjZCJ9fX0="
),
),
TROPICAL_FISH_RED_CICHLID(
"Red Cichlid", "tropical_fish.red_cichlid",
"7cf27041-6a3b-4d2e-b0a2-87d56bbee139",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzM3ZThjYzc2MDYzMDI4Mjc3YjhiOGZkZGNmMTA3MmY4MjZkNmM3MjAyY2IyOTU0ODFiZGU3MGJhYzk1MGY4OCJ9fX0="
),
),
TROPICAL_FISH_RED_LIPPED_BLENNY(
"Red Lipped Blenny", "tropical_fish.red_lipped_blenny",
"574519ee-7185-427e-b006-73268e705829",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjJkMjJiZTEyZTE0NjY0Y2FlM2ZlMjI2ZDFiMzQ4N2VlMmE3ZDhlNzZhMGUyOWJjYTRkYmY5OTY0YjJhMGY2MyJ9fX0="
),
),
TROPICAL_FISH_RED_SNAPPER(
"Red Snapper", "tropical_fish.red_snapper",
"9f2f51a4-7635-43bf-889d-3728342708a6",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmI4MjI2YWI5ODcwYjdjNmM1OWQzMWI0MGI4NmVkZjczMjRlY2E2NzRiNzBkNTViZThhYWU2YTBiODllN2Q4YiJ9fX0="
),
),
TROPICAL_FISH_THREADFIN(
"Threadfin", "tropical_fish.threadfin",
"b5ea6ce5-4bcd-4b8b-b5a0-a65f9a946e24",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzI0NDRkOTA1YTk5MzA0NGFhNTBkZThjNTUwYmI2NTgzYWQ1N2E4ZDE5OWRhMjliOTkxYmVmMDMwYjQ2ZTM3In19fQ=="
),
),
TROPICAL_FISH_TOMATO_CLOWNFISH(
"Tomato Clownfish", "tropical_fish.tomato_clownfish",
"ff8a2a72-c071-4307-8a31-5186949e01f6",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2U5MmYwYjBmNzljN2JhOWRjODE1NzdiZTMzYTVjNzEzZmJlMzk3MjgyNmI0NGJjZjI2ZmViNmNjMTJiNjQzNCJ9fX0="
),
),
TROPICAL_FISH_TRIGGERFISH(
"Triggerfish", "tropical_fish.triggerfish",
"27a63885-df8d-49aa-bfcf-c591d7b99c2d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmMwZDFlNmQyYTlkZjRmMGViYmNkNTQ3NzhmZjMxYjdjMjQ5ZTBkOTk0NGZjZWQ0YjFhODNhZmJhZWVhY2ZjYSJ9fX0="
),
),
TROPICAL_FISH_YELLOWTAIL_PARROTFISH(
"Yellowtail Parrotfish‌", "tropical_fish.yellow_parrotfish",
"9bb8e0ff-0fdf-49c3-849b-eaf31ad93c63",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWQ3YjkyYTFiZGM3MzNkMmE1M2ZiMDk1YzcwYjczMmI3MWEyMjNhOWNlY2E3MmI4NzJlMWVlOTkwYTZhOTA3ZSJ9fX0="
),
),
TROPICAL_FISH_YELLOW_TANG(
"Yellow Tang", "tropical_fish.yellow_tang",
"e52ecf57-6857-49df-8b20-c9457cb215df",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2IwMjZhYjBmNDcxZjVjNmUzOWM5ZDM1ODViYzg4MjRlOWQxYjVmMzczZDJjYjcwMThlNjE1MDBlMWE5ZWIzYSJ9fX0="
),
),
TROPICAL_FISH_TROPICAL_FISH(
"Tropical Fish", "tropical_fish.tropical_fish", "6c2851ca-37b3-4f29-8415-c3d03f32e27d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzRhMGM4NGRjM2MwOTBkZjdiYWZjNDM2N2E5ZmM2Yzg1MjBkYTJmNzNlZmZmYjgwZTkzNGQxMTg5ZWFkYWM0MSJ9fX0="
),
"Tropical Fish", "tropical_fish.tropical_fish",
"6c2851ca-37b3-4f29-8415-c3d03f32e27d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzRhMGM4NGRjM2MwOTBkZjdiYWZjNDM2N2E5ZmM2Yzg1MjBkYTJmNzNlZmZmYjgwZTkzNGQxMTg5ZWFkYWM0MSJ9fX0="
),
;
//private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
TropicalFishHeads(String name, String nameString, String ownerUUID, String texture){
//this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(RabbitHeads verbosity : RabbitHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) )
return verbosity.getNameString() ;
}
return null;
}
/**
private final String owner;
private final String texture;
private final String name;
private final String nameString;
TropicalFishHeads(String name, String nameString, String ownerUUID, String texture){
//this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.name = name;
this.owner = ownerUUID;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(RabbitHeads verbosity : RabbitHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) ) {
return verbosity.getNameString() ;
}
}
return null;
}
/**
* @return the owner
*/
/**public UUID getOwner() {
public String getOwner() {
return owner;
}//*/
}
/**
* @return the texture
@@ -190,7 +193,7 @@ public enum TropicalFishHeads {
public String getName() {
return name;
}
/**
* @return the name
*/
@@ -5,463 +5,467 @@ package com.github.joelgodofwar.mmh.enums;
public enum VillagerHeads {
VILLAGER_ARMORER_PLAINS(//
"Plains Armorer Villager", "villager.plains.armorer",
"eyJ0aW1lc3RhbXAiOjE1ODQzMzgyMTc0MjcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjAxMzExYzUxODUyOWFmNTRhMWMzZWU2M2MzNmQwM2IwNGFjMjFkZmRiNmE5MGM1N2VhOTU0NzhhYWI2MDNlNSJ9fX0="
),
VILLAGER_BUTCHER_PLAINS(//
"Plains Butcher Villager", "villager.plains.butcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDExNDE5OTYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzFhNzAzZjY0NzA0M2UwY2U5ZjAzYzQyOGQxM2EwYTZmZWY0NWUzMGE3MzMxN2ViMTIxY2ZiZjFmYjg4YzE5NyJ9fX0="
),
"Plains Armorer Villager", "villager.plains.armorer", "a576057c-02d0-4af9-ae5a-acb4458b4971",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjAxMzExYzUxODUyOWFmNTRhMWMzZWU2M2MzNmQwM2IwNGFjMjFkZmRiNmE5MGM1N2VhOTU0NzhhYWI2MDNlNSJ9fX0="
),
VILLAGER_BUTCHER_PLAINS(//
"Plains Butcher Villager", "villager.plains.butcher", "c25bcd22-83d9-46fa-92ac-145e4681d08d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzFhNzAzZjY0NzA0M2UwY2U5ZjAzYzQyOGQxM2EwYTZmZWY0NWUzMGE3MzMxN2ViMTIxY2ZiZjFmYjg4YzE5NyJ9fX0="
),
VILLAGER_CARTOGRAPHER_PLAINS(//
"Plains Cartographer Villager", "villager.plains.cartographer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDE5OTM4MDAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTZhZDhiMGQ0MGIwNTRkNjlhYTI4OTQ5NzQwYmU3NmY4NWU4M2RiMzljMjcyZGU2NzIzYTQzOTJhYmUyMzA1YiJ9fX0="
),
"Plains Cartographer Villager", "villager.plains.cartographer", "dfa9dc67-8091-4b78-9bbb-00cb4c34cb58",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTZhZDhiMGQ0MGIwNTRkNjlhYTI4OTQ5NzQwYmU3NmY4NWU4M2RiMzljMjcyZGU2NzIzYTQzOTJhYmUyMzA1YiJ9fX0="
),
VILLAGER_CLERIC_PLAINS(//
"Plains Cleric Villager", "villager.plains.cleric",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDI2MDI3ODQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzViNmYxNmQ0NmQ3MmEyMTgwNTE4ZTU5ZWU3Mjc1OWQ5ODYwMmI0YmJkZTA0ZjBhNDk3MjhlYTE1ZjBlMTIifX19"
),
"Plains Cleric Villager", "villager.plains.cleric", "4130d8bc-7d79-498f-8ead-b9e32a86d4a6",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzViNmYxNmQ0NmQ3MmEyMTgwNTE4ZTU5ZWU3Mjc1OWQ5ODYwMmI0YmJkZTA0ZjBhNDk3MjhlYTE1ZjBlMTIifX19"
),
VILLAGER_FARMER_PLAINS(//
"Plains Farmer Villager", "villager.plains.farmer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDMyOTI3NTEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2ZkNjQwZjg1Nzg3NTU3NWI1YzhmNzlmY2E5YjM1NGMxNTgwZWJhNTViY2M4MGMzZWY1NzkxNDVhZTFlZDJmNiJ9fX0="
),
"Plains Farmer Villager", "villager.plains.farmer", "1d307c7a-6e3f-4862-9503-740dd9bb92d9",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2ZkNjQwZjg1Nzg3NTU3NWI1YzhmNzlmY2E5YjM1NGMxNTgwZWJhNTViY2M4MGMzZWY1NzkxNDVhZTFlZDJmNiJ9fX0="
),
VILLAGER_FISHERMAN_PLAINS(//
"Plains Fisherman Villager", "villager.plains.fisherman",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDM5NDc4MjAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTIxMzM4NDRiOGViMjVmODA1NmZiN2IyNWU2YTYwODM0N2Q2ZWY3YmMxYmY4NzZiMzY4ZTlkN2E2MDdhNDMxMyJ9fX0="
),
"Plains Fisherman Villager", "villager.plains.fisherman", "49ecebb5-0a3c-4e7a-ac18-f7b7c1c6b42b",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTIxMzM4NDRiOGViMjVmODA1NmZiN2IyNWU2YTYwODM0N2Q2ZWY3YmMxYmY4NzZiMzY4ZTlkN2E2MDdhNDMxMyJ9fX0="
),
VILLAGER_FLETCHER_PLAINS(//
"Plains Fletcher Villager", "villager.plains.fletcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDQ5MTUwODUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODIxNTg4ODRlYzQ1YzI2YjA2ZTA0YWM4MDM1NWJjYjViNzYwYjQ2MDkzOTYzODljOTQ4MzA1ZjAzYjNkYWU5ZCJ9fX0="
),
VILLAGER_LEATHERWORKER_PLAINS(//\\
"Plains Leatherworker Villager", "villager.plains.leatherworker",
"eyJ0aW1lc3RhbXAiOjE1ODQyNTM3Njc0NzcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWQ0NDY4YjMzYzE2Mzc1OGUzYmM3Yzk2OTRjODA0YmZmNGY4YTVmMTYwYjUxZTUyYzBlOTY3OWRhNjcwMTk4NSJ9fX0="
),
"Plains Fletcher Villager", "villager.plains.fletcher", "85a158e9-23a3-495c-a7cf-0bb4b924bfaf",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODIxNTg4ODRlYzQ1YzI2YjA2ZTA0YWM4MDM1NWJjYjViNzYwYjQ2MDkzOTYzODljOTQ4MzA1ZjAzYjNkYWU5ZCJ9fX0="
),
VILLAGER_LEATHERWORKER_PLAINS(//
"Plains Leatherworker Villager", "villager.plains.leatherworker", "641edbf6-0351-4eb8-a0f2-2ad61bfc93dc",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWQ0NDY4YjMzYzE2Mzc1OGUzYmM3Yzk2OTRjODA0YmZmNGY4YTVmMTYwYjUxZTUyYzBlOTY3OWRhNjcwMTk4NSJ9fX0="
),
VILLAGER_LIBRARIAN_PLAINS(//
"Plains Librarian Villager", "villager.plains.librarian",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTgxODYyNDEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjBmMjQ3YjU3MDI0NmMyYWYzOTRhZDA0MGM4YWYzZjYwNWY4ZWI2YTNiYjg2OTExY2QyNDkyNDM4MjU2ZWFhOSJ9fX0="
),
"Plains Librarian Villager", "villager.plains.librarian", "4df8080c-dcb8-4aee-8c1e-3e8d02aa6c4d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjBmMjQ3YjU3MDI0NmMyYWYzOTRhZDA0MGM4YWYzZjYwNWY4ZWI2YTNiYjg2OTExY2QyNDkyNDM4MjU2ZWFhOSJ9fX0="
),
VILLAGER_MASON_PLAINS(
"Plains Mason Villager", "villager.plains.mason",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWUwZTk1OTFlMTFhYWVmNGMyYzUxZDlhYzY5NTE0ZTM0MDQ4NWRlZmNjMmMxMmMzOGNkMTIzODZjMmVjNmI3OCJ9fX0="
),
"Plains Mason Villager", "villager.plains.mason", "08d93422-d0e1-4020-a38f-b38995e05e6f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWUwZTk1OTFlMTFhYWVmNGMyYzUxZDlhYzY5NTE0ZTM0MDQ4NWRlZmNjMmMxMmMzOGNkMTIzODZjMmVjNmI3OCJ9fX0="
),
VILLAGER_NITWIT_PLAINS(//
"Plains Nitwit Villager", "villager.plains.nitwit",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTkxODUxMTIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTM0NWEwY2FkZDAyZmI3NTYwNjZlMDNjNDBjYzFhYjVmZTlhZDRhZjQ5NmU4OGYyYmJhMTBmNWUxNDZmMDkyNSJ9fX0="
),
VILLAGER_NONE_PLAINS(//
"Plains Villager", "villager.plains.none",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk2MTMwOTgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWEwZTNkNDFlNmViZTUzOWUwZWI0OGE2N2YyZWM1ZGEyMjRkNDA4Yjk5Y2VkOTI0ZjlkMGE4MTVlMzVjNTdhMiJ9fX0="
),
"Plains Nitwit Villager", "villager.plains.nitwit", "ee0b1913-5636-4358-86bd-fcf48ea59462",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTM0NWEwY2FkZDAyZmI3NTYwNjZlMDNjNDBjYzFhYjVmZTlhZDRhZjQ5NmU4OGYyYmJhMTBmNWUxNDZmMDkyNSJ9fX0="
),
VILLAGER_NONE_PLAINS(//
"Plains Villager", "villager.plains.none", "58404a7e-a14d-4b48-b97c-cdb98a8e1018",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWEwZTNkNDFlNmViZTUzOWUwZWI0OGE2N2YyZWM1ZGEyMjRkNDA4Yjk5Y2VkOTI0ZjlkMGE4MTVlMzVjNTdhMiJ9fX0="
),
VILLAGER_SHEPHERD_PLAINS(//
"Plains Shepherd Villager", "villager.plains.sheperd",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA1NDAzMTUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmVkNmI1ZTFlOWNiNDUxYzEyMWUyZWMwZmExYjU5MDc3OThlOWZkM2E1ZWE2ZDAwY2NmYWYxZDdlMmMxMDc5ZCJ9fX0="
),
"Plains Shepherd Villager", "villager.plains.sheperd", "1e93dceb-d8d8-45c4-88e8-890a54e80b40",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmVkNmI1ZTFlOWNiNDUxYzEyMWUyZWMwZmExYjU5MDc3OThlOWZkM2E1ZWE2ZDAwY2NmYWYxZDdlMmMxMDc5ZCJ9fX0="
),
VILLAGER_TOOLSMITH_PLAINS(//
"Plains Toolsmith Villager", "villager.plains.toolsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA5ODQyMDcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzU5YjYzMmEwOTI1MTZkOTYzMzk4MDY5YjFhZTI4YzFjZGJiY2NjMThkNjcyMDAzM2M2M2YzODhkODc5YzRhIn19fQ=="
),
"Plains Toolsmith Villager", "villager.plains.toolsmith", "75cf97b1-4d89-4ac8-94e1-f85fac14871a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzU5YjYzMmEwOTI1MTZkOTYzMzk4MDY5YjFhZTI4YzFjZGJiY2NjMThkNjcyMDAzM2M2M2YzODhkODc5YzRhIn19fQ=="
),
VILLAGER_WEAPONSMITH_PLAINS(
"Plains Weaponsmith Villager", "villager.plains.weaponsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDE0ODQ1MzksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWIxZWE1NGI3ZmQ5M2ZlNmMzNmQwYjVkODE1YWIwZGI4OWRiODk0ZGI3NWNlMGZjZTczOGU5NGVlOGI3OWUxOSJ9fX0="
),
VILLAGER_ARMORER_DESERT(//
"Desert Armorer Villager", "villager.desert.armorer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDAwNTA5MDcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWI2N2EwZmI1MGYyOGM2ODhiZTk3ZDEyN2NmZTQwNTQ3ZDE4N2U4MjliZDI5MTUwMjA1MzNjZmU5M2JhMDRhIn19fQ=="
),
VILLAGER_BUTCHER_DESERT(//
"Desert Butcher Villager", "villager.desert.butcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDEyOTIzNDQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDc3NjZhZWJlYzJlZGU4ZTg2MGU0YjMwYmNlZWIyYTM0NTU4MTYzMzM5MDkwZWZiMzhhNDY1MDBmYjUwOTMyYyJ9fX0="
),
VILLAGER_CARTOGRAPHER_DESERT(//
"Desert Cartographer Villager", "villager.desert.cartographer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDIwNzc2NTUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmY5NDY3Y2QzNTc0ZTNiNjUwNDJiZjIxZjM2OWRhNjBmNzc3ODMyNzVkNDkzMGQzZTBkODI2MWI4YjUyOTc3MyJ9fX0="
),
VILLAGER_CLERIC_DESERT(//
"Desert Cleric Villager", "villager.desert.cleric",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDI3MDEwODEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTE0NWViMDM5ODljMTA0MDFhYzdhM2U0ZWE4MTY1MmFjM2MxZDg4NTM3NTA4NThiY2ZiZDhmOTQyYmU5NTI5ZiJ9fX0="
),
VILLAGER_FARMER_DESERT(//
"Desert Farmer Villager", "villager.desert.farmer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDM0MjgyMTksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODZmYjM4OGY3MGNkMjQ5Yzk4YWQ2MTcwZThjOWIzNzU3NjY3MWQ3NzFjZDhmNzJkOWYxNzMwYjhjOWNhMjMyNSJ9fX0="
),
VILLAGER_FISHERMAN_DESERT(//
"Desert Fisherman Villager", "villager.desert.fisherman",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDQwNTc1NTAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmQ1NDk1NDNhMTg2NTc5NGE4N2YwMDI1YjZmOWJiZWU3ZmM3OGNiYmI0ZThhZWVmYTFjMzdmODU3MzRlNDAxOCJ9fX0="
),
VILLAGER_FLETCHER_DESERT(//
"Desert Fletcher Villager", "villager.desert.fletcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDUwMTYyMjUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGJiMjBmMjljY2NlZWEwNzA3M2I4NDY4NGIzOTA5NjMwZGQxMTVhNDdkMmZlYzdjM2QyZjhiY2I5OTkyZTEwZCJ9fX0="
),
VILLAGER_LEATHERWORKER_DESERT(
"Desert Leatherworker Villager", "villager.desert.leatherworker",
"eyJ0aW1lc3RhbXAiOjE1ODQzMDkxOTk0MTIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODgzZTliYzgyN2U3MzFhYTFkODAxNDg3ZTViNmEzOTE2YWQ0NTJkZGMyMDY4ZmFiMDdlOTdlYmM5MDM2ZTA1MiJ9fX0="
),
VILLAGER_LIBRARIAN_DESERT(//
"Desert Librarian Villager", "villager.desert.librarian",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTg0Mjc4ODAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjljMzA4M2JkMjQ5MGVlNDExZTAyZTE1MDliZDRmYzhiZjU0ZjkwZTkzMmI0YzU0NGE1YzEwNzg4MzA1NzhhYyJ9fX0="
),
VILLAGER_MASON_DESERT(
"Desert Mason Villager", "villager.desert.mason",
"eyJ0aW1lc3RhbXAiOjE1Nzc2MDk0NTI1NDQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGRkODUxNTJlMzk5ZDMwMGM4NmRkYjRkZWUxNDIzNGJmODM2M2ZiYTE3NjQyZGI0MjE5ZTM3ODJhZjE5OGM4ZCJ9fX0="
),
VILLAGER_NITWIT_DESERT(//
"Desert Nitwit Villager", "villager.desert.nitwit",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTkyNjgwMjMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjNkZDYxZjQxODRkY2Q0Y2YxMDBiNjAyMTQxZDNjMmJiNmMyMjIxN2E3ZWQ4MjZhNzgxNWI1ODdkZWNhNTBiNyJ9fX0="
),
VILLAGER_NONE_DESERT(//
"Desert Villager", "villager.desert.none",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk2Nzc0MjMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTMzOTM1MjdhZmE2ZjcyYmQxYjJkNTliOTU3Yzc0MDk1ZWViNDNjZjkzNzg0ZTAxZmM2NjdmMzkyNmFhNzU5ZSJ9fX0="
),
VILLAGER_SHEPHERD_DESERT(//
"Desert Sheperd Villager", "villager.desert.sheperd",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA2MDI2MzIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzM5ZTEwNzVmNjhiZDVhYmM1ZWY5NjllMzA4NTZjODU5MjgzNTc4MGYwM2FhZjViMmY1OTYyMTZmYWIwMDI2YiJ9fX0="
),
VILLAGER_TOOLSMITH_DESERT(//
"Desert Toolsmith Villager", "villager.desert.toolsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDEwNjkzMzMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTE0ZGJmNDYzMGQ0YTFmMTA5ZWE3YWYwZDAyNDc4OGVmMTQyNWJkYjg5OWMxYWUxNDA4Y2UzZjlmZTRiMmRmMyJ9fX0="
),
VILLAGER_WEAPONSMITH_DESERT(
"Desert Weaponsmith Villager", "villager.desert.weaponsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDE1ODIzOTEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjFiZDhlYjMwMDkzYzkxZDA2NzVmNzMyMWY5NWFkOTFkNjk1ZjRhMmNjNmIxZTk2MzcyOTk4ZGUyNGQ2NmI2OSJ9fX0="
),
VILLAGER_ARMORER_JUNGLE(//
"Jungle Armorer Villager", "villager.jungle.armorer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDA2NDkyNDAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjFkNTk5ODM1OTQ0NDc2ODk4MzAxODI2MDkyNjU3OTJlZGYxZTM1ZTlkMzYyYWQ2MmRiOWNmNzY5ZWE3MTVhMiJ9fX0="
),
VILLAGER_BUTCHER_JUNGLE(//
"Jungle Butcher Villager", "villager.jungle.butcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDEzNDc2MTYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2EwNTE1MDYwMjM3ZDVhZmFhNGI3NmY3ZWQyOTgwYzdmMDUzZjcwNTAzYTlhNjg5Y2M2ZjE0NmFiZGM3ZDk4MCJ9fX0="
),
VILLAGER_CARTOGRAPHER_JUNGLE(//
"Jungle Cartographer Villager", "villager.jungle.cartographer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDIxNjAwMjUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2I0YjA5MTYwOTZhNjBkM2UzODVhNGZmOTc4Njg2OWU3ZDNlZGE1MjM4MzUxYzM5OThjZmI2ZDcwYzY2MWVhIn19fQ=="
),
VILLAGER_CLERIC_JUNGLE(//
"Jungle Cleric Villager", "villager.jungle.cleric",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDI3NzkwNTksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGEwZWYyYWRkMThhNDU3ZGY4YTk4ZWM1OTFiMGEwZGJkOTk0MmEyZDU4OTY1YTA0M2VhMDM5NzIwNDg3MTRmNSJ9fX0="
),
VILLAGER_FARMER_JUNGLE(//
"Jungle Farmer Villager", "villager.jungle.farmer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDM0OTY0MjAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTQ5YmZjZTFkZTRkOWU3OGE3YTk2ODc3MzE5ZWRhOTBlOTg5YzI4YWZlMWI0YmYxZGM1NjNhNWQ0NGIxYWM3NCJ9fX0="
),
VILLAGER_FISHERMAN_JUNGLE(//
"Jungle Fisherman Villager", "villager.jungle.fisherman",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDQxMTI0MzcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmU0M2MwYmYyNmVlMWE0MzMyYzExZTJjMjA4N2FhZDkwM2JlNDE4YWNhNTE0ZmQ4MGE3MDI4YjM3MWY1MzI4NyJ9fX0="
),
VILLAGER_FLETCHER_JUNGLE(//
"Jungle Fletcher Villager", "villager.jungle.fletcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDUwNzY5MjcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjg0MjM4ZjA2YTg1YWM2NzY1MWQ5NmNmOTYzYzUyNTA1NTI5OTI5MGMzNDg4MjlhNjZjNDQ1OTdiYWY1NzQwYiJ9fX0="
),
VILLAGER_LEATHERWORKER_JUNGLE(
"Jungle Leatherworker Villager", "villager.jungle.leatherworker",
"eyJ0aW1lc3RhbXAiOjE1ODQzMDg3NTk3OTIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWEwOTU1ODdhYWZlMThjNWZlZTBiYWExNzk1ZmMyMjk0OGYyZGRjZGMzMmEzMzUwMzIzZDMzZGI1OTE4MDBlNiJ9fX0="
),
VILLAGER_LIBRARIAN_JUNGLE(//
"Jungle Librarian Villager", "villager.jungle.librarian",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTg0OTY3NTMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTJmYzNkMGU3Yjc0ZGY1N2NiNGJkNmM0MjZhYTM2MWY3NjIwMjk2ODUzMGU3NmNmZjU5YjM5MzQ3MTc5NTk4YiJ9fX0="
),
VILLAGER_MASON_JUNGLE(
"Jungle Mason Villager", "villager.jungle.mason",
"eyJ0aW1lc3RhbXAiOjE1Nzc2Njk1NTU2MjksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTk2NzU2ZWU5MDNkMThkNTFhNWQ5NTk2M2I4NWJiYzEyYmI3YmQzOTEzZTg4NzE3ZDA2ZjhmOGU5YzU0MDFhYyJ9fX0="
),
VILLAGER_NITWIT_JUNGLE(//
"Jungle Nitwit Villager", "villager.jungle.nitwit",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTkzMjg3MjQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzFhZWY1MTNkYjdhYzJhZjViNmI4YzliOGUyYjlmNDYyMjgxNGQxNTFlNzUwNGQ5YWUyYzQ1ZjRkOTRlOTBhNiJ9fX0="
),
VILLAGER_NONE_JUNGLE(//
"Jungle Villager", "villager.jungle.none",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk3Mjk3NzAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTgwNmU5YTRkNzI4ZTcxNjgwM2ZjN2M3N2MxNTg2YzBkNWVkODViODZmNDA4MGI1YmRiNjQwY2M0N2YxNWI3ZCJ9fX0="
),
VILLAGER_SHEPHERD_JUNGLE(//
"Jungle Sheperd Villager", "villager.jungle.sheperd",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA2NTAxNjYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTk3NzY0MzBjZDIwYTY5YTFkODk5ZDlkMWRjZDMxZDdkMTM0MmI1NWU4MDE1YjdmMDYwZmIzYTJjMzM4YzhiYyJ9fX0="
),
VILLAGER_TOOLSMITH_JUNGLE(//
"Jungle Toolsmith Villager", "villager.jungle.toolsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDExMTk4MjUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjg2MzVmOTFhYjhlOTU0MmQwNTAyODRmMDhkMGRjYTVkZjZjMWYzYmQ2ZDhhNzE0MjdkMTQxOWUzNzk5YzJiYyJ9fX0="
),
VILLAGER_WEAPONSMITH_JUNGLE(
"Jungle Weaponsmith Villager", "villager.jungle.weaponsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDE2Mjg2ODgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjQ1ZmFiMzAzMjk0NGQxOGIyNjYyMWFkMjliM2Y5M2M0OTI3NmVkNmViNDY5ZWUyMTNhYmVjOGU0MmIyMjZhMiJ9fX0="
),
VILLAGER_ARMORER_SAVANNA(//
"Savanna Armorer Villager", "villager.savanna.armorer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDA4NDU1MDAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmU4YTBlZGQwODFmZTE3NjFhZDU0YzY3ODNjOWZjZDJkZjA2M2YzYzIyODc0MjBmYzZkMjkzOWRmY2Q3MDI0OSJ9fX0="
),
VILLAGER_BUTCHER_SAVANNA(//
"Savanna Butcher Villager", "villager.savanna.butcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDE1MzkwNzEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTUxZTAwMzkxZjcxNGI1NTk5ZmQ4NTQ4NWE3NGZkM2MxOTkwY2VmMWJkMjgyMTBjZThmNTk2NjE4MDA4ZDVjYiJ9fX0="
),
VILLAGER_CARTOGRAPHER_SAVANNA(//
"Savanna Cartographer Villager", "villager.savanna.cartographer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDIyMzIwMzAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjAxZWEzNzc2M2M4OTU5MzY3ZTNjM2FmOTM4YjU3YzU2NjI2OTMxMmM4YjBlY2VmMWY5ZjY0ZmQ4ZmE4YTkxOCJ9fX0="
),
VILLAGER_CLERIC_SAVANNA(//
"Savanna Cleric Villager", "villager.savanna.cleric",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDI4NDYxMjAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWIyY2JmZjE0ZjZiYTdmYjFjMjdkM2ZiZDYyOGI2MjM0NzUyNTMyMmJhMGY4ZWU5Y2EwZWIzNDkzYjg4Yjc2NCJ9fX0="
),
VILLAGER_FARMER_SAVANNA(//
"Savanna Farmer Villager", "villager.savanna.farmer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDM1NTU5MTgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzc0OGM3Yjk5MGFkMjMzNWFjNTU5MWQ4ZWNiYzYxZGRjNjdhMzU4YTcwN2VkMWIyNmUyYmFmMjM5YTcwZGEwOSJ9fX0="
),
VILLAGER_FISHERMAN_SAVANNA(//
"Savanna Fisherman Villager", "villager.savanna.fisherman",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDQxODg4MzcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTkzZDQ5NzljMDU0NGI2ZGQxNjNhMjBjODZlM2E0NjZhN2ExMDI4NDA5M2QwZGMzMTNlOWY3ZGUyMGZlZTRlZiJ9fX0="
),
VILLAGER_FLETCHER_SAVANNA(//
"Savanna Fletcher Villager", "villager.savanna.fletcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDUyMzEyOTksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzAyODEyZmU3ZTA3NTI0MTEyMDkxYTI5ZTBiOTE1MmNkMmEyZmJmOTRhYTlhN2EyMTkzYzRkOGE3M2RjNzNmMSJ9fX0="
),
VILLAGER_LEATHERWORKER_SAVANNA(
"Savanna Leatherworker Villager", "villager.savanna.leatherworker",
"eyJ0aW1lc3RhbXAiOjE1ODQzMDgxNDk5ODQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjVhN2U2YThlZGVlZjc0ZGM1ODQzOGQ3MDZmYjJhZWQ4ZWNiZWI0ZjlhNGQ5NTY2MGY1ZmY1MDZlNGFmM2Q1In19fQ=="
),
VILLAGER_LIBRARIAN_SAVANNA(//
"Savanna Librarian Villager", "villager.savanna.librarian",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTg1NjE5MzUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTNjNTQ3NmJmYTMxYWY4YTQ2NDliNjc5NzY1MjhiMzAxZmY5YjhiYTU3NWE4YzgxYzlmMDlmMzhiNjkyNzgxZSJ9fX0="
),
VILLAGER_MASON_SAVANNA(
"Savanna Mason Villager", "villager.savanna.mason",
"eyJ0aW1lc3RhbXAiOjE1Nzc2NzEwMzEzMTUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODQwNWZkMTNkNGU3ODYwNzUxMmZmNzE2OWYzZWQ1NmIzMTA0MjM5M2QzMDlhYzhlODIzNjZlMTE3OGJiYTVjMiJ9fX0="
),
VILLAGER_NITWIT_SAVANNA(//
"Savanna Nitwit Villager", "villager.savanna.nitwit",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTkzODQyOTksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjE0N2MzMTVlMzg1ZmRlMWFmZjI5MmMwNGEzNzM5MGE2ODNlZGQ0NTYxMGU1OThmOTBjOWExYThhNTY3YjJmNSJ9fX0="
),
VILLAGER_NONE_SAVANNA(//
"Savanna Villager", "villager.savanna.none",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk3Nzc0ODMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTQ3MGIyNTE2ODhiYTU4MDc3NDIwOWQ1MjY2NDAwMmYzMDk3MzRkYzljOWMyMTZmMGQ3MTY3ZjNkYmRhZGVjMCJ9fX0="
),
VILLAGER_SHEPHERD_SAVANNA(//
"Savanna Sheperd Villager", "villager.savanna.sheperd",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA3MDA2MTgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDczMTgxYzA0NzNiN2IxMTExYTVkYmFiMmQyNDQzNzI5ZTdiZmQ4NDk5YjEyZmZlODdhYWYxZTExM2IyYWEyOSJ9fX0="
),
VILLAGER_TOOLSMITH_SAVANNA(//
"Savanna Toolsmith Villager", "villager.savanna.toolsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDExNjY5MzQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGQ4YzBlZjEzMGZiNWIxZDcxNjlhY2M4NThhOGU3ZGNlMzM2NjI5YWNmN2JmODhkMzE2MmNiNDA5YjQ5NzE0MCJ9fX0="
),
VILLAGER_WEAPONSMITH_SAVANNA(
"Savanna Weaponsmith Villager", "villager.savanna.weaponsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDE2ODk4NjIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzc0MmE0ZGJiYjgwZWRhZDk2MmM3NThmYjc0NmEyMzYzOTYzODdhMDIzNzA2NDVhYWZjOGM0NDJjOThlMjk3ZiJ9fX0="
),
VILLAGER_ARMORER_SNOW(//
"Snow Armorer Villager", "villager.snow.armorer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDA5MTAyNDUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmEwODUxMGI1ZjYwNjlkOTQ4OGFkY2FmMDc4OTViM2U3OWEzMTViMTlhYWYwMTM5NTQ5NDkyMmE2ZjlkYTdhZiJ9fX0="
),
VILLAGER_BUTCHER_SNOW(//
"Snow Butcher Villager", "villager.snow.butcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDE1OTUzNzgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzMwMjJjMzY4MGEyNDAyMTA1YTc5MjIxNDM2OTU0ZmNmYWQwZDNjN2E3YWZjOTg4MmQyOTMxMDM1ZTA3NzI4NyJ9fX0="
),
VILLAGER_CARTOGRAPHER_SNOW(//
"Snow Cartographer Villager", "villager.snow.cartographer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDIzMDUwMzcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2JkNDM5NjZmYjNmN2M3NDA2OGNmYjRjOGM4OWU4NjJhNWNkZWMzNDk5NWMxMjI4NTk2NzVlOGQ5ZjlmNGMwZiJ9fX0="
),
VILLAGER_CLERIC_SNOW(//
"Snow Cleric Villager", "villager.snow.cleric",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDI4OTYwOTYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWUwMjNkMWE5ZGVhODM0OGIxM2Y0YzFlZDI2YmM3MWNlZWQ1N2VkYmEwNTZjYjc3NTI4ODc1YzUwNDMxYmFmYSJ9fX0="
),
VILLAGER_FARMER_SNOW(//
"Snow Farmer Villager", "villager.snow.farmer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDM2MDE0NTksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2M0OTEyYmNiOTY0NmQ5MjkyYzZhNzIwZTU0Nzc1NDE2MTVlNDg0MjQxZTE3NDU0NzcwY2I0ZGRhMzBlM2FlMyJ9fX0="
),
VILLAGER_FISHERMAN_SNOW(//
"Snow Fisherman Villager", "villager.snow.fisherman",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDQzNjY5MjQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTBmMWVjNzEwZmFhMzhiYWUzZTc5NTE5NjEzODkyNTQ1ZjM0ZTRlZDM4N2ZhZGEyZDdkODA0YmM5ZDg0NjUwMiJ9fX0="
),
VILLAGER_FLETCHER_SNOW(//
"Snow Fletcher Villager", "villager.snow.fletcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDUzNTY2MjksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzY1NDcwYTM1NzU1ZTU1OGI2MzNmZDM0ZTEwOGFhNTYyMDJhYjNlZDU5NmNmNGYxOTQ3ZTk3YTEzMmU1MDNjNyJ9fX0="
),
VILLAGER_LEATHERWORKER_SNOW(
"Snow Leatherworker Villager", "villager.snow.leatherworker",
"eyJ0aW1lc3RhbXAiOjE1ODQyNTgyNTUwNDQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDM1MjMwNDFhMjE2M2Y3MTY5NDFjZGE3MjcxMzhiOTgxMzdhZTRmNzU3ZjNlNzFlOGIwYzFiMmEyOGQ3ZTZiYSJ9fX0="
),
VILLAGER_LIBRARIAN_SNOW(//
"Snow Librarian Villager", "villager.snow.librarian",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTg3MTI2NTQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWI5NmIwNmM3ODZhOTAwNjI4MTgwNDVjYzZlMTNmNWI3MTA0NjQ5MTg0MDFiYzVhMjExODZiOWZjNGM4NGYzYiJ9fX0="
),
VILLAGER_MASON_SNOW(
"Snow Mason Villager", "villager.snow.mason",
"eyJ0aW1lc3RhbXAiOjE1Nzc2NzIzNTE2OTUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGUyYThlNjRmYzBlYjIxZDNlMjNkMWQxZGU0ZThiODA2MmQ3NGI4NjY3NTliYzk2OGYxMzA5NmRmYWUzYzM4YiJ9fX0="
),
VILLAGER_NITWIT_SNOW(//
"Snow Nitwit Villager", "villager.snow.nitwit",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk0NDQyOTgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzRhYTk1ODc3NDAxNWQ3N2UwM2Q5Mjg1ZDcxYTI4NzY5NDUxNWU3N2RjZmYzYjAwNjBlYjg1NjU4YWZmYzU3NiJ9fX0="
),
VILLAGER_NONE_SNOW(//
"Snow Villager", "villager.snow.none",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk5OTc3MzgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzRiYWNjOTBlYTFjNGQ4ZjBiOGQ4YjkzMTY1NWY0ZWM0OTFkYmY1OGYwNTIzNzJmNDQ5MDkyZWY1YTkxNDIzNyJ9fX0="
),
VILLAGER_SHEPHERD_SNOW(//
"Snow Sheperd Villager", "villager.snow.sheperd",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA3Njk2MjAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2U5YzI5YTJhYjkxOWE5N2Y4NGEwYjVlMGFkYjA0NTEzZWJlZmExNzRjM2IwZDcyNWI4NGNlNDgyNjkzZjdjZCJ9fX0="
),
VILLAGER_TOOLSMITH_SNOW(//
"Snow Toolsmith Villager", "villager.snow.toolsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDEyMTQzMzUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzZTA5MjI2ZWNkNTM2ZDZjNThiNTQ5NzkwZTFmYTdlOTMyNjAyOGE3MDM3MDUyOTIxYTAwNDI2Y2FmMTBlYSJ9fX0="
),
VILLAGER_WEAPONSMITH_SNOW(
"Snow Weaponsmith Villager", "villager.snow.weaponsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDE3MzM5NzUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDkyNjZhZGZjYWRiYjBiODY5YjEwZTIyNjQwOWMwODNjNmRjN2EwNzE5YTZjODA4Y2JjMjM2NzY1YWI4YzBlZSJ9fX0="
),
VILLAGER_ARMORER_SWAMP(//
"Swamp Armorer Villager", "villager.swamp.armorer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDA5Njc0ODAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzE4NDBlNDA4MmY0YWVhYTY5NTM1NDg0YTdhYjdkOGY3ZTEzYzVjNTc4OWEyYTM4M2JkN2U4N2IzODc5MDZjNSJ9fX0="
),
VILLAGER_BUTCHER_SWAMP(//
"Swamp Butcher Villager", "villager.swamp.butcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDE2NTM2NzQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjUyNmZhYTQ2NmRkMzcxNjhhYWMxYzYwY2JjN2U1YTI0MDVjOTM1MDcxZGVhNmNjMWUzYjllYWJmODdlYmExYyJ9fX0="
),
VILLAGER_CARTOGRAPHER_SWAMP(//
"Swamp Cartographer Villager", "villager.swamp.cartographer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDIzNjUyMDcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDIwNWVkM2Q4MmRjNTI1MDExZWMzOTIwODA5MTY1MDVmZjg2Y2E5NjE3YWQwZjJjNzI5NmI5OTA3ZmEyZmI4YiJ9fX0="
),
VILLAGER_CLERIC_SWAMP(//
"Swamp Cleric Villager", "villager.swamp.cleric",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDI5OTg3OTMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGM0Mjk2NDg2Y2NlYzgxYWNkZjIxNDUxYzY5MGIyMzRiYzlmZGFjNWFiNmVmZDU0NTFhYjVjZGFmYWZiNzk3OSJ9fX0="
),
VILLAGER_FARMER_SWAMP(//
"Swamp Farmer Villager", "villager.swamp.farmer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDM2NDY4MDEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzQ4ZDVmODA4NDc4NTRlOGNkNTU2ZjE4NDdjZTM3M2E5OGJiYzdkZDAyMzJiNmUyMDY0NzQyNDQxMTk2NDg5NiJ9fX0="
),
VILLAGER_FISHERMAN_SWAMP(//
"Swamp Fisherman Villager", "villager.swamp.fisherman",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDQ0MzA4OTcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODMyZTk4ZmM1ZDc3OGE0ZTM0ZDU1OTc2MWQ1YWY0YTYzYjJlMTZlZDkxZmE3N2E1NDJhZDdmNDlmZjA0MTM1MCJ9fX0="
),
VILLAGER_FLETCHER_SWAMP(//
"Swamp Fletcher Villager", "villager.swamp.fletcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDU0MzgwNTcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmQ0ODQ0ZmUyZmI4N2QxNzEzMDY5MjU2MjRkYmM5ZjVjZGNmOGYyYTJjYjlkYjI1ZTJiNzdhMjc3OTAyYmFhZCJ9fX0="
),
VILLAGER_LEATHERWORKER_SWAMP(
"Swamp Leatherworker Villager", "villager.swamp.leatherworker",
"eyJ0aW1lc3RhbXAiOjE1Nzc2NzMzMTcyMTEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTMzNGQ3OWY4ZmI0MjAzZDRiZmFhYWM4ZDU5NTVmZTA5NjVkY2JiMDMzNmVmMTY3NTc1NjdlMWQwODQyYjhhMyJ9fX0="
),
VILLAGER_LIBRARIAN_SWAMP(//
"Swamp Librarian Villager", "villager.swamp.librarian",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTg3NjQzMDYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDI0MzU3ZWVkMGEzYTM5NGEyNmJkZjhmYzhlNjEzZTgzNjE5NmE3MmE1OTRkNWUwYWFiNmYzMDcyM2NjNDRkNCJ9fX0="
),
VILLAGER_MASON_SWAMP(
"Swamp Mason Villager", "villager.swamp.mason",
"eyJ0aW1lc3RhbXAiOjE1Nzc2NzMzMTcyMTEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTMzNGQ3OWY4ZmI0MjAzZDRiZmFhYWM4ZDU5NTVmZTA5NjVkY2JiMDMzNmVmMTY3NTc1NjdlMWQwODQyYjhhMyJ9fX0="
),
VILLAGER_NITWIT_SWAMP(//
"Swamp Nitwit Villager", "villager.swamp.nitwit",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk1MDM4MzQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzNmMGE2ZmRkNTkxNmRlNjhmN2Q5ZGM5ZmY5ZGQ5YWFkNWM3ZjNmMWQyYmJmNzJiOTU2YWQ0NTMzZTJkNmU1ZSJ9fX0="
),
VILLAGER_NONE_SWAMP(//
"Swamp Villager", "villager.swamp.none",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDAwNTIyMTksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTU1MWE0OTlmN2FiMTc3YmE0M2FjYTQxZGZkOGZjNDExYjFkMWU1MDJhMzQ2ZjljNGQ2MGI3YTVlNTdmNDM2YyJ9fX0="
),
VILLAGER_SHEPHERD_SWAMP(//
"Swamp Sheperd Villager", "villager.swamp.sheperd",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA4Mzg4ODAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDU0NWM0MWY3NGU1OThiYjE0ZDRlMTUyNGRhMzg1ZDU0YTRhZjYxMDA5YmM5MGRlYTA3MGZhY2NjZjMwYzViMiJ9fX0="
),
VILLAGER_TOOLSMITH_SWAMP(//
"Swamp Toolsmith Villager", "villager.swamp.toolsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDEyNTc2MzksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmM0YjE4Mzk4MWU0ODUxNDYxZTdiYjljNzEwY2Y4ZTViNjU3NWZiMjI0YzJhYzBjNmU1Y2FkM2MyYTVkMmY5OSJ9fX0="
),
VILLAGER_WEAPONSMITH_SWAMP(
"Swamp Weaponsmith Villager", "villager.swamp.weaponsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDE4MTIzNzgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTUwZjUxNzkxNjBmYTM1NDU0NzUyOTM1MDI2YmViY2Q5ODc1MTk5Njc1MGY1NGVjNWY5NTNhY2I4N2NiZWJjOSJ9fX0="
),
VILLAGER_ARMORER_TAIGA(//
"Taiga Armorer Villager", "villager.taiga.armorer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDEwMzA1MjIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2YxYmE0MTQ1NGZjNTVkZGUwNzlmYzE1YmYyMTQ2MTg5MWQ1YjVkMDNkZDQwMTZhZTdjZGViNDZhOGZjODQwNCJ9fX0="
),
VILLAGER_BUTCHER_TAIGA(//
"Taiga Butcher Villager", "villager.taiga.butcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDE3MDE5MTQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjI4ODdhMGZmNzg5YTdmY2FiOGU3NzhlNzEwZGJhZDBkOTYxZWJiYWFmYTVlZTA0MGUyMTM1YzVkM2E4Y2M3MiJ9fX0="
),
VILLAGER_CARTOGRAPHER_TAIGA(//
"Taiga Cartographer Villager", "villager.taiga.cartographer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDI0Mjk5NDEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzA4OWZiMjQ2YWEyZjNlZjI3NGQwMTQyOWQzNTM0NzE5ZTQyYjFhODE1MWNkYWMzMDM1NGQ0OTY3YjczYTc4MCJ9fX0="
),
VILLAGER_CLERIC_TAIGA(//
"Taiga Cleric Villager", "villager.taiga.cleric",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDMwOTUwNDgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2M1ZGVhOTljMjg2NzE4NTczOGJlMDJiNzFjYWNmMmNkYTRhMjczYzExNDliZTdhNTE2NGRhNzJmMGY5NDZmNyJ9fX0="
),
VILLAGER_FARMER_TAIGA(//
"Taiga Farmer Villager", "villager.taiga.farmer",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDM2OTI4ODcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTU3ZjM4M2IwYmRiNDU2OTU3Y2ZiY2M1MmIyODA4YTBmOGEwMGMzNTlkMTVhZWIwMTNmNTYzNGZhMDAzNzkyIn19fQ=="
),
VILLAGER_FISHERMAN_TAIGA(//
"Taiga Fisherman Villager", "villager.taiga.fisherman",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDQ1MTkyMjUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWI5YzZlZGI1YzBiYTU0YjIwM2RiZjA0ODI3M2M0YmFkYjE1Y2VjMWQzMjI4NjUwZDJjMmZlNWIzZmFiM2NiYiJ9fX0="
),
VILLAGER_FLETCHER_TAIGA(//
"Taiga Fletcher Villager", "villager.taiga.fletcher",
"eyJ0aW1lc3RhbXAiOjE1ODQzNDU0OTE1MDYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDllMTVmODQ4YjUwODMyMGRiZjE2NGJmMGQ0YmFjODlkNTE3MWYxODZiZWViNDFjN2JkMzQ2MDExYzg3YmQ4YiJ9fX0="
),
VILLAGER_LEATHERWORKER_TAIGA(
"Taiga Leatherworker Villager", "villager.taiga.leatherworker",
"eyJ0aW1lc3RhbXAiOjE1ODQyNTY5MDM3MzYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2VhNTI0ZjM3ZjYyYmI1YWQyMWYyZmEzNjU1ZmE2MWZhOGQxNWRjNWE3MjYwNzEyNGFjOGU4NDkwNzUxZmYyNyJ9fX0="
),
VILLAGER_LIBRARIAN_TAIGA(//
"Taiga Librarian Villager", "villager.taiga.librarian",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTg4MjA1MTAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjcxNzYyYzg5Mzk2OWQyN2U3Njk5YjgzNWViODU5ZTIxOTk2YzM2NDUwM2Q4NzBlNTQ1MTBiY2JmOGUzOWFmYiJ9fX0="
),
VILLAGER_MASON_TAIGA(
"Taiga Mason Villager", "villager.taiga.mason",
"eyJ0aW1lc3RhbXAiOjE1Nzc2NzQ2NzI4MDMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDZkZmIwNGVjYTQ5NmU1MjQ3NjNlMjg4Y2YxY2Q1MzZmNGNjN2U5ZmY4MjJlYjI3OWVjZmU3YmU2N2JjYWMzOSJ9fX0="
),
VILLAGER_NITWIT_TAIGA(//
"Taiga Nitwit Villager", "villager.taiga.nitwit",
"eyJ0aW1lc3RhbXAiOjE1ODQzOTk1NTYzMDIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjhhOGMzNDk1OTA3MTVhNmZkNDFhMTc3MDI4MzNlMjdlMThhODFmNGQ2ZGM1Njk4MWUwNTEyNDU5MGUyMGUwYSJ9fX0="
),
VILLAGER_NONE_TAIGA(//
"Taiga Villager", "villager.taiga.none",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDAwOTI0MjYsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWE5OTZlNmZiODdkNDAzODU1ODIxODAzYjYwODc3OTRhMTEwMjA2MGIyYjA2NGY1NmQwMDg4YjliMDhmMTk4MiJ9fX0="
),
VILLAGER_SHEPHERD_TAIGA(//
"Taiga Sheperd Villager", "villager.taiga.sheperd",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDA4ODg3NDIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjRkNWI2YjQ2OTQ0MzBmNzA2OGQxMDFkNWU4YWE4OTdkM2Q2NjUwODYwNWZiY2ZjNjRkZDNkOTcyYmM0NTUyYyJ9fX0="
),
VILLAGER_TOOLSMITH_TAIGA(//
"Taiga Toolsmith Villager", "villager.taiga.toolsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDEzMDcwNTUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDFlMmRkNzQ4ZDk2MTkyYTU4N2FjODU0M2ZiZWZjYTQ2MjgyYWQwMDgxMGZhMzM0OTdlZmZlMGM5ZDU2NzhiMCJ9fX0="
),
VILLAGER_WEAPONSMITH_TAIGA(
"Taiga Weaponsmith Villager", "villager.taiga.weaponsmith",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDE4NzgzMjcsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTUxMzMxMzE4N2RiM2MwOWJlYzc4NzU1NzdmZmY2ZGM0YjYxZDlmZDA3NDMwZmRhOTBmNzhiOTUwYjQwOTY2OCJ9fX0="
),
/**
"Plains Weaponsmith Villager", "villager.plains.weaponsmith", "e5d0f43c-58d7-4bf0-afc9-ed7a1ab73a8c",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWIxZWE1NGI3ZmQ5M2ZlNmMzNmQwYjVkODE1YWIwZGI4OWRiODk0ZGI3NWNlMGZjZTczOGU5NGVlOGI3OWUxOSJ9fX0="
),
VILLAGER_ARMORER_DESERT(//
"Desert Armorer Villager", "villager.desert.armorer", "28af2ba5-de7d-452b-8028-8e9bdb4b231a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWI2N2EwZmI1MGYyOGM2ODhiZTk3ZDEyN2NmZTQwNTQ3ZDE4N2U4MjliZDI5MTUwMjA1MzNjZmU5M2JhMDRhIn19fQ=="
),
VILLAGER_BUTCHER_DESERT(//
"Desert Butcher Villager", "villager.desert.butcher", "a8db08e1-4b3f-4d0c-be93-b646903032e3",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzFhNzAzZjY0NzA0M2UwY2U5ZjAzYzQyOGQxM2EwYTZmZWY0NWUzMGE3MzMxN2ViMTIxY2ZiZjFmYjg4YzE5NyJ9fX0="
),
VILLAGER_CARTOGRAPHER_DESERT(//
"Desert Cartographer Villager", "villager.desert.cartographer", "df957172-1c07-48af-b3ee-252b9fa80f7f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmY5NDY3Y2QzNTc0ZTNiNjUwNDJiZjIxZjM2OWRhNjBmNzc3ODMyNzVkNDkzMGQzZTBkODI2MWI4YjUyOTc3MyJ9fX0="
),
VILLAGER_CLERIC_DESERT(//
"Desert Cleric Villager", "villager.desert.cleric", "3f37a7fc-9881-42c9-b356-90c84cea760d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTE0NWViMDM5ODljMTA0MDFhYzdhM2U0ZWE4MTY1MmFjM2MxZDg4NTM3NTA4NThiY2ZiZDhmOTQyYmU5NTI5ZiJ9fX0="
),
VILLAGER_FARMER_DESERT(//
"Desert Farmer Villager", "villager.desert.farmer", "c98bbc1d-5e0c-4a3e-94ec-339da6ceb6fa",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODZmYjM4OGY3MGNkMjQ5Yzk4YWQ2MTcwZThjOWIzNzU3NjY3MWQ3NzFjZDhmNzJkOWYxNzMwYjhjOWNhMjMyNSJ9fX0="
),
VILLAGER_FISHERMAN_DESERT(//
"Desert Fisherman Villager", "villager.desert.fisherman", "7bcdbd9d-0235-4d6e-8041-90bac45318cc",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmQ1NDk1NDNhMTg2NTc5NGE4N2YwMDI1YjZmOWJiZWU3ZmM3OGNiYmI0ZThhZWVmYTFjMzdmODU3MzRlNDAxOCJ9fX0="
),
VILLAGER_FLETCHER_DESERT(//
"Desert Fletcher Villager", "villager.desert.fletcher", "bbf415db-7b53-4d9c-9e5d-06e6f81c2286",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGJiMjBmMjljY2NlZWEwNzA3M2I4NDY4NGIzOTA5NjMwZGQxMTVhNDdkMmZlYzdjM2QyZjhiY2I5OTkyZTEwZCJ9fX0="
),
VILLAGER_LEATHERWORKER_DESERT(
"Desert Leatherworker Villager", "villager.desert.leatherworker", "987bf436-578e-45bd-bf49-1af0681b86f1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODgzZTliYzgyN2U3MzFhYTFkODAxNDg3ZTViNmEzOTE2YWQ0NTJkZGMyMDY4ZmFiMDdlOTdlYmM5MDM2ZTA1MiJ9fX0="
),
VILLAGER_LIBRARIAN_DESERT(//
"Desert Librarian Villager", "villager.desert.librarian", "939fd80a-4c34-4bf4-9050-a57844a33d06",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjljMzA4M2JkMjQ5MGVlNDExZTAyZTE1MDliZDRmYzhiZjU0ZjkwZTkzMmI0YzU0NGE1YzEwNzg4MzA1NzhhYyJ9fX0="
),
VILLAGER_MASON_DESERT(
"Desert Mason Villager", "villager.desert.mason", "2eb4ff3b-54ec-4697-9dde-836749e3fb31",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGRkODUxNTJlMzk5ZDMwMGM4NmRkYjRkZWUxNDIzNGJmODM2M2ZiYTE3NjQyZGI0MjE5ZTM3ODJhZjE5OGM4ZCJ9fX0="
),
VILLAGER_NITWIT_DESERT(//
"Desert Nitwit Villager", "villager.desert.nitwit", "85085a91-b0b1-458f-93fd-2a9c7485a649",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjNkZDYxZjQxODRkY2Q0Y2YxMDBiNjAyMTQxZDNjMmJiNmMyMjIxN2E3ZWQ4MjZhNzgxNWI1ODdkZWNhNTBiNyJ9fX0="
),
VILLAGER_NONE_DESERT(//
"Desert Villager", "villager.desert.none", "ecd82373-83bb-4232-9c31-1dc8c789a305",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTMzOTM1MjdhZmE2ZjcyYmQxYjJkNTliOTU3Yzc0MDk1ZWViNDNjZjkzNzg0ZTAxZmM2NjdmMzkyNmFhNzU5ZSJ9fX0="
),
VILLAGER_SHEPHERD_DESERT(//
"Desert Sheperd Villager", "villager.desert.sheperd", "e1cb8198-d0be-4484-b160-38533d3e0590",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzM5ZTEwNzVmNjhiZDVhYmM1ZWY5NjllMzA4NTZjODU5MjgzNTc4MGYwM2FhZjViMmY1OTYyMTZmYWIwMDI2YiJ9fX0="
),
VILLAGER_TOOLSMITH_DESERT(//
"Desert Toolsmith Villager", "villager.desert.toolsmith", "0a8b3bee-3867-4dac-ada5-8962741a885f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTE0ZGJmNDYzMGQ0YTFmMTA5ZWE3YWYwZDAyNDc4OGVmMTQyNWJkYjg5OWMxYWUxNDA4Y2UzZjlmZTRiMmRmMyJ9fX0="
),
VILLAGER_WEAPONSMITH_DESERT(
"Desert Weaponsmith Villager", "villager.desert.weaponsmith", "5265f792-2484-4c3d-8819-bab6fc8a7046",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjFiZDhlYjMwMDkzYzkxZDA2NzVmNzMyMWY5NWFkOTFkNjk1ZjRhMmNjNmIxZTk2MzcyOTk4ZGUyNGQ2NmI2OSJ9fX0="
),
VILLAGER_ARMORER_JUNGLE(//
"Jungle Armorer Villager", "villager.jungle.armorer", "af40e80c-8d4c-4df4-8ac3-7f83fe43b438",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjFkNTk5ODM1OTQ0NDc2ODk4MzAxODI2MDkyNjU3OTJlZGYxZTM1ZTlkMzYyYWQ2MmRiOWNmNzY5ZWE3MTVhMiJ9fX0="
),
VILLAGER_BUTCHER_JUNGLE(//
"Jungle Butcher Villager", "villager.jungle.butcher", "9db68563-7fdb-4e51-8982-a8aa3079d238",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzFhNzAzZjY0NzA0M2UwY2U5ZjAzYzQyOGQxM2EwYTZmZWY0NWUzMGE3MzMxN2ViMTIxY2ZiZjFmYjg4YzE5NyJ9fX0="
),
VILLAGER_CARTOGRAPHER_JUNGLE(//
"Jungle Cartographer Villager", "villager.jungle.cartographer", "a00a562d-b265-4937-ad95-56e1d35bf7bc",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmY5NDY3Y2QzNTc0ZTNiNjUwNDJiZjIxZjM2OWRhNjBmNzc3ODMyNzVkNDkzMGQzZTBkODI2MWI4YjUyOTc3MyJ9fX0="
),
VILLAGER_CLERIC_JUNGLE(//
"Jungle Cleric Villager", "villager.jungle.cleric", "f590f4b6-665a-49a1-996c-6e6a4716bb69",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGEwZWYyYWRkMThhNDU3ZGY4YTk4ZWM1OTFiMGEwZGJkOTk0MmEyZDU4OTY1YTA0M2VhMDM5NzIwNDg3MTRmNSJ9fX0="
),
VILLAGER_FARMER_JUNGLE(//
"Jungle Farmer Villager", "villager.jungle.farmer", "b88396a8-a5d3-4127-a2bb-dba8d420edea",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTQ5YmZjZTFkZTRkOWU3OGE3YTk2ODc3MzE5ZWRhOTBlOTg5YzI4YWZlMWI0YmYxZGM1NjNhNWQ0NGIxYWM3NCJ9fX0="
),
VILLAGER_FISHERMAN_JUNGLE(//
"Jungle Fisherman Villager", "villager.jungle.fisherman", "f69bd527-6d52-4ba8-b20b-ac10ecf39fbc",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmU0M2MwYmYyNmVlMWE0MzMyYzExZTJjMjA4N2FhZDkwM2JlNDE4YWNhNTE0ZmQ4MGE3MDI4YjM3MWY1MzI4NyJ9fX0="
),
VILLAGER_FLETCHER_JUNGLE(//
"Jungle Fletcher Villager", "villager.jungle.fletcher", "248b07f6-41bc-4346-bbf0-38e6e0250920",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjg0MjM4ZjA2YTg1YWM2NzY1MWQ5NmNmOTYzYzUyNTA1NTI5OTI5MGMzNDg4MjlhNjZjNDQ1OTdiYWY1NzQwYiJ9fX0="
),
VILLAGER_LEATHERWORKER_JUNGLE(
"Jungle Leatherworker Villager", "villager.jungle.leatherworker", "fec3382a-446a-400a-9beb-6a89cf097cf8",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWEwOTU1ODdhYWZlMThjNWZlZTBiYWExNzk1ZmMyMjk0OGYyZGRjZGMzMmEzMzUwMzIzZDMzZGI1OTE4MDBlNiJ9fX0="
),
VILLAGER_LIBRARIAN_JUNGLE(//
"Jungle Librarian Villager", "villager.jungle.librarian", "f7b45265-55f9-4177-8c16-f5056f4f03b5",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTJmYzNkMGU3Yjc0ZGY1N2NiNGJkNmM0MjZhYTM2MWY3NjIwMjk2ODUzMGU3NmNmZjU5YjM5MzQ3MTc5NTk4YiJ9fX0="
),
VILLAGER_MASON_JUNGLE(
"Jungle Mason Villager", "villager.jungle.mason", "3d03efe3-3d28-4002-b7b6-c2ba565c376b",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTk2NzU2ZWU5MDNkMThkNTFhNWQ5NTk2M2I4NWJiYzEyYmI3YmQzOTEzZTg4NzE3ZDA2ZjhmOGU5YzU0MDFhYyJ9fX0="
),
VILLAGER_NITWIT_JUNGLE(//
"Jungle Nitwit Villager", "villager.jungle.nitwit", "8fd6b95a-a26a-4688-ae35-531e4eb7b3da",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzFhZWY1MTNkYjdhYzJhZjViNmI4YzliOGUyYjlmNDYyMjgxNGQxNTFlNzUwNGQ5YWUyYzQ1ZjRkOTRlOTBhNiJ9fX0="
),
VILLAGER_NONE_JUNGLE(//
"Jungle Villager", "villager.jungle.none", "0a9e8efb-9191-4c81-80f5-e27ca5433156",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTgwNmU5YTRkNzI4ZTcxNjgwM2ZjN2M3N2MxNTg2YzBkNWVkODViODZmNDA4MGI1YmRiNjQwY2M0N2YxNWI3ZCJ9fX0="
),
VILLAGER_SHEPHERD_JUNGLE(//
"Jungle Sheperd Villager", "villager.jungle.sheperd", "ce2bdf44-297d-41ec-8fb6-63c6b6949712",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTk3NzY0MzBjZDIwYTY5YTFkODk5ZDlkMWRjZDMxZDdkMTM0MmI1NWU4MDE1YjdmMDYwZmIzYTJjMzM4YzhiYyJ9fX0="
),
VILLAGER_TOOLSMITH_JUNGLE(//
"Jungle Toolsmith Villager", "villager.jungle.toolsmith", "c2ddbfa1-fc57-4b05-b0ea-27735b2dbf2a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjg2MzVmOTFhYjhlOTU0MmQwNTAyODRmMDhkMGRjYTVkZjZjMWYzYmQ2ZDhhNzE0MjdkMTQxOWUzNzk5YzJiYyJ9fX0="
),
VILLAGER_WEAPONSMITH_JUNGLE(
"Jungle Weaponsmith Villager", "villager.jungle.weaponsmith", "ef636711-2d3f-4431-a075-aacd858a682c",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjQ1ZmFiMzAzMjk0NGQxOGIyNjYyMWFkMjliM2Y5M2M0OTI3NmVkNmViNDY5ZWUyMTNhYmVjOGU0MmIyMjZhMiJ9fX0="
),
VILLAGER_ARMORER_SAVANNA(//
"Savanna Armorer Villager", "villager.savanna.armorer", "95c3984d-4d67-4b09-9267-08191c12b669",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmU4YTBlZGQwODFmZTE3NjFhZDU0YzY3ODNjOWZjZDJkZjA2M2YzYzIyODc0MjBmYzZkMjkzOWRmY2Q3MDI0OSJ9fX0="
),
VILLAGER_BUTCHER_SAVANNA(//
"Savanna Butcher Villager", "villager.savanna.butcher", "65803c56-c6ba-4725-95ee-0ccd87071f59",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTUxZTAwMzkxZjcxNGI1NTk5ZmQ4NTQ4NWE3NGZkM2MxOTkwY2VmMWJkMjgyMTBjZThmNTk2NjE4MDA4ZDVjYiJ9fX0="
),
VILLAGER_CARTOGRAPHER_SAVANNA(//
"Savanna Cartographer Villager", "villager.savanna.cartographer", "e61975d2-8668-4244-a920-24b9986272b3",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjAxZWEzNzc2M2M4OTU5MzY3ZTNjM2FmOTM4YjU3YzU2NjI2OTMxMmM4YjBlY2VmMWY5ZjY0ZmQ4ZmE4YTkxOCJ9fX0="
),
VILLAGER_CLERIC_SAVANNA(//
"Savanna Cleric Villager", "villager.savanna.cleric", "3781698f-f01e-4513-8da7-c9eb628eefea",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWIyY2JmZjE0ZjZiYTdmYjFjMjdkM2ZiZDYyOGI2MjM0NzUyNTMyMmJhMGY4ZWU5Y2EwZWIzNDkzYjg4Yjc2NCJ9fX0="
),
VILLAGER_FARMER_SAVANNA(//
"Savanna Farmer Villager", "villager.savanna.farmer", "c27750ca-ef49-464e-b8a0-0507b7d90fe8",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzc0OGM3Yjk5MGFkMjMzNWFjNTU5MWQ4ZWNiYzYxZGRjNjdhMzU4YTcwN2VkMWIyNmUyYmFmMjM5YTcwZGEwOSJ9fX0="
),
VILLAGER_FISHERMAN_SAVANNA(//
"Savanna Fisherman Villager", "villager.savanna.fisherman", "1959d4b5-4ffd-48ee-a3f8-c6a53ba9b067",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTkzZDQ5NzljMDU0NGI2ZGQxNjNhMjBjODZlM2E0NjZhN2ExMDI4NDA5M2QwZGMzMTNlOWY3ZGUyMGZlZTRlZiJ9fX0="
),
VILLAGER_FLETCHER_SAVANNA(//
"Savanna Fletcher Villager", "villager.savanna.fletcher", "80173e34-dba5-4809-a43d-320b228e567c",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzAyODEyZmU3ZTA3NTI0MTEyMDkxYTI5ZTBiOTE1MmNkMmEyZmJmOTRhYTlhN2EyMTkzYzRkOGE3M2RjNzNmMSJ9fX0="
),
VILLAGER_LEATHERWORKER_SAVANNA(
"Savanna Leatherworker Villager", "villager.savanna.leatherworker", "bf9ac4c7-585f-452a-82c7-16c85cfb2b3a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjVhN2U2YThlZGVlZjc0ZGM1ODQzOGQ3MDZmYjJhZWQ4ZWNiZWI0ZjlhNGQ5NTY2MGY1ZmY1MDZlNGFmM2Q1In19fQ=="
),
VILLAGER_LIBRARIAN_SAVANNA(//
"Savanna Librarian Villager", "villager.savanna.librarian", "f7ed8ecf-0162-42aa-8840-5a5544873b96",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTNjNTQ3NmJmYTMxYWY4YTQ2NDliNjc5NzY1MjhiMzAxZmY5YjhiYTU3NWE4YzgxYzlmMDlmMzhiNjkyNzgxZSJ9fX0="
),
VILLAGER_MASON_SAVANNA(
"Savanna Mason Villager", "villager.savanna.mason", "82259e14-9ed7-4a92-b3eb-4d01a0864f7a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODQwNWZkMTNkNGU3ODYwNzUxMmZmNzE2OWYzZWQ1NmIzMTA0MjM5M2QzMDlhYzhlODIzNjZlMTE3OGJiYTVjMiJ9fX0="
),
VILLAGER_NITWIT_SAVANNA(//
"Savanna Nitwit Villager", "villager.savanna.nitwit", "c670fbae-98e7-41f3-bfdd-321218dcb5ff",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjE0N2MzMTVlMzg1ZmRlMWFmZjI5MmMwNGEzNzM5MGE2ODNlZGQ0NTYxMGU1OThmOTBjOWExYThhNTY3YjJmNSJ9fX0="
),
VILLAGER_NONE_SAVANNA(//
"Savanna Villager", "villager.savanna.none", "531fe1ca-24e1-4851-9971-acb678cdef26",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTQ3MGIyNTE2ODhiYTU4MDc3NDIwOWQ1MjY2NDAwMmYzMDk3MzRkYzljOWMyMTZmMGQ3MTY3ZjNkYmRhZGVjMCJ9fX0="
),
VILLAGER_SHEPHERD_SAVANNA(//
"Savanna Sheperd Villager", "villager.savanna.sheperd", "83581234-bd51-4f52-ac7f-496d092e7fea",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDczMTgxYzA0NzNiN2IxMTExYTVkYmFiMmQyNDQzNzI5ZTdiZmQ4NDk5YjEyZmZlODdhYWYxZTExM2IyYWEyOSJ9fX0="
),
VILLAGER_TOOLSMITH_SAVANNA(//
"Savanna Toolsmith Villager", "villager.savanna.toolsmith", "8d032c47-42a8-4778-a1c0-1924ce5449b2",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGQ4YzBlZjEzMGZiNWIxZDcxNjlhY2M4NThhOGU3ZGNlMzM2NjI5YWNmN2JmODhkMzE2MmNiNDA5YjQ5NzE0MCJ9fX0="
),
VILLAGER_WEAPONSMITH_SAVANNA(
"Savanna Weaponsmith Villager", "villager.savanna.weaponsmith", "c74c6dce-ce0b-4267-baee-b502b55653c9",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzc0MmE0ZGJiYjgwZWRhZDk2MmM3NThmYjc0NmEyMzYzOTYzODdhMDIzNzA2NDVhYWZjOGM0NDJjOThlMjk3ZiJ9fX0="
),
VILLAGER_ARMORER_SNOW(//
"Snow Armorer Villager", "villager.snow.armorer", "1a9ae0b9-ab32-4f01-9af9-4d0b1ed9f428",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmEwODUxMGI1ZjYwNjlkOTQ4OGFkY2FmMDc4OTViM2U3OWEzMTViMTlhYWYwMTM5NTQ5NDkyMmE2ZjlkYTdhZiJ9fX0="
),
VILLAGER_BUTCHER_SNOW(//
"Snow Butcher Villager", "villager.snow.butcher", "07841184-4b23-4de9-8d86-94faa672fe86",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzFhNzAzZjY0NzA0M2UwY2U5ZjAzYzQyOGQxM2EwYTZmZWY0NWUzMGE3MzMxN2ViMTIxY2ZiZjFmYjg4YzE5NyJ9fX0="
),
VILLAGER_CARTOGRAPHER_SNOW(//
"Snow Cartographer Villager", "villager.snow.cartographer", "cbbfcde1-7dc5-4b99-ad03-e991e40b032e",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2JkNDM5NjZmYjNmN2M3NDA2OGNmYjRjOGM4OWU4NjJhNWNkZWMzNDk5NWMxMjI4NTk2NzVlOGQ5ZjlmNGMwZiJ9fX0="
),
VILLAGER_CLERIC_SNOW(//
"Snow Cleric Villager", "villager.snow.cleric", "14c643d6-f288-4fd0-9968-5e97ba4aa4ee",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWUwMjNkMWE5ZGVhODM0OGIxM2Y0YzFlZDI2YmM3MWNlZWQ1N2VkYmEwNTZjYjc3NTI4ODc1YzUwNDMxYmFmYSJ9fX0="
),
VILLAGER_FARMER_SNOW(//
"Snow Farmer Villager", "villager.snow.farmer", "e2c33986-5783-4f6d-be18-c75600773ac1",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2M0OTEyYmNiOTY0NmQ5MjkyYzZhNzIwZTU0Nzc1NDE2MTVlNDg0MjQxZTE3NDU0NzcwY2I0ZGRhMzBlM2FlMyJ9fX0="
),
VILLAGER_FISHERMAN_SNOW(//
"Snow Fisherman Villager", "villager.snow.fisherman", "dff05a7b-9724-4891-a0a8-ce713cbcedde",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTBmMWVjNzEwZmFhMzhiYWUzZTc5NTE5NjEzODkyNTQ1ZjM0ZTRlZDM4N2ZhZGEyZDdkODA0YmM5ZDg0NjUwMiJ9fX0="
),
VILLAGER_FLETCHER_SNOW(//
"Snow Fletcher Villager", "villager.snow.fletcher", "e17a9dcb-a93f-4032-ab1b-04b0a55028f8",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzY1NDcwYTM1NzU1ZTU1OGI2MzNmZDM0ZTEwOGFhNTYyMDJhYjNlZDU5NmNmNGYxOTQ3ZTk3YTEzMmU1MDNjNyJ9fX0="
),
VILLAGER_LEATHERWORKER_SNOW(
"Snow Leatherworker Villager", "villager.snow.leatherworker", "bac6f170-0849-4dde-b13c-535e8e8d2683",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDM1MjMwNDFhMjE2M2Y3MTY5NDFjZGE3MjcxMzhiOTgxMzdhZTRmNzU3ZjNlNzFlOGIwYzFiMmEyOGQ3ZTZiYSJ9fX0="
),
VILLAGER_LIBRARIAN_SNOW(//
"Snow Librarian Villager", "villager.snow.librarian", "6a374869-79d5-4b69-a74d-17b9d6a3745a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWI5NmIwNmM3ODZhOTAwNjI4MTgwNDVjYzZlMTNmNWI3MTA0NjQ5MTg0MDFiYzVhMjExODZiOWZjNGM4NGYzYiJ9fX0="
),
VILLAGER_MASON_SNOW(
"Snow Mason Villager", "villager.snow.mason", "d4698632-f271-4efb-807b-0d22390d4217",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGUyYThlNjRmYzBlYjIxZDNlMjNkMWQxZGU0ZThiODA2MmQ3NGI4NjY3NTliYzk2OGYxMzA5NmRmYWUzYzM4YiJ9fX0="
),
VILLAGER_NITWIT_SNOW(//
"Snow Nitwit Villager", "villager.snow.nitwit", "8c1c3dd8-7921-4cee-a843-594285e7dfda",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzRhYTk1ODc3NDAxNWQ3N2UwM2Q5Mjg1ZDcxYTI4NzY5NDUxNWU3N2RjZmYzYjAwNjBlYjg1NjU4YWZmYzU3NiJ9fX0="
),
VILLAGER_NONE_SNOW(//
"Snow Villager", "villager.snow.none", "b88666ac-efc7-49ef-a6bd-51db174ed605",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzRiYWNjOTBlYTFjNGQ4ZjBiOGQ4YjkzMTY1NWY0ZWM0OTFkYmY1OGYwNTIzNzJmNDQ5MDkyZWY1YTkxNDIzNyJ9fX0="
),
VILLAGER_SHEPHERD_SNOW(//
"Snow Sheperd Villager", "villager.snow.sheperd", "2663f068-b88d-4a9e-b389-bac5ded550a5",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2U5YzI5YTJhYjkxOWE5N2Y4NGEwYjVlMGFkYjA0NTEzZWJlZmExNzRjM2IwZDcyNWI4NGNlNDgyNjkzZjdjZCJ9fX0="
),
VILLAGER_TOOLSMITH_SNOW(//
"Snow Toolsmith Villager", "villager.snow.toolsmith", "13dc3b0e-c95c-478d-9272-18c9a144880e",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzZTA5MjI2ZWNkNTM2ZDZjNThiNTQ5NzkwZTFmYTdlOTMyNjAyOGE3MDM3MDUyOTIxYTAwNDI2Y2FmMTBlYSJ9fX0="
),
VILLAGER_WEAPONSMITH_SNOW(
"Snow Weaponsmith Villager", "villager.snow.weaponsmith", "ae80f2f6-558f-4e14-97d8-6c818babbcca",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDkyNjZhZGZjYWRiYjBiODY5YjEwZTIyNjQwOWMwODNjNmRjN2EwNzE5YTZjODA4Y2JjMjM2NzY1YWI4YzBlZSJ9fX0="
),
VILLAGER_ARMORER_SWAMP(//
"Swamp Armorer Villager", "villager.swamp.armorer", "ba517ac7-b732-45c4-a940-1fa2ef5fa864",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzE4NDBlNDA4MmY0YWVhYTY5NTM1NDg0YTdhYjdkOGY3ZTEzYzVjNTc4OWEyYTM4M2JkN2U4N2IzODc5MDZjNSJ9fX0="
),
VILLAGER_BUTCHER_SWAMP(//
"Swamp Butcher Villager", "villager.swamp.butcher", "c1470a6a-4635-4a67-9f69-231ea893e7b4",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjUyNmZhYTQ2NmRkMzcxNjhhYWMxYzYwY2JjN2U1YTI0MDVjOTM1MDcxZGVhNmNjMWUzYjllYWJmODdlYmExYyJ9fX0="
),
VILLAGER_CARTOGRAPHER_SWAMP(//
"Swamp Cartographer Villager", "villager.swamp.cartographer", "43860f33-abb2-462c-8b04-62b898cb7a88",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDIwNWVkM2Q4MmRjNTI1MDExZWMzOTIwODA5MTY1MDVmZjg2Y2E5NjE3YWQwZjJjNzI5NmI5OTA3ZmEyZmI4YiJ9fX0="
),
VILLAGER_CLERIC_SWAMP(//
"Swamp Cleric Villager", "villager.swamp.cleric", "7f73a0d6-f9d5-4849-abf2-b2fd792d4991",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGM0Mjk2NDg2Y2NlYzgxYWNkZjIxNDUxYzY5MGIyMzRiYzlmZGFjNWFiNmVmZDU0NTFhYjVjZGFmYWZiNzk3OSJ9fX0="
),
VILLAGER_FARMER_SWAMP(//
"Swamp Farmer Villager", "villager.swamp.farmer", "1c26bd8c-6fea-45df-a1c7-687a48ab839e",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzQ4ZDVmODA4NDc4NTRlOGNkNTU2ZjE4NDdjZTM3M2E5OGJiYzdkZDAyMzJiNmUyMDY0NzQyNDQxMTk2NDg5NiJ9fX0="
),
VILLAGER_FISHERMAN_SWAMP(//
"Swamp Fisherman Villager", "villager.swamp.fisherman", "324a9f09-4c86-450c-989a-421198b42249",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODMyZTk4ZmM1ZDc3OGE0ZTM0ZDU1OTc2MWQ1YWY0YTYzYjJlMTZlZDkxZmE3N2E1NDJhZDdmNDlmZjA0MTM1MCJ9fX0="
),
VILLAGER_FLETCHER_SWAMP(//
"Swamp Fletcher Villager", "villager.swamp.fletcher", "1ea813a0-2921-4f3f-b6c1-e4783b232cc4",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmQ0ODQ0ZmUyZmI4N2QxNzEzMDY5MjU2MjRkYmM5ZjVjZGNmOGYyYTJjYjlkYjI1ZTJiNzdhMjc3OTAyYmFhZCJ9fX0="
),
VILLAGER_LEATHERWORKER_SWAMP(
"Swamp Leatherworker Villager", "villager.swamp.leatherworker", "3542f7b3-e263-4a21-a67b-302af77a57c9",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTMzNGQ3OWY4ZmI0MjAzZDRiZmFhYWM4ZDU5NTVmZTA5NjVkY2JiMDMzNmVmMTY3NTc1NjdlMWQwODQyYjhhMyJ9fX0="
),
VILLAGER_LIBRARIAN_SWAMP(//
"Swamp Librarian Villager", "villager.swamp.librarian", "ad599620-65b0-4932-bd96-1307bd160942",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDI0MzU3ZWVkMGEzYTM5NGEyNmJkZjhmYzhlNjEzZTgzNjE5NmE3MmE1OTRkNWUwYWFiNmYzMDcyM2NjNDRkNCJ9fX0="
),
VILLAGER_MASON_SWAMP(
"Swamp Mason Villager", "villager.swamp.mason", "f0e90655-0b2c-4281-af5f-9c76edfa8e04",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTMzNGQ3OWY4ZmI0MjAzZDRiZmFhYWM4ZDU5NTVmZTA5NjVkY2JiMDMzNmVmMTY3NTc1NjdlMWQwODQyYjhhMyJ9fX0="
),
VILLAGER_NITWIT_SWAMP(//
"Swamp Nitwit Villager", "villager.swamp.nitwit", "29147086-eb71-437a-bc23-a35612119e3d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzNmMGE2ZmRkNTkxNmRlNjhmN2Q5ZGM5ZmY5ZGQ5YWFkNWM3ZjNmMWQyYmJmNzJiOTU2YWQ0NTMzZTJkNmU1ZSJ9fX0="
),
VILLAGER_NONE_SWAMP(//
"Swamp Villager", "villager.swamp.none", "d8fcd176-507e-4eaf-9d3b-e956aac5e211",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTU1MWE0OTlmN2FiMTc3YmE0M2FjYTQxZGZkOGZjNDExYjFkMWU1MDJhMzQ2ZjljNGQ2MGI3YTVlNTdmNDM2YyJ9fX0="
),
VILLAGER_SHEPHERD_SWAMP(//
"Swamp Sheperd Villager", "villager.swamp.sheperd", "27461b58-8e1e-4dbc-a898-8655bb57e77a",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDU0NWM0MWY3NGU1OThiYjE0ZDRlMTUyNGRhMzg1ZDU0YTRhZjYxMDA5YmM5MGRlYTA3MGZhY2NjZjMwYzViMiJ9fX0="
),
VILLAGER_TOOLSMITH_SWAMP(//
"Swamp Toolsmith Villager", "villager.swamp.toolsmith", "17c01e16-ae97-4738-8536-1f2892fcf069",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmM0YjE4Mzk4MWU0ODUxNDYxZTdiYjljNzEwY2Y4ZTViNjU3NWZiMjI0YzJhYzBjNmU1Y2FkM2MyYTVkMmY5OSJ9fX0="
),
VILLAGER_WEAPONSMITH_SWAMP(
"Swamp Weaponsmith Villager", "villager.swamp.weaponsmith", "c30f8885-38c2-4933-bf3b-71e27771b61b",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTUwZjUxNzkxNjBmYTM1NDU0NzUyOTM1MDI2YmViY2Q5ODc1MTk5Njc1MGY1NGVjNWY5NTNhY2I4N2NiZWJjOSJ9fX0="
),
VILLAGER_ARMORER_TAIGA(//
"Taiga Armorer Villager", "villager.taiga.armorer", "451d1524-c582-4ffa-afb0-4bd8d2586dd2",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2YxYmE0MTQ1NGZjNTVkZGUwNzlmYzE1YmYyMTQ2MTg5MWQ1YjVkMDNkZDQwMTZhZTdjZGViNDZhOGZjODQwNCJ9fX0="
),
VILLAGER_BUTCHER_TAIGA(//
"Taiga Butcher Villager", "villager.taiga.butcher", "b2f48a4c-6b6d-4718-8a5a-257f91ec116d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjI4ODdhMGZmNzg5YTdmY2FiOGU3NzhlNzEwZGJhZDBkOTYxZWJiYWFmYTVlZTA0MGUyMTM1YzVkM2E4Y2M3MiJ9fX0="
),
VILLAGER_CARTOGRAPHER_TAIGA(//
"Taiga Cartographer Villager", "villager.taiga.cartographer", "4db86d7e-f9e2-4962-b61f-2e84d07d1653",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzA4OWZiMjQ2YWEyZjNlZjI3NGQwMTQyOWQzNTM0NzE5ZTQyYjFhODE1MWNkYWMzMDM1NGQ0OTY3YjczYTc4MCJ9fX0="
),
VILLAGER_CLERIC_TAIGA(//
"Taiga Cleric Villager", "villager.taiga.cleric", "7c507bcd-766a-4c11-8724-7104cae126f3",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2M1ZGVhOTljMjg2NzE4NTczOGJlMDJiNzFjYWNmMmNkYTRhMjczYzExNDliZTdhNTE2NGRhNzJmMGY5NDZmNyJ9fX0="
),
VILLAGER_FARMER_TAIGA(//
"Taiga Farmer Villager", "villager.taiga.farmer", "d1893e4b-b62f-49ed-8ccb-5d4f6a283c65",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTU3ZjM4M2IwYmRiNDU2OTU3Y2ZiY2M1MmIyODA4YTBmOGEwMGMzNTlkMTVhZWIwMTNmNTYzNGZhMDAzNzkyIn19fQ=="
),
VILLAGER_FISHERMAN_TAIGA(//
"Taiga Fisherman Villager", "villager.taiga.fisherman", "a2eb3270-0257-427e-9889-5e92852b0fec",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWI5YzZlZGI1YzBiYTU0YjIwM2RiZjA0ODI3M2M0YmFkYjE1Y2VjMWQzMjI4NjUwZDJjMmZlNWIzZmFiM2NiYiJ9fX0="
),
VILLAGER_FLETCHER_TAIGA(//
"Taiga Fletcher Villager", "villager.taiga.fletcher", "649921fd-8bef-4986-8386-e2590c401259",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDllMTVmODQ4YjUwODMyMGRiZjE2NGJmMGQ0YmFjODlkNTE3MWYxODZiZWViNDFjN2JkMzQ2MDExYzg3YmQ4YiJ9fX0="
),
VILLAGER_LEATHERWORKER_TAIGA(
"Taiga Leatherworker Villager", "villager.taiga.leatherworker", "e70b3715-29a1-4b6f-a839-93d217ea53ba",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2VhNTI0ZjM3ZjYyYmI1YWQyMWYyZmEzNjU1ZmE2MWZhOGQxNWRjNWE3MjYwNzEyNGFjOGU4NDkwNzUxZmYyNyJ9fX0="
),
VILLAGER_LIBRARIAN_TAIGA(//
"Taiga Librarian Villager", "villager.taiga.librarian", "099b6827-bad3-43e2-9b31-de60e7647e90",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjcxNzYyYzg5Mzk2OWQyN2U3Njk5YjgzNWViODU5ZTIxOTk2YzM2NDUwM2Q4NzBlNTQ1MTBiY2JmOGUzOWFmYiJ9fX0="
),
VILLAGER_MASON_TAIGA(
"Taiga Mason Villager", "villager.taiga.mason", "4b262358-041a-41e0-9db9-da8d20b0b1d6",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDZkZmIwNGVjYTQ5NmU1MjQ3NjNlMjg4Y2YxY2Q1MzZmNGNjN2U5ZmY4MjJlYjI3OWVjZmU3YmU2N2JjYWMzOSJ9fX0="
),
VILLAGER_NITWIT_TAIGA(//
"Taiga Nitwit Villager", "villager.taiga.nitwit", "254ad245-7348-4681-8306-e3aef9f40b0e",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjhhOGMzNDk1OTA3MTVhNmZkNDFhMTc3MDI4MzNlMjdlMThhODFmNGQ2ZGM1Njk4MWUwNTEyNDU5MGUyMGUwYSJ9fX0="
),
VILLAGER_NONE_TAIGA(//
"Taiga Villager", "villager.taiga.none", "7b8fbb6c-e6aa-4b66-bf85-6e9439ea8563",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWE5OTZlNmZiODdkNDAzODU1ODIxODAzYjYwODc3OTRhMTEwMjA2MGIyYjA2NGY1NmQwMDg4YjliMDhmMTk4MiJ9fX0="
),
VILLAGER_SHEPHERD_TAIGA(//
"Taiga Sheperd Villager", "villager.taiga.sheperd", "4308f5e7-591c-4f60-904b-690f445f2785",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjRkNWI2YjQ2OTQ0MzBmNzA2OGQxMDFkNWU4YWE4OTdkM2Q2NjUwODYwNWZiY2ZjNjRkZDNkOTcyYmM0NTUyYyJ9fX0="
),
VILLAGER_TOOLSMITH_TAIGA(//
"Taiga Toolsmith Villager", "villager.taiga.toolsmith", "5bffb69b-616b-49bb-a024-b338abc54e9d",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDFlMmRkNzQ4ZDk2MTkyYTU4N2FjODU0M2ZiZWZjYTQ2MjgyYWQwMDgxMGZhMzM0OTdlZmZlMGM5ZDU2NzhiMCJ9fX0="
),
VILLAGER_WEAPONSMITH_TAIGA(
"Taiga Weaponsmith Villager", "villager.taiga.weaponsmith", "1f2a4e15-4ebe-4183-84bc-181dc8e33045",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTUxMzMxMzE4N2RiM2MwOWJlYzc4NzU1NzdmZmY2ZGM0YjYxZDlmZDA3NDMwZmRhOTBmNzhiOTUwYjQwOTY2OCJ9fX0="
),
/**
VILLAGER_(
""
),
*/
;
//private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
VillagerHeads(String name, String nameString, String texture){
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(VillagerHeads verbosity : VillagerHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) )
return verbosity.getNameString() ;
}
*/
;
private final String owner;
private final String texture;
private final String name;
private final String nameString;
VillagerHeads(String name, String nameString, String ownerUUID, String texture){
this.texture = texture;
this.name = name;
this.owner = ownerUUID;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(VillagerHeads verbosity : VillagerHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) ) {
return verbosity.getNameString() ;
}
}
return null;
}
/**
* @return the owner
*/
public String getOwner() {
return owner;
}
return null;
}
public String getTexture() {
return texture;
}
@@ -469,7 +473,7 @@ public enum VillagerHeads {
public String getName() {
return name;
}
/**
* @return the name
*/
@@ -478,5 +482,5 @@ public enum VillagerHeads {
}
}
@@ -4,118 +4,104 @@ import java.util.UUID;
public enum ZombieVillagerHeads {
// Zombie Villager variants
ZOMBIE_VILLAGER_ARMORER(
"Zombie Armorer", "zombie_villager.armorer",
"7cfb4bb2-3205-42fb-afd6-70fd580fb8a5",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDg2Nzc3NjgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTZhYmY1OWIxMTg3MzRkMzI4ZWQ0YjkwMDVhYTRhMzI1NzE3YjRhMzkyZjM5OWViMmRhNGYxMWEwZjVlM2E4OSJ9fX0="
),
ZOMBIE_VILLAGER_BUTCHER(
"Zombie Butcher", "zombie_villager.butcher",
"6c399981-91ff-4d93-b283-ca9af1228382",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDg3NjU5MjAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzAxZDhiNDcxNmE5ZGE0NWVlNGEzZTRjMmE4Njg0NmFkMWQ3NWE5NzNlYmVlMTgzZjE5YWRjZmI3YTRiYmM2ZiJ9fX0="
),
ZOMBIE_VILLAGER_CARTOGRAPHER(
"Zombie Cartographer", "zombie_villager.cartographer",
"be6c92ff-fd94-4d56-b9ca-20f0050f3b41",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDg4MjU4ODgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGU3YjJmYTVmNmE4MjYxZGM0MzhmMmE0YzE1OTc3YTkxM2ZhNmNjMTRmZTk5ZjQ3ZWJmNzcxN2I3YzJiZDA4ZiJ9fX0="
),
ZOMBIE_VILLAGER_CLERIC(
"Zombie Cleric", "zombie_villager.cleric",
"26a97cfb-4cbc-4f75-b847-8d41201abd49",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDg4ODMxMTAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmEwNWY3ZWFmNjkxYjRhYjc0MTc0MDUyNGNjMjMwYTcwYTNlY2UxZTRjNWFjMmRjNzhlNmYwOGRmOWUzODBhNiJ9fX0="
),
ZOMBIE_VILLAGER_FARMER(
"Zombie Farmer", "zombie_villager.farmer",
"469641b7-ec99-4a62-b597-c7da85426aae",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDg5Mzk0MzIsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjZkZTg4NWMwMzFhYjkwZTcyMmFhMTNkZjg2N2E3NGI4Yzg4NTZhMjkwZWEwOTViNDJiNjQ3ZDdiYzMwZDEzOSJ9fX0="
),
ZOMBIE_VILLAGER_FISHERMAN(
"Zombie Fisherman", "zombie_villager.fisherman",
"1812010d-e392-4c3c-b468-6c9066e26c1b",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDg5OTY4NTAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjFkNGQyMjZkMzk2NWJhZWM0NTFhMTFlMDE2YzVlN2EwOTUxMTRjNDRhYmY1MmUzNGRjNDQ4YWJmZmJhYjI2ZiJ9fX0="
),
ZOMBIE_VILLAGER_FLETCHER(
"Zombie Fletcher", "zombie_villager.fletcher",
"7d20b4d0-05c1-468d-a0fd-e4d8673f9c6e",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDkwNDgzMDgsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTgxOThjZmFmMWExOTIzY2E2MmZmYWY5OTUyOTE2YmE5MzgyNzkxZjBjN2VhZjhiNmQ4MDRlNmMxZTJmNDE0ZiJ9fX0="
),
ZOMBIE_VILLAGER_LEATHERWORKER(
"Zombie Leatherworker", "zombie_villager.leatherworker",
"87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDkxNzQ2OTMsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzlkZjRmYjY3Yjk1OGEzNzViODg0ZWVlNjMwNTM3ZjhmYmQzOWZiY2I3MzczOTgzMzRjNTg2YTA4NDNmMjdkZiJ9fX0="
),
ZOMBIE_VILLAGER_LIBRARIAN(
"Zombie Librarian", "zombie_villager.librarian",
"2069d306-ad23-4bb9-a6d0-d9e2f57757e6",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDkyMzU1OTAsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTQzZDg0ZDdjMTk5MDBjODUzY2RkODBmZTJlNWE1YzQxZTVjM2U2ODNjNzUzYTgxZDZiOWM0ZTZiNDdkNjU4ZSJ9fX0="
),
ZOMBIE_VILLAGER_MASON(
"Zombie Mason", "zombie_villager.Mason",
"87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmI1NTJjOTBmMjEyZTg1NWQxMjI1NWQ1Y2Q2MmVkMzhiOWNkN2UzMGU3M2YwZWE3NzlkMTc2NDMzMGU2OTI2NCJ9fX0="
),
ZOMBIE_VILLAGER_NITWIT(
"Zombie Nitwit", "zombie_villager.nitwit",
"87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDkzMDkwMzksInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzk3MjlhMDgzZTQ4OGExNTEwMGIyYmIxOWVjZTVlZmYzY2MzZmVmYzIzMGI5NzI2NGI3M2U3MTk3NTkwZTI1MyJ9fX0="
),
ZOMBIE_VILLAGER_NONE(
"Zombie Villager", "zombie_villager.none",
"87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDkzNzM2MDEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjdjZTM4Yjk5YzBlNTgxZGY3ZmJlYmYzMWM3NjM3ZjYyNDI1ZGRlMjg1MDhmZWU1OTU3ODE4MTU4OTFhZmQ2In19fQ=="
),
ZOMBIE_VILLAGER_SHEPHERD(
"Zombie Shepherd", "zombie_villager.shepherd",
"47f729f2-a01c-46c8-982b-82d2ac59437f",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDk0NDMyNjQsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTc2NjI4ZGU3ZjUyZjhjNTM2YmZjZDJiMzAwMGEyODJiN2Q1OGY0NjI0NTgxYmEwMGVkZmYwNWRiYWRkZmFhNyJ9fX0="
),
ZOMBIE_VILLAGER_TOOLSMITH(
"Zombie Toolsmith", "zombie_villager.toolsmith",
"87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDk1MTc1MDEsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGVlNTczMmNhMmYxYmIwYmVkYmNiYzk0ODIwYzBmMDE1OTRlNjY4ZWZiZTFjOGQ2OGM3MDEzMDE3NTMxNjcwYyJ9fX0="
),
ZOMBIE_VILLAGER_WEAPONSMITH(
"Zombie Weaponsmith", "zombie_villager.weaponsmith",
"920e0f3f-f4f4-4b99-8eac-509a974a1393",
"eyJ0aW1lc3RhbXAiOjE1ODQ0MDk1ODMwMDUsInByb2ZpbGVJZCI6IjVlNmUzNDRkYmY3ODRiZmU5NjQ2YWRkNjc1YjcyMTVmIiwicHJvZmlsZU5hbWUiOiJKb2VsR29kT2ZXYXIiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWYxZmYzZTc0MDQ0NjczYzkxOTA2MDEyZTI0YzY3NmZmMjA1OGEzZWE5ZjM2NTMzN2FlOWI0YjkwZTg1OTgwIn19fQ=="
),
/**
ZOMBIE_VILLAGER_ARMORER(
"Zombie Armorer", "zombie_villager.armorer", "7cfb4bb2-3205-42fb-afd6-70fd580fb8a5",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTZhYmY1OWIxMTg3MzRkMzI4ZWQ0YjkwMDVhYTRhMzI1NzE3YjRhMzkyZjM5OWViMmRhNGYxMWEwZjVlM2E4OSJ9fX0="
),
ZOMBIE_VILLAGER_BUTCHER(
"Zombie Butcher", "zombie_villager.butcher", "6c399981-91ff-4d93-b283-ca9af1228382",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzAxZDhiNDcxNmE5ZGE0NWVlNGEzZTRjMmE4Njg0NmFkMWQ3NWE5NzNlYmVlMTgzZjE5YWRjZmI3YTRiYmM2ZiJ9fX0="
),
ZOMBIE_VILLAGER_CARTOGRAPHER(
"Zombie Cartographer", "zombie_villager.cartographer", "be6c92ff-fd94-4d56-b9ca-20f0050f3b41",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGU3YjJmYTVmNmE4MjYxZGM0MzhmMmE0YzE1OTc3YTkxM2ZhNmNjMTRmZTk5ZjQ3ZWJmNzcxN2I3YzJiZDA4ZiJ9fX0="
),
ZOMBIE_VILLAGER_CLERIC(
"Zombie Cleric", "zombie_villager.cleric", "26a97cfb-4cbc-4f75-b847-8d41201abd49",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmEwNWY3ZWFmNjkxYjRhYjc0MTc0MDUyNGNjMjMwYTcwYTNlY2UxZTRjNWFjMmRjNzhlNmYwOGRmOWUzODBhNiJ9fX0="
),
ZOMBIE_VILLAGER_FARMER(
"Zombie Farmer", "zombie_villager.farmer", "469641b7-ec99-4a62-b597-c7da85426aae",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjZkZTg4NWMwMzFhYjkwZTcyMmFhMTNkZjg2N2E3NGI4Yzg4NTZhMjkwZWEwOTViNDJiNjQ3ZDdiYzMwZDEzOSJ9fX0="
),
ZOMBIE_VILLAGER_FISHERMAN(
"Zombie Fisherman", "zombie_villager.fisherman", "1812010d-e392-4c3c-b468-6c9066e26c1b",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjFkNGQyMjZkMzk2NWJhZWM0NTFhMTFlMDE2YzVlN2EwOTUxMTRjNDRhYmY1MmUzNGRjNDQ4YWJmZmJhYjI2ZiJ9fX0="
),
ZOMBIE_VILLAGER_FLETCHER(
"Zombie Fletcher", "zombie_villager.fletcher", "7d20b4d0-05c1-468d-a0fd-e4d8673f9c6e",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTgxOThjZmFmMWExOTIzY2E2MmZmYWY5OTUyOTE2YmE5MzgyNzkxZjBjN2VhZjhiNmQ4MDRlNmMxZTJmNDE0ZiJ9fX0="
),
ZOMBIE_VILLAGER_LEATHERWORKER(
"Zombie Leatherworker", "zombie_villager.leatherworker", "87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzlkZjRmYjY3Yjk1OGEzNzViODg0ZWVlNjMwNTM3ZjhmYmQzOWZiY2I3MzczOTgzMzRjNTg2YTA4NDNmMjdkZiJ9fX0="
),
ZOMBIE_VILLAGER_LIBRARIAN(
"Zombie Librarian", "zombie_villager.librarian", "2069d306-ad23-4bb9-a6d0-d9e2f57757e6",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTQzZDg0ZDdjMTk5MDBjODUzY2RkODBmZTJlNWE1YzQxZTVjM2U2ODNjNzUzYTgxZDZiOWM0ZTZiNDdkNjU4ZSJ9fX0="
),
ZOMBIE_VILLAGER_MASON(
"Zombie Mason", "zombie_villager.Mason", "87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmI1NTJjOTBmMjEyZTg1NWQxMjI1NWQ1Y2Q2MmVkMzhiOWNkN2UzMGU3M2YwZWE3NzlkMTc2NDMzMGU2OTI2NCJ9fX0="
),
ZOMBIE_VILLAGER_NITWIT(
"Zombie Nitwit", "zombie_villager.nitwit", "87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzk3MjlhMDgzZTQ4OGExNTEwMGIyYmIxOWVjZTVlZmYzY2MzZmVmYzIzMGI5NzI2NGI3M2U3MTk3NTkwZTI1MyJ9fX0="
),
ZOMBIE_VILLAGER_NONE(
"Zombie Villager", "zombie_villager.none", "87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjdjZTM4Yjk5YzBlNTgxZGY3ZmJlYmYzMWM3NjM3ZjYyNDI1ZGRlMjg1MDhmZWU1OTU3ODE4MTU4OTFhZmQ2In19fQ=="
),
ZOMBIE_VILLAGER_SHEPHERD(
"Zombie Shepherd", "zombie_villager.shepherd", "47f729f2-a01c-46c8-982b-82d2ac59437f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTc2NjI4ZGU3ZjUyZjhjNTM2YmZjZDJiMzAwMGEyODJiN2Q1OGY0NjI0NTgxYmEwMGVkZmYwNWRiYWRkZmFhNyJ9fX0="
),
ZOMBIE_VILLAGER_TOOLSMITH(
"Zombie Toolsmith", "zombie_villager.toolsmith", "87b57113-d8ca-4fa4-8214-ea6896e2ce4f",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGVlNTczMmNhMmYxYmIwYmVkYmNiYzk0ODIwYzBmMDE1OTRlNjY4ZWZiZTFjOGQ2OGM3MDEzMDE3NTMxNjcwYyJ9fX0="
),
ZOMBIE_VILLAGER_WEAPONSMITH(
"Zombie Weaponsmith", "zombie_villager.weaponsmith", "920e0f3f-f4f4-4b99-8eac-509a974a1393",
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWYxZmYzZTc0MDQ0NjczYzkxOTA2MDEyZTI0YzY3NmZmMjA1OGEzZWE5ZjM2NTMzN2FlOWI0YjkwZTg1OTgwIn19fQ=="
),
/**
VILLAGER_(
"",
"",
""
),
*/
;
private final UUID owner;
private final String texture;
private final String name;
private final String nameString;
ZombieVillagerHeads(String name, String nameString, String ownerUUID, String texture){
if(ownerUUID == null){
ownerUUID = UUID.randomUUID().toString();
}
this.owner = UUID.fromString(ownerUUID);
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(ZombieVillagerHeads verbosity : ZombieVillagerHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) )
return verbosity.getNameString() ;
}
*/
;
private final String owner;
private final String texture;
private final String name;
private final String nameString;
return null;
}
public UUID getOwner() {
ZombieVillagerHeads(String name, String nameString, String ownerUUID, String texture){
if(ownerUUID == null){
ownerUUID = UUID.randomUUID().toString();
}
this.owner = ownerUUID;
this.texture = texture;
this.name = name;
this.nameString = nameString;
}
public static final String getNameFromTexture(String texture)
{
//MoreMobHeads.logger.info("texture=" + texture);
for(ZombieVillagerHeads verbosity : ZombieVillagerHeads.values())
{
//MoreMobHeads.logger.info(verbosity.getName() + "=" + verbosity.getTexture());
if(verbosity.getTexture().contains(texture) ) {
return verbosity.getNameString() ;
}
}
return null;
}
public String getOwner() {
return owner;
}
@@ -126,7 +112,7 @@ public enum ZombieVillagerHeads {
public String getName() {
return name;
}
/**
* @return the name
*/
File diff suppressed because it is too large. Load diff
@@ -5,15 +5,14 @@ import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.Plugin;
import com.github.joelgodofwar.mmh.MoreMobHeads;
public class ConfigAPI {
public final static Logger logger = Logger.getLogger("Minecraft");
public static void CheckForConfig(Plugin plugin){
public void CheckForConfig(MoreMobHeads plugin){
try{
if(!plugin.getDataFolder().exists()){
log(": Data Folder doesn't exist");
@@ -27,33 +26,33 @@ public class ConfigAPI {
log("config.yml not found, creating!");
plugin.saveDefaultConfig();
FileConfiguration config = plugin.getConfig();
config.options().copyDefaults(true);
plugin.saveConfig();
}
}catch(Exception e){
e.printStackTrace();
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void Reloadconfig(Plugin plugin){
public static void Reloadconfig(MoreMobHeads plugin){
// Load config.
FileConfiguration config = plugin.getConfig();
String daString = config.getString("debug").replace("'", "") + ",";
if(daString.contains("true")){
MoreMobHeads.debug = true;
plugin.debug = true;
}else{
MoreMobHeads.debug = false;
plugin.debug = false;
}
String daString2 = config.getString("auto_update_check").replace("'", "") + ",";
if(daString2.contains("true")){
MoreMobHeads.UpdateCheck = true;
plugin.UpdateCheck = true;
}else{
MoreMobHeads.UpdateCheck = false;
plugin.UpdateCheck = false;
}
if(MoreMobHeads.debug){log("UpdateCheck = " + MoreMobHeads.UpdateCheck);} //TODO: Logger
if(plugin.debug){log("UpdateCheck = " + plugin.UpdateCheck);} //TODO: Logger
}
public static void log(String dalog){
Bukkit.getLogger().info(dalog);
@@ -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<Object> 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<Object> 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<String> 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);
}
}
@@ -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<MinecraftVersion>, 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());
}
}
@@ -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<SnapshotVersion>, 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.
* <p>
* 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();
}
}
@@ -3,7 +3,13 @@ package com.github.joelgodofwar.mmh.util;
import org.bukkit.ChatColor;
public class StrUtils {
/** StringRight */
/**
* 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);
@@ -14,7 +20,13 @@ public class StrUtils {
}
}
/** StringLeft */
/**
* 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);
@@ -26,7 +38,13 @@ public class StrUtils {
}
}
/** */
/**
* 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++){
@@ -37,6 +55,13 @@ public class StrUtils {
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);
@@ -46,6 +71,13 @@ public class StrUtils {
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;
@@ -1,5 +1,6 @@
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;
@@ -8,15 +9,15 @@ 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);
@@ -24,7 +25,7 @@ public class Utils {
return string;
}
}
public String parsePAPI(Player player, String string) {
if(Bukkit.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null){
return PlaceholderAPI.setPlaceholders(player, string);
@@ -32,7 +33,7 @@ public class Utils {
return string;
}
}
public String parsePAPI(OfflinePlayer player, String string) {
if(Bukkit.getServer().getPluginManager().getPlugin("PlaceholderAPI") != null){
return PlaceholderAPI.setPlaceholders(player, string);
@@ -40,5 +41,75 @@ public class Utils {
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;}
}
}
@@ -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));
}
}
@@ -4,13 +4,15 @@ 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 java.util.Scanner;
//import java.io.IOException;
//import java.io.InputStream;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -18,135 +20,135 @@ import org.w3c.dom.NodeList;
@SuppressWarnings("unused")
public class VersionChecker {
private String pluginName;
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";
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<String> releaseList = new ArrayList<>();
private List<String> developerList = new ArrayList<>();
private String recommendedVersion = "uptodate";
private List<String> developerList = new ArrayList<>();
private String recommendedVersion = "uptodate";
public VersionChecker(JavaPlugin plugin, int projectID, String githubURL) {
this.plugin = 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://spigotmc.org/resources/" + projectID;}
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("<version>", "").replace("</version>", ""));
releaseList.add(element.getElementsByTagName("notes").item(0).getTextContent().replace("<notes>", "").replace("</notes>", ""));
releaseList.add(element.getElementsByTagName("link").item(0).getTextContent().replace("<link>", "").replace("</link>", ""));
}
}
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("<version>", "").replace("</version>", ""));
developerList.add(element.getElementsByTagName("notes").item(0).getTextContent().replace("<notes>", "").replace("</notes>", ""));
developerList.add(element.getElementsByTagName("link").item(0).getTextContent().replace("<link>", "").replace("</link>", ""));
}
}
if (connection != null) {
connection.getInputStream().close();
}
String releaseVersion = releaseList.get(0);
String developerVersion = developerList.get(0);
Bukkit.getLogger().warning(Ansi.LIGHT_YELLOW + "currentVersion=" + currentVersion + Ansi.RESET);
Bukkit.getLogger().warning(Ansi.LIGHT_YELLOW + "releaseVersion=" + releaseVersion + Ansi.RESET);
Bukkit.getLogger().warning(Ansi.LIGHT_YELLOW + "developerVersion=" + developerVersion + Ansi.RESET);
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;
}
}
public VersionChecker(JavaPlugin plugin, int projectID, String githubURL) {
this.plugin = 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("<version>", "").replace("</version>", ""));
releaseList.add(element.getElementsByTagName("notes").item(0).getTextContent().replace("<notes>", "").replace("</notes>", ""));
releaseList.add(element.getElementsByTagName("link").item(0).getTextContent().replace("<link>", "").replace("</link>", ""));
}
}
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("<version>", "").replace("</version>", ""));
developerList.add(element.getElementsByTagName("notes").item(0).getTextContent().replace("<notes>", "").replace("</notes>", ""));
developerList.add(element.getElementsByTagName("link").item(0).getTextContent().replace("<link>", "").replace("</link>", ""));
}
}
if (connection != null) {
connection.getInputStream().close();
}
String releaseVersion = releaseList.get(0);
String developerVersion = developerList.get(0);
Bukkit.getLogger().warning(Ansi.LIGHT_YELLOW + "currentVersion=" + currentVersion + Ansi.RESET);
Bukkit.getLogger().warning(Ansi.LIGHT_YELLOW + "releaseVersion=" + releaseVersion + Ansi.RESET);
Bukkit.getLogger().warning(Ansi.LIGHT_YELLOW + "developerVersion=" + developerVersion + Ansi.RESET);
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<String> getReleaseList() {
return releaseList;
}
public List<String> 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() {
}
public List<String> getReleaseList() {
return releaseList;
}
public List<String> 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{
@@ -176,7 +178,7 @@ public class VersionChecker {
}
}//*/
/**private String[] getLatestVersion(String versionType) {
/**private String[] getLatestVersion(String versionType) {
try {
URL url = new URL(githubURL);
InputStream stream = url.openStream();
@@ -210,7 +212,7 @@ public class VersionChecker {
}
}//*/
/**public static void main(String[] args) {
/**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";
@@ -12,8 +12,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import jdk.internal.joptsimple.internal.Strings;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
@@ -25,99 +23,92 @@ 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<Integer, String> commentContainer = new HashMap<>();
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;
}
private final DumperOptions yamlOptions = new DumperOptions();
private final Representer yamlRepresenter = new YamlRepresenter();
private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions);
private Map<Integer, String> 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;
}
}
@@ -22,10 +22,10 @@ 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){
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