diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 0000000..04a23c4 --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,145 @@ + + + 4.0.0 + me.hulipvp.hcf + HCF + 1.0.0-FORK + + ${basedir}/src/main/java + clean package install + + + . + true + ${basedir}/src/main/resources/ + + *.yml + + + + ${project.artifactId} + + + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + maven-shade-plugin + 3.1.1 + + + package + + shade + + + ${project.artifactId} + + + org.apache.commons.pool2 + ${project.groupId}.lib.org.apache.commons.pool2 + + + org.bson + ${project.groupId}.lib.org.bson + + + redis.clients + ${project.groupId}.lib.redis.clients + + + com.google.gson + ${project.groupId}.lib.com.google.gson + + + com.mongodb + ${project.groupId}.lib.com.mongodb + + + + + + + + + + + org.github.paperspigot + paperspigot-api + 1.7.10-R0.1-SNAPSHOT + provided + + + org.github.paperspigot + paperspigot + 1.7.10-R0.1-SNAPSHOT + provided + + + me.joeleoli.nucleus + nucleus + 1.0-SNAPSHOT + provided + + + commons-lang3 + org.apache.commons + + + httpclient + org.apache.httpcomponents + + + commons-io + commons-io + + + + + com.comphenix.protocol + ProtocolLib + 3.6.5-SNAPSHOT + provided + + + cglib-nodep + cglib + + + BukkitExecutors + com.comphenix.executors + + + + + org.projectlombok + lombok + 1.16.20 + provided + + + net.milkbowl.vault + VaultAPI + LATEST + system + ${project.basedir}/libs/VaultAPI-1.4.jar + + + com.verispvp.core + core + 1.0-SNAPSHOT + system + ${project.basedir}/libs/core-0.0.1-SNAPSHOT.jar + + + + UTF-8 + 1.8 + 1.8 + ${project.groupId}.${project.artifactId} + + diff --git a/libs/Vault.jar b/libs/Vault.jar new file mode 100644 index 0000000..63fc042 Binary files /dev/null and b/libs/Vault.jar differ diff --git a/libs/VaultAPI-1.4.jar b/libs/VaultAPI-1.4.jar new file mode 100644 index 0000000..17a70a2 Binary files /dev/null and b/libs/VaultAPI-1.4.jar differ diff --git a/libs/core-0.0.1-SNAPSHOT.jar b/libs/core-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000..c32bede Binary files /dev/null and b/libs/core-0.0.1-SNAPSHOT.jar differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..daf7794 --- /dev/null +++ b/pom.xml @@ -0,0 +1,172 @@ + + + 4.0.0 + + me.hulipvp.hcf + HCF + 1.0.0-FORK + jar + + + ${project.groupId}.${project.artifactId} + 1.8 + 1.8 + UTF-8 + + + + ${project.artifactId} + clean package install + ${basedir}/src/main/java + + + . + true + ${basedir}/src/main/resources/ + + *.yml + + + + + + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.1 + + + package + + shade + + + ${project.artifactId} + + + org.apache.commons.pool2 + ${project.groupId}.lib.org.apache.commons.pool2 + + + org.bson + ${project.groupId}.lib.org.bson + + + redis.clients + ${project.groupId}.lib.redis.clients + + + com.google.gson + ${project.groupId}.lib.com.google.gson + + + com.mongodb + ${project.groupId}.lib.com.mongodb + + + + + + + + + + + + + + + org.github.paperspigot + paperspigot-api + 1.7.10-R0.1-SNAPSHOT + provided + + + org.github.paperspigot + paperspigot + 1.7.10-R0.1-SNAPSHOT + provided + + + + + + org.mongodb + mongo-java-driver + 3.7.0 + + + redis.clients + jedis + 2.9.0 + + + + + + net.milkbowl.vault + VaultAPI + LATEST + system + ${project.basedir}/libs/VaultAPI-1.4.jar + + + me.joeleoli.nucleus + nucleus + 1.0-SNAPSHOT + provided + + + com.verispvp.core + core + 1.0-SNAPSHOT + system + ${project.basedir}/libs/core-0.0.1-SNAPSHOT.jar + + + com.comphenix.protocol + ProtocolLib + 3.6.5-SNAPSHOT + provided + + + + + + com.google.code.gson + gson + 2.6.2 + + + org.projectlombok + lombok + 1.16.20 + provided + + + + \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/HCF.java b/src/main/java/me/hulipvp/hcf/HCF.java new file mode 100644 index 0000000..3fecbaf --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/HCF.java @@ -0,0 +1,198 @@ +package me.hulipvp.hcf; + +import lombok.Getter; +import me.hulipvp.hcf.api.hooks.plugins.core.CoreHook; +import me.hulipvp.hcf.api.hooks.plugins.nucleus.NucleusHook; +import me.hulipvp.hcf.api.hooks.plugins.PlayerHook; +import me.hulipvp.hcf.api.hooks.plugins.vault.VaultHook; +import me.hulipvp.hcf.api.hooks.server.ProtocolHook; +import me.hulipvp.hcf.api.modules.ModuleManager; +import me.hulipvp.hcf.backend.BackendType; +import me.hulipvp.hcf.backend.HCFBackend; +import me.hulipvp.hcf.backend.backends.MongoBackend; +import me.hulipvp.hcf.backend.backends.RedisBackend; +import me.hulipvp.hcf.backend.creds.MongoCredentials; +import me.hulipvp.hcf.backend.creds.RedisCredentials; +import me.hulipvp.hcf.backend.files.KitsFile; +import me.hulipvp.hcf.backend.files.LocationsFile; +import me.hulipvp.hcf.backend.files.MessagesFile; +import me.hulipvp.hcf.backend.files.ReclaimFile; +import me.hulipvp.hcf.commands.CommandManager; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.koth.Koth; +import me.hulipvp.hcf.game.event.mountain.Mountain; +import me.hulipvp.hcf.game.faction.Faction; +import me.hulipvp.hcf.game.kits.Kit; +import me.hulipvp.hcf.game.player.HCFProfile; +import me.hulipvp.hcf.game.player.data.PlayerLogger; +import me.hulipvp.hcf.game.player.data.mod.item.ModItems; +import me.hulipvp.hcf.game.timer.Timer; +import me.hulipvp.hcf.listeners.ListenerManager; +import me.hulipvp.hcf.ui.board.BoardManager; +import me.hulipvp.hcf.ui.board.provider.HCFScoreboard; +import me.hulipvp.hcf.ui.tab.TablistManager; +import me.hulipvp.hcf.ui.tab.provider.HCFTablist; +import me.hulipvp.hcf.utils.Locale; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +public class HCF extends JavaPlugin { + + @Getter private static HCF instance; + + @Getter private PlayerHook playerHook; + + @Getter private KitsFile kitsFile; + @Getter private LocationsFile locationsFile; + @Getter private MessagesFile messagesFile; + @Getter private ReclaimFile reclaimFile; + + @Getter private HCFBackend backend; + + @Getter private CommandManager commandManager; + @Getter private ListenerManager listenerManager; + @Getter private ModuleManager moduleManager; + @Getter private BoardManager boardManager; + @Getter private TablistManager tablistManager; + + public void onEnable() { + instance = this; + + /*=============================*/ + // Saving config. + saveDefaultConfig(); + + Locale.load(this, false); + /*=============================*/ + + /*=============================*/ + // Loading plugin hooks + if(VaultHook.canHook()) + playerHook = new VaultHook(); + + if(NucleusHook.canHook()) + playerHook = new NucleusHook(); + + if(CoreHook.canHook()) + playerHook = new CoreHook(); + + if(ProtocolHook.canHook()) + new ProtocolHook(); + /*=============================*/ + + /*=============================*/ + // Loading Backend + BackendType type = BackendType.getOrDefault(getConfig().getString("backend.driver")); + switch(type) { + case REDIS: { + backend = new RedisBackend( + new RedisCredentials( + getConfig().getString("backend.redis.host"), + getConfig().getInt("backend.redis.port"), + getConfig().getString("backend.redis.pass") + ) + ); + break; + } + case MONGO: { + backend = new MongoBackend( + new MongoCredentials( + getConfig().getString("backend.mongo.host"), + getConfig().getInt("backend.mongo.port"), + getConfig().getString("backend.mongo.auth.username"), + getConfig().getString("backend.mongo.auth.password"), + getConfig().getString("backend.mongo.database"), + getConfig().getString("backend.mongo.auth.authDb") + ) + ); + break; + } + } + + if(!backend.isLoaded()) { + getLogger().severe("Unable to connect to backend. Shutting down."); + Bukkit.getServer().shutdown(); + } + /*=============================*/ + + /*=============================*/ + // Files + kitsFile = new KitsFile(); + locationsFile = new LocationsFile(); + messagesFile = new MessagesFile(); + reclaimFile = new ReclaimFile(); + /*=============================*/ + + /*=============================*/ + // Scoreboard & Tablist + boardManager = new BoardManager(new HCFScoreboard()); + tablistManager = new TablistManager(new HCFTablist()); + /*=============================*/ + + /*=============================*/ + // Events + listenerManager = new ListenerManager(); + listenerManager.registerListeners(); + /*=============================*/ + + /*=============================*/ + // Commands + commandManager = new CommandManager(); + commandManager.registerCommands(); + /*=============================*/ + + /*=============================*/ + // Instantiating object tasks + Conquest.instate(); + Faction.instate(); + Kit.instate(); + Koth.instate(); + HCFProfile.instate(); + ModItems.instate(); + Mountain.instate(); + Timer.instate(); + /*=============================*/ + + /*=============================*/ + // Loading Data + backend.loadConquests(); + backend.loadKoths(); + backend.loadMountains(); + backend.loadFactions(); + + kitsFile.init(); + locationsFile.init(); + /*=============================*/ + + /*=============================*/ + // Modules + moduleManager = new ModuleManager(); + moduleManager.loadModules(); + /*=============================*/ + } + + public void onDisable() { + if(moduleManager != null) + moduleManager.disableModules(); + + if(backend != null && backend.isLoaded()) { + HCFProfile.getProfiles().values().forEach(backend::saveProfileSync); + Faction.getFactions().values().forEach(backend::saveFactionSync); + Koth.getKoths().values().forEach(backend::saveKothSync); + Conquest.getConquests().values().forEach(backend::saveConquestSync); + Mountain.getMountains().values().forEach(backend::saveMountainSync); + + backend.close(); + } + + if(boardManager != null) + boardManager.onDisable(); + + if(tablistManager != null) + tablistManager.onDisable(); + + reclaimFile.saveReclaimed(); + + PlayerLogger.despawnVillagers(); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/chat/ArrayWrapper.java b/src/main/java/me/hulipvp/hcf/api/chat/ArrayWrapper.java new file mode 100644 index 0000000..0b80883 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/chat/ArrayWrapper.java @@ -0,0 +1,111 @@ +package me.hulipvp.hcf.api.chat; + +import org.apache.commons.lang.Validate; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; + +/** + * Represents a wrapper around an array class of an arbitrary reference type, + * which properly implements "value" hash code and equality functions. + *

+ * This class is intended for use as a key to a map. + *

+ * + * @param The type of elements in the array. + * @author Glen Husman + * @see Arrays + */ +public final class ArrayWrapper { + + /** + * Creates an array wrapper with some elements. + * + * @param elements The elements of the array. + */ + @SafeVarargs + public ArrayWrapper(E... elements) { + setArray(elements); + } + + private E[] _array; + + /** + * Retrieves a reference to the wrapped array instance. + * + * @return The array wrapped by this instance. + */ + public E[] getArray() { + return _array; + } + + /** + * Set this wrapper to wrap a punish array instance. + * + * @param array The punish wrapped array. + */ + public void setArray(E[] array) { + Validate.notNull(array, "The array must not be null."); + _array = array; + } + + /** + * Determines if this object has a value equivalent to another object. + * + * @see Arrays#equals(Object[], Object[]) + */ + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object other) { + if(!(other instanceof ArrayWrapper)) { + return false; + } + return Arrays.equals(_array, ((ArrayWrapper) other)._array); + } + + /** + * Gets the hash code represented by this objects value. + * + * @return This object's hash code. + * @see Arrays#hashCode(Object[]) + */ + @Override + public int hashCode() { + return Arrays.hashCode(_array); + } + + /** + * Converts an iterable element collection to an array of elements. + * The iteration order of the specified object will be used as the array element order. + * + * @param list The iterable of objects which will be converted to an array. + * @param c The type of the elements of the array. + * @return An array of elements in the specified iterable. + */ + @SuppressWarnings("unchecked") + public static T[] toArray(Iterable list, Class c) { + int size = -1; + if(list instanceof Collection) { + @SuppressWarnings("rawtypes") Collection coll = (Collection) list; + size = coll.size(); + } + + + if(size < 0) { + size = 0; + // Ugly hack: Count it ourselves + for(@SuppressWarnings("unused") T element : list) { + size++; + } + } + + T[] result = (T[]) Array.newInstance(c, size); + int i = 0; + for(T element : list) { // Assumes iteration order is consistent + result[i++] = element; // Assign array element at index THEN increment counter + } + return result; + } + +} diff --git a/src/main/java/me/hulipvp/hcf/api/chat/C.java b/src/main/java/me/hulipvp/hcf/api/chat/C.java new file mode 100644 index 0000000..302a8ac --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/chat/C.java @@ -0,0 +1,23 @@ +package me.hulipvp.hcf.api.chat; + +import org.bukkit.ChatColor; + +import java.util.List; +import java.util.stream.Collectors; + +public class C { + + public static String color(String input) { + return ChatColor.translateAlternateColorCodes('&', input); + } + + public static String strip(String input) { + return ChatColor.stripColor(input); + } + + public static List color(List input) { + return input.stream() + .map(C::color) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/chat/FancyMessage.java b/src/main/java/me/hulipvp/hcf/api/chat/FancyMessage.java new file mode 100644 index 0000000..df1e8bb --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/chat/FancyMessage.java @@ -0,0 +1,648 @@ +package me.hulipvp.hcf.api.chat; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonWriter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.*; +import java.util.logging.Level; + +/** + * Represents a formattable message. Such messages can use elements such as colors, formatting codes, hover and click backend, and other features provided by the vanilla Minecraft JSON message formatter. + * This class allows plugins to emulate the functionality of the vanilla Minecraft tellraw command. + *

+ * This class follows the builder pattern, allowing for method chaining. + * It is set up such that invocations of property-setting methods will affect the current kits component, + * and a call to {@link #then()} or {@link #then(String)} will append a punish kits component to the end of the message, + * optionally initializing it with text. Further property-setting method calls will affect that kits component. + *

+ */ +public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable, ConfigurationSerializable { + + static { + ConfigurationSerialization.registerClass(FancyMessage.class); + } + + private List messageParts; + private String jsonString; + private boolean dirty; + + @Override + public FancyMessage clone() throws CloneNotSupportedException { + FancyMessage instance = (FancyMessage) super.clone(); + instance.messageParts = new ArrayList(messageParts.size()); + for(int i = 0; i < messageParts.size(); i++) { + instance.messageParts.add(i, messageParts.get(i).clone()); + } + instance.dirty = false; + instance.jsonString = null; + return instance; + } + + /** + * Creates a JSON message with text. + * + * @param firstPartText The existing text in the message. + */ + public FancyMessage(final String firstPartText) { + this(TextualComponent.rawText(firstPartText)); + } + + public FancyMessage(final TextualComponent firstPartText) { + messageParts = new ArrayList(); + messageParts.add(new MessagePart(firstPartText)); + jsonString = null; + dirty = false; + } + + /** + * Creates a JSON message without text. + */ + public FancyMessage() { + this((TextualComponent) null); + } + + /** + * Sets the text of the current kits component to a value. + * + * @param text The punish text of the current kits component. + * @return This builder instance. + */ + public FancyMessage text(String text) { + MessagePart latest = latest(); + latest.text = TextualComponent.rawText(text); + dirty = true; + return this; + } + + /** + * Sets the text of the current kits component to a value. + * + * @param text The punish text of the current kits component. + * @return This builder instance. + */ + public FancyMessage text(TextualComponent text) { + MessagePart latest = latest(); + latest.text = text; + dirty = true; + return this; + } + + /** + * Sets the color of the current kits component to a value. + * + * @param color The punish color of the current kits component. + * @return This builder instance. + * @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value). + */ + public FancyMessage color(final ChatColor color) { + if(!color.isColor()) { + throw new IllegalArgumentException(color.name() + " is not a color"); + } + latest().color = color; + dirty = true; + return this; + } + + /** + * Sets the stylization of the current kits component. + * + * @param styles The array of styles to apply to the kits component. + * @return This builder instance. + * @throws IllegalArgumentException If any of the enumeration values in the array do not represent formatters. + */ + public FancyMessage style(ChatColor... styles) { + for(final ChatColor style : styles) { + if(!style.isFormat()) { + throw new IllegalArgumentException(style.name() + " is not a style"); + } + } + latest().styles.addAll(Arrays.asList(styles)); + dirty = true; + return this; + } + + /** + * Set the behavior of the current kits component to instruct the client to open a file on the client side filesystem when the currently edited part of the {@code FancyMessage} is clicked. + * + * @param path The path of the file on the client filesystem. + * @return This builder instance. + */ + public FancyMessage file(final String path) { + onClick("open_file", path); + return this; + } + + /** + * Set the behavior of the current kits component to instruct the client to open a webpage in the client's web browser when the currently edited part of the {@code FancyMessage} is clicked. + * + * @param url The URL of the page to open when the link is clicked. + * @return This builder instance. + */ + public FancyMessage link(final String url) { + onClick("open_url", url); + return this; + } + + /** + * Set the behavior of the current kits component to instruct the client to replace the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is clicked. + * The client will not immediately send the command to the server to be executed unless the client profiles submits the command/chat message, usually with the enter key. + * + * @param command The text to display in the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage suggest(final String command) { + onClick("suggest_command", command); + return this; + } + + /** + * Set the behavior of the current kits component to instruct the client to append the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is SHIFT-CLICKED. + * The client will not immediately send the command to the server to be executed unless the client profiles submits the command/chat message, usually with the enter key. + * + * @param command The text to append to the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage insert(final String command) { + latest().insertionData = command; + dirty = true; + return this; + } + + /** + * Set the behavior of the current kits component to instruct the client to send the specified string to the server as a chat message when the currently edited part of the {@code FancyMessage} is clicked. + * The client will immediately send the command to the server to be executed when the kits component is clicked. + * + * @param command The text to display in the chat bar of the client. + * @return This builder instance. + */ + public FancyMessage command(final String command) { + onClick("run_command", command); + return this; + } + + /** + * Set the behavior of the current kits component to display information about an achievement when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param name The name of the achievement to display, excluding the "achievement." prefix. + * @return This builder instance. + */ + public FancyMessage achievementTooltip(final String name) { + onHover("show_achievement", new JsonString("achievement." + name)); + return this; + } + + /** + * Set the behavior of the current kits component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param text The text, which supports newlines, which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage tooltip(final String text) { + onHover("show_text", new JsonString(text)); + return this; + } + + /** + * Set the behavior of the current kits component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. + * @return This builder instance. + */ + public FancyMessage tooltip(final Iterable lines) { + tooltip(ArrayWrapper.toArray(lines, String.class)); + return this; + } + + /** + * Set the behavior of the current kits component to display raw text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage tooltip(final String... lines) { + StringBuilder builder = new StringBuilder(); + for(int i = 0; i < lines.length; i++) { + builder.append(lines[i]); + if(i != lines.length - 1) { + builder.append('\n'); + } + } + tooltip(builder.toString()); + return this; + } + + /** + * Set the behavior of the current kits component to display formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param text The formatted text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(FancyMessage text) { + for(MessagePart component : text.messageParts) { + if(component.clickActionData != null && component.clickActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have click backend."); + } else if(component.hoverActionData != null && component.hoverActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); + } + } + onHover("show_text", text); + return this; + } + + /** + * Set the behavior of the current kits component to display the specified lines of formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of formatted text which will be displayed to the client upon hovering. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(FancyMessage... lines) { + if(lines.length < 1) { + onHover(null, null); // Clear tooltip + return this; + } + + FancyMessage result = new FancyMessage(); + result.messageParts.clear(); // Remove the one existing text component that exists by default, which destabilizes the object + + for(int i = 0; i < lines.length; i++) { + try { + for(MessagePart component : lines[i]) { + if(component.clickActionData != null && component.clickActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have click backend."); + } else if(component.hoverActionData != null && component.hoverActionName != null) { + throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); + } + if(component.hasText()) { + result.messageParts.add(component.clone()); + } + } + if(i != lines.length - 1) { + result.messageParts.add(new MessagePart(TextualComponent.rawText("\n"))); + } + } catch(CloneNotSupportedException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to clone object", e); + return this; + } + } + return formattedTooltip(result.messageParts.isEmpty() ? null : result); // Throws NPE if size is 0, intended + } + + /** + * Set the behavior of the current kits component to display the specified lines of formatted text when the client hovers over the text. + *

Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.

+ * + * @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. + * @return This builder instance. + */ + public FancyMessage formattedTooltip(final Iterable lines) { + return formattedTooltip(ArrayWrapper.toArray(lines, FancyMessage.class)); + } + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final String... replacements) { + for(String str : replacements) { + latest().translationReplacements.add(new JsonString(str)); + } + dirty = true; + + return this; + } + /* + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ /* ------------ + public FancyMessage translationReplacements(final Iterable replacements){ + for (CharSequence str : replacements){ + latest().translationReplacements.add(punish JsonString(str)); + } + + return this; + } + + */ + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final FancyMessage... replacements) { + for(FancyMessage str : replacements) { + latest().translationReplacements.add(str); + } + + dirty = true; + + return this; + } + + /** + * If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message. + * + * @param replacements The replacements, in order, that will be used in the language-specific message. + * @return This builder instance. + */ + public FancyMessage translationReplacements(final Iterable replacements) { + return translationReplacements(ArrayWrapper.toArray(replacements, FancyMessage.class)); + } + + /** + * Terminate construction of the current kits component, and begin construction of a punish message component. + * After a successful call to this method, all setter methods will refer to a punish message component, created as a result of the call to this method. + * + * @param text The text which will populate the punish message component. + * @return This builder instance. + */ + public FancyMessage then(final String text) { + return then(TextualComponent.rawText(text)); + } + + /** + * Terminate construction of the current kits component, and begin construction of a punish message component. + * After a successful call to this method, all setter methods will refer to a punish message component, created as a result of the call to this method. + * + * @param text The text which will populate the punish message component. + * @return This builder instance. + */ + public FancyMessage then(final TextualComponent text) { + if(!latest().hasText()) { + throw new IllegalStateException("previous message part has no text"); + } + messageParts.add(new MessagePart(text)); + dirty = true; + return this; + } + + /** + * Terminate construction of the current kits component, and begin construction of a punish message component. + * After a successful call to this method, all setter methods will refer to a punish message component, created as a result of the call to this method. + * + * @return This builder instance. + */ + public FancyMessage then() { + if(!latest().hasText()) + throw new IllegalStateException("previous message part has no text"); + + messageParts.add(new MessagePart()); + dirty = true; + return this; + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + if(messageParts.size() == 1) { + latest().writeJson(writer); + } else { + writer.beginObject().name("text").value("").name("me/hulipvp/hcf/api/chat").beginArray(); + for(final MessagePart part : this) { + part.writeJson(writer); + } + writer.endArray().endObject(); + } + } + + /** + * Serialize this fancy message, converting it into syntactically-valid JSON using a {@link JsonWriter}. + * This JSON should be compatible with vanilla formatter commands such as {@code /tellraw}. + * + * @return The JSON string representing this object. + */ + public String toJSONString() { + if(!dirty && jsonString != null) { + return jsonString; + } + StringWriter string = new StringWriter(); + JsonWriter json = new JsonWriter(string); + try { + writeJson(json); + json.close(); + } catch(IOException e) { + throw new RuntimeException("invalid message"); + } + jsonString = string.toString(); + dirty = false; + return jsonString; + } + + /** + * Sends this message to a profiles. The profiles will receive the fully-fledged formatted display of this message. + * + * @param player The profiles who will receive the message. + */ + public void send(Player player) { + send(player, toJSONString()); + } + + private void send(CommandSender sender, String jsonString) { + if(!(sender instanceof Player)) { + sender.sendMessage(toOldMessageFormat()); + return; + } + Player player = (Player) sender; + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + player.getName() + " " + jsonString); + } + + /** + * Sends this message to a command sender. + * If the sender is a profiles, they will receive the fully-fledged formatted display of this message. + * Otherwise, they will receive a version of this message with less formatting. + * + * @param sender The command sender who will receive the message. + * @see #toOldMessageFormat() + */ + public void send(CommandSender sender) { + send(sender, toJSONString()); + } + + /** + * Sends this message to multiple command senders. + * + * @param senders The command senders who will receive the message. + * @see #send(CommandSender) + */ + public void send(final Iterable senders) { + String string = toJSONString(); + for(final CommandSender sender : senders) { + send(sender, string); + } + } + + /** + * Convert this message to a human-readable string with limited formatting. + * This method is used to send this message to clients without JSON formatting support. + *

+ * Serialization of this message by using this message will include (in this order for each message part): + *

    + *
  1. The color of each message part.
  2. + *
  3. The applicable stylizations for each message part.
  4. + *
  5. The core text of the message part.
  6. + *
+ * The primary omissions are tooltips and clickable actions. Consequently, this method should be used only as a last resort. + *

+ *

+ * Color and formatting can be removed from the returned string by using {@link ChatColor#stripColor(String)}.

+ * + * @return A human-readable string representing limited formatting in addition to the core text of this message. + */ + public String toOldMessageFormat() { + StringBuilder result = new StringBuilder(); + for(MessagePart part : this) { + result.append(part.color == null ? "" : part.color); + for(ChatColor formatSpecifier : part.styles) { + result.append(formatSpecifier); + } + result.append(part.text); + } + return result.toString(); + } + + private MessagePart latest() { + return messageParts.get(messageParts.size() - 1); + } + + private void onClick(final String name, final String data) { + final MessagePart latest = latest(); + latest.clickActionName = name; + latest.clickActionData = data; + dirty = true; + } + + private void onHover(final String name, final JsonRepresentedObject data) { + final MessagePart latest = latest(); + latest.hoverActionName = name; + latest.hoverActionData = data; + dirty = true; + } + + // Doc copied from interface + public Map serialize() { + HashMap map = new HashMap(); + map.put("messageParts", messageParts); + // map.put("JSON", toJSONString()); + return map; + } + + /** + * Deserializes a JSON-represented message from a mapping of key-value pairs. + * This is called by the Bukkit serialization API. + * It is not intended for direct public API consumption. + * + * @param serialized The key-value mapping which represents a fancy message. + */ + @SuppressWarnings("unchecked") + public static FancyMessage deserialize(Map serialized) { + FancyMessage msg = new FancyMessage(); + msg.messageParts = (List) serialized.get("messageParts"); + msg.jsonString = serialized.containsKey("JSON") ? serialized.get("JSON").toString() : null; + msg.dirty = !serialized.containsKey("JSON"); + return msg; + } + + /** + * Internally called method. Not for API consumption. + */ + public Iterator iterator() { + return messageParts.iterator(); + } + + private static JsonParser _stringParser = new JsonParser(); + + /** + * Deserializes a fancy message from its JSON representation. This JSON representation is of the format of + * that returned by {@link #toJSONString()}, and is compatible with vanilla inputs. + * + * @param json The JSON string which represents a fancy message. + * @return A {@code FancyMessage} representing the parameterized JSON message. + */ + public static FancyMessage deserialize(String json) { + JsonObject serialized = _stringParser.parse(json).getAsJsonObject(); + JsonArray extra = serialized.getAsJsonArray("me/hulipvp/hcf/api/chat"); // Get the utils component + FancyMessage returnVal = new FancyMessage(); + returnVal.messageParts.clear(); + for(JsonElement mPrt : extra) { + MessagePart component = new MessagePart(); + JsonObject messagePart = mPrt.getAsJsonObject(); + for(Map.Entry entry : messagePart.entrySet()) { + // Deserialize text + if(TextualComponent.isTextKey(entry.getKey())) { + // The map mimics the YAML serialization, which has a "key" field and one or more "value" fields + Map serializedMapForm = new HashMap(); // Must be object due to Bukkit serializer API compliance + serializedMapForm.put("key", entry.getKey()); + if(entry.getValue().isJsonPrimitive()) { + // Assume string + serializedMapForm.put("value", entry.getValue().getAsString()); + } else { + // Composite object, but we assume each element is a string + for(Map.Entry compositeNestedElement : entry.getValue().getAsJsonObject().entrySet()) { + serializedMapForm.put("value." + compositeNestedElement.getKey(), compositeNestedElement.getValue().getAsString()); + } + } + component.text = TextualComponent.deserialize(serializedMapForm); + } else if(MessagePart.stylesToNames.inverse().containsKey(entry.getKey())) { + if(entry.getValue().getAsBoolean()) { + component.styles.add(MessagePart.stylesToNames.inverse().get(entry.getKey())); + } + } else if(entry.getKey().equals("color")) { + component.color = ChatColor.valueOf(entry.getValue().getAsString().toUpperCase()); + } else if(entry.getKey().equals("clickEvent")) { + JsonObject object = entry.getValue().getAsJsonObject(); + component.clickActionName = object.get("action").getAsString(); + component.clickActionData = object.get("value").getAsString(); + } else if(entry.getKey().equals("hoverEvent")) { + JsonObject object = entry.getValue().getAsJsonObject(); + component.hoverActionName = object.get("action").getAsString(); + if(object.get("value").isJsonPrimitive()) { + // Assume string + component.hoverActionData = new JsonString(object.get("value").getAsString()); + } else { + // Assume composite type + // The only composite type we currently store is another FancyMessage + // Therefore, recursion time! + component.hoverActionData = deserialize(object.get("value").toString() /* This should properly serialize the JSON object as a JSON string */); + } + } else if(entry.getKey().equals("insertion")) { + component.insertionData = entry.getValue().getAsString(); + } else if(entry.getKey().equals("with")) { + for(JsonElement object : entry.getValue().getAsJsonArray()) { + if(object.isJsonPrimitive()) { + component.translationReplacements.add(new JsonString(object.getAsString())); + } else { + // Only composite type stored in this array is - again - FancyMessages + // Recurse within this function to parse this as a translation replacement + component.translationReplacements.add(deserialize(object.toString())); + } + } + } + } + returnVal.messageParts.add(component); + } + return returnVal; + } + +} diff --git a/src/main/java/me/hulipvp/hcf/api/chat/JsonRepresentedObject.java b/src/main/java/me/hulipvp/hcf/api/chat/JsonRepresentedObject.java new file mode 100644 index 0000000..c80264e --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/chat/JsonRepresentedObject.java @@ -0,0 +1,20 @@ +package me.hulipvp.hcf.api.chat; + +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +/** + * Represents an object that can be serialized to a JSON writer instance. + */ +interface JsonRepresentedObject { + + /** + * Writes the JSON representation of this object to the specified writer. + * + * @param writer The JSON writer which will receive the object. + * @throws IOException If an error occurs writing to the stream. + */ + void writeJson(JsonWriter writer) throws IOException; + +} diff --git a/src/main/java/me/hulipvp/hcf/api/chat/JsonString.java b/src/main/java/me/hulipvp/hcf/api/chat/JsonString.java new file mode 100644 index 0000000..acd84a7 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/chat/JsonString.java @@ -0,0 +1,47 @@ +package me.hulipvp.hcf.api.chat; + +import com.google.gson.stream.JsonWriter; +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a JSON string value. + * Writes by this object will not write name values nor begin/end objects in the JSON stream. + * All writes merely write the represented string value. + */ +final class JsonString implements JsonRepresentedObject, ConfigurationSerializable { + + private String _value; + + public JsonString(CharSequence value) { + _value = value == null ? null : value.toString(); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.value(getValue()); + } + + public String getValue() { + return _value; + } + + public Map serialize() { + HashMap theSingleValue = new HashMap(); + theSingleValue.put("stringValue", _value); + return theSingleValue; + } + + public static JsonString deserialize(Map map) { + return new JsonString(map.get("stringValue").toString()); + } + + @Override + public String toString() { + return _value; + } + +} diff --git a/src/main/java/me/hulipvp/hcf/api/chat/MessagePart.java b/src/main/java/me/hulipvp/hcf/api/chat/MessagePart.java new file mode 100644 index 0000000..78619e4 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/chat/MessagePart.java @@ -0,0 +1,148 @@ +package me.hulipvp.hcf.api.chat; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.gson.stream.JsonWriter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +/** + * Internal class: Represents a component of a JSON-serializable {@link FancyMessage}. + */ +final class MessagePart implements JsonRepresentedObject, ConfigurationSerializable, Cloneable { + + ChatColor color = ChatColor.WHITE; + ArrayList styles = new ArrayList(); + String clickActionName = null, clickActionData = null, hoverActionName = null; + JsonRepresentedObject hoverActionData = null; + TextualComponent text = null; + String insertionData = null; + ArrayList translationReplacements = new ArrayList(); + + MessagePart(final TextualComponent text) { + this.text = text; + } + + MessagePart() { + this.text = null; + } + + boolean hasText() { + return text != null; + } + + @Override + @SuppressWarnings("unchecked") + public MessagePart clone() throws CloneNotSupportedException { + MessagePart obj = (MessagePart) super.clone(); + obj.styles = (ArrayList) styles.clone(); + if(hoverActionData instanceof JsonString) { + obj.hoverActionData = new JsonString(((JsonString) hoverActionData).getValue()); + } else if(hoverActionData instanceof FancyMessage) { + obj.hoverActionData = ((FancyMessage) hoverActionData).clone(); + } + obj.translationReplacements = (ArrayList) translationReplacements.clone(); + return obj; + + } + + static final BiMap stylesToNames; + + static { + ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); + for(final ChatColor style : ChatColor.values()) { + if(!style.isFormat()) { + continue; + } + + String styleName; + switch(style) { + case MAGIC: + styleName = "obfuscated"; + break; + case UNDERLINE: + styleName = "underlined"; + break; + default: + styleName = style.name().toLowerCase(); + break; + } + + builder.put(style, styleName); + } + stylesToNames = builder.build(); + } + + public void writeJson(JsonWriter json) { + try { + json.beginObject(); + text.writeJson(json); + json.name("color").value(color.name().toLowerCase()); + for(final ChatColor style : styles) { + json.name(stylesToNames.get(style)).value(true); + } + if(clickActionName != null && clickActionData != null) { + json.name("clickEvent").beginObject().name("action").value(clickActionName).name("value").value(clickActionData).endObject(); + } + if(hoverActionName != null && hoverActionData != null) { + json.name("hoverEvent").beginObject().name("action").value(hoverActionName).name("value"); + hoverActionData.writeJson(json); + json.endObject(); + } + if(insertionData != null) { + json.name("insertion").value(insertionData); + } + if(translationReplacements.size() > 0 && text != null && TextualComponent.isTranslatableText(text)) { + json.name("with").beginArray(); + for(JsonRepresentedObject obj : translationReplacements) { + obj.writeJson(json); + } + json.endArray(); + } + json.endObject(); + } catch(IOException e) { + Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e); + } + } + + public Map serialize() { + HashMap map = new HashMap(); + map.put("text", text); + map.put("styles", styles); + map.put("color", color.getChar()); + map.put("hoverActionName", hoverActionName); + map.put("hoverActionData", hoverActionData); + map.put("clickActionName", clickActionName); + map.put("clickActionData", clickActionData); + map.put("insertion", insertionData); + map.put("translationReplacements", translationReplacements); + return map; + } + + @SuppressWarnings("unchecked") + public static MessagePart deserialize(Map serialized) { + MessagePart part = new MessagePart((TextualComponent) serialized.get("text")); + part.styles = (ArrayList) serialized.get("styles"); + part.color = ChatColor.getByChar(serialized.get("color").toString()); + part.hoverActionName = (String) serialized.get("hoverActionName"); + part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData"); + part.clickActionName = (String) serialized.get("clickActionName"); + part.clickActionData = (String) serialized.get("clickActionData"); + part.insertionData = (String) serialized.get("insertion"); + part.translationReplacements = (ArrayList) serialized.get("translationReplacements"); + return part; + } + + static { + ConfigurationSerialization.registerClass(MessagePart.class); + } + +} diff --git a/src/main/java/me/hulipvp/hcf/api/chat/TextualComponent.java b/src/main/java/me/hulipvp/hcf/api/chat/TextualComponent.java new file mode 100644 index 0000000..dba7d3f --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/chat/TextualComponent.java @@ -0,0 +1,294 @@ +package me.hulipvp.hcf.api.chat; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.gson.stream.JsonWriter; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a textual component of a message part. + * This can be used to not only represent string literals in a JSON message, + * but also to represent localized strings and other text values. + *

Different instances of this class can be created with static constructor methods.

+ */ +public abstract class TextualComponent implements Cloneable { + + static { + ConfigurationSerialization.registerClass(ArbitraryTextTypeComponent.class); + ConfigurationSerialization.registerClass(ComplexTextTypeComponent.class); + } + + @Override + public String toString() { + return getReadableString(); + } + + /** + * @return The JSON key used to represent text components of this type. + */ + public abstract String getKey(); + + /** + * @return A readable String + */ + public abstract String getReadableString(); + + /** + * Clones a textual component instance. + * The returned object should not reference this textual component instance, but should maintain the same key and value. + */ + @Override + public abstract TextualComponent clone() throws CloneNotSupportedException; + + /** + * Writes the text backend represented by this textual component to the specified JSON writer object. + * A punish object within the writer is not started. + * + * @param writer The object to which to write the JSON backend. + * @throws IOException If an error occurs while writing to the stream. + */ + public abstract void writeJson(JsonWriter writer) throws IOException; + + static TextualComponent deserialize(Map map) { + if(map.containsKey("key") && map.size() == 2 && map.containsKey("value")) { + // Arbitrary text component + return ArbitraryTextTypeComponent.deserialize(map); + } else if(map.size() >= 2 && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) { + // Complex JSON object + return ComplexTextTypeComponent.deserialize(map); + } + + return null; + } + + static boolean isTextKey(String key) { + return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector"); + } + + static boolean isTranslatableText(TextualComponent component) { + return component instanceof ComplexTextTypeComponent && ((ComplexTextTypeComponent) component).getKey().equals("translate"); + } + + /** + * Internal class used to represent all types of text components. + * Exception validating done is on keys and values. + */ + private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable { + + public ArbitraryTextTypeComponent(String key, String value) { + setKey(key); + setValue(value); + } + + @Override + public String getKey() { + return _key; + } + + public void setKey(String key) { + Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified."); + _key = key; + } + + public String getValue() { + return _value; + } + + public void setValue(String value) { + Preconditions.checkArgument(value != null, "The value must be specified."); + _value = value; + } + + private String _key; + private String _value; + + @Override + public TextualComponent clone() throws CloneNotSupportedException { + // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone + return new ArbitraryTextTypeComponent(getKey(), getValue()); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.name(getKey()).value(getValue()); + } + + @SuppressWarnings("serial") + public Map serialize() { + return new HashMap() {{ + put("key", getKey()); + put("value", getValue()); + }}; + } + + public static ArbitraryTextTypeComponent deserialize(Map map) { + return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString()); + } + + @Override + public String getReadableString() { + return getValue(); + } + } + + /** + * Internal class used to represent a text component with a nested JSON value. + * Exception validating done is on keys and values. + */ + private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable { + + public ComplexTextTypeComponent(String key, Map values) { + setKey(key); + setValue(values); + } + + @Override + public String getKey() { + return _key; + } + + public void setKey(String key) { + Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified."); + _key = key; + } + + public Map getValue() { + return _value; + } + + public void setValue(Map value) { + Preconditions.checkArgument(value != null, "The value must be specified."); + _value = value; + } + + private String _key; + private Map _value; + + @Override + public TextualComponent clone() throws CloneNotSupportedException { + // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone + return new ComplexTextTypeComponent(getKey(), getValue()); + } + + @Override + public void writeJson(JsonWriter writer) throws IOException { + writer.name(getKey()); + writer.beginObject(); + for(Map.Entry jsonPair : _value.entrySet()) { + writer.name(jsonPair.getKey()).value(jsonPair.getValue()); + } + writer.endObject(); + } + + @SuppressWarnings("serial") + public Map serialize() { + return new HashMap() {{ + put("key", getKey()); + for(Entry valEntry : getValue().entrySet()) { + put("value." + valEntry.getKey(), valEntry.getValue()); + } + }}; + } + + public static ComplexTextTypeComponent deserialize(Map map) { + String key = null; + Map value = new HashMap(); + for(Map.Entry valEntry : map.entrySet()) { + if(valEntry.getKey().equals("key")) { + key = (String) valEntry.getValue(); + } else if(valEntry.getKey().startsWith("value.")) { + value.put(((String) valEntry.getKey()).substring(6) /* Strips out the value prefix */, valEntry.getValue().toString()); + } + } + return new ComplexTextTypeComponent(key, value); + } + + @Override + public String getReadableString() { + return getKey(); + } + } + + /** + * Create a textual component representing a string literal. + * This is the default type of textual component when a single string literal is given to a method. + * + * @param textValue The text which will be represented. + * @return The text component representing the specified literal text. + */ + public static TextualComponent rawText(String textValue) { + return new ArbitraryTextTypeComponent("text", textValue); + } + + + /** + * Create a textual component representing a localized string. + * The client will see this text component as their localized version of the specified string key, which can be overridden by a resource pack. + *

+ * If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to the client. + *

+ * + * @param translateKey The string key which maps to localized text. + * @return The text component representing the specified localized text. + */ + public static TextualComponent localizedText(String translateKey) { + return new ArbitraryTextTypeComponent("translate", translateKey); + } + + private static void throwUnsupportedSnapshot() { + throw new UnsupportedOperationException("This feature is only supported in snapshot releases."); + } + + /** + * Create a textual component representing a me.joeleoli.practice.board value. + * The client will see their own score for the specified objective as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param scoreboardObjective The name of the objective for which to display the score. + * @return The text component representing the specified me.joeleoli.practice.board score (for the viewing profiles), or {@code null} if an error occurs during JSON serialization. + */ + public static TextualComponent objectiveScore(String scoreboardObjective) { + return objectiveScore("*", scoreboardObjective); + } + + /** + * Create a textual component representing a me.joeleoli.practice.board value. + * The client will see the score of the specified profiles for the specified objective as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param playerName The name of the profiles whos score will be shown. If this string represents the single-character sequence "*", the viewing profiles's score will be displayed. + * Standard minecraft selectors (@a, @p, etc) are not supported. + * @param scoreboardObjective The name of the objective for which to display the score. + * @return The text component representing the specified me.joeleoli.practice.board score for the specified profiles, or {@code null} if an error occurs during JSON serialization. + */ + public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) { + throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly + + return new ComplexTextTypeComponent("score", ImmutableMap.builder().put("name", playerName).put("objective", scoreboardObjective).build()); + } + + /** + * Create a textual component representing a profiles name, retrievable by using a standard minecraft selector. + * The client will see the players or entities captured by the specified selector as the text represented by this component. + *

+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients. + *

+ * + * @param selector The minecraft profiles or entity selector which will capture the entities whose string representations will be displayed in the place of this text component. + * @return The text component representing the name of the entities captured by the selector. + */ + public static TextualComponent selector(String selector) { + throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly + + return new ArbitraryTextTypeComponent("selector", selector); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/command/BukkitCommand.java b/src/main/java/me/hulipvp/hcf/api/command/BukkitCommand.java new file mode 100644 index 0000000..221e1b9 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/command/BukkitCommand.java @@ -0,0 +1,75 @@ +package me.hulipvp.hcf.api.command; + +import org.apache.commons.lang.Validate; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.plugin.Plugin; + +import java.util.List; + +public class BukkitCommand extends org.bukkit.command.Command { + private final Plugin owningPlugin; + private CommandExecutor executor; + protected BukkitCompleter completer; + + protected BukkitCommand(String label, CommandExecutor executor, Plugin owner) { + super(label); + this.executor = executor; + this.owningPlugin = owner; + this.usageMessage = ""; + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if(!this.owningPlugin.isEnabled()) + return false; + if(!testPermission(sender)) + return true; + boolean success; + + try { + success = this.executor.onCommand(sender, this, commandLabel, args); + } catch(Throwable ex) { + throw new CommandException("Unhandled exception executing command '" + commandLabel + "' in plugin " + this.owningPlugin.getDescription().getFullName(), ex); + } + + if(!success && this.usageMessage.length() > 0) { + for(String line : this.usageMessage.replace("", commandLabel).split("\n")) { + sender.sendMessage(line); + } + } + + return success; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws CommandException, IllegalArgumentException { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + List completions = null; + + try { + if(this.completer != null) + completions = this.completer.onTabComplete(sender, this, alias, args); + if(completions == null && this.executor instanceof TabCompleter) + completions = ((TabCompleter) this.executor).onTabComplete(sender, this, alias, args); + } catch(Throwable ex) { + StringBuilder message = new StringBuilder(); + message.append("Unhandled exception during tab completion for command '/").append(alias).append(' '); + for(String arg : args) + message.append(arg).append(' '); + message.deleteCharAt(message.length() - 1).append("' in plugin ").append(this.owningPlugin.getDescription().getFullName()); + throw new CommandException(message.toString(), ex); + } + + if(completions == null) + return super.tabComplete(sender, alias, args); + + return completions; + } + +} \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/api/command/BukkitCompleter.java b/src/main/java/me/hulipvp/hcf/api/command/BukkitCompleter.java new file mode 100644 index 0000000..be26c25 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/command/BukkitCompleter.java @@ -0,0 +1,51 @@ +package me.hulipvp.hcf.api.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; + +import java.lang.reflect.Method; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class BukkitCompleter implements TabCompleter { + private Map> completers = new HashMap<>(); + + public void addCompleter(String label, Method m, Object obj) { + completers.put(label, new AbstractMap.SimpleEntry<>(m, obj)); + } + + @SuppressWarnings("unchecked") + @Override + public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { + for(int i = args.length; i >= 0; i--) { + StringBuilder builder = new StringBuilder(); + builder.append(label.toLowerCase()); + + for(int x = 0; x < i; x++) { + if(!args[x].equals("") && !args[x].equals(" ")) { + builder.append("."); + builder.append(args[x].toLowerCase()); + } + } + + String cmdLabel = builder.toString(); + + if(this.completers.containsKey(cmdLabel)) { + Entry entry = this.completers.get(cmdLabel); + + try { + return (List) entry.getKey().invoke(entry.getValue(), new CommandData(sender, command, label, args, cmdLabel.split("\\.").length - 1)); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + + return null; + } + +} diff --git a/src/main/java/me/hulipvp/hcf/api/command/Command.java b/src/main/java/me/hulipvp/hcf/api/command/Command.java new file mode 100644 index 0000000..d499781 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/command/Command.java @@ -0,0 +1,19 @@ +package me.hulipvp.hcf.api.command; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Command { + + String label(); + + String permission() default ""; + + String[] aliases() default {}; + + boolean playerOnly() default false; +} \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/api/command/CommandAPI.java b/src/main/java/me/hulipvp/hcf/api/command/CommandAPI.java new file mode 100644 index 0000000..b84073e --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/command/CommandAPI.java @@ -0,0 +1,202 @@ +package me.hulipvp.hcf.api.command; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandMap; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.bukkit.help.GenericCommandHelpTopic; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicComparator; +import org.bukkit.help.IndexHelpTopic; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.SimplePluginManager; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +public class CommandAPI implements CommandExecutor { + private Plugin plugin; + private Map> commandMap = new HashMap<>(); + private CommandMap map; + private String root, noPermission, noConsole, unknownCommand; + + public CommandAPI(Plugin plugin, String root, String noPermission, String noConsole, String unknownCommand) { + this.plugin = plugin; + + if(plugin.getServer().getPluginManager() instanceof SimplePluginManager) { + SimplePluginManager manager = (SimplePluginManager) plugin.getServer().getPluginManager(); + + try { + Field field = SimplePluginManager.class.getDeclaredField("commandMap"); + field.setAccessible(true); + this.map = (CommandMap) field.get(manager); + } catch(IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + this.root = root; + this.noPermission = noPermission; + this.noConsole = noConsole; + this.unknownCommand = unknownCommand; + } + + @Override + public boolean onCommand(CommandSender sender, org.bukkit.command.Command cmd, String label, String[] args) { + return handleCommand(sender, cmd, label, args); + } + + public boolean handleCommand(CommandSender sender, org.bukkit.command.Command cmd, String label, String[] args) { + for(int i = args.length; i >= 0; i--) { + StringBuilder builder = new StringBuilder(); + builder.append(label.toLowerCase()); + + for(int x = 0; x < i; x++) { + builder.append("."); + builder.append(args[x].toLowerCase()); + } + + String cmdLabel = builder.toString(); + + if(this.commandMap.containsKey(cmdLabel)) { + Method method = this.commandMap.get(cmdLabel).getKey(); + Object methodObject = this.commandMap.get(cmdLabel).getValue(); + Command command = method.getAnnotation(Command.class); + + if(command.permission().length() > 0 && !sender.hasPermission(this.root + "." + command.permission())) { + sender.sendMessage(this.noPermission); + return true; + } + + if(command.playerOnly() && !(sender instanceof Player)) { + sender.sendMessage(this.noConsole); + return true; + } + + try { + method.invoke(methodObject, new CommandData(sender, cmd, label, args, cmdLabel.contains("\\.") ? cmdLabel.split("\\.").length - 1 : 0)); + } catch(IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + + return true; + } + } + + this.defaultCommand(new CommandData(sender, cmd, label, args, 0)); + + return true; + } + + public void registerCommands(Object obj) { + for(Method m : obj.getClass().getMethods()) { + if(m.getAnnotation(Command.class) != null) { + Command command = m.getAnnotation(Command.class); + if(m.getParameterTypes().length > 1 || m.getParameterTypes()[0] != CommandData.class) { + System.out.println("Unable to register command " + m.getName() + ". Unexpected method arguments"); + continue; + } + + this.registerCommand(command, command.label(), m, obj); + for(String alias : command.aliases()) + this.registerCommand(command, alias, m, obj); + } else if(m.getAnnotation(Completer.class) != null) { + Completer comp = m.getAnnotation(Completer.class); + if(m.getParameterTypes().length > 1 || m.getParameterTypes().length == 0 || m.getParameterTypes()[0] != CommandData.class) { + System.out.println("Unable to register tab completer " + m.getName() + ". Unexpected method arguments"); + continue; + } + + if(m.getReturnType() != List.class) { + System.out.println("Unable to register tab completer " + m.getName() + ". Unexpected return type"); + continue; + } + + this.registerCompleter(comp.label(), m, obj); + for(String alias : comp.aliases()) + this.registerCompleter(alias, m, obj); + } + } + } + + public void registerHelp() { + Set help = new TreeSet<>(HelpTopicComparator.helpTopicComparatorInstance()); + + for(String s : this.commandMap.keySet()) { + if(!s.contains(".")) { + org.bukkit.command.Command cmd = this.map.getCommand(s); + HelpTopic topic = new GenericCommandHelpTopic(cmd); + help.add(topic); + } + } + + IndexHelpTopic topic = new IndexHelpTopic(this.plugin.getName(), "All commands for " + this.plugin.getName(), null, help, "Below is a list of all " + this.plugin.getName() + " commands:"); + Bukkit.getServer().getHelpMap().addTopic(topic); + } + + public void unregisterCommands(Object obj) { + for(Method m : obj.getClass().getMethods()) { + if(m.getAnnotation(Command.class) != null) { + Command command = m.getAnnotation(Command.class); + this.commandMap.remove(command.label().toLowerCase()); + this.commandMap.remove(this.plugin.getName() + ":" + command.label().toLowerCase()); + this.map.getCommand(command.label().toLowerCase()).unregister(this.map); + } + } + } + + public void registerCommand(Command command, String label, Method m, Object obj) { + this.commandMap.put(label.toLowerCase(), new AbstractMap.SimpleEntry<>(m, obj)); + this.commandMap.put(this.plugin.getName() + ':' + label.toLowerCase(), new AbstractMap.SimpleEntry<>(m, obj)); + String cmdLabel = label.replace(".", ",").split(",")[0].toLowerCase(); + + if(this.map.getCommand(cmdLabel) == null) { + org.bukkit.command.Command cmd = new BukkitCommand(cmdLabel, this, this.plugin); + this.map.register(this.plugin.getName(), cmd); + } + } + + public void registerCompleter(String label, Method m, Object obj) { + String cmdLabel = label.replace(".", ",").split(",")[0].toLowerCase(); + + if(this.map.getCommand(cmdLabel) == null) { + org.bukkit.command.Command command = new BukkitCommand(cmdLabel, this, this.plugin); + this.map.register(this.plugin.getName(), command); + } + + if(this.map.getCommand(cmdLabel) instanceof BukkitCommand) { + BukkitCommand command = (BukkitCommand) this.map.getCommand(cmdLabel); + if(command.completer == null) + command.completer = new BukkitCompleter(); + command.completer.addCompleter(label, m, obj); + } else if(this.map.getCommand(cmdLabel) instanceof PluginCommand) { + try { + Object command = this.map.getCommand(cmdLabel); + Field field = command.getClass().getDeclaredField("completer"); + field.setAccessible(true); + + if(field.get(command) == null) { + BukkitCompleter completer = new BukkitCompleter(); + completer.addCompleter(label, m, obj); + field.set(command, completer); + } else if(field.get(command) instanceof BukkitCompleter) { + BukkitCompleter completer = (BukkitCompleter) field.get(command); + completer.addCompleter(label, m, obj); + } else { + System.out.println("Unable to register tab completer " + m.getName() + ". A tab completer is already registered for that command!"); + } + } catch(Exception ex) { + ex.printStackTrace(); + } + } + } + + private void defaultCommand(CommandData args) { + args.getSender().sendMessage(this.unknownCommand); + } + +} diff --git a/src/main/java/me/hulipvp/hcf/api/command/CommandData.java b/src/main/java/me/hulipvp/hcf/api/command/CommandData.java new file mode 100644 index 0000000..4b3f4cd --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/command/CommandData.java @@ -0,0 +1,67 @@ +package me.hulipvp.hcf.api.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class CommandData { + private final CommandSender sender; + private final org.bukkit.command.Command command; + private final String label; + private final String[] args; + + public CommandData(CommandSender sender, org.bukkit.command.Command command, String label, String[] args, int subCommand) { + String[] modArgs = new String[args.length - subCommand]; + + for(int i = 0; i < args.length - subCommand; i++) { + modArgs[i] = args[i + subCommand]; + } + + StringBuilder builder = new StringBuilder(); + builder.append(label); + + for(int x = 0; x < subCommand; x++) { + builder.append("."); + builder.append(args[x]); + } + + String cmdLabel = builder.toString(); + + this.sender = sender; + this.command = command; + this.label = cmdLabel; + this.args = modArgs; + } + + public CommandSender getSender() { + return this.sender; + } + + public org.bukkit.command.Command getCommand() { + return this.command; + } + + public String getLabel() { + return this.label; + } + + public String[] getArgs() { + return this.args; + } + + public String getArg(int index) { + return this.args[index]; + } + + public int length() { + return this.args.length; + } + + public Player getPlayer() { + if(this.sender instanceof Player) { + return (Player) this.sender; + } else { + return null; + } + } + +} diff --git a/src/main/java/me/hulipvp/hcf/api/command/Completer.java b/src/main/java/me/hulipvp/hcf/api/command/Completer.java new file mode 100644 index 0000000..379b617 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/command/Completer.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.command; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Completer { + + String label(); + + String[] aliases() default {}; + +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestControlEvent.java b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestControlEvent.java new file mode 100644 index 0000000..51c0836 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestControlEvent.java @@ -0,0 +1,19 @@ +package me.hulipvp.hcf.api.events.conquest; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.conquest.data.ConquestZone; +import org.bukkit.entity.Player; + +public class ConquestControlEvent extends ConquestEvent { + + @Getter private final ConquestZone zone; + @Getter private final Player player; + + public ConquestControlEvent(Conquest conquest, ConquestZone zone, Player player) { + super(conquest); + + this.zone = zone; + this.player = player; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestEndEvent.java b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestEndEvent.java new file mode 100644 index 0000000..2996c8e --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestEndEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.conquest; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.conquest.data.ConquestEndReason; + +public class ConquestEndEvent extends ConquestEvent { + + @Getter private final ConquestEndReason reason; + + public ConquestEndEvent(Conquest conquest, ConquestEndReason reason) { + super(conquest); + + this.reason = reason; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestEvent.java b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestEvent.java new file mode 100644 index 0000000..b5d8a4e --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestEvent.java @@ -0,0 +1,25 @@ +package me.hulipvp.hcf.api.events.conquest; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public abstract class ConquestEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + @Getter private final Conquest conquest; + + ConquestEvent(Conquest conquest) { + this.conquest = conquest; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestKnockEvent.java b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestKnockEvent.java new file mode 100644 index 0000000..cb3e0c4 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestKnockEvent.java @@ -0,0 +1,21 @@ +package me.hulipvp.hcf.api.events.conquest; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.conquest.data.ConquestZone; +import org.bukkit.entity.Player; + +public class ConquestKnockEvent extends ConquestEvent { + + @Getter private final ConquestZone zone; + @Getter private final int knockTime; + @Getter private final Player player; + + public ConquestKnockEvent(Conquest conquest, ConquestZone zone, int knockTime, Player player) { + super(conquest); + + this.zone = zone; + this.knockTime = knockTime; + this.player = player; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestStartEvent.java b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestStartEvent.java new file mode 100644 index 0000000..f4522f8 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/conquest/ConquestStartEvent.java @@ -0,0 +1,10 @@ +package me.hulipvp.hcf.api.events.conquest; + +import me.hulipvp.hcf.game.event.conquest.Conquest; + +public class ConquestStartEvent extends ConquestEvent { + + public ConquestStartEvent(Conquest conquest) { + super(conquest); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionClaimEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionClaimEvent.java new file mode 100644 index 0000000..95e2513 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionClaimEvent.java @@ -0,0 +1,29 @@ +package me.hulipvp.hcf.api.events.faction.normal; + +import lombok.Getter; +import lombok.Setter; +import me.hulipvp.hcf.game.faction.Claim; +import me.hulipvp.hcf.game.faction.Faction; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; + +public class FactionClaimEvent extends FactionEvent implements Cancellable { + + @Getter private final Player player; + + @Getter private final Claim claim; + + @Getter @Setter private boolean cancelled; + + public FactionClaimEvent(Faction faction, Player player, Claim claim) { + super(faction); + + this.player = player; + this.claim = claim; + } + + @Override + public boolean isCancelled() { + return cancelled; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionCreateEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionCreateEvent.java new file mode 100644 index 0000000..36be56f --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionCreateEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.faction.normal; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.Faction; +import org.bukkit.entity.Player; + +public class FactionCreateEvent extends FactionEvent { + + @Getter private final Player player; + + public FactionCreateEvent(Faction faction, Player player) { + super(faction); + + this.player = player; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionDisbandEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionDisbandEvent.java new file mode 100644 index 0000000..0fa5584 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionDisbandEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.faction.normal; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.Faction; +import org.bukkit.entity.Player; + +public class FactionDisbandEvent extends FactionEvent { + + @Getter private final Player player; + + public FactionDisbandEvent(Faction faction, Player player) { + super(faction); + + this.player = player; + } +} \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionEvent.java new file mode 100644 index 0000000..427d030 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionEvent.java @@ -0,0 +1,25 @@ +package me.hulipvp.hcf.api.events.faction.normal; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.Faction; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class FactionEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + @Getter private final Faction faction; + + FactionEvent(Faction faction) { + this.faction = faction; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionJoinEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionJoinEvent.java new file mode 100644 index 0000000..594d150 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionJoinEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.faction.normal; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.Faction; +import org.bukkit.entity.Player; + +public class FactionJoinEvent extends FactionEvent { + + @Getter private final Player player; + + public FactionJoinEvent(Faction faction, Player player) { + super(faction); + + this.player = player; + } +} \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionLeaveEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionLeaveEvent.java new file mode 100644 index 0000000..e7b55ee --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/normal/FactionLeaveEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.faction.normal; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.Faction; +import org.bukkit.entity.Player; + +public class FactionLeaveEvent extends FactionEvent { + + @Getter private final Player player; + + public FactionLeaveEvent(Faction faction, Player player) { + super(faction); + + this.player = player; + } +} \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionAllyEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionAllyEvent.java new file mode 100644 index 0000000..ffea04c --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionAllyEvent.java @@ -0,0 +1,15 @@ +package me.hulipvp.hcf.api.events.faction.player; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.type.player.PlayerFaction; + +public class FactionAllyEvent extends PlayerFactionEvent { + + @Getter private final PlayerFaction allied; + + public FactionAllyEvent(PlayerFaction faction, PlayerFaction allied) { + super(faction); + + this.allied = allied; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionKickEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionKickEvent.java new file mode 100644 index 0000000..5a31010 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionKickEvent.java @@ -0,0 +1,19 @@ +package me.hulipvp.hcf.api.events.faction.player; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.type.player.FactionMember; +import me.hulipvp.hcf.game.faction.type.player.PlayerFaction; +import org.bukkit.entity.Player; + +public class FactionKickEvent extends PlayerFactionEvent { + + @Getter private final Player player; + @Getter private final FactionMember kicked; + + public FactionKickEvent(PlayerFaction faction, Player player, FactionMember kicked) { + super(faction); + + this.player = player; + this.kicked = kicked; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionPromoteEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionPromoteEvent.java new file mode 100644 index 0000000..b6f2850 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionPromoteEvent.java @@ -0,0 +1,19 @@ +package me.hulipvp.hcf.api.events.faction.player; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.type.player.FactionMember; +import me.hulipvp.hcf.game.faction.type.player.PlayerFaction; +import org.bukkit.entity.Player; + +public class FactionPromoteEvent extends PlayerFactionEvent { + + @Getter private final Player player; + @Getter private final FactionMember promoted; + + public FactionPromoteEvent(PlayerFaction faction, Player player, FactionMember promoted) { + super(faction); + + this.player = player; + this.promoted = promoted; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionUnallyEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionUnallyEvent.java new file mode 100644 index 0000000..4aff3c5 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/player/FactionUnallyEvent.java @@ -0,0 +1,15 @@ +package me.hulipvp.hcf.api.events.faction.player; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.type.player.PlayerFaction; + +public class FactionUnallyEvent extends PlayerFactionEvent { + + @Getter private final PlayerFaction allied; + + public FactionUnallyEvent(PlayerFaction faction, PlayerFaction allied) { + super(faction); + + this.allied = allied; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/faction/player/PlayerFactionEvent.java b/src/main/java/me/hulipvp/hcf/api/events/faction/player/PlayerFactionEvent.java new file mode 100644 index 0000000..3ea231c --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/faction/player/PlayerFactionEvent.java @@ -0,0 +1,25 @@ +package me.hulipvp.hcf.api.events.faction.player; + +import lombok.Getter; +import me.hulipvp.hcf.game.faction.type.player.PlayerFaction; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class PlayerFactionEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + @Getter private final PlayerFaction faction; + + PlayerFactionEvent(PlayerFaction faction) { + this.faction = faction; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/kit/KitDisableEvent.java b/src/main/java/me/hulipvp/hcf/api/events/kit/KitDisableEvent.java new file mode 100644 index 0000000..d35bbbc --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/kit/KitDisableEvent.java @@ -0,0 +1,11 @@ +package me.hulipvp.hcf.api.events.kit; + +import me.hulipvp.hcf.game.kits.Kit; +import org.bukkit.entity.Player; + +public class KitDisableEvent extends KitEvent { + + public KitDisableEvent(Player player, Kit kit) { + super(player, kit); + } +} \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/api/events/kit/KitEnableEvent.java b/src/main/java/me/hulipvp/hcf/api/events/kit/KitEnableEvent.java new file mode 100644 index 0000000..7334e2a --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/kit/KitEnableEvent.java @@ -0,0 +1,11 @@ +package me.hulipvp.hcf.api.events.kit; + +import me.hulipvp.hcf.game.kits.Kit; +import org.bukkit.entity.Player; + +public class KitEnableEvent extends KitEvent { + + public KitEnableEvent(Player player, Kit kit) { + super(player, kit); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/kit/KitEvent.java b/src/main/java/me/hulipvp/hcf/api/events/kit/KitEvent.java new file mode 100644 index 0000000..74d93a9 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/kit/KitEvent.java @@ -0,0 +1,27 @@ +package me.hulipvp.hcf.api.events.kit; + +import lombok.Getter; +import me.hulipvp.hcf.game.kits.Kit; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class KitEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + @Getter private final Player player; + @Getter private final Kit kit; + + KitEvent(Player player, Kit kit) { + this.player = player; + this.kit = kit; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/koth/KothControlEvent.java b/src/main/java/me/hulipvp/hcf/api/events/koth/KothControlEvent.java new file mode 100644 index 0000000..ccf4059 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/koth/KothControlEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.koth; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.koth.Koth; +import org.bukkit.entity.Player; + +public class KothControlEvent extends KothEvent { + + @Getter private final Player player; + + public KothControlEvent(Koth koth, Player player) { + super(koth); + + this.player = player; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/koth/KothEndEvent.java b/src/main/java/me/hulipvp/hcf/api/events/koth/KothEndEvent.java new file mode 100644 index 0000000..4d3c132 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/koth/KothEndEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.koth; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.koth.Koth; +import me.hulipvp.hcf.game.event.koth.data.KothEndReason; + +public class KothEndEvent extends KothEvent { + + @Getter private final KothEndReason reason; + + public KothEndEvent(Koth koth, KothEndReason reason) { + super(koth); + + this.reason = reason; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/koth/KothEvent.java b/src/main/java/me/hulipvp/hcf/api/events/koth/KothEvent.java new file mode 100644 index 0000000..283fcf0 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/koth/KothEvent.java @@ -0,0 +1,24 @@ +package me.hulipvp.hcf.api.events.koth; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.koth.Koth; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public abstract class KothEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + @Getter private final Koth koth; + + KothEvent(Koth koth) { + this.koth = koth; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/koth/KothKnockEvent.java b/src/main/java/me/hulipvp/hcf/api/events/koth/KothKnockEvent.java new file mode 100644 index 0000000..bbc058a --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/koth/KothKnockEvent.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.events.koth; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.koth.Koth; +import org.bukkit.entity.Player; + +public class KothKnockEvent extends KothEvent { + + @Getter private final Player player; + + public KothKnockEvent(Koth koth, Player player) { + super(koth); + + this.player = player; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/koth/KothStartEvent.java b/src/main/java/me/hulipvp/hcf/api/events/koth/KothStartEvent.java new file mode 100644 index 0000000..492d430 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/koth/KothStartEvent.java @@ -0,0 +1,10 @@ +package me.hulipvp.hcf.api.events.koth; + +import me.hulipvp.hcf.game.event.koth.Koth; + +public class KothStartEvent extends KothEvent { + + public KothStartEvent(Koth koth) { + super(koth); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/mountain/MountainEvent.java b/src/main/java/me/hulipvp/hcf/api/events/mountain/MountainEvent.java new file mode 100644 index 0000000..b3bb554 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/mountain/MountainEvent.java @@ -0,0 +1,25 @@ +package me.hulipvp.hcf.api.events.mountain; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.mountain.Mountain; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class MountainEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + @Getter private final Mountain mountain; + + MountainEvent(Mountain mountain) { + this.mountain = mountain; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/mountain/MountainResetEvent.java b/src/main/java/me/hulipvp/hcf/api/events/mountain/MountainResetEvent.java new file mode 100644 index 0000000..b112bbe --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/mountain/MountainResetEvent.java @@ -0,0 +1,15 @@ +package me.hulipvp.hcf.api.events.mountain; + +import lombok.Getter; +import me.hulipvp.hcf.game.event.mountain.Mountain; + +public class MountainResetEvent extends MountainEvent { + + @Getter private final Long resetTime; + + public MountainResetEvent(Mountain mountain, Long resetTime) { + super(mountain); + + this.resetTime = resetTime; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/timer/TimerEvent.java b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerEvent.java new file mode 100644 index 0000000..d571bd8 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerEvent.java @@ -0,0 +1,25 @@ +package me.hulipvp.hcf.api.events.timer; + +import lombok.Getter; +import me.hulipvp.hcf.game.timer.Timer; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public abstract class TimerEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + @Getter private Timer timer; + + TimerEvent(Timer timer) { + this.timer = timer; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/timer/TimerExpireEvent.java b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerExpireEvent.java new file mode 100644 index 0000000..ecf99c0 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerExpireEvent.java @@ -0,0 +1,10 @@ +package me.hulipvp.hcf.api.events.timer; + +import me.hulipvp.hcf.game.timer.Timer; + +public class TimerExpireEvent extends TimerEvent { + + public TimerExpireEvent(Timer timer) { + super(timer); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/timer/TimerPauseEvent.java b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerPauseEvent.java new file mode 100644 index 0000000..86b1e72 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerPauseEvent.java @@ -0,0 +1,10 @@ +package me.hulipvp.hcf.api.events.timer; + +import me.hulipvp.hcf.game.timer.Timer; + +public class TimerPauseEvent extends TimerEvent { + + public TimerPauseEvent(Timer timer) { + super(timer); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/events/timer/TimerUnpauseEvent.java b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerUnpauseEvent.java new file mode 100644 index 0000000..f9ab44c --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/events/timer/TimerUnpauseEvent.java @@ -0,0 +1,10 @@ +package me.hulipvp.hcf.api.events.timer; + +import me.hulipvp.hcf.game.timer.Timer; + +public class TimerUnpauseEvent extends TimerEvent { + + public TimerUnpauseEvent(Timer timer) { + super(timer); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/plugins/PlayerHook.java b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/PlayerHook.java new file mode 100644 index 0000000..81db9c7 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/PlayerHook.java @@ -0,0 +1,14 @@ +package me.hulipvp.hcf.api.hooks.plugins; + +import org.bukkit.entity.Player; + +public interface PlayerHook { + + boolean canChat(Player player); + + String getRankName(Player player); + + String getRankPrefix(Player player); + + String getRankSuffix(Player player); +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/plugins/core/CoreHook.java b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/core/CoreHook.java new file mode 100644 index 0000000..c154b60 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/core/CoreHook.java @@ -0,0 +1,73 @@ +package me.hulipvp.hcf.api.hooks.plugins.core; + +import com.verispvp.core.api.CoreAPI; +import com.verispvp.core.api.minecrafter.Minecrafter; +import com.verispvp.core.api.rank.Rank; +import lombok.Getter; +import me.hulipvp.hcf.api.hooks.plugins.PlayerHook; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class CoreHook implements PlayerHook { + + @Getter private boolean hooked; + + public CoreHook() { + this.hooked = true; + Bukkit.getLogger().info("Successfully hooked into Core."); + } + + public static boolean canHook() { + if(Bukkit.getServer().getPluginManager().getPlugin("Core") == null) + return false; + + try { + Class.forName("com.verispvp.core.api.CoreAPI"); + return true; + } catch(ClassNotFoundException ex) { + return false; + } + } + + public Minecrafter getMinecrafter(Player player) { + return CoreAPI.INSTANCE.getMinecrafter(player); + } + + public boolean isMuted(Player player) { + return getMinecrafter(player).getMute() != null; + } + + @Override + public boolean canChat(Player player) { + Minecrafter minecrafter = getMinecrafter(player); + + return !minecrafter.isAdminChat() && !minecrafter.isStaffChat() && !isMuted(player); + } + + public Rank getRank(Player player) { + return CoreAPI.INSTANCE.getRank(player); + } + + @Override + public String getRankName(Player player) { + Rank rank = getRank(player); + if(rank == null) + return ""; + + return rank.getName(); + } + + @Override + public String getRankPrefix(Player player) { + Rank rank = getRank(player); + if(rank == null) + return ""; + + return rank.getPrefix() + " " + rank.getColor(); + } + + @Override + public String getRankSuffix(Player player) { + return ""; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/DataImpl.java b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/DataImpl.java new file mode 100644 index 0000000..cc511b1 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/DataImpl.java @@ -0,0 +1,12 @@ +package me.hulipvp.hcf.api.hooks.plugins.nucleus; + +import org.bukkit.entity.Player; + +public interface DataImpl { + + boolean canChat(Player player); + + String getRankName(Player player); + + String getRankPrefix(Player player); +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/NucleusHook.java b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/NucleusHook.java new file mode 100644 index 0000000..eb64911 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/NucleusHook.java @@ -0,0 +1,50 @@ +package me.hulipvp.hcf.api.hooks.plugins.nucleus; + +import lombok.Getter; +import me.hulipvp.hcf.api.hooks.plugins.PlayerHook; +import me.hulipvp.hcf.api.hooks.plugins.nucleus.impl.NucleusPlayerImpl; +import me.hulipvp.hcf.api.hooks.plugins.nucleus.impl.PlayerDataImpl; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class NucleusHook implements PlayerHook { + + @Getter private boolean hooked; + @Getter private DataImpl dataImpl; + + public NucleusHook() { + try { + Class.forName("me.joeleoli.nucleus.player.PlayerData"); + dataImpl = new PlayerDataImpl(); + } catch(ClassNotFoundException ex) { + dataImpl = new NucleusPlayerImpl(); + } // We do this cuz this fool joeleoli keeps fricken changing his fricken API for Nucleus + + this.hooked = true; + Bukkit.getLogger().info("Successfully hooked into Nucleus."); + } + + public static boolean canHook() { + return Bukkit.getServer().getPluginManager().getPlugin("Nucleus") != null; + } + + @Override + public boolean canChat(Player player) { + return dataImpl.canChat(player); + } + + @Override + public String getRankName(Player player) { + return dataImpl.getRankName(player); + } + + @Override + public String getRankPrefix(Player player) { + return dataImpl.getRankPrefix(player); + } + + @Override + public String getRankSuffix(Player player) { + return ""; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/impl/NucleusPlayerImpl.java b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/impl/NucleusPlayerImpl.java new file mode 100644 index 0000000..be947df --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/impl/NucleusPlayerImpl.java @@ -0,0 +1,51 @@ +package me.hulipvp.hcf.api.hooks.plugins.nucleus.impl; + +import me.hulipvp.hcf.api.hooks.plugins.nucleus.DataImpl; +import me.joeleoli.nucleus.Nucleus; +import me.joeleoli.nucleus.chat.ChatManager; +import me.joeleoli.nucleus.player.NucleusPlayer; +import me.joeleoli.nucleus.rank.Rank; +import org.bukkit.entity.Player; + +public class NucleusPlayerImpl implements DataImpl { + + private NucleusPlayer getData(Player player) { + NucleusPlayer nucleusPlayer = NucleusPlayer.getByUuid(player.getUniqueId()); + if(!nucleusPlayer.isLoaded()) + nucleusPlayer.load(); + + return nucleusPlayer; + } + + @Override + public boolean canChat(Player player) { + ChatManager chatManager = Nucleus.getInstance().getChatManager(); + NucleusPlayer nucleusPlayer = NucleusPlayer.getByUuid(player.getUniqueId()); + + return !chatManager.isChatMuted() && chatManager.getDelayTime() <= 0 && nucleusPlayer.getActiveMute() == null; + } + + public Rank getRank(Player player) { + NucleusPlayer nucleusPlayer = getData(player); + + return nucleusPlayer.getGlobalRank() != null ? nucleusPlayer.getGlobalRank() : nucleusPlayer.getActiveRank(); + } + + @Override + public String getRankName(Player player) { + Rank rank = getRank(player); + if(rank == null) + return ""; + + return rank.getName(); + } + + @Override + public String getRankPrefix(Player player) { + Rank rank = getRank(player); + if(rank == null) + return ""; + + return rank.getPrefix(); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/impl/PlayerDataImpl.java b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/impl/PlayerDataImpl.java new file mode 100644 index 0000000..26aeff2 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/nucleus/impl/PlayerDataImpl.java @@ -0,0 +1,73 @@ +package me.hulipvp.hcf.api.hooks.plugins.nucleus.impl; + +import me.hulipvp.hcf.api.hooks.plugins.nucleus.DataImpl; +import me.joeleoli.nucleus.chat.ChatManager; +import me.joeleoli.nucleus.player.NucleusPlayer; +import me.joeleoli.nucleus.rank.Rank; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public class PlayerDataImpl implements DataImpl { + + private static Field chatMuted, chatDelay; + + private NucleusPlayer getData(Player player) { + NucleusPlayer playerData = NucleusPlayer.getByUuid(player.getUniqueId()); + if(!playerData.isLoaded()) + playerData.load(); + + return playerData; + } + + @Override + public boolean canChat(Player player) { + if(chatMuted == null || chatDelay == null) + return true; + + NucleusPlayer playerData = getData(player); + + try { + return !chatMuted.getBoolean(null) && chatDelay.getInt(null) <= 0 && playerData.getActiveMute() == null; + } catch(IllegalArgumentException | IllegalAccessException ex) { + return true; + } + } + + public Rank getRank(Player player) { + NucleusPlayer playerData = getData(player); + + return playerData.getGlobalRank() != null ? playerData.getGlobalRank() : playerData.getActiveRank(); + } + + @Override + public String getRankName(Player player) { + Rank rank = getRank(player); + if(rank == null) + return ""; + + return rank.getName(); + } + + @Override + public String getRankPrefix(Player player) { + Rank rank = getRank(player); + if(rank == null) + return ""; + + return rank.getPrefix(); + } + + static { + for(Field field : ChatManager.class.getDeclaredFields()) { + if(!Modifier.isStatic(field.getModifiers())) + continue; + + if(field.getName().equals("publicChatMuted")) + chatMuted = field; + else if(field.getName().equals("chatDelay")) + chatDelay = field; + } + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/plugins/vault/VaultHook.java b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/vault/VaultHook.java new file mode 100644 index 0000000..1660718 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/plugins/vault/VaultHook.java @@ -0,0 +1,63 @@ +package me.hulipvp.hcf.api.hooks.plugins.vault; + +import lombok.Getter; +import me.hulipvp.hcf.api.hooks.plugins.PlayerHook; +import net.milkbowl.vault.chat.Chat; +import net.milkbowl.vault.permission.Permission; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; + +public class VaultHook implements PlayerHook { + + @Getter private Permission perms; + @Getter private Chat chat; + + @Getter private boolean hooked; + + public VaultHook() { + RegisteredServiceProvider permsRsp = Bukkit.getServer().getServicesManager().getRegistration(Permission.class); + if(permsRsp != null && permsRsp.getProvider() != null) + this.perms = permsRsp.getProvider(); + + RegisteredServiceProvider chatRsp = Bukkit.getServer().getServicesManager().getRegistration(Chat.class); + if(chatRsp != null && chatRsp.getProvider() != null) + this.chat = chatRsp.getProvider(); + + this.hooked = true; + Bukkit.getLogger().info("Successfully hooked into Vault."); + } + + public static boolean canHook() { + return Bukkit.getServer().getPluginManager().getPlugin("Vault") != null; + } + + @Override + public boolean canChat(Player player) { + return true; + } + + @Override + public String getRankName(Player player) { + if(chat == null) + return ""; + + return chat.getPrimaryGroup(player); + } + + @Override + public String getRankPrefix(Player player) { + if(chat == null) + return ""; + + return chat.getPlayerPrefix(player); + } + + @Override + public String getRankSuffix(Player player) { + if(chat == null) + return ""; + + return chat.getPlayerSuffix(player); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/hooks/server/ProtocolHook.java b/src/main/java/me/hulipvp/hcf/api/hooks/server/ProtocolHook.java new file mode 100644 index 0000000..50757e4 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/hooks/server/ProtocolHook.java @@ -0,0 +1,105 @@ +package me.hulipvp.hcf.api.hooks.server; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.StructureModifier; +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.listeners.GlassListener; +import net.minecraft.server.v1_7_R4.EntityPlayer; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class ProtocolHook { + + public ProtocolHook() { // Thank you iHCF + try { + ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); + protocolManager.addPacketListener(new PacketAdapter(HCF.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Client.BLOCK_PLACE) { + @Override + public void onPacketReceiving(PacketEvent event) { + StructureModifier modifier = event.getPacket().getIntegers(); + Player player = event.getPlayer(); + + try { + int face = modifier.read(3); + if(face == 255) + return; + + Location clickedBlock = new Location(player.getWorld(), modifier.read(0), modifier.read(1), modifier.read(2)); + if(GlassListener.hasGlass(player, clickedBlock)) { + Location placedLocation = clickedBlock.clone(); + switch(face) { + case 2: + placedLocation.add(0, 0, -1); + break; + case 3: + placedLocation.add(0, 0, 1); + break; + case 4: + placedLocation.add(-1, 0, 0); + break; + case 5: + placedLocation.add(1, 0, 0); + break; + default: + return; + } + + event.setCancelled(true); + + if(!GlassListener.hasGlass(player, clickedBlock)) { + event.setCancelled(true); + player.sendBlockChange(placedLocation, Material.AIR, (byte) 0); + } + } + } catch(FieldAccessException ex) { + ex.printStackTrace(); + } + } + }); + + protocolManager.addPacketListener(new PacketAdapter(HCF.getInstance(), ListenerPriority.NORMAL, PacketType.Play.Client.BLOCK_DIG) { + @Override + public void onPacketReceiving(PacketEvent event) { + StructureModifier modifier = event.getPacket().getIntegers(); + + try { + int status = modifier.read(4); + if(status == 0 || status == 2) { + Player player = event.getPlayer(); + int x = modifier.read(0), y = modifier.read(1), z = modifier.read(2); + Location location = new Location(player.getWorld(), x, y, z); + if(GlassListener.hasGlass(player, location)) { + event.setCancelled(true); + if(status == 2) { + player.sendBlockChange(location, 95, (byte) 14); + } else if(status == 0) { // we check this because Blocks that broke pretty much straight away do not send a FINISHED for some weird reason. + EntityPlayer entityPlayer = ((CraftPlayer) player).getHandle(); + if(player.getGameMode() == GameMode.CREATIVE || entityPlayer.world.getType(x, y, z).getDamage(entityPlayer, entityPlayer.world, x, y, z) >= 1.0F) + player.sendBlockChange(location, 95, (byte) 14); + } + } + } + } catch(FieldAccessException ex) { + ex.printStackTrace(); + } + } + }); + } catch(Exception ex) { + ex.printStackTrace(); + } + } + + public static boolean canHook() { + return Bukkit.getServer().getPluginManager().getPlugin("ProtocolLib") != null; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/IModule.java b/src/main/java/me/hulipvp/hcf/api/modules/IModule.java new file mode 100644 index 0000000..90a3b3f --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/IModule.java @@ -0,0 +1,19 @@ +package me.hulipvp.hcf.api.modules; + +public interface IModule { + + /** + * Called when the module has started loading + */ + void onLoad(); + + /** + * Called when the module is done loading + */ + void onEnable(); + + /** + * Called when the module gets disabled... duh + */ + void onDisable(); +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/JavaModuleLoader.java b/src/main/java/me/hulipvp/hcf/api/modules/JavaModuleLoader.java new file mode 100644 index 0000000..f40b3d4 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/JavaModuleLoader.java @@ -0,0 +1,146 @@ +package me.hulipvp.hcf.api.modules; + +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.api.modules.ex.InvalidModuleException; +import me.hulipvp.hcf.api.modules.ex.InvalidModuleInfoException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; + +public class JavaModuleLoader { + + private final Map> classes = new ConcurrentHashMap<>(); + private final Map loaders = new LinkedHashMap<>(); + + public Module loadModule(File file) + throws InvalidModuleException { + if(!file.exists()) + throw new InvalidModuleException(new FileNotFoundException(file.getPath() + " does not exist")); + + ModuleInfoFile information; + + try { + information = this.getModuleInfoFile(file); + } catch(InvalidModuleInfoException ex) { + throw new InvalidModuleException(ex); + } + + ModuleClassLoader loader; + + try { + loader = new ModuleClassLoader(this.getClass().getClassLoader(), this, information, file); + } catch(InvalidModuleException ex) { + throw ex; + } catch(Throwable ex) { + throw new InvalidModuleException(ex); + } + + this.loaders.put(information.getName(), loader); + + return loader.getModule(); + } + + + public void enableModule(Module module) { + if(!module.isEnabled()) { + if(!this.loaders.containsKey(module.getInfo().getName())) + this.loaders.put(module.getInfo().getName(), (ModuleClassLoader) module.getClassLoader()); + + try { + module.setEnabled(true); + } catch(Throwable e) { + HCF.getInstance().getLogger().log(Level.SEVERE, "Error occurred while enabling " + module.getInfo().getName(), e); + } + } + } + + + public void disableModule(Module module) { + if(module.isEnabled()) { + ClassLoader classLoader = module.getClassLoader(); + try { + module.setEnabled(false); + module.kill(); + } catch(Throwable e) { + HCF.getInstance().getLogger().log(Level.SEVERE, "Error occurred while disabling " + module.getInfo().getName(), e); + } + + this.loaders.remove(module.getInfo().getName()); + if(!(classLoader instanceof ModuleClassLoader)) + return; + + ModuleClassLoader loader = (ModuleClassLoader) classLoader; + Set names = loader.getClasses().keySet(); + + names.forEach(this::removeClass); + } + } + + public ModuleInfoFile getModuleInfoFile(File file) + throws InvalidModuleInfoException { + JarFile jar = null; + InputStream stream = null; + + try { + jar = new JarFile(file); + JarEntry entry = jar.getJarEntry("info.yml"); + if(entry == null) + throw new InvalidModuleInfoException(new FileNotFoundException("Module file does not contain info.yml")); + + stream = jar.getInputStream(entry); + + return new ModuleInfoFile(stream); + } catch(IOException e) { + throw new InvalidModuleInfoException(e); + } finally { + if(jar != null) { + try { + jar.close(); + } catch(IOException ignored) { } + } + + if(stream != null) { + try { + stream.close(); + } catch(IOException ignored) { } + } + } + } + + public Class getClassByName(String name) { + Class cachedClass = this.classes.get(name); + if(cachedClass != null) + return cachedClass; + + for(String current : this.loaders.keySet()) { + ModuleClassLoader loader = this.loaders.get(current); + + try { + cachedClass = loader.findClass(name, false); + } catch(ClassNotFoundException ignored) { } + + if(cachedClass != null) + return cachedClass; + } + + return null; + } + + public void setClass(String name, Class clazz) { + if(!this.classes.containsKey(name)) + this.classes.put(name, clazz); + } + + private void removeClass(String name) { + this.classes.remove(name); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/Module.java b/src/main/java/me/hulipvp/hcf/api/modules/Module.java new file mode 100644 index 0000000..977ba51 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/Module.java @@ -0,0 +1,128 @@ +package me.hulipvp.hcf.api.modules; + +import lombok.Getter; +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.api.modules.ex.InvalidModuleLoaderException; +import me.hulipvp.hcf.api.modules.ex.ModuleLoadException; +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashSet; +import java.util.Set; + +public abstract class Module implements IModule { + + @Getter private static HCF plugin = HCF.getInstance(); + + @Getter private JavaModuleLoader loader; + + @Getter private boolean loaded, enabled; + + @Getter private ClassLoader classLoader; + @Getter private ModuleInfoFile info; + + private Set commands; + private Set listeners; + private Set tasks; + + public Module() { + ClassLoader loader = this.getClass().getClassLoader(); + if(!(loader instanceof ModuleClassLoader)) + throw new InvalidModuleLoaderException(this.getClass().getName()); + + ((ModuleClassLoader) loader).instate(this); + + commands = new HashSet<>(); + listeners = new HashSet<>(); + tasks = new HashSet<>(); + } + + public void onLoad() { } + + public void onEnable() { } + + public void onDisable() { } + + public void kill() { + unregisterCommands(); + unregisterListeners(); + unregisterTasks(); + } + + public void setEnabled(boolean enabled) + throws ModuleLoadException { + if(this.enabled == enabled) + throw new ModuleLoadException(getInfo().getName(), "Attempted to enable when initialized."); + + this.enabled = enabled; + + if(enabled) + this.onEnable(); + else + this.onDisable(); + } + + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + + public void registerCommand(Object object) { + if(commands.add(object)) + HCF.getInstance().getCommandManager().registerCommands(object); + } + + public void unregisterCommand(Object object) { + if(commands.remove(object)) + HCF.getInstance().getCommandManager().unregisterCommands(object); + } + + public void unregisterCommands() { + commands.forEach(this::unregisterCommand); + commands.clear(); + } + + public void registerListener(T t) { + if(listeners.add(t)) + HCF.getInstance().getListenerManager().registerListener(t); + } + + public void unregisterListener(T t) { + if(listeners.remove(t)) + HandlerList.unregisterAll(t); + } + + public void unregisterListeners() { + listeners.forEach(this::unregisterListener); + listeners.clear(); + } + + public void registerSyncRepeatingTask(Runnable runnable, long delay, long period) { + registerTask(Bukkit.getScheduler().runTaskTimer(getPlugin(), runnable, delay, period)); + } + + public void registerAsyncRepeatingTask(Runnable runnable, long delay, long period) { + registerTask(Bukkit.getScheduler().runTaskTimerAsynchronously(getPlugin(), runnable, delay, period)); + } + + public void registerTask(BukkitTask task) { + tasks.add(task.getTaskId()); + } + + public void unregisterTask(BukkitTask task) { + if(tasks.remove(task.getTaskId())) + Bukkit.getScheduler().cancelTask(task.getTaskId()); + } + + public void unregisterTasks() { + tasks.forEach(Bukkit.getScheduler()::cancelTask); + tasks.clear(); + } + + protected void instate(JavaModuleLoader loader, ModuleInfoFile info, ClassLoader classLoader) { + this.loader = loader; + this.info = info; + this.classLoader = classLoader; + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/ModuleClassLoader.java b/src/main/java/me/hulipvp/hcf/api/modules/ModuleClassLoader.java new file mode 100644 index 0000000..75fc86a --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/ModuleClassLoader.java @@ -0,0 +1,108 @@ +package me.hulipvp.hcf.api.modules; + +import lombok.Getter; +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.api.modules.ex.InvalidModuleException; +import me.hulipvp.hcf.api.modules.ex.ModuleLoadException; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +public class ModuleClassLoader extends URLClassLoader { + + @Getter private JavaModuleLoader loader; + + @Getter private final Map> classes = new ConcurrentHashMap<>(); + @Getter private final Module module; + @Getter private final ModuleInfoFile info; + + private Module moduleInit; + private IllegalStateException state; + + static { + try { + Method method = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable"); + if(method != null) { + boolean oldAccessible = method.isAccessible(); + + method.setAccessible(true); + method.invoke(null); + method.setAccessible(oldAccessible); + HCF.getInstance().getLogger().info("Successfully set ModuleClassLoader as parallel capable"); + } + } catch(Exception e) { + HCF.getInstance().getLogger().log(Level.WARNING, "Error setting ModuleClassLoader as parallel capable", e); + } + } + + public ModuleClassLoader(ClassLoader parent, JavaModuleLoader loader, ModuleInfoFile info, File file) throws InvalidModuleException, MalformedURLException { + super(new URL[]{file.toURI().toURL()}, parent); + + this.loader = loader; + this.info = info; + + try { + Class jarClass; + try { + jarClass = Class.forName(info.getMain(), true, this); + } catch(ClassNotFoundException e) { + throw new InvalidModuleException("Module " + info.getName() + "'s main class does not exist. (" + info.getMain() + ")", e); + } + + Class mainClass; + try { + mainClass = jarClass.asSubclass(Module.class); + } catch(ClassCastException e) { + throw new InvalidModuleException("Module " + info.getName() + "'s main class does not extend Module. (" + info.getMain() + ")", e); + } + + this.module = mainClass.newInstance(); + } catch(IllegalAccessException e) { + throw new InvalidModuleException("Module " + info.getName() + " doesn't have a public constructor", e); + } catch(InstantiationException e) { + throw new InvalidModuleException("Module " + info.getName() + " is not a proper type.", e); + } + } + + @Override + public Class findClass(String name) throws ClassNotFoundException { + return this.findClass(name, true); + } + + public Class findClass(String name, boolean global) throws ClassNotFoundException { + Class result = this.classes.get(name); + if(result == null) { + if(global) + result = this.loader.getClassByName(name); + + if(result == null) { + result = super.findClass(name); + if(result != null) + this.loader.setClass(name, result); + } + + this.classes.put(name, result); + } + + return result; + } + + public void instate(Module module) { + if(module.getClass().getClassLoader() != this) + throw new ModuleLoadException(module.getInfo().getName(), "Cannot initialize module outside of ModuleClassLoader."); + + if(this.module != null || this.moduleInit != null) + throw new IllegalArgumentException("Module already instated.", this.state); + + this.state = new IllegalStateException("Already instated"); + this.moduleInit = module; + + module.instate(this.loader, this.info, this); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/ModuleInfoFile.java b/src/main/java/me/hulipvp/hcf/api/modules/ModuleInfoFile.java new file mode 100644 index 0000000..4e11b0f --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/ModuleInfoFile.java @@ -0,0 +1,67 @@ +package me.hulipvp.hcf.api.modules; + +import lombok.Getter; +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.api.modules.ex.InvalidModuleInfoException; +import org.yaml.snakeyaml.Yaml; + +import java.io.InputStream; +import java.util.Map; + +public class ModuleInfoFile { + + private static final Yaml YAML = new Yaml(); + + @Getter private String name; + @Getter private String rawName; + @Getter private String version; + @Getter private String main; + + public ModuleInfoFile(InputStream stream) + throws InvalidModuleInfoException { + this.loadMap(this.asMap(YAML.load(stream))); + } + + private void loadMap(Map map) + throws InvalidModuleInfoException { + try { + this.name = this.rawName = map.get("name").toString(); + + if(!this.name.matches("^[A-Za-z0-9 _.-]+$")) + throw new InvalidModuleInfoException("Module Name '" + this.name + "' contains invalid characters."); + + this.name = this.name.replace(' ', '_'); + } catch(NullPointerException e) { + throw new InvalidModuleInfoException("Module name is null", e); + } catch(ClassCastException e) { + throw new InvalidModuleInfoException("Module name is not a string", e); + } + + try { + this.version = map.get("version").toString(); + } catch(NullPointerException e) { + throw new InvalidModuleInfoException("Module version is null", e); + } catch(ClassCastException e) { + throw new InvalidModuleInfoException("Module version is not a string", e); + } + + try { + this.main = map.get("main").toString(); + + if(this.main.startsWith(HCF.class.getPackage().getName())) + throw new InvalidModuleInfoException("A module cannot be within the namespace of " + HCF.class.getPackage().getName()); + } catch(NullPointerException e) { + throw new InvalidModuleInfoException("Main Class is null", e); + } catch(ClassCastException e) { + throw new InvalidModuleInfoException("Main Class is not a string", e); + } + } + + private Map asMap(Object object) + throws InvalidModuleInfoException { + if(object instanceof Map) + return (Map) object; + + throw new InvalidModuleInfoException(object + " is not properly structured."); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/ModuleManager.java b/src/main/java/me/hulipvp/hcf/api/modules/ModuleManager.java new file mode 100644 index 0000000..89df7fb --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/ModuleManager.java @@ -0,0 +1,133 @@ +package me.hulipvp.hcf.api.modules; + +import lombok.Getter; +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.api.modules.ex.InvalidModuleException; +import me.hulipvp.hcf.api.modules.ex.InvalidModuleInfoException; +import org.bukkit.Bukkit; + +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ModuleManager { + + @Getter private final Map modules = new HashMap<>(); + @Getter private final Pattern pattern = Pattern.compile("\\.jar$"); + @Getter private final JavaModuleLoader loader; + + public ModuleManager() { + this.loader = new JavaModuleLoader(); + } + + public Module loadModule(File file) + throws InvalidModuleException { + Module result = null; + + String name = file.getName(); + Matcher matcher = this.pattern.matcher(name); + if(matcher.find()) + result = this.loader.loadModule(file); + + if(result != null) { + this.modules.put(result.getInfo().getName(), result); + + result.onLoad(); + result.setLoaded(true); + + return result; + } + + return null; + } + + public boolean enableModule(Module module) { + if(!module.isEnabled()) { + try { + module.getLoader().enableModule(module); + + return true; + } catch(Throwable e) { + HCF.getInstance().getLogger().log(Level.SEVERE, "Error occurred (in the module loader) while enabling " + module.getInfo().getName(), e); + } + } + + return false; + } + + public void disableModule(Module module) { + if(module.isEnabled()) { + try { + module.getLoader().disableModule(module); + } catch(Throwable e) { + HCF.getInstance().getLogger().log(Level.SEVERE, "Error occurred (in the module loader) while disabling " + module.getInfo().getName(), e); + } + } + + modules.remove(module.getInfo().getName()); + } + + public void loadModules() { + File directory = new File(HCF.getInstance().getDataFolder() + File.separator + "modules"); + if(!directory.exists()) { + if(directory.mkdir()) + Bukkit.getLogger().info("Created modules directory."); + else + Bukkit.getLogger().info("Failed to create modules directory."); + } + + if(!directory.isDirectory()) + throw new IllegalStateException("Invalid directory."); + + Map tempModules = new HashMap<>(); + for(File file : Objects.requireNonNull(directory.listFiles())) { + ModuleInfoFile info; + + try { + info = this.loader.getModuleInfoFile(file); + String name = info.getName(); + + if(name.equalsIgnoreCase("HCF")) { + HCF.getInstance().getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + ", Restricted Name."); + continue; + } + } catch(InvalidModuleInfoException e) { + HCF.getInstance().getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "'", e); + continue; + } + + tempModules.put(info.getName(), file); + } + + while(!tempModules.isEmpty()) { + Iterator ite = tempModules.keySet().iterator(); + + while(ite.hasNext()) { + String plugin = ite.next(); + File file = tempModules.get(plugin); + ite.remove(); + + try { + this.loadModule(file); + } catch(InvalidModuleException e) { + HCF.getInstance().getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "'", e); + } + } + } + + modules.values().forEach(this::enableModule); + } + + public void disableModules() { + modules.values().forEach(this::disableModule); + } + + public Module getModule(String name) { + return getModules().get(name); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleException.java b/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleException.java new file mode 100644 index 0000000..7771575 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleException.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.modules.ex; + +public class InvalidModuleException extends Exception { + + public InvalidModuleException(String message) { + super("[Module] " + message); + } + + public InvalidModuleException(Throwable throwable) { + super(throwable); + } + + public InvalidModuleException(String message, Throwable throwable) { + super("[Module] " + message, throwable); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleInfoException.java b/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleInfoException.java new file mode 100644 index 0000000..0afc729 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleInfoException.java @@ -0,0 +1,16 @@ +package me.hulipvp.hcf.api.modules.ex; + +public class InvalidModuleInfoException extends Exception { + + public InvalidModuleInfoException(String module) { + super("[Module] Module '" + module + "' has an invalid info.yml file."); + } + + public InvalidModuleInfoException(Throwable throwable) { + super(throwable); + } + + public InvalidModuleInfoException(String message, Throwable throwable) { + super("[Module] " + message, throwable); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleLoaderException.java b/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleLoaderException.java new file mode 100644 index 0000000..d592588 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/ex/InvalidModuleLoaderException.java @@ -0,0 +1,8 @@ +package me.hulipvp.hcf.api.modules.ex; + +public class InvalidModuleLoaderException extends RuntimeException { + + public InvalidModuleLoaderException(String module) { + super("[Module] Module '" + module + "' failed to be instated due to an invalid class loader"); + } +} diff --git a/src/main/java/me/hulipvp/hcf/api/modules/ex/ModuleLoadException.java b/src/main/java/me/hulipvp/hcf/api/modules/ex/ModuleLoadException.java new file mode 100644 index 0000000..5dc65a5 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/api/modules/ex/ModuleLoadException.java @@ -0,0 +1,8 @@ +package me.hulipvp.hcf.api.modules.ex; + +public class ModuleLoadException extends RuntimeException { + + public ModuleLoadException(String module, String message) { + super("[Module] Failed to load Module '" + module + "', error: " + message); + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/BackendType.java b/src/main/java/me/hulipvp/hcf/backend/BackendType.java new file mode 100644 index 0000000..47ac175 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/BackendType.java @@ -0,0 +1,29 @@ +package me.hulipvp.hcf.backend; + +import lombok.Getter; + +public enum BackendType { + + REDIS("Redis"), + MONGO("Mongo"); + + @Getter private static BackendType fallback = BackendType.MONGO; + + @Getter private String verboseName; + + BackendType(String verboseName) { + this.verboseName = verboseName; + } + + public static BackendType getOrDefault(String match) { + if(match == null) + return getFallback(); + + for(BackendType type : values()) { + if(type.getVerboseName().toLowerCase().equals(match.toLowerCase())) + return type; + } + + return getFallback(); + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/BackendUtils.java b/src/main/java/me/hulipvp/hcf/backend/BackendUtils.java new file mode 100644 index 0000000..e4e7141 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/BackendUtils.java @@ -0,0 +1,254 @@ +package me.hulipvp.hcf.backend; + +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.conquest.data.ConquestZone; +import me.hulipvp.hcf.game.event.conquest.data.ConquestZoneType; +import me.hulipvp.hcf.game.event.koth.Koth; +import me.hulipvp.hcf.game.event.koth.data.KothPoint; +import me.hulipvp.hcf.game.event.koth.data.KothTime; +import me.hulipvp.hcf.game.event.mountain.Mountain; +import me.hulipvp.hcf.game.event.mountain.type.MountainType; +import me.hulipvp.hcf.game.faction.Claim; +import me.hulipvp.hcf.game.faction.Faction; +import me.hulipvp.hcf.game.faction.type.FactionType; +import me.hulipvp.hcf.game.faction.type.event.ConquestFaction; +import me.hulipvp.hcf.game.faction.type.event.KothFaction; +import me.hulipvp.hcf.game.faction.type.event.MountainFaction; +import me.hulipvp.hcf.game.faction.type.player.FactionMember; +import me.hulipvp.hcf.game.faction.type.player.FactionRank; +import me.hulipvp.hcf.game.faction.type.player.PlayerFaction; +import me.hulipvp.hcf.game.faction.type.system.RoadFaction; +import me.hulipvp.hcf.game.faction.type.system.SafezoneFaction; +import me.hulipvp.hcf.game.faction.type.system.SystemFaction; +import me.hulipvp.hcf.game.faction.type.system.WarzoneFaction; +import me.hulipvp.hcf.utils.LocUtils; +import me.hulipvp.hcf.utils.TaskUtils; +import me.hulipvp.hcf.utils.TimeUtils; +import me.hulipvp.hcf.api.chat.C; +import org.bson.Document; +import org.bukkit.ChatColor; + +import java.util.*; + +public class BackendUtils { + + public static Faction factionFromDocument(Document doc) { + FactionType type = FactionType.valueOf(doc.getString("type")); + + Faction faction; + switch(type) { + case PLAYER: + PlayerFaction pf = new PlayerFaction(UUID.fromString(doc.getString("uuid")), doc.getString("name"), null); + + pf.setDeathban(doc.getBoolean("deathban")); + pf.setDtr(doc.getDouble("dtr")); + + if(doc.getString("startRegen") != null) + pf.setStartRegen(TimeUtils.deserializeTimestamp(doc.getString("startRegen"))); + + if(doc.get("open") != null) + pf.setOpen(doc.getBoolean("open")); + + pf.setRegening(doc.getBoolean("regening")); + pf.setLives(doc.getInteger("lives")); + pf.setBalance(doc.getInteger("balance")); + pf.setPowerFaction(doc.getBoolean("powerfaction")); + pf.setAnnouncement(doc.getString("announcement")); + + if(doc.get("home") != null) + pf.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.get("kothCaptures") != null) + pf.setKothCaptures(doc.getInteger("kothCaptures")); + + if(doc.get("members") != null) { + Document members = doc.get("members", Document.class); + for(String member : members.keySet()) { + FactionMember facMember = new FactionMember(UUID.fromString(member), FactionRank.valueOf(members.getString(member))); + if(facMember.getRank() == FactionRank.LEADER) + pf.setLeader(facMember); + + pf.addMember(facMember); + } + } + + if(doc.get("allies") != null) { + ArrayList allies = doc.get("allies", ArrayList.class); + for(String ally : allies) + pf.addAlly(UUID.fromString(ally)); + } + + if(doc.get("requestedAllies") != null) { + ArrayList requestedAllies = doc.get("requestedAllies", ArrayList.class); + for(String requestedAlly : requestedAllies) + pf.addRequestedAlly(UUID.fromString(requestedAlly)); + } + + if(doc.get("invited") != null) { + ArrayList invited = doc.get("invited", ArrayList.class); + for(String invitee : invited) + pf.addInvite(UUID.fromString(invitee)); + } + + if(doc.getString("claim") != null) + pf.addClaim(Claim.fromString(doc.getString("claim"))); + faction = pf; + break; + case SAFEZONE: + SafezoneFaction safezone = new SafezoneFaction(UUID.fromString(doc.getString("uuid")), doc.getString("name")); + safezone.setDeathban(doc.getBoolean("deathban")); + safezone.setColor(ChatColor.valueOf(doc.getString("color"))); + + if(doc.get("home") != null) + safezone.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.getString("claim") != null) + safezone.addClaim(Claim.fromString(doc.getString("claim"))); + faction = safezone; + break; + case WARZONE: + WarzoneFaction warzone = new WarzoneFaction(UUID.fromString(doc.getString("uuid"))); + warzone.setDeathban(doc.getBoolean("deathban")); + warzone.setColor(ChatColor.valueOf(doc.getString("color"))); + + if(doc.get("home") != null) + warzone.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.getString("claim") != null) + warzone.addClaim(Claim.fromString(doc.getString("claim"))); + faction = warzone; + break; + case KOTH: + KothFaction koth = new KothFaction(UUID.fromString(doc.getString("uuid")), doc.getString("name"), Koth.getKoth(doc.getString("koth"))); + koth.setDeathban(doc.getBoolean("deathban")); + koth.setColor(ChatColor.valueOf(doc.getString("color"))); + + if(doc.get("home") != null) + koth.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.getString("claim") != null) + koth.addClaim(Claim.fromString(doc.getString("claim"))); + faction = koth; + break; + case CONQUEST: + ConquestFaction conquest = new ConquestFaction(UUID.fromString(doc.getString("uuid")), doc.getString("name"), Conquest.getConquest(doc.getString("conquest"))); + conquest.setDeathban(doc.getBoolean("deathban")); + conquest.setColor(ChatColor.valueOf(doc.getString("color"))); + + if(doc.get("home") != null) conquest.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.getString("claim") != null) + conquest.addClaim(Claim.fromString(doc.getString("claim"))); + faction = conquest; + break; + case ROAD: + RoadFaction road = new RoadFaction(UUID.fromString(doc.getString("uuid")), doc.getString("name")); + road.setDeathban(doc.getBoolean("deathban")); + road.setColor(ChatColor.valueOf(doc.getString("color"))); + + if(doc.get("home") != null) + road.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.getString("claim") != null) + road.addClaim(Claim.fromString(doc.getString("claim"))); + faction = road; + break; + case SYSTEM: + SystemFaction system = new SystemFaction(UUID.fromString(doc.getString("uuid")), doc.getString("name")); + system.setDeathban(doc.getBoolean("deathban")); + system.setColor(ChatColor.valueOf(doc.getString("color"))); + + if(doc.get("home") != null) + system.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.getString("claim") != null) + system.addClaim(Claim.fromString(doc.getString("claim"))); + faction = system; + break; + case MOUNTAIN: + MountainFaction mountain = new MountainFaction(UUID.fromString(doc.getString("uuid")), doc.getString("name"), Mountain.getMountain(doc.getString("name"))); + mountain.setDeathban(doc.getBoolean("deathban")); + mountain.setColor(ChatColor.valueOf(doc.getString("color"))); + + if(doc.get("home") != null) + mountain.setHome(LocUtils.stringToLocation(doc.getString("home"))); + + if(doc.getString("claim") != null) + mountain.addClaim(Claim.fromString(doc.getString("claim"))); + faction = mountain; + break; + default: + faction = null; + return null; + } + + Set claims = null; + if(doc.containsKey("claims")) { + claims = new HashSet<>(); + List claimList = (ArrayList) doc.get("claims", ArrayList.class); + for(Document document : claimList) { + if(document == null || !document.containsKey("claim")) + continue; + + claims.add(Claim.fromString(document.getString("claim"))); + } + } + + if(faction != null && claims != null && !claims.isEmpty()) + claims.forEach(faction::addClaim); + + return null; + } + + public static Koth kothFromDocument(Document doc) { + Koth koth = new Koth(doc.getString("name")); + if(doc.getString("point") != null) + koth.setPoint(KothPoint.fromString(doc.getString("point"))); + + if(HCF.getInstance().getConfig().getString("koth.special-times." + koth.getName().toLowerCase()) != null) + koth.setTime(KothTime.valueOf(HCF.getInstance().getConfig().getString("koth.special-times." + koth.getName().toLowerCase()))); + + koth.setSpecial(doc.getBoolean("special")); + return koth; + } + + public static Conquest conquestFromDocument(Document doc) { + Conquest conquest = new Conquest(doc.getString("name")); + + if(doc.getString("red") != null) + conquest.getZones().put(ConquestZoneType.RED, ConquestZone.fromString(C.strip(doc.getString("red")).toUpperCase())); + if(doc.getString("yellow") != null) + conquest.getZones().put(ConquestZoneType.YELLOW, ConquestZone.fromString(C.strip(doc.getString("yellow")).toUpperCase())); + if(doc.getString("green") != null) + conquest.getZones().put(ConquestZoneType.GREEN, ConquestZone.fromString(C.strip(doc.getString("green")).toUpperCase())); + if(doc.getString("blue") != null) + conquest.getZones().put(ConquestZoneType.BLUE, ConquestZone.fromString(C.strip(doc.getString("blue")).toUpperCase())); + + return conquest; + } + + public static Mountain mountainFromDocument(Document doc) { + Mountain mountain = new Mountain(doc.getString("name"), MountainType.valueOf(doc.getString("type"))); + mountain.setResetTime(doc.getInteger("resetTime")); + + if(doc.getString("point1") != null) + mountain.setPoint1(LocUtils.stringToLocation(doc.getString("point1"))); + + if(doc.getString("point2") != null) + mountain.setPoint2(LocUtils.stringToLocation(doc.getString("point2"))); + + return mountain; + } + + public static void invalidFactionCheck() { + TaskUtils.runAsync(() -> { + Faction.getFactions().values().stream() + .filter(PlayerFaction.class::isInstance) + .map(PlayerFaction.class::cast) + .forEach(playerFaction -> { + playerFaction.getAllies().removeIf(Faction::isNotAFaction); + }); + }); + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/HCFBackend.java b/src/main/java/me/hulipvp/hcf/backend/HCFBackend.java new file mode 100644 index 0000000..409c7f4 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/HCFBackend.java @@ -0,0 +1,27 @@ +package me.hulipvp.hcf.backend; + +import lombok.Getter; +import lombok.Setter; +import me.hulipvp.hcf.HCF; + +public abstract class HCFBackend implements IBackend { + + @Getter private final BackendType type; + + @Getter @Setter private boolean loaded; + + public HCFBackend(BackendType type) { + this.type = type; + } + + protected void logInfoMessage(String message) { + HCF.getInstance().getLogger().info("(Backend) {" + this.getType().getVerboseName() + "} - " + message); + } + + protected void logException(String message, Exception e) { + HCF.getInstance().getLogger().severe("(Backend) {" + this.getType().getVerboseName() + "} - " + message); + HCF.getInstance().getLogger().severe("-------------------------------------------"); + e.printStackTrace(); + HCF.getInstance().getLogger().severe("-------------------------------------------"); + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/IBackend.java b/src/main/java/me/hulipvp/hcf/backend/IBackend.java new file mode 100644 index 0000000..26a2b08 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/IBackend.java @@ -0,0 +1,81 @@ +package me.hulipvp.hcf.backend; + +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.koth.Koth; +import me.hulipvp.hcf.game.event.mountain.Mountain; +import me.hulipvp.hcf.game.faction.Faction; +import me.hulipvp.hcf.game.player.HCFProfile; + +public interface IBackend { + + /** + * Called when the plugin is being disabled. + */ + void close(); + + // Profile // + void createProfile(HCFProfile profile); + + void deleteProfile(HCFProfile profile); + + void deleteProfiles(); + + void saveProfile(HCFProfile profile); + + void saveProfileSync(HCFProfile profile); + + void loadProfile(HCFProfile profile); + + void loadProfiles(); + // End Profile // + + // Factions // + void createFaction(Faction faction); + + void deleteFaction(Faction faction); + + void deleteFactions(); + + void saveFaction(Faction faction); + + void saveFactionSync(Faction faction); + + void loadFactions(); + // End Factions // + + // Koths // + void createKoth(Koth koth); + + void deleteKoth(Koth koth); + + void saveKoth(Koth koth); + + void saveKothSync(Koth koth); + + void loadKoths(); + // End Koths // + + // Conquests // + void createConquest(Conquest conquest); + + void deleteConquest(Conquest conquest); + + void saveConquest(Conquest conquest); + + void saveConquestSync(Conquest conquest); + + void loadConquests(); + // End Conquests // + + // Mountains // + void createMountain(Mountain mountain); + + void deleteMountain(Mountain mountain); + + void saveMountain(Mountain mountain); + + void saveMountainSync(Mountain mountain); + + void loadMountains(); + // End Mountains // +} diff --git a/src/main/java/me/hulipvp/hcf/backend/backends/MongoBackend.java b/src/main/java/me/hulipvp/hcf/backend/backends/MongoBackend.java new file mode 100644 index 0000000..12dcd3c --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/backends/MongoBackend.java @@ -0,0 +1,333 @@ +package me.hulipvp.hcf.backend.backends; + +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.backend.BackendType; +import me.hulipvp.hcf.backend.BackendUtils; +import me.hulipvp.hcf.backend.HCFBackend; +import me.hulipvp.hcf.backend.IBackend; +import me.hulipvp.hcf.backend.creds.MongoCredentials; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.koth.Koth; +import me.hulipvp.hcf.game.event.mountain.Mountain; +import me.hulipvp.hcf.game.faction.Faction; +import me.hulipvp.hcf.game.faction.type.FactionType; +import me.hulipvp.hcf.game.faction.type.event.ConquestFaction; +import me.hulipvp.hcf.game.faction.type.event.KothFaction; +import me.hulipvp.hcf.game.faction.type.event.MountainFaction; +import me.hulipvp.hcf.game.faction.type.player.PlayerFaction; +import me.hulipvp.hcf.game.player.HCFProfile; +import me.hulipvp.hcf.utils.TaskUtils; +import org.bson.Document; +import org.bukkit.Bukkit; + +import java.util.Collections; +import java.util.UUID; + +import static com.mongodb.client.model.Filters.eq; + +public class MongoBackend extends HCFBackend implements IBackend { + + private MongoClient mongo; + private MongoDatabase db; + private MongoCollection profiles, factions, koths, conquests, mountains; + + public MongoBackend(MongoCredentials credentials) { + super(BackendType.MONGO); + + try { + ServerAddress address = new ServerAddress(credentials.getHostname(), credentials.getPort()); + + if(HCF.getInstance().getConfig().getBoolean("backend.mongo.auth.enable")) { + MongoCredential credential = MongoCredential.createCredential(credentials.getUsername(), credentials.getAuthDb(), credentials.getPassword().toCharArray()); + this.mongo = new MongoClient(address, Collections.singletonList(credential)); + } else { + this.mongo = new MongoClient(address); + } + + this.db = this.mongo.getDatabase(credentials.getDatabase()); + this.profiles = this.db.getCollection("profiles"); + this.factions = this.db.getCollection("factions"); + this.koths = this.db.getCollection("koths"); + this.conquests = this.db.getCollection("conquests"); + this.mountains = this.db.getCollection("mountains"); + + this.logInfoMessage("Mongo Driver successfully loaded."); + setLoaded(true); + } catch(Exception e) { + this.logException("Mongo Driver failed to load.", e); + } + } + + @Override + public void close() { + if(this.mongo != null) + this.mongo.close(); + } + + /*=============================*/ + // Profiles + + @Override + public void createProfile(HCFProfile profile) { + TaskUtils.runAsync(() -> { + this.profiles.insertOne(profile.toDocument()); + }); + } + + @Override + public void deleteProfile(HCFProfile profile) { + TaskUtils.runAsync(() -> { + this.profiles.deleteOne(eq("uuid", profile.getUuid().toString())); + }); + } + + @Override + public void deleteProfiles() { + TaskUtils.runAsync(() -> { + this.profiles.drop(); + this.profiles = this.db.getCollection("profiles"); + }); + } + + @Override + public void saveProfile(HCFProfile profile) { + TaskUtils.runAsync(() -> { + this.saveProfileSync(profile); + }); + } + + @Override + public void saveProfileSync(HCFProfile profile) { + Document doc = profile.toDocument(); + this.profiles.findOneAndReplace(eq("uuid", profile.getUuid().toString()), doc); + } + + @Override + public void loadProfile(HCFProfile profile) { + Document doc = this.profiles.find(eq("uuid", profile.getUuid().toString())).first(); + + if(doc != null) { + profile.load(doc); + } else { + this.createProfile(profile); + } + } + + @Override + public void loadProfiles() { + for(Document doc : this.profiles.find()) { + if(!doc.containsKey("uuid")) + continue; + + UUID uuid = UUID.fromString(doc.getString("uuid")); + HCFProfile.getByUuid(uuid); + } + } + + /*=============================*/ + + /*=============================*/ + // Factions + + @Override + public void createFaction(Faction faction) { + TaskUtils.runAsync(() -> { + this.factions.insertOne(faction.toDocument()); + }); + } + + @Override + public void deleteFaction(Faction faction) { + TaskUtils.runAsync(() -> { + this.factions.deleteOne(eq("uuid", faction.getUuid().toString())); + }); + } + + @Override + public void deleteFactions() { + TaskUtils.runAsync(() -> { + this.factions.drop(); + this.factions = this.db.getCollection("factions"); + }); + } + + @Override + public void saveFaction(Faction faction) { + TaskUtils.runAsync(() -> { + this.saveFactionSync(faction); + }); + } + + @Override + public void saveFactionSync(Faction faction) { + Document doc = faction.toDocument(); + if(doc != null) + this.factions.findOneAndReplace(eq("uuid", faction.getUuid().toString()), doc); + } + + private synchronized void loadFaction(Document doc) { + if(doc != null) { + Faction faction = BackendUtils.factionFromDocument(doc); + if(faction == null) + return; + + if(faction.getType() == FactionType.KOTH) + Koth.getKoth(faction.getName()).setFaction((KothFaction) faction); + + if(faction.getType() == FactionType.CONQUEST) + Conquest.getConquest(faction.getName()).setFaction((ConquestFaction) faction); + + if(faction.getType() == FactionType.MOUNTAIN) + Mountain.getMountain(faction.getName()).setFaction((MountainFaction) faction); + } + } + + @Override + public void loadFactions() { + TaskUtils.runAsync(() -> { + for(Document doc : this.factions.find()) + this.loadFaction(doc); + }); + + BackendUtils.invalidFactionCheck(); + } + /*=============================*/ + + /*=============================*/ + // Koths + + @Override + public void createKoth(Koth koth) { + TaskUtils.runAsync(() -> { + this.koths.insertOne(koth.toDocument()); + }); + } + + @Override + public void deleteKoth(Koth koth) { + TaskUtils.runAsync(() -> { + this.koths.deleteOne(eq("name", koth.getName())); + }); + } + + @Override + public void saveKoth(Koth koth) { + TaskUtils.runAsync(() -> { + this.saveKothSync(koth); + }); + } + + @Override + public void saveKothSync(Koth koth) { + this.koths.findOneAndReplace(eq("name", koth.getName()), koth.toDocument()); + } + + private synchronized void loadKoth(Document doc) { + if(doc != null) { + Koth koth = BackendUtils.kothFromDocument(doc); + Koth.getKoths().put(koth.getName(), koth); + } + } + + @Override + public void loadKoths() { + for(Document doc : this.koths.find()) + this.loadKoth(doc); + } + /*=============================*/ + + /*=============================*/ + // Conquests + + @Override + public void createConquest(Conquest conquest) { + TaskUtils.runAsync(() -> { + this.conquests.insertOne(conquest.toDocument()); + }); + } + + @Override + public void deleteConquest(Conquest conquest) { + TaskUtils.runAsync(() -> { + this.conquests.deleteOne(eq("name", conquest.getName())); + }); + } + + @Override + public void saveConquest(Conquest conquest) { + TaskUtils.runAsync(() -> { + this.saveConquestSync(conquest); + }); + } + + @Override + public void saveConquestSync(Conquest conquest) { + this.conquests.findOneAndReplace(eq("name", conquest.getName()), conquest.toDocument()); + } + + private synchronized void loadConquest(Document doc) { + if(doc != null) { + Conquest conquest = BackendUtils.conquestFromDocument(doc); + Conquest.getConquests().put(conquest.getName(), conquest); + } + } + + @Override + public void loadConquests() { + for(Document doc : this.conquests.find()) + this.loadConquest(doc); + } + /*=============================*/ + + /*=============================*/ + // Mountains + + @Override + public void createMountain(Mountain mountain) { + TaskUtils.runAsync(() -> { + this.mountains.insertOne(mountain.toDocument()); + }); + } + + @Override + public void deleteMountain(Mountain mountain) { + TaskUtils.runAsync(() -> { + this.mountains.deleteOne(eq("name", mountain.getName())); + }); + } + + @Override + public void saveMountain(Mountain mountain) { + TaskUtils.runAsync(() -> { + this.saveMountainSync(mountain); + }); + } + + @Override + public void saveMountainSync(Mountain mountain) { + try { + this.mountains.findOneAndReplace(eq("name", mountain.getName()), mountain.toDocument()); + } catch(Exception ex) { + deleteMountain(mountain); + } + } + + private synchronized void loadMountain(Document doc) { + if(doc != null) { + Mountain mountain = BackendUtils.mountainFromDocument(doc); + Mountain.getMountains().put(mountain.getName(), mountain); + } + } + + @Override + public void loadMountains() { + for(Document doc : this.mountains.find()) + this.loadMountain(doc); + } + /*=============================*/ +} diff --git a/src/main/java/me/hulipvp/hcf/backend/backends/RedisBackend.java b/src/main/java/me/hulipvp/hcf/backend/backends/RedisBackend.java new file mode 100644 index 0000000..0287d75 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/backends/RedisBackend.java @@ -0,0 +1,381 @@ +package me.hulipvp.hcf.backend.backends; + +import lombok.Getter; +import me.hulipvp.hcf.utils.ConfigValues; +import me.hulipvp.hcf.HCF; +import me.hulipvp.hcf.backend.BackendType; +import me.hulipvp.hcf.backend.BackendUtils; +import me.hulipvp.hcf.backend.HCFBackend; +import me.hulipvp.hcf.backend.creds.RedisCredentials; +import me.hulipvp.hcf.game.event.conquest.Conquest; +import me.hulipvp.hcf.game.event.koth.Koth; +import me.hulipvp.hcf.game.event.mountain.Mountain; +import me.hulipvp.hcf.game.faction.Faction; +import me.hulipvp.hcf.game.faction.type.FactionType; +import me.hulipvp.hcf.game.faction.type.event.ConquestFaction; +import me.hulipvp.hcf.game.faction.type.event.KothFaction; +import me.hulipvp.hcf.game.faction.type.event.MountainFaction; +import me.hulipvp.hcf.game.player.HCFProfile; +import me.hulipvp.hcf.utils.TaskUtils; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.bson.Document; +import org.bukkit.Bukkit; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +import java.util.Set; +import java.util.UUID; + +public class RedisBackend extends HCFBackend { + + @Getter private JedisPool pool; + + public RedisBackend(RedisCredentials credentials) { + super(BackendType.REDIS); + + if(!credentials.password()) { + this.pool = new JedisPool(new GenericObjectPoolConfig(), credentials.getHost(), credentials.getPort(), 3000); + } else { + this.pool = new JedisPool(new GenericObjectPoolConfig(), credentials.getHost(), credentials.getPort(), 3000, credentials.getPassword()); + } + + try(Jedis jedis = pool.getResource()) { + setLoaded(jedis.isConnected()); + if(isLoaded()) + logInfoMessage("Redis Driver successfully loaded."); + else + throw new Exception("Unable to establish Jedis connection."); + } catch(Exception ex) { + logException("Redis Driver failed to load.", ex); + } + } + + @Override + public void close() { + if(this.pool != null) + if(!this.pool.isClosed()) + this.pool.close(); + } + + /*=============================*/ + // Profiles + + @Override + public void createProfile(HCFProfile profile) { + this.saveProfile(profile); + } + + @Override + public void deleteProfile(HCFProfile profile) { + try(Jedis jedis = this.getPool().getResource()) { + jedis.del(this.getKey(KeyType.PROFILE, profile.getUuid().toString())); + } + } + + @Override + public void deleteProfiles() { + TaskUtils.runAsync(() -> { + try(Jedis jedis = this.getPool().getResource()) { + jedis.del(this.getKey(KeyType.PROFILE) + "*"); + } + }); + } + + @Override + public void saveProfile(HCFProfile profile) { + TaskUtils.runAsync(() -> { + this.saveProfileSync(profile); + }); + } + + @Override + public void saveProfileSync(HCFProfile profile) { + try(Jedis jedis = this.getPool().getResource()) { + jedis.set(this.getKey(KeyType.PROFILE, profile.getUuid().toString()), profile.toDocument().toJson()); + } + } + + @Override + public void loadProfile(HCFProfile profile) { + try(Jedis jedis = this.getPool().getResource()) { + String json = jedis.get(this.getKey(KeyType.PROFILE, profile.getUuid().toString())); + + if(json != null) { + Document doc = Document.parse(json); + profile.load(doc); + } else { + this.createProfile(profile); + } + } + } + + @Override + public void loadProfiles() { + try(Jedis jedis = this.getPool().getResource()) { + Set profiles = jedis.keys(this.getKey(KeyType.PROFILE) + "*"); + + profiles.forEach(profile -> { + Document doc = Document.parse(jedis.get(profile)); + if(doc == null || !doc.containsKey("uuid")) + return; + + UUID uuid = UUID.fromString(doc.getString("uuid")); + HCFProfile.getByUuid(uuid); + }); + } + } + + /*=============================*/ + + /*=============================*/ + // Factions + + @Override + public void createFaction(Faction faction) { + this.saveFaction(faction); + } + + @Override + public void deleteFaction(Faction faction) { + TaskUtils.runAsync(() -> { + try(Jedis jedis = this.getPool().getResource()) { + jedis.del(this.getKey(KeyType.FACTION, faction.getUuid().toString())); + } + }); + } + + @Override + public void deleteFactions() { + TaskUtils.runAsync(() -> { + try(Jedis jedis = this.getPool().getResource()) { + jedis.del(this.getKey(KeyType.FACTION) + "*"); + } + }); + } + + @Override + public void saveFaction(Faction faction) { + TaskUtils.runAsync(() -> { + this.saveFactionSync(faction); + }); + } + + @Override + public void saveFactionSync(Faction faction) { + try(Jedis jedis = this.getPool().getResource()) { + jedis.set(this.getKey(KeyType.FACTION, faction.getUuid().toString()), faction.toDocument().toJson()); + } + } + + private synchronized void loadFaction(Document doc) { + if(doc != null) { + Faction faction = BackendUtils.factionFromDocument(doc); + if(faction == null) + return; + + if(faction.getType() == FactionType.KOTH) + Koth.getKoth(faction.getName()).setFaction((KothFaction) faction); + + if(faction.getType() == FactionType.CONQUEST) + Conquest.getConquest(faction.getName()).setFaction((ConquestFaction) faction); + + if(faction.getType() == FactionType.MOUNTAIN) + Mountain.getMountain(faction.getName()).setFaction((MountainFaction) faction); + } + } + + @Override + public void loadFactions() { + try(Jedis jedis = this.getPool().getResource()) { + Set factions = jedis.keys(this.getKey(KeyType.FACTION) + "*"); + + factions.forEach(faction -> { + Document doc = Document.parse(jedis.get(faction)); + if(doc != null) + this.loadFaction(doc); + }); + } + + BackendUtils.invalidFactionCheck(); + } + /*=============================*/ + + /*=============================*/ + // Koths + + @Override + public void createKoth(Koth koth) { + this.saveKoth(koth); + } + + @Override + public void deleteKoth(Koth koth) { + TaskUtils.runAsync(() -> { + try(Jedis jedis = this.getPool().getResource()) { + jedis.del(this.getKey(KeyType.KOTH, koth.getName())); + } + }); + } + + @Override + public void saveKoth(Koth koth) { + TaskUtils.runAsync(() -> { + this.saveKothSync(koth); + }); + } + + @Override + public void saveKothSync(Koth koth) { + try(Jedis jedis = this.getPool().getResource()) { + jedis.set(this.getKey(KeyType.KOTH, koth.getName()), koth.toDocument().toJson()); + } + } + + private synchronized void loadKoth(Document doc) { + if(doc != null) { + Koth koth = BackendUtils.kothFromDocument(doc); + Koth.getKoths().put(koth.getName(), koth); + } + } + + @Override + public void loadKoths() { + try(Jedis jedis = this.getPool().getResource()) { + Set koths = jedis.keys(this.getKey(KeyType.KOTH) + "*"); + + koths.forEach(koth -> { + Document doc = Document.parse(jedis.get(koth)); + if(doc != null) + this.loadKoth(doc); + }); + } + } + /*=============================*/ + + /*=============================*/ + // Conquests + + @Override + public void createConquest(Conquest conquest) { + this.saveConquest(conquest); + } + + @Override + public void deleteConquest(Conquest conquest) { + TaskUtils.runAsync(() -> { + try(Jedis jedis = this.getPool().getResource()) { + jedis.del(this.getKey(KeyType.CONQUEST, conquest.getName())); + } + }); + } + + @Override + public void saveConquest(Conquest conquest) { + TaskUtils.runAsync(() -> { + this.saveConquestSync(conquest); + }); + } + + @Override + public void saveConquestSync(Conquest conquest) { + try(Jedis jedis = this.getPool().getResource()) { + jedis.set(this.getKey(KeyType.CONQUEST, conquest.getName()), conquest.toDocument().toJson()); + } + } + + private synchronized void loadConquest(Document doc) { + if(doc != null) { + Conquest conquest = BackendUtils.conquestFromDocument(doc); + Conquest.getConquests().put(conquest.getName(), conquest); + } + } + + @Override + public void loadConquests() { + try(Jedis jedis = this.getPool().getResource()) { + Set conquests = jedis.keys(this.getKey(KeyType.CONQUEST) + "*"); + + conquests.forEach(conquest -> { + Document doc = Document.parse(jedis.get(conquest)); + if(doc != null) + this.loadConquest(doc); + }); + } + } + /*=============================*/ + + /*=============================*/ + // Mountains + + @Override + public void createMountain(Mountain mountain) { + this.saveMountain(mountain); + } + + @Override + public void deleteMountain(Mountain mountain) { + TaskUtils.runAsync(() -> { + try(Jedis jedis = this.getPool().getResource()) { + jedis.del(this.getKey(KeyType.MOUNTAIN, mountain.getName())); + } + }); + } + + @Override + public void saveMountain(Mountain mountain) { + TaskUtils.runAsync(() -> { + this.saveMountainSync(mountain); + }); + } + + @Override + public void saveMountainSync(Mountain mountain) { + try(Jedis jedis = this.getPool().getResource()) { + jedis.set(this.getKey(KeyType.MOUNTAIN, mountain.getName()), mountain.toDocument().toJson()); + } + } + + private synchronized void loadMountain(Document doc) { + if(doc != null) { + Mountain mountain = BackendUtils.mountainFromDocument(doc); + Mountain.getMountains().put(mountain.getName(), mountain); + } + } + + @Override + public void loadMountains() { + try(Jedis jedis = this.getPool().getResource()) { + Set mountains = jedis.keys(this.getKey(KeyType.MOUNTAIN) + "*"); + + mountains.forEach(mountain -> { + Document doc = Document.parse(jedis.get(mountain)); + if(doc != null) + this.loadMountain(doc); + }); + } + } + /*=============================*/ + + private String getKey(KeyType type) { + return "hcf:map" + ConfigValues.MAP_NUMBER + ":" + type.getPrefix() + ":"; + } + + private String getKey(KeyType type, String identifier) { + return getKey(type) + identifier; + } + + private enum KeyType { + + PROFILE("profile"), + FACTION("faction"), + KOTH("koth"), + CONQUEST("conquest"), + MOUNTAIN("mountain"); + + @Getter private String prefix; + + KeyType(String prefix) { + this.prefix = prefix; + } + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/creds/MongoCredentials.java b/src/main/java/me/hulipvp/hcf/backend/creds/MongoCredentials.java new file mode 100644 index 0000000..77fe755 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/creds/MongoCredentials.java @@ -0,0 +1,19 @@ +package me.hulipvp.hcf.backend.creds; + +import lombok.Getter; +import me.hulipvp.hcf.utils.ConfigValues; + +public class MongoCredentials { + + @Getter private final String hostname, username, password, database, authDb; + @Getter private final int port; + + public MongoCredentials(String hostname, int port, String username, String password, String database, String authDb) { + this.hostname = hostname; + this.port = port; + this.username = username; + this.password = password; + this.database = database + (database.endsWith(ConfigValues.MAP_NUMBER + "") ? "" : ConfigValues.MAP_NUMBER); + this.authDb = authDb; + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/creds/RedisCredentials.java b/src/main/java/me/hulipvp/hcf/backend/creds/RedisCredentials.java new file mode 100644 index 0000000..4cdec85 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/creds/RedisCredentials.java @@ -0,0 +1,19 @@ +package me.hulipvp.hcf.backend.creds; + +import lombok.Getter; + +public class RedisCredentials { + + @Getter private final String host, password; + @Getter private final int port; + + public RedisCredentials(String host, int port, String password) { + this.host = host; + this.port = port; + this.password = password; + } + + public boolean password() { + return this.getPassword().length() > 0; + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/files/ConfigFile.java b/src/main/java/me/hulipvp/hcf/backend/files/ConfigFile.java new file mode 100644 index 0000000..efe8617 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/files/ConfigFile.java @@ -0,0 +1,66 @@ +package me.hulipvp.hcf.backend.files; + +import me.hulipvp.hcf.HCF; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; + +public abstract class ConfigFile { + + private File file; + public FileConfiguration config; + + public ConfigFile(String name, HCF plugin) { + name += (name.endsWith(".yml") ? "" : ".yml"); + + file = new File(plugin.getDataFolder() + File.separator + name); + if(!file.exists()) { + try { + plugin.getLogger().info(name + " doesn't exist, now creating..."); + + file.getParentFile().mkdirs(); + if(plugin.getResource(name) != null) { + plugin.saveResource(name, false); + HCF.getInstance().getLogger().info("Successfully created " + file + "."); + } else { + if(file.createNewFile()) + HCF.getInstance().getLogger().info("Successfully created " + file + "."); + } + + plugin.getLogger().info(name + " has successfully been created"); + } catch(IOException ex) { + plugin.getLogger().severe(name + " wasn't able to be created: " + ex.getMessage()); + ex.printStackTrace(); + } + } + + this.config = YamlConfiguration.loadConfiguration(this.file); + } + + public ConfigFile(String name) { + this(name, HCF.getInstance()); + } + + public File getFile() { + return this.file; + } + + public FileConfiguration getConfig() { + return this.config; + } + + public boolean save() { + try { + this.getConfig().save(this.getFile()); + return true; + } catch(IOException ignored) { } + + return false; + } + + public void reload() { + this.config = YamlConfiguration.loadConfiguration(this.file); + } +} \ No newline at end of file diff --git a/src/main/java/me/hulipvp/hcf/backend/files/KitsFile.java b/src/main/java/me/hulipvp/hcf/backend/files/KitsFile.java new file mode 100644 index 0000000..02effc6 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/files/KitsFile.java @@ -0,0 +1,112 @@ +package me.hulipvp.hcf.backend.files; + +import lombok.Getter; +import me.hulipvp.hcf.game.kits.Kit; +import me.hulipvp.hcf.game.kits.MapKit; +import me.hulipvp.hcf.game.player.data.PlayerInv; +import me.hulipvp.hcf.utils.item.InvUtils; +import me.hulipvp.hcf.utils.StringUtils; +import org.bukkit.ChatColor; + +import java.util.HashMap; +import java.util.Map; + +public class KitsFile extends ConfigFile { + + @Getter private Map kits = new HashMap<>(); + + public KitsFile() { + super("kits.yml"); + } + + public void init() { + if(!config.contains("kits")) { + config.createSection("kits"); + save(); + return; + } + + for(String kit : config.getConfigurationSection("kits").getKeys(false)) { + PlayerInv playerInv = InvUtils.invFromString(config.getString("kits." + kit + ".inv")); + ChatColor color = ChatColor.valueOf(config.getString("kits." + kit + ".color")); + boolean enabled = config.getBoolean("kits." + kit + ".enabled"); + + MapKit mapKit = new MapKit(kit); + mapKit.setPlayerInv(playerInv); + mapKit.setColor(color); + mapKit.setEnabled(enabled); + + kits.put(kit, mapKit); + } + } + + public MapKit getKit(String kit) { + return kits.get(kit); + } + + public void saveKit(MapKit kit) { + config.set("kits." + kit.getName() + ".inv", kit.getPlayerInv().toString()); + config.set("kits." + kit.getName() + ".color", kit.getColor().name()); + config.set("kits." + kit.getName() + ".enabled", kit.isEnabled()); + } + + public void saveKits() { + getKits().values().forEach(this::saveKit); + + this.save(); + } + + public boolean isValidKit(String kit) { + return getKit(kit) != null; + } + + public void setEnabled(String kit, boolean enabled) { + if(isValidKit(kit)) { + getKit(kit).setEnabled(enabled); + } + + config.set("kits." + kit + ".enabled", enabled); + this.save(); + } + + public boolean isEnabled(String kit) { + return getKit(kit).isEnabled(); + } + + public void setMapkitInv(String kit, PlayerInv playerInv) { + if(isValidKit(kit)) { + getKit(kit).setPlayerInv(playerInv); + } + + config.set("kits." + kit + ".inv", playerInv.toString()); + this.save(); + } + + public PlayerInv getMapkitInv(String kit) { + return getKit(kit).getPlayerInv(); + } + + public void setColor(String kit, ChatColor color) { + if(isValidKit(kit)) { + getKit(kit).setColor(color); + } + + config.set("kits." + kit + ".color", StringUtils.chatColorToString(color)); + this.save(); + } + + public ChatColor getColor(String kit) { + ChatColor color; + try { + color = ChatColor.valueOf(config.getString("kits." + kit + ".color")); + } catch(Exception ex) { + color = ChatColor.GREEN; + } + + return color; + } + + public String getDisplay(String kit) { + return getKit(kit).getDisplay(); + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/files/LocationsFile.java b/src/main/java/me/hulipvp/hcf/backend/files/LocationsFile.java new file mode 100644 index 0000000..642cb30 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/files/LocationsFile.java @@ -0,0 +1,58 @@ +package me.hulipvp.hcf.backend.files; + +import lombok.Getter; +import me.hulipvp.hcf.utils.LocUtils; +import org.bukkit.Location; + +public class LocationsFile extends ConfigFile { + + @Getter private Location endSpawn, endExit, netherSpawn, netherExit; + + public LocationsFile() { + super("locations.yml"); + } + + public void init() { + String endSpawn = this.getConfig().getString("locations.end.spawn"); + String endExit = this.getConfig().getString("locations.end.exit"); + String netherSpawn = this.getConfig().getString("locations.nether.exit"); + String netherExit = this.getConfig().getString("locations.nether.exit"); + + if(endSpawn != null) + this.endSpawn = (endSpawn.length() > 0) ? LocUtils.stringToLocation(endSpawn) : null; + + if(endExit != null) + this.endExit = (endExit.length() > 0) ? LocUtils.stringToLocation(endExit) : null; + + if(netherSpawn != null) + this.netherSpawn = (netherSpawn.length() > 0) ? LocUtils.stringToLocation(netherSpawn) : null; + + if(netherExit != null) + this.netherExit = (netherExit.length() > 0) ? LocUtils.stringToLocation(netherExit) : null; + } + + public void setEndSpawn( Location location) { + this.endSpawn = location; + + this.getConfig().set("locations.end.spawn", LocUtils.locationToString(location)); + } + + + public void setEndExit( Location location) { + this.endExit = location; + + this.getConfig().set("locations.end.exit", LocUtils.locationToString(location)); + } + + public void setNetherSpawn( Location location) { + this.netherSpawn = location; + + this.getConfig().set("locations.nether.spawn", LocUtils.locationToString(location)); + } + + public void setNetherExit( Location location) { + this.netherExit = location; + + this.getConfig().set("locations.nether.exit", LocUtils.locationToString(location)); + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/files/MessagesFile.java b/src/main/java/me/hulipvp/hcf/backend/files/MessagesFile.java new file mode 100644 index 0000000..b526d15 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/files/MessagesFile.java @@ -0,0 +1,89 @@ +package me.hulipvp.hcf.backend.files; + +import me.hulipvp.hcf.api.chat.C; + +import java.util.List; +import java.util.stream.Collectors; + +public class MessagesFile extends ConfigFile { + + public MessagesFile() { + super("messages.yml"); + } + + public List getHelp() { + return this.getConfig().getStringList("help.main"); + } + + public List getCoords() { + return this.getConfig().getStringList("help.coords"); + } + + public List getFactionSubclaiming() { + return this.getConfig().getStringList("faction.help.subclaiming"); + } + + public List getFactionClaimingStart() { + return this.getConfig().getStringList("faction.help.claiming"); + } + + public List getFactionHelp() { + return this.getConfig().getStringList("faction.help.main"); + } + + public List getFactionCaptainHelp() { + return this.getConfig().getStringList("faction.help.captain"); + } + + public List getFactionColeaderHelp() { + return this.getConfig().getStringList("faction.help.coleader"); + } + + public List getPlayerFactionShow() { + return this.getConfig().getStringList("faction.show.player"); + } + + public List getSystemFactionShow() { + return this.getConfig().getStringList("faction.show.system"); + } + + public List getEconomyHelp() { + return this.getConfig().getStringList("help.economy"); + } + + public List getLivesHelp() { + return this.getConfig().getStringList("help.lives"); + } + + public List getPvpHelp() { + return this.getConfig().getStringList("help.pvp"); + } + + public List getFilterHelp() { + return this.getConfig().getStringList("help.filter"); + } + + public List getLff() { + return this.getConfig().getStringList("other.lff"); + } + + public List getShopBuyLines() { + return this.getConfig().getStringList("shop.lines.buy").stream() + .map(C::color).collect(Collectors.toList()); + } + + public List getShopSellLines() { + return this.getConfig().getStringList("shop.lines.sell").stream() + .map(C::color).collect(Collectors.toList()); + } + + public List getShopInsufficientLines() { + return this.getConfig().getStringList("shop.lines.insufficient").stream() + .map(C::color).collect(Collectors.toList()); + } + + public List getShopNoItemLines() { + return this.getConfig().getStringList("shop.lines.no-items").stream() + .map(C::color).collect(Collectors.toList()); + } +} diff --git a/src/main/java/me/hulipvp/hcf/backend/files/ReclaimFile.java b/src/main/java/me/hulipvp/hcf/backend/files/ReclaimFile.java new file mode 100644 index 0000000..c0b14b9 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/backend/files/ReclaimFile.java @@ -0,0 +1,47 @@ +package me.hulipvp.hcf.backend.files; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ReclaimFile extends ConfigFile { + + @Getter private Map> groups; + @Getter private List reclaimed; + + public ReclaimFile() { + super("reclaim.yml"); + + groups = new HashMap<>(); + reclaimed = new ArrayList<>(); + } + + public void loadGroups() { + if(config.contains("reclaim")) { + for(String key : config.getConfigurationSection("reclaim").getKeys(false)) + groups.put(key, config.getStringList("reclaim." + key + ".commands")); + } else { + config.createSection("reclaim"); + save(); + } + } + + public void loadReclaimed() { + if(config.contains("reclaimed")) { + reclaimed = config.getStringList("reclaimed"); + } else { + config.createSection("reclaimed"); + save(); + } + } + + public void saveReclaimed() { + reload(); + config.set("reclaimed", reclaimed); + save(); + reload(); + } +} diff --git a/src/main/java/me/hulipvp/hcf/commands/BalanceCommand.java b/src/main/java/me/hulipvp/hcf/commands/BalanceCommand.java new file mode 100644 index 0000000..b63a0db --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/commands/BalanceCommand.java @@ -0,0 +1,30 @@ +package me.hulipvp.hcf.commands; + +import me.hulipvp.hcf.game.player.HCFProfile; +import me.hulipvp.hcf.utils.Locale; +import me.hulipvp.hcf.api.command.Command; +import me.hulipvp.hcf.api.command.CommandData; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class BalanceCommand { + + @Command(label = "balance", aliases = {"money", "$", "bal"}, playerOnly = true) + public void onCommand(CommandData args) { + Player p = (Player) args.getSender(); + HCFProfile profile = HCFProfile.getByPlayer(p); + + if(args.length() != 1) { + p.sendMessage(Locale.COMMAND_BALANCE.toString().replaceAll("%balance%", String.valueOf(profile.getBalance()))); + } else { + Player target = Bukkit.getPlayer(args.getArg(0)); + if(target == null) { + p.sendMessage(Locale.PLAYER_NOT_FOUND.toString().replaceAll("%name%", args.getArg(0))); + return; + } + + HCFProfile targetProfile = HCFProfile.getByPlayer(target); + p.sendMessage(Locale.COMMAND_BALANCE_OTHER.toString().replaceAll("%player%", target.getName()).replaceAll("%balance%", String.valueOf(targetProfile.getBalance()))); + } + } +} diff --git a/src/main/java/me/hulipvp/hcf/commands/BottleCommand.java b/src/main/java/me/hulipvp/hcf/commands/BottleCommand.java new file mode 100644 index 0000000..47ac968 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/commands/BottleCommand.java @@ -0,0 +1,59 @@ +package me.hulipvp.hcf.commands; + +import me.hulipvp.hcf.api.chat.C; +import me.hulipvp.hcf.api.command.Command; +import me.hulipvp.hcf.api.command.CommandData; +import me.hulipvp.hcf.utils.ExpUtils; +import me.hulipvp.hcf.utils.Locale; +import me.hulipvp.hcf.utils.item.ItemBuilder; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class BottleCommand { + + @Command(label = "xpbottle", aliases = { "bottle" }, permission = "command.bottle", playerOnly = true) + public void onCommand(CommandData args) { + Player player = args.getPlayer(); + int currentXp = ExpUtils.getTotalExperience(player); + if(currentXp <= 25) { + player.sendMessage(Locale.COMMAND_BOTTLE_NOT_ENOUGH.toString()); + return; + } + + if(player.getInventory().firstEmpty() == -1) { + player.sendMessage(Locale.COMMAND_BOTTLE_FULL_INVENTORY.toString()); + return; + } + + ExpUtils.setTotalExperience(player, 0); + + player.getInventory().addItem(getXpBottle(currentXp, player.getName())); + player.sendMessage(Locale.COMMAND_BOTTLE_SUCCESS.toString().replace("%amount%", currentXp + "")); + } + + public static ItemStack getXpBottle(int xpAmount, String enchanter) { + return new ItemBuilder(Material.EXP_BOTTLE) + .amount(1) + .name(C.color("&e&lXP Bottle &7(Throw)")) + .lore( + "&6Amount: " + xpAmount + " XP", + "&6Enchanter: " + enchanter + ) + .get(); + } + + public static boolean isXpBottle(ItemStack item) { + return item != null && item.getType() == Material.EXP_BOTTLE + && item.hasItemMeta() && item.getItemMeta().hasDisplayName() + && item.getItemMeta().hasLore() && item.getItemMeta().getLore().get(0).contains("XP"); + } + + public static int getXpAmount(ItemStack item) { + try { + return Integer.parseInt(C.strip(item.getItemMeta().getLore().get(0).split(" ")[1].replace("XP", ""))); + } catch(Exception err) { + return 0; + } + } +} diff --git a/src/main/java/me/hulipvp/hcf/commands/ChatCommand.java b/src/main/java/me/hulipvp/hcf/commands/ChatCommand.java new file mode 100644 index 0000000..b964113 --- /dev/null +++ b/src/main/java/me/hulipvp/hcf/commands/ChatCommand.java @@ -0,0 +1,83 @@ +package me.hulipvp.hcf.commands; + +import com.google.common.base.Joiner; +import me.hulipvp.hcf.api.chat.C; +import me.hulipvp.hcf.api.command.Command; +import me.hulipvp.hcf.api.command.CommandData; +import me.hulipvp.hcf.game.player.HCFProfile; +import me.hulipvp.hcf.utils.Locale; +import me.hulipvp.hcf.utils.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class ChatCommand { + + public static int slow = 0; + public static boolean muted = false; + + @Command(label = "clearchat", aliases = { "cc", "chatclear" }, permission = "command.clearchat") + public void onClear(CommandData args) { + Bukkit.getOnlinePlayers().stream() + .filter(player -> !player.hasPermission("hcf.staff")) + .forEach(player -> { + for(int i = 0; i < 256; i++) + player.sendMessage(" "); + }); + + Bukkit.broadcastMessage(Locale.COMMAND_CHAT_CLEARED.toString()); + } + + @Command(label = "mutechat", aliases = { "mc", "umc", "chatmute", "unmutechat" }, permission = "command.mutechat") + public void onMute(CommandData args) { + muted = !muted; + + if(muted) + Bukkit.broadcastMessage(Locale.COMMAND_CHAT_MUTED.toString()); + else + Bukkit.broadcastMessage(Locale.COMMAND_CHAT_UNMUTED.toString()); + } + + @Command(label = "slowchat", aliases = { "slow", "unslow", "unslowchat" }, permission = "command.slowchat") + public void onSlow(CommandData args) { + if(args.getLabel().contains("unslow")) { + slow = 0; + Bukkit.broadcastMessage(Locale.COMMAND_CHAT_UNSLOWED.toString()); + return; + } + + if(args.length() < 1) { + args.getSender().sendMessage(C.color("&cUsage: /" + args.getLabel() + "