diff --git a/README.md b/README.md index 813f3436..57ecfad4 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,11 @@ CivModCore is derived from Humbug. ## Versions -* 1.8.2 - Spigot 1.16.4 +* 1.9.0 - Paper 1.17.1 -* [1.8.0](https://github.com/CivClassic/CivModCore/tree/08ad95297eb041cf99bd0eb0aaffc70ca87af4f2) - Spigot 1.16.1 +* 1.8.4 - Paper 1.16.5 + +* [1.8.0](https://github.com/CivClassic/CivModCore/tree/08ad95297eb041cf99bd0eb0aaffc70ca87af4f2) - Paper 1.16.1 * [1.7.9](https://github.com/CivClassic/CivModCore/tree/306b4f7268a3c5d3bd551fe66992f2a4335e86f7) - Spigot 1.14.4 @@ -57,6 +59,6 @@ Include the following in your dependency list in your plugin's POM file: vg.civcraft.mc.civmodcore CivModCore - 1.8.2 + 1.8.4 provided diff --git a/pom.xml b/pom.xml index addbae0d..9848ce57 100644 --- a/pom.xml +++ b/pom.xml @@ -6,12 +6,11 @@ com.github.civclassic civclassic-parent - 1.0.0 + 1.0.1 - vg.civcraft.mc.civmodcore CivModCore - 1.8.2 + 1.9.0 CivModCore https://github.com/Civclassic/CivModCore/ @@ -19,21 +18,49 @@ org.apache.maven.plugins - maven-compiler-plugin - - - -proc:none - + maven-shade-plugin + 3.3.0-SNAPSHOT + + + package + + shade + + + + + + it.unimi.dsi:fastutil + co.aikar:cleaner + + + + + *:* + + META-INF/**/* + + + + + + - com.destroystokyo.paper + io.papermc.paper paper - 1.16.4-R0.1-SNAPSHOT + 1.17.1-R0.1-SNAPSHOT provided + + + io.papermc + minecraft-server + + com.zaxxer @@ -41,56 +68,73 @@ 3.4.2 - org.apache.commons - commons-lang3 - 3.11 + net.kyori + adventure-text-minimessage + 4.1.0-SNAPSHOT - org.apache.commons - commons-collections4 - 4.4 + co.aikar + acf-bukkit + 0.5.0-SNAPSHOT - - org.slf4j - slf4j-api - 1.7.26 + co.aikar + taskchain-bukkit + 3.7.2 - co.aikar - acf-bukkit - 0.5.0-SNAPSHOT + + com.github.IPVP-MC + canvas + 91ec97f076 + + + org.apache.commons + commons-lang3 + 3.12.0 - org.jetbrains - annotations - 20.1.0 + org.apache.commons + commons-collections4 + 4.4 com.google.code.findbugs jsr305 3.0.2 + + + it.unimi.dsi + fastutil + 8.2.2 + + + co.aikar + cleaner + 1.0-SNAPSHOT + junit junit - 4.13 + 4.13.2 test + + civ-github-repo + https://raw.githubusercontent.com/CivClassic/artifacts/master/ + aikar https://repo.aikar.co/content/groups/aikar/ - civ-github-repo - https://raw.githubusercontent.com/CivClassic/artifacts/master/ + jitpack.io + https://jitpack.io diff --git a/src/main/java/org/bukkit/pseudo/PseudoServer.java b/src/main/java/org/bukkit/pseudo/PseudoServer.java new file mode 100644 index 00000000..8937bee0 --- /dev/null +++ b/src/main/java/org/bukkit/pseudo/PseudoServer.java @@ -0,0 +1,963 @@ +package org.bukkit.pseudo; + +import com.destroystokyo.paper.entity.ai.MobGoals; +import com.destroystokyo.paper.profile.PlayerProfile; +import io.papermc.paper.datapack.DatapackManager; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; +import net.minecraft.server.DispenserRegistry; +import org.apache.commons.lang3.NotImplementedException; +import org.bukkit.BanList; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Keyed; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.OfflinePlayer; +import org.bukkit.Server; +import org.bukkit.StructureType; +import org.bukkit.Tag; +import org.bukkit.UnsafeValues; +import org.bukkit.Warning; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.advancement.Advancement; +import org.bukkit.block.data.BlockData; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.boss.KeyedBossBar; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandMap; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.craftbukkit.v1_17_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_17_R1.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.v1_17_R1.util.Versioning; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.help.HelpMap; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Recipe; +import org.bukkit.loot.LootTable; +import org.bukkit.map.MapView; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scoreboard.ScoreboardManager; +import org.bukkit.util.CachedServerIcon; + +@SuppressWarnings("deprecation") +public class PseudoServer implements Server { + + public static final PseudoServer INSTANCE = new PseudoServer(); + private static final Logger LOGGER = Logger.getLogger(PseudoServer.class.getSimpleName()); + + public static void setup() { + if (Bukkit.getServer() == null) { // Ignore highlighter + final var previousLevel = LOGGER.getLevel(); + LOGGER.setLevel(Level.OFF); // This is to prevent unnecessary logging + SharedConstants.a(); // SharedConstants.tryDetectVersion() + DispenserRegistry.init(); + DispenserRegistry.c(); // DispenserRegistry.validate() + Bukkit.setServer(INSTANCE); + LOGGER.setLevel(previousLevel); + } + } + + @Nonnull + @Override + public Logger getLogger() { + return LOGGER; + } + + @Nonnull + @Override + public ItemFactory getItemFactory() { + return CraftItemFactory.instance(); + } + + @Nonnull + @Override + public UnsafeValues getUnsafe() { + return CraftMagicNumbers.INSTANCE; + } + + @Nonnull + @Override + public BlockData createBlockData(@Nonnull final Material material) { + return CraftBlockData.newData(material, null); + } + + // ------------------------------------------------------------ + // Not implemented + // ------------------------------------------------------------ + + @Nonnull + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Nonnull + @Override + public String getVersion() { + return getClass().getPackage().getImplementationVersion(); + } + + @Nonnull + @Override + public String getBukkitVersion() { + return Versioning.getBukkitVersion(); + } + + @Nonnull + @Override + public String getMinecraftVersion() { + return SharedConstants.getGameVersion().getName(); + } + + @Nonnull + @Override + public Collection getOnlinePlayers() { + throw new NotImplementedException(); + } + + @Override + public int getMaxPlayers() { + throw new NotImplementedException(); + } + + @Override + public void setMaxPlayers(final int i) { + throw new NotImplementedException(); + } + + @Override + public int getPort() { + throw new NotImplementedException(); + } + + @Override + public int getViewDistance() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public String getIp() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public String getWorldType() { + throw new NotImplementedException(); + } + + @Override + public boolean getGenerateStructures() { + throw new NotImplementedException(); + } + + @Override + public int getMaxWorldSize() { + throw new NotImplementedException(); + } + + @Override + public boolean getAllowEnd() { + throw new NotImplementedException(); + } + + @Override + public boolean getAllowNether() { + throw new NotImplementedException(); + } + + @Override + public boolean hasWhitelist() { + throw new NotImplementedException(); + } + + @Override + public void setWhitelist(final boolean b) { + throw new NotImplementedException(); + } + + @Override + public boolean isWhitelistEnforced() { + throw new NotImplementedException(); + } + + @Override + public void setWhitelistEnforced(boolean bl) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Set getWhitelistedPlayers() { + throw new NotImplementedException(); + } + + @Override + public void reloadWhitelist() { + throw new NotImplementedException(); + } + + @Override + public int broadcastMessage(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public String getUpdateFolder() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public File getUpdateFolderFile() { + throw new NotImplementedException(); + } + + @Override + public long getConnectionThrottle() { + throw new NotImplementedException(); + } + + @Override + public int getTicksPerAnimalSpawns() { + throw new NotImplementedException(); + } + + @Override + public int getTicksPerMonsterSpawns() { + throw new NotImplementedException(); + } + + @Override + public int getTicksPerWaterSpawns() { + throw new NotImplementedException(); + } + + @Override + public int getTicksPerWaterAmbientSpawns() { + throw new NotImplementedException(); + } + + @Override + public int getTicksPerAmbientSpawns() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Player getPlayer(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Player getPlayerExact(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public List matchPlayer(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Player getPlayer(@Nonnull final UUID uuid) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public UUID getPlayerUniqueId(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public PluginManager getPluginManager() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public BukkitScheduler getScheduler() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ServicesManager getServicesManager() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public List getWorlds() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public World createWorld(@Nonnull final WorldCreator worldCreator) { + throw new NotImplementedException(); + } + + @Override + public boolean unloadWorld(@Nonnull final String s, final boolean b) { + throw new NotImplementedException(); + } + + @Override + public boolean unloadWorld(@Nonnull final World world, final boolean b) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public World getWorld(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public World getWorld(@Nonnull final UUID uuid) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public World getWorld(@Nonnull final NamespacedKey namespacedKey) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public MapView getMap(final int i) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public MapView createMap(@Nonnull final World world) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ItemStack createExplorerMap(@Nonnull final World world, @Nonnull final Location location, @Nonnull final StructureType structureType) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ItemStack createExplorerMap(@Nonnull final World world, @Nonnull final Location location, @Nonnull final StructureType structureType, final int i, final boolean b) { + throw new NotImplementedException(); + } + + @Override + public void reload() { + throw new NotImplementedException(); + } + + @Override + public void reloadData() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public PluginCommand getPluginCommand(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Override + public void savePlayers() { + throw new NotImplementedException(); + } + + @Override + public boolean dispatchCommand(@Nonnull final CommandSender commandSender, @Nonnull final String s) throws CommandException { + throw new NotImplementedException(); + } + + @Override + public boolean addRecipe(@Nullable final Recipe recipe) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public List getRecipesFor(@Nonnull final ItemStack itemStack) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Recipe getRecipe(@Nonnull final NamespacedKey namespacedKey) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Recipe getCraftingRecipe(@Nonnull ItemStack[] itemStacks, @Nonnull World world) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ItemStack craftItem(@Nonnull ItemStack[] itemStacks, @Nonnull World world, @Nonnull Player player) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Iterator recipeIterator() { + throw new NotImplementedException(); + } + + @Override + public void clearRecipes() { + throw new NotImplementedException(); + } + + @Override + public void resetRecipes() { + throw new NotImplementedException(); + } + + @Override + public boolean removeRecipe(@Nonnull final NamespacedKey namespacedKey) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Map getCommandAliases() { + throw new NotImplementedException(); + } + + @Override + public int getSpawnRadius() { + throw new NotImplementedException(); + } + + @Override + public void setSpawnRadius(final int i) { + throw new NotImplementedException(); + } + + @Override + public boolean getOnlineMode() { + throw new NotImplementedException(); + } + + @Override + public boolean getAllowFlight() { + throw new NotImplementedException(); + } + + @Override + public boolean isHardcore() { + throw new NotImplementedException(); + } + + @Override + public void shutdown() { + throw new NotImplementedException(); + } + + @Override + public int broadcast(@Nonnull final String s, @Nonnull final String s1) { + throw new NotImplementedException(); + } + + @Override + public int broadcast(@Nonnull Component component) { + throw new NotImplementedException(); + } + + @Override + public int broadcast(@Nonnull final Component component, @Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public OfflinePlayer getOfflinePlayer(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public OfflinePlayer getOfflinePlayerIfCached(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public OfflinePlayer getOfflinePlayer(@Nonnull final UUID uuid) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Set getIPBans() { + throw new NotImplementedException(); + } + + @Override + public void banIP(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Override + public void unbanIP(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Set getBannedPlayers() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public BanList getBanList(@Nonnull final BanList.Type type) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Set getOperators() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public GameMode getDefaultGameMode() { + throw new NotImplementedException(); + } + + @Override + public void setDefaultGameMode(@Nonnull final GameMode gameMode) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ConsoleCommandSender getConsoleSender() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public File getWorldContainer() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public OfflinePlayer[] getOfflinePlayers() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Messenger getMessenger() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public HelpMap getHelpMap() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Inventory createInventory(@Nullable final InventoryHolder inventoryHolder, @Nonnull final InventoryType inventoryType) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Inventory createInventory(@Nullable final InventoryHolder inventoryHolder, @Nonnull final InventoryType inventoryType, @Nonnull final Component component) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Inventory createInventory(@Nullable final InventoryHolder inventoryHolder, @Nonnull final InventoryType inventoryType, @Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Inventory createInventory(@Nullable final InventoryHolder inventoryHolder, final int i) throws IllegalArgumentException { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Inventory createInventory(@Nullable final InventoryHolder inventoryHolder, final int i, @Nonnull final Component component) throws IllegalArgumentException { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Inventory createInventory(@Nullable final InventoryHolder inventoryHolder, final int i, @Nonnull final String s) throws IllegalArgumentException { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Merchant createMerchant(@Nullable final Component component) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Merchant createMerchant(@Nullable final String s) { + throw new NotImplementedException(); + } + + @Override + public int getMonsterSpawnLimit() { + throw new NotImplementedException(); + } + + @Override + public int getAnimalSpawnLimit() { + throw new NotImplementedException(); + } + + @Override + public int getWaterAnimalSpawnLimit() { + throw new NotImplementedException(); + } + + @Override + public int getWaterAmbientSpawnLimit() { + throw new NotImplementedException(); + } + + @Override + public int getAmbientSpawnLimit() { + throw new NotImplementedException(); + } + + @Override + public boolean isPrimaryThread() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Component motd() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public String getMotd() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Component shutdownMessage() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public String getShutdownMessage() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Warning.WarningState getWarningState() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ScoreboardManager getScoreboardManager() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public CachedServerIcon getServerIcon() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public CachedServerIcon loadServerIcon(@Nonnull final File file) throws IllegalArgumentException, Exception { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public CachedServerIcon loadServerIcon(@Nonnull final BufferedImage bufferedImage) throws IllegalArgumentException, Exception { + throw new NotImplementedException(); + } + + @Override + public void setIdleTimeout(final int i) { + throw new NotImplementedException(); + } + + @Override + public int getIdleTimeout() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ChunkGenerator.ChunkData createChunkData(@Nonnull final World world) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public ChunkGenerator.ChunkData createVanillaChunkData(@Nonnull final World world, final int i, final int i1) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public BossBar createBossBar(@Nullable final String s, @Nonnull final BarColor barColor, @Nonnull final BarStyle barStyle, @Nonnull final BarFlag... barFlags) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public KeyedBossBar createBossBar(@Nonnull final NamespacedKey namespacedKey, @Nullable final String s, @Nonnull final BarColor barColor, @Nonnull final BarStyle barStyle, @Nonnull final BarFlag... barFlags) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Iterator getBossBars() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public KeyedBossBar getBossBar(@Nonnull final NamespacedKey namespacedKey) { + throw new NotImplementedException(); + } + + @Override + public boolean removeBossBar(@Nonnull final NamespacedKey namespacedKey) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Entity getEntity(@Nonnull final UUID uuid) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public double[] getTPS() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public long[] getTickTimes() { + throw new NotImplementedException(); + } + + @Override + public double getAverageTickTime() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public CommandMap getCommandMap() { + throw new NotImplementedException(); + } + + @Nullable + @Override + public Advancement getAdvancement(@Nonnull final NamespacedKey namespacedKey) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Iterator advancementIterator() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public BlockData createBlockData(@Nonnull final Material material, @Nullable final Consumer consumer) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public BlockData createBlockData(@Nonnull final String s) throws IllegalArgumentException { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public BlockData createBlockData(@Nullable final Material material, @Nullable final String s) throws IllegalArgumentException { + throw new NotImplementedException(); + } + + @Override + public Tag getTag(@Nonnull final String s, @Nonnull final NamespacedKey namespacedKey, @Nonnull final Class aClass) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Iterable> getTags(@Nonnull final String s, @Nonnull final Class aClass) { + throw new NotImplementedException(); + } + + @Nullable + @Override + public LootTable getLootTable(@Nonnull final NamespacedKey namespacedKey) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public List selectEntities(@Nonnull final CommandSender commandSender, @Nonnull final String s) throws IllegalArgumentException { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Spigot spigot() { + throw new NotImplementedException(); + } + + @Override + public void reloadPermissions() { + throw new NotImplementedException(); + } + + @Override + public boolean reloadCommandAliases() { + throw new NotImplementedException(); + } + + @Override + public boolean suggestPlayerNamesWhenNullTabCompletions() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public String getPermissionMessage() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public PlayerProfile createProfile(@Nonnull final UUID uuid) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public PlayerProfile createProfile(@Nonnull final String s) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public PlayerProfile createProfile(@Nullable final UUID uuid, @Nullable final String s) { + throw new NotImplementedException(); + } + + @Override + public int getCurrentTick() { + throw new NotImplementedException(); + } + + @Override + public boolean isStopping() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public MobGoals getMobGoals() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public DatapackManager getDatapackManager() { + throw new NotImplementedException(); + } + + @Override + public void sendPluginMessage(@Nonnull final Plugin plugin, @Nonnull final String s, @Nonnull final byte[] bytes) { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Set getListeningPluginChannels() { + throw new NotImplementedException(); + } + + @Nonnull + @Override + public Iterable audiences() { + throw new NotImplementedException(); + } + +} diff --git a/src/main/java/org/ipvp/canvas/type/AbstractCivMenu.java b/src/main/java/org/ipvp/canvas/type/AbstractCivMenu.java new file mode 100644 index 00000000..df544310 --- /dev/null +++ b/src/main/java/org/ipvp/canvas/type/AbstractCivMenu.java @@ -0,0 +1,317 @@ +package org.ipvp.canvas.type; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import net.kyori.adventure.text.Component; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.ipvp.canvas.Menu; +import org.ipvp.canvas.slot.DefaultSlot; +import org.ipvp.canvas.slot.Slot; +import vg.civcraft.mc.civmodcore.chat.ChatUtils; +import vg.civcraft.mc.civmodcore.inventory.InventoryUtils; +import vg.civcraft.mc.civmodcore.inventory.gui.canvas.MenuUtils; +import vg.civcraft.mc.civmodcore.utilities.NullUtils; + +public abstract class AbstractCivMenu extends AbstractMenu { + + private static final Field VIEWERS_FIELD; + private static final Field SLOTS_FIELD; + private static final Method UPDATE_INV_CONTENTS_METHOD; + + static { + VIEWERS_FIELD = FieldUtils.getDeclaredField(AbstractMenu.class, "viewers", true); + SLOTS_FIELD = FieldUtils.getDeclaredField(AbstractMenu.class, "slots", true); + UPDATE_INV_CONTENTS_METHOD = MethodUtils.getMatchingMethod(AbstractMenu.class, + "updateInventoryContents", Player.class, Inventory.class); + UPDATE_INV_CONTENTS_METHOD.setAccessible(true); + } + + private final Component title; + private OpenHandler openHandler; + private UpdateHandler updateHandler; + protected boolean callCloseHandlerRegardless; + + protected AbstractCivMenu(final Component title, + final int slots, + final Menu parent, + final boolean redraw) { + super(null, slots, parent, redraw); + if (!InventoryUtils.isValidChestSize(slots)) { + throw new IllegalArgumentException("Not a valid chest size."); + } + this.title = ChatUtils.isNullOrEmpty(title) ? InventoryType.CHEST.defaultTitle() : title; + } + + protected AbstractCivMenu(final Component title, + final InventoryType type, + final Menu parent, + final boolean redraw) { + super(null, type, parent, redraw); + this.title = ChatUtils.isNullOrEmpty(title) ? type.defaultTitle() : title; + this.inventorySlots = type.getDefaultSize(); + } + + /** + * @return Returns a clone of this menu's title. + */ + protected Component getTitle() { + return this.title; + } + + /** + * @return Retrieves this menu's open event handler. + */ + public Optional getOpenHandler() { + return Optional.ofNullable(this.openHandler); + } + + /** + * Sets the open event handler for this menu. Use + * {@link MenuUtils#overrideOpenHandler(AbstractCivMenu, OpenHandler, boolean)} instead if you'd prefer to + * override the event handler rather than replace it. + * + * @param openHandler The open event handler to set for this menu. + */ + public void setOpenHandler(final OpenHandler openHandler) { + this.openHandler = openHandler; + } + + /** + * @return Retrieves this menu's update event handler. + */ + public Optional getUpdateHandler() { + return Optional.ofNullable(this.updateHandler); + } + + /** + * Sets the update event handler for this menu. Use + * {@link MenuUtils#overrideUpdateHandler(AbstractCivMenu, UpdateHandler, boolean)} instead if you'd prefer to + * override the event handler rather than replace it. + * + * @param updateHandler The update event handler to set for this menu. + */ + public void setUpdateHandler(final UpdateHandler updateHandler) { + this.updateHandler = updateHandler; + } + + /** + * Sets the close event handler for this menu. Use + * {@link MenuUtils#overrideCloseHandler(AbstractCivMenu, CloseHandler, boolean)} instead if you'd prefer to + * override the event handler rather than replace it. + * + * @param closeHandler The close event handler to set for this menu. + */ + @Override + public void setCloseHandler(final CloseHandler closeHandler) { + super.setCloseHandler(closeHandler); + } + + /** + * @return Returns whether or not this menu will invoke its close event handler regardless of whether + * {@link #closedByPlayer(Player, boolean)}'s second parameter, "triggerCloseHandler", is false. + */ + public boolean isCallingCloseHandlerRegardless() { + return this.callCloseHandlerRegardless; + } + + /** + * @param callCloseHandlerRegardless Set this to true if you want this menu to invoke its close event handler + * regardless of whether {@link #closedByPlayer(Player, boolean)}'s second + * parameter, "triggerCloseHandler", is false. + */ + public void setCallCloseHandlerRegardless(final boolean callCloseHandlerRegardless) { + this.callCloseHandlerRegardless = callCloseHandlerRegardless; + } + + /** + * @return Returns the total slot count for this menu. + */ + protected int getSlotCount() { + return this.inventorySlots; + } + + /** + * @return Returns the underlying viewers via reflection. + */ + @SuppressWarnings("unchecked") + protected final Set getRawViewers() { + try { + return (Set) FieldUtils.readField(VIEWERS_FIELD, this); + } + catch (final IllegalAccessException exception) { + throw new RuntimeException(exception); + } + } + + /** + * @return Returns the underlying slots via reflection. + */ + protected final DefaultSlot[] getRawSlots() { + try { + return (DefaultSlot[]) FieldUtils.readField(SLOTS_FIELD, this); + } + catch (final IllegalAccessException exception) { + throw new RuntimeException(exception); + } + } + + /** + * Switches from this menu to a given menu. + * + * @param viewer The viewer to switch. + * @param otherMenu The other menu to switch to. + */ + public void openOtherMenu(final Player viewer, final AbstractCivMenu otherMenu) { + if (otherMenu == null) { + viewer.closeInventory(); + return; + } + otherMenu.open(viewer); + } + + /** + * Opens this menu's parent, or closes if it doesn't exist. + * + * @param viewer The viewer to navigate. + */ + public void openParent(final Player viewer) { + final var parent = getParent().orElse(null); + if (parent == null) { + viewer.closeInventory(); + return; + } + if (parent instanceof AbstractCivMenu) { + ((AbstractCivMenu) parent).openOtherMenu(viewer, this); + return; + } + parent.open(viewer); + } + + /** + * Basically a carbon copy of {@link AbstractMenu#closedByPlayer(Player, boolean)}, except + * for the additional logic of {@link #callCloseHandlerRegardless} to force the menu to call + * the close handler even if {@code triggerCloseHandler} is false. + * + * @param viewer The closing viewer. + * @param triggerCloseHandler Whether to call the close handler, which defers to + * {@link #callCloseHandlerRegardless}. + */ + @Override + public void closedByPlayer(final Player viewer, final boolean triggerCloseHandler) { + final var currentInventory = viewer.getOpenInventory().getTopInventory().getHolder(); + final var viewers = getRawViewers(); + if (!(currentInventory instanceof MenuHolder) + || !viewers.contains(currentInventory)) { + return; + } + if (triggerCloseHandler || this.callCloseHandlerRegardless) { + getCloseHandler().ifPresent(handler -> handler.close(viewer, this)); + } + viewers.remove((MenuHolder) currentInventory); + } + + /** + * @param viewer The viewer to update this menu for. + */ + @Override + public void update(final Player viewer) throws IllegalStateException { + if (!isOpen(viewer)) { + return; + } + + final var currentHolder = viewer.getOpenInventory().getTopInventory().getHolder(); + final var currentInventory = NullUtils.isNotNull(currentHolder).getInventory(); + + for (final Slot slot : getRawSlots()) { + currentInventory.setItem(slot.getIndex(), slot.getItem(viewer)); + } + + getUpdateHandler().ifPresent(handler -> handler.handle(viewer, this)); + viewer.updateInventory(); + } + + /** + * Basically a carbon copy of {@link AbstractMenu#open(Player)}, which is needed to override the + * {@code createInventory()} method, but it's private so the entire function needs to be copied, + * alongside its other dependent private methods. + */ + @Override + public void open(final Player viewer) { + final var currentHolder = viewer.getOpenInventory().getTopInventory().getHolder(); + final MenuHolder holder; + if (currentHolder instanceof MenuHolder) { + holder = (MenuHolder) currentHolder; + final var currentMenu = holder.getMenu(); + + if (currentMenu == this) { + return; + } + + Inventory inventory; + if (isRedraw() && Objects.equals(getDimensions(), currentMenu.getDimensions())) { + inventory = holder.getInventory(); + if (currentMenu instanceof AbstractMenu) { + ((AbstractMenu) currentMenu).closedByPlayer(viewer, false); + } + } + else { + currentMenu.close(viewer); + inventory = createInventory(holder); + holder.setInventory(inventory); + viewer.openInventory(inventory); + } + + updateInventoryContents(viewer, inventory); + holder.setMenu(this); + } + else { + // Create new MenuHolder for the player + holder = new MenuHolder(viewer, this); + final var inventory = createInventory(holder); + updateInventoryContents(viewer, inventory); + holder.setInventory(inventory); + viewer.openInventory(inventory); + } + getRawViewers().add(holder); + getOpenHandler().ifPresent(handler -> handler.handle(viewer, this)); + } + + /** + * Basically a carbon copy of {@link AbstractMenu}'s version, but uses a component title instead. + * + * @param holder The menu's inventory holder. + * @return Returns the new inventory. + */ + protected Inventory createInventory(final InventoryHolder holder) { + if (this.inventoryType == null) { + return Bukkit.createInventory(holder, this.inventorySlots, this.title); + } + return Bukkit.createInventory(holder, this.inventoryType, this.title); + } + + /** + * Invokes {@link AbstractMenu}'s version via reflection. + * + * @param viewer The viewer to update. + * @param inventory The inventory to update. + */ + protected void updateInventoryContents(final Player viewer, final Inventory inventory) { + try { + UPDATE_INV_CONTENTS_METHOD.invoke(this, viewer, inventory); + } + catch (final InvocationTargetException | IllegalAccessException exception) { + throw new RuntimeException(exception); + } + } + +} diff --git a/src/main/java/org/ipvp/canvas/type/CivChestMenu.java b/src/main/java/org/ipvp/canvas/type/CivChestMenu.java new file mode 100644 index 00000000..95252097 --- /dev/null +++ b/src/main/java/org/ipvp/canvas/type/CivChestMenu.java @@ -0,0 +1,91 @@ +package org.ipvp.canvas.type; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.apache.commons.lang3.StringUtils; +import org.ipvp.canvas.Menu; +import vg.civcraft.mc.civmodcore.chat.ChatUtils; + +public class CivChestMenu extends AbstractCivMenu { + + private Dimension dimension; + + public CivChestMenu(final Component title, final int rows) { + this(title, rows, true); + } + + public CivChestMenu(final Component title, final int rows, final boolean redraw) { + this(title, rows, null, redraw); + } + + public CivChestMenu(final Component title, final int rows, final Menu parent, final boolean redraw) { + super(title, rows * 9, parent, redraw); + } + + /** + * @return Return this chest menu's row and column counts. + */ + @Override + public Dimension getDimensions() { + if (this.dimension == null) { + this.dimension = new Dimension(getSlotCount() / 9, 9); + } + return this.dimension; + } + + /** + * Returns a new builder. + * + * @param rows The amount of rows for the inventory to contain + * @throws IllegalArgumentException if rows is not between 1 and 6 inclusive + */ + public static Builder builder(final int rows) { + if (rows < 1 || rows > 6) { + throw new IllegalArgumentException("rows must be a value from 1 to 6"); + } + return new Builder(rows); + } + + /** + * A builder for creating a CustomChestMenu instance. + */ + public static class Builder extends AbstractMenu.Builder { + + private Component title; + + Builder(final int rows) { + super(new Dimension(rows, 9)); + } + + @Deprecated + @Override + public Builder title(final String title) { + if (StringUtils.isBlank(title)) { + this.title = null; + return this; + } + this.title = LegacyComponentSerializer.legacyAmpersand().deserialize(title); + if (ChatUtils.isNullOrEmpty(this.title)) { + this.title = null; + } + return this; + } + + public Builder title(final Component title) { + this.title = title; + return this; + } + + public Component getComponentTitle() { + return this.title; + } + + @Override + public CivChestMenu build() { + return new CivChestMenu(getComponentTitle(), + getDimensions().getArea(), getParent(), isRedraw()); + } + + } + +} diff --git a/src/main/java/org/ipvp/canvas/type/OpenHandler.java b/src/main/java/org/ipvp/canvas/type/OpenHandler.java new file mode 100644 index 00000000..a845cb52 --- /dev/null +++ b/src/main/java/org/ipvp/canvas/type/OpenHandler.java @@ -0,0 +1,16 @@ +package org.ipvp.canvas.type; + +import org.bukkit.entity.Player; + +@FunctionalInterface +public interface OpenHandler { + + /** + * Is called by {@link AbstractCivMenu} when one of its menus are opened by a viewer. + * + * @param viewer The viewer of the opened menu. + * @param menu The menu being opened. + */ + void handle(Player viewer, AbstractCivMenu menu); + +} diff --git a/src/main/java/org/ipvp/canvas/type/UpdateHandler.java b/src/main/java/org/ipvp/canvas/type/UpdateHandler.java new file mode 100644 index 00000000..e145b975 --- /dev/null +++ b/src/main/java/org/ipvp/canvas/type/UpdateHandler.java @@ -0,0 +1,17 @@ +package org.ipvp.canvas.type; + +import org.bukkit.entity.Player; + +@FunctionalInterface +public interface UpdateHandler { + + /** + * Is called by {@link AbstractCivMenu} when one of its menus are updated. This is called after the menu has been + * updated but before the update packets have been sent to the player. + * + * @param viewer The viewer of the updating menu. + * @param menu The menu being updated. + */ + void handle(Player viewer, AbstractCivMenu menu); + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java b/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java index d1410f56..96cf88c3 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java @@ -1,20 +1,21 @@ package vg.civcraft.mc.civmodcore; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Collections; -import java.util.List; -import java.util.function.Consumer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.craftbukkit.libs.org.apache.commons.io.FileUtils; import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; @@ -22,36 +23,19 @@ import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.command.CommandHandler; -import vg.civcraft.mc.civmodcore.command.StandaloneCommandHandler; -import vg.civcraft.mc.civmodcore.serialization.NBTSerializable; -import vg.civcraft.mc.civmodcore.serialization.NBTSerialization; +import org.jetbrains.annotations.Contract; -@SuppressWarnings("deprecation") public abstract class ACivMod extends JavaPlugin { - private final List> serializableClasses = Lists.newArrayList(); - - @Deprecated - protected CommandHandler handle = null; - - protected StandaloneCommandHandler newCommandHandler; - - protected boolean useNewCommandHandler = true; + private final Set> configClasses = new HashSet<>(0); @Override public void onEnable() { - // Allow plugins to disable the new command handler without breaking other plugins that - // rely on this being set automatically. - if (this.useNewCommandHandler) { - this.newCommandHandler = new StandaloneCommandHandler(this); - } // Self disable when a hard dependency is disabled registerListener(new Listener() { @EventHandler - public void onPluginDisable(PluginDisableEvent event) { - String pluginName = event.getPlugin().getName(); + public void onPluginDisable(final PluginDisableEvent event) { + final String pluginName = event.getPlugin().getName(); if (getDescription().getDepend().contains(pluginName)) { warning("Plugin [" + pluginName + "] has been disabled, disabling this plugin."); disable(); @@ -62,17 +46,11 @@ public void onPluginDisable(PluginDisableEvent event) { @Override public void onDisable() { - this.useNewCommandHandler = true; - if (this.newCommandHandler != null) { - this.newCommandHandler.reset(); - this.newCommandHandler = null; - } - this.serializableClasses.forEach(NBTSerialization::unregisterNBTSerializable); - this.serializableClasses.clear(); HandlerList.unregisterAll(this); Bukkit.getMessenger().unregisterIncomingPluginChannel(this); Bukkit.getMessenger().unregisterOutgoingPluginChannel(this); Bukkit.getScheduler().cancelTasks(this); + this.configClasses.forEach(ConfigurationSerialization::unregisterClass); } /** @@ -80,36 +58,27 @@ public void onDisable() { * * @param listener The listener class to register. */ - public void registerListener(Listener listener) { - if (listener == null) { - throw new IllegalArgumentException("Cannot register a listener if it's null, you dummy"); - } - getServer().getPluginManager().registerEvents(listener, this); + public void registerListener(@Nonnull final Listener listener) { + getServer().getPluginManager().registerEvents( + Objects.requireNonNull(listener, "Cannot register a listener if it's null, you dummy"), this); } /** - *

Registers a serializable.

+ * Convenience method you can use to register {@link ConfigurationSerializable} classes, which will be + * automatically un-registered when this plugin is disabled. * - *

Note: This is a tracked single use registration. The given serializable will be de-registered when this - * plugin is disabled, thus you should call this within the plugin's onEnable() method.

- * - * @param The type of the serializable. - * @param serializable The serializable class. - * - * @deprecated This is no longer necessary and was added to ease usage of serializables, - * but {@link List#of(Object[])} and {@link java.util.Collection#forEach(Consumer)} - * has made this largely redundant. + * @param clazz The serializable class to register and automatically unregister upon disable. */ - @Deprecated - public void registerSerializable(Class serializable) { - NBTSerialization.registerNBTSerializable(serializable); - this.serializableClasses.add(serializable); + public void registerConfigClass(@Nonnull final Class clazz) { + Objects.requireNonNull(clazz, "Cannot register a config class if it's null, you dummy"); + ConfigurationSerialization.registerClass(clazz); + this.configClasses.add(clazz); } /** * Determines whether this plugin is in debug mode, which is determined by a config value. * - * @return Returns true if this plguin is in debug mode. + * @return Returns true if this plugin is in debug mode. */ public boolean isDebugEnabled() { return getConfig().getBoolean("debug", false); @@ -121,8 +90,8 @@ public boolean isDebugEnabled() { * @param path The path of the file relative to the data folder. * @return Returns a file instance of the generated path. */ - public File getResourceFile(String path) { - return new File(getDataFolder(), path); + public File getDataFile(@Nonnull final String path) { + return new File(getDataFolder(), Objects.requireNonNull(path)); } /** @@ -130,8 +99,8 @@ public File getResourceFile(String path) { * * @param path The path to the default resource AND the data file. */ - public void saveDefaultResource(String path) { - if (!getResourceFile(path).exists()) { + public void saveDefaultResource(@Nonnull final String path) { + if (!getDataFile(path).exists()) { saveResource(path, false); } } @@ -142,10 +111,9 @@ public void saveDefaultResource(String path) { * @param defaultPath The path of the file within the plugin's jar. * @param dataPath The path the file should take within the plugin's data folder. */ - public void saveDefaultResourceAs(String defaultPath, String dataPath) { - Preconditions.checkNotNull(defaultPath, "defaultPath cannot be null."); - Preconditions.checkNotNull(dataPath, "dataPath cannot be null."); - if (getResourceFile(defaultPath).exists()) { + public void saveDefaultResourceAs(@Nonnull String defaultPath, + @Nonnull String dataPath) { + if (getDataFile(defaultPath).exists()) { return; } defaultPath = defaultPath.replace('\\', '/'); @@ -155,83 +123,16 @@ public void saveDefaultResourceAs(String defaultPath, String dataPath) { throw new IllegalArgumentException("The embedded resource '" + defaultPath + "' cannot be found in " + getFile()); } - final File outFile = new File(getDataFolder(), dataPath); + final var outFile = new File(getDataFolder(), dataPath); try { FileUtils.copyInputStreamToFile(data, outFile); } - catch (IOException exception) { + catch (final IOException exception) { severe("Could not save " + outFile.getName() + " to " + outFile); exception.printStackTrace(); } } - @Override - public boolean onCommand(@NotNull CommandSender sender, - @NotNull Command command, - @NotNull String label, - String[] arguments) { - if (this.handle != null) { - return this.handle.execute(sender, command, arguments); - } - if (this.newCommandHandler != null) { - return this.newCommandHandler.executeCommand(sender, command, arguments); - } - return false; - } - - @Override - public List onTabComplete(@NotNull CommandSender sender, - @NotNull Command command, - @NotNull String label, - String[] arguments) { - if (this.handle != null) { - return this.handle.complete(sender, command, arguments); - } - if (this.newCommandHandler != null) { - return this.newCommandHandler.tabCompleteCommand(sender, command, arguments); - } - return Collections.emptyList(); - } - - /** - * Retrieves this plugin's legacy command handler, if it has one. - * - * @return Returns this plugin's legacy command handler, or null. - */ - public CommandHandler getCommandHandler() { - return this.handle; - } - - /** - * Registers (or de-registers) a legacy command handler with this plugin. - * - * @param handler The legacy command handler to set. Null will cause de-registration. - */ - public void setCommandHandler(CommandHandler handler) { - this.handle = handler; - } - - /** - *

Retrieves this plugin's standalone command handler, if it has one.

- * - *

Note: You can use {@code this.useNewCommandHandler = false;} within your plugin's onEnable() method prior to - * the super call to disable the automatic generation of a standalone command handler.

- * - * @return Returns this plugin's standalone command handler, or null. - */ - public StandaloneCommandHandler getStandaloneCommandHandler() { - return this.newCommandHandler; - } - - /** - * Registers (or de-registers) a standalone command handler with this plugin. - * - * @param handler The standalone command handler to set. Null will cause de-registration. - */ - public void setStandaloneCommandHandler(StandaloneCommandHandler handler) { - this.newCommandHandler = handler; - } - /** * Disables this plugin. */ @@ -320,46 +221,57 @@ public void debug(String message, Object... vars) { * *
    *
  1. - * If there's an instance of the class currently enabled. (Don't request ACivMod.class, or you'll just get - * the the first result. + * If there's an instance of the class currently enabled. Don't request ACivMod, JavaPlugin, PluginBase, + * or Plugin or you'll just get the the first result. *
  2. - *
  3. If there's a public static .getInstance() method.
  4. - *
  5. If there's a static instance field.
  6. + *
  7. If there's a public static .getInstance() or .getPlugin() method.
  8. + *
  9. If there's a static "instance" or "plugin" field.
  10. *
* * @param The type of the plugin. * @param clazz The class object of the plugin. * @return Returns the first found instance of the plugin, or null. Nulls don't necessarily mean there isn't an - * instance of the plugin in existence. It could just be that it's located some unexpected place. Additionally, - * just because an instance has been returned does not mean that instance is enabled. + * instance of the plugin in existence. It could just be that it's located some unexpected place. + * Additionally, just because an instance has been returned does not mean that instance is enabled. */ + @Contract("null -> null") + @Nullable @SuppressWarnings("unchecked") - public static T getInstance(final Class clazz) { + public static T getInstance(@Nullable final Class clazz) { if (clazz == null) { return null; } + try { + return JavaPlugin.getPlugin(clazz); + } + catch (final IllegalArgumentException | IllegalStateException ignored) { } for (final Plugin plugin : Bukkit.getPluginManager().getPlugins()) { - if (clazz.isAssignableFrom(plugin.getClass())) { + if (clazz.equals(plugin.getClass())) { return (T) plugin; } } - try { - final Method method = clazz.getDeclaredMethod("getInstance"); - if (Modifier.isPublic(method.getModifiers()) - && Modifier.isStatic(method.getModifiers()) - && clazz.isAssignableFrom(method.getReturnType())) { - return (T) method.invoke(null); + for (final String methodName : Arrays.asList("getInstance", "getPlugin")) { + try { + final Method method = clazz.getDeclaredMethod(methodName); + if (Modifier.isPublic(method.getModifiers()) + && Modifier.isStatic(method.getModifiers()) + && method.getParameterCount() == 0 + && clazz.isAssignableFrom(method.getReturnType())) { + return (T) method.invoke(null); + } } + catch (final Throwable ignored) { } } - catch (final Exception ignored) { } - try { - final Field field = clazz.getField("instance"); - if (Modifier.isStatic(field.getModifiers()) - && clazz.isAssignableFrom(field.getType())) { - return (T) field.get(null); + for (final String fieldName : Arrays.asList("instance", "plugin")) { + try { + final Field field = clazz.getField(fieldName); + if (Modifier.isStatic(field.getModifiers()) + && clazz.isAssignableFrom(field.getType())) { + return (T) field.get(null); + } } + catch (final Throwable ignored) { } } - catch (final Exception ignored) { } // Otherwise there's no instance of the plugin, or it's stored in an unusual way return null; } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/CivModCoreConfig.java b/src/main/java/vg/civcraft/mc/civmodcore/CivModCoreConfig.java new file mode 100644 index 00000000..066ecd59 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/CivModCoreConfig.java @@ -0,0 +1,47 @@ +package vg.civcraft.mc.civmodcore; + +import javax.annotation.Nonnull; +import lombok.Getter; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.configuration.ConfigurationSection; +import vg.civcraft.mc.civmodcore.config.ConfigParser; +import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials; + +public final class CivModCoreConfig extends ConfigParser { + + @Getter + private DatabaseCredentials databaseCredentials; + private static final DatabaseCredentials DEFAULT_DATABASE_CREDENTIALS = null; + + @Getter + private String scoreboardHeader; + private static final String DEFAULT_SCOREBOARD_HEADER = " Info "; + + @Getter + private int skinCacheThreads; + private static final int DEFAULT_SKIN_CACHE_THREADS = Runtime.getRuntime().availableProcessors() / 2; + + CivModCoreConfig(@Nonnull final CivModCorePlugin plugin) { + super(plugin); + reset(); + } + + @Override + protected boolean parseInternal(@Nonnull final ConfigurationSection config) { + this.databaseCredentials = config.getObject("database", + DatabaseCredentials.class, DEFAULT_DATABASE_CREDENTIALS); + this.scoreboardHeader = ChatColor.translateAlternateColorCodes('&', + config.getString("scoreboardHeader", DEFAULT_SCOREBOARD_HEADER)); + this.skinCacheThreads = config.getInt("skin-download-threads", DEFAULT_SKIN_CACHE_THREADS); + return true; + } + + @Override + public void reset() { + super.reset(); + this.databaseCredentials = DEFAULT_DATABASE_CREDENTIALS; + this.scoreboardHeader = DEFAULT_SCOREBOARD_HEADER; + this.skinCacheThreads = DEFAULT_SKIN_CACHE_THREADS; + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java b/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java index 8321dc59..0353a427 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java @@ -1,68 +1,59 @@ package vg.civcraft.mc.civmodcore; import java.sql.SQLException; -import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.entity.HumanEntity; -import vg.civcraft.mc.civmodcore.api.PotionNames; +import org.ipvp.canvas.MenuFunctionListener; import vg.civcraft.mc.civmodcore.chat.dialog.DialogManager; -import vg.civcraft.mc.civmodcore.chatDialog.ChatListener; -import vg.civcraft.mc.civmodcore.command.AikarCommandManager; +import vg.civcraft.mc.civmodcore.commands.CommandManager; +import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials; import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; import vg.civcraft.mc.civmodcore.events.CustomEventMapper; +import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventoryListener; import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; import vg.civcraft.mc.civmodcore.inventory.items.MoreTags; -import vg.civcraft.mc.civmodcore.inventory.items.PotionUtils; import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; import vg.civcraft.mc.civmodcore.inventory.items.TreeTypeUtils; -import vg.civcraft.mc.civmodcore.inventorygui.ClickableInventoryListener; -import vg.civcraft.mc.civmodcore.inventorygui.paged.PagedGUIManager; -import vg.civcraft.mc.civmodcore.locations.chunkmeta.GlobalChunkMetaManager; -import vg.civcraft.mc.civmodcore.locations.chunkmeta.api.ChunkMetaAPI; -import vg.civcraft.mc.civmodcore.locations.global.CMCWorldDAO; -import vg.civcraft.mc.civmodcore.locations.global.WorldIDManager; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; -import vg.civcraft.mc.civmodcore.playersettings.gui.ConfigCommand; -import vg.civcraft.mc.civmodcore.playersettings.gui.ConfigGetAnyCommand; -import vg.civcraft.mc.civmodcore.playersettings.gui.ConfigSetAnyCommand; -import vg.civcraft.mc.civmodcore.scoreboard.bottom.BottomLineAPI; -import vg.civcraft.mc.civmodcore.scoreboard.side.ScoreBoardAPI; -import vg.civcraft.mc.civmodcore.scoreboard.side.ScoreBoardListener; -import vg.civcraft.mc.civmodcore.serialization.NBTSerialization; +import vg.civcraft.mc.civmodcore.maps.MapColours; +import vg.civcraft.mc.civmodcore.players.scoreboard.bottom.BottomLineAPI; +import vg.civcraft.mc.civmodcore.players.scoreboard.side.ScoreBoardAPI; +import vg.civcraft.mc.civmodcore.players.scoreboard.side.ScoreBoardListener; +import vg.civcraft.mc.civmodcore.players.settings.commands.ConfigCommand; +import vg.civcraft.mc.civmodcore.utilities.SkinCache; import vg.civcraft.mc.civmodcore.world.WorldTracker; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.GlobalChunkMetaManager; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.api.ChunkMetaAPI; +import vg.civcraft.mc.civmodcore.world.locations.global.CMCWorldDAO; +import vg.civcraft.mc.civmodcore.world.locations.global.WorldIDManager; import vg.civcraft.mc.civmodcore.world.operations.ChunkOperationManager; -@SuppressWarnings("deprecation") public final class CivModCorePlugin extends ACivMod { private static CivModCorePlugin instance; + private CivModCoreConfig config; private GlobalChunkMetaManager chunkMetaManager; - private ManagedDatasource database; - private WorldIDManager worldIdManager; - - private AikarCommandManager manager; + private CommandManager commands; + private SkinCache skinCache; @Override public void onEnable() { instance = this; - this.useNewCommandHandler = true; - ConfigurationSerialization.registerClass(ManagedDatasource.class); + registerConfigClass(DatabaseCredentials.class); // Save default resources saveDefaultResource("enchants.yml"); - saveDefaultResource("materials.yml"); - saveDefaultResource("potions.csv"); - saveDefaultConfig(); super.onEnable(); + // Load Config + this.config = new CivModCoreConfig(this); + this.config.parse(); // Load Database try { - this.database = (ManagedDatasource) getConfig().get("database"); + this.database = ManagedDatasource.construct(this, this.config.getDatabaseCredentials()); if (this.database != null) { - CMCWorldDAO dao = new CMCWorldDAO(this.database, this); + final var dao = new CMCWorldDAO(this.database, this); if (dao.updateDatabase()) { this.worldIdManager = new WorldIDManager(dao); this.chunkMetaManager = new GlobalChunkMetaManager(dao, this.worldIdManager); @@ -73,47 +64,37 @@ public void onEnable() { } } } - catch (Exception error) { + catch (final Throwable error) { warning("Cannot get database from config.", error); this.database = null; } - String scoreboardHeader = ChatColor.translateAlternateColorCodes('&', getConfig().getString("scoreboardHeader"," Info ")); - ScoreBoardAPI.setDefaultHeader(scoreboardHeader); + ScoreBoardAPI.setDefaultHeader(this.config.getScoreboardHeader()); // Register listeners + registerListener(new MenuFunctionListener()); registerListener(new ClickableInventoryListener()); - registerListener(new PagedGUIManager()); registerListener(DialogManager.INSTANCE); - registerListener(new ChatListener()); registerListener(new ScoreBoardListener()); registerListener(new CustomEventMapper()); registerListener(new WorldTracker()); registerListener(ChunkOperationManager.INSTANCE); // Register commands - this.manager = new AikarCommandManager(this) { - @Override - public void registerCommands() { - registerCommand(new ConfigCommand()); - registerCommand(ChunkOperationManager.INSTANCE); - } - }; + this.commands = new CommandManager(this); + this.commands.init(); + this.commands.registerCommand(new ConfigCommand()); + this.commands.registerCommand(ChunkOperationManager.INSTANCE); // Load APIs EnchantUtils.loadEnchantAbbreviations(this); - ItemUtils.loadItemNames(this); MoreTags.init(); - PotionUtils.init(); SpawnEggUtils.init(); TreeTypeUtils.init(); BottomLineAPI.init(); - newCommandHandler.registerCommand(new ConfigSetAnyCommand()); - newCommandHandler.registerCommand(new ConfigGetAnyCommand()); - // Deprecated - PotionNames.loadPotionNames(); + MapColours.init(); + this.skinCache = new SkinCache(this, this.config.getSkinCacheThreads()); } @Override public void onDisable() { Bukkit.getOnlinePlayers().forEach(HumanEntity::closeInventory); - PotionNames.resetPotionNames(); ChunkMetaAPI.saveAll(); this.chunkMetaManager = null; // Disconnect database @@ -128,12 +109,18 @@ public void onDisable() { } DialogManager.resetDialogs(); WorldTracker.reset(); - PlayerSettingAPI.saveAll(); - ConfigurationSerialization.unregisterClass(ManagedDatasource.class); - NBTSerialization.clearAllRegistrations(); - if (this.manager != null) { - this.manager.reset(); - this.manager = null; + ConfigurationSerialization.unregisterClass(DatabaseCredentials.class); + if (this.commands != null) { + this.commands.reset(); + this.commands = null; + } + if (this.skinCache != null) { + this.skinCache.shutdown(); + this.skinCache = null; + } + if (this.config != null) { + this.config.reset(); + this.config = null; } super.onDisable(); } @@ -154,4 +141,8 @@ public ManagedDatasource getDatabase() { return this.database; } + public SkinCache getSkinCache() { + return this.skinCache; + } + } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/CoreConfigManager.java b/src/main/java/vg/civcraft/mc/civmodcore/CoreConfigManager.java deleted file mode 100644 index 384ed0b5..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/CoreConfigManager.java +++ /dev/null @@ -1,135 +0,0 @@ -package vg.civcraft.mc.civmodcore; - -import com.google.common.base.Strings; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.logging.Logger; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; - -/** - * This is a config parsing class intended to make handling configs a little easier, and automatically parse commonly - * seen things within civ configs. - */ -public class CoreConfigManager { - - protected final ACivMod plugin; - protected final Logger logger; - - private boolean debug; - private boolean logReplies; - - public CoreConfigManager(ACivMod plugin) { - this.plugin = plugin; - this.logger = plugin.getLogger(); - } - - /** - * Parses this civ plugin's config. It will also save a default config (if one is not already present) and reload - * the config, so there's no need to do so yourself beforehand. - * - * @return Returns true if the config was successfully parsed. - */ - public final boolean parse() { - this.plugin.info(ChatColor.BLUE + "Parsing config."); - this.plugin.saveDefaultConfig(); - this.plugin.reloadConfig(); - FileConfiguration config = this.plugin.getConfig(); - // Parse debug value - this.debug = config.getBoolean("debug", false); - this.plugin.info("Debug mode: " + (this.debug ? "enabled" : "disabled")); - // Parse reply logging value - this.logReplies = config.getBoolean("logReplies", false); - this.plugin.info("Logging replies: " + (this.logReplies ? "enabled" : "disabled")); - // Allow child class parsing - boolean worked = parseInternal(config); - if (worked) { - plugin.info(ChatColor.BLUE + "Config parsed."); - } - else { - plugin.warning("Failed to parse config!"); - } - return worked; - } - - /** - * An internal parser method intended to be overriden by child classes. - * - * @param config The root config section. - * @return Return true if the - */ - protected boolean parseInternal(ConfigurationSection config) { - return true; - } - - /** - * This should reset all config values back to their defaults. Child classes should override this if they parse - * additional values that should be reset. - */ - public void reset() { - this.debug = false; - this.logReplies = false; - } - - // ------------------------------------------------------------ // - // Getters - // ------------------------------------------------------------ // - - public final boolean isDebugEnabled() { - return this.debug; - } - - public final boolean logReplies() { - return this.logReplies; - } - - // ------------------------------------------------------------ // - // Predefined parsing utilities - // ------------------------------------------------------------ // - - /** - * Attempts to retrieve a list of materials from a config section. - * - * @param config The config section. - * @param key The key of the list. - * @return Returns a list of materials, or null. - */ - protected final List parseMaterialList(ConfigurationSection config, String key) { - return parseList(config, key, slug -> { - Material found = MaterialUtils.getMaterial(slug); - if (found == null) { - this.logger.warning("Could not parse material \"" + slug + "\" at: " + config.getCurrentPath()); - return null; - } - return found; - }); - } - - /** - * Attempts to retrieve a list from a config section. - * - * @param The type to parse the list into. - * @param config The config section. - * @param key The key of the list. - * @param parser The parser to convert the string value into the correct type. - * @return Returns a list, or null. - */ - protected static List parseList(ConfigurationSection config, String key, Function parser) { - if (config == null || Strings.isNullOrEmpty(key) || !config.isList(key) || parser == null) { - return null; - } - List result = new ArrayList<>(); - for (String entry : config.getStringList(key)) { - T item = parser.apply(entry); - if (item != null) { - result.add(item); - } - } - return result; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/BlockAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/BlockAPI.java deleted file mode 100644 index 1655203e..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/BlockAPI.java +++ /dev/null @@ -1,390 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import static vg.civcraft.mc.civmodcore.util.NullCoalescing.chain; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import net.minecraft.server.v1_16_R3.BlockProperties; -import net.minecraft.server.v1_16_R3.IBlockState; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.Chest; -import org.bukkit.block.data.type.Switch; -import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_16_R3.block.CraftBlock; -import org.bukkit.util.BlockIterator; -import vg.civcraft.mc.civmodcore.world.WorldUtils; - -/** - * Class of utility functions for Blocks, and BlockFaces referencing Blocks around a Block. - * - * @deprecated Use {@link WorldUtils} and {@link vg.civcraft.mc.civmodcore.world.BlockProperties} instead. - */ -@Deprecated -public final class BlockAPI { - - private BlockAPI() { } - - /** - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#ALL_SIDES} instead. - */ - @Deprecated - public static final List ALL_SIDES = ImmutableList.of( - BlockFace.UP, - BlockFace.DOWN, - BlockFace.NORTH, - BlockFace.SOUTH, - BlockFace.WEST, - BlockFace.EAST); - - /** - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#PLANAR_SIDES} instead. - */ - @Deprecated - public static final List PLANAR_SIDES = ImmutableList.of( - BlockFace.NORTH, - BlockFace.SOUTH, - BlockFace.WEST, - BlockFace.EAST); - - private static final Map> blockStateByIdentifier = new HashMap<>(); - - static { - for(Field field : BlockProperties.class.getFields()) { - if (!IBlockState.class.isAssignableFrom(field.getType())) { - continue; - } - field.setAccessible(true); - IBlockState bs; - try { - bs = (IBlockState)field.get(null); - } catch (IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); - continue; - } - //when updating, search for the method returning the string given in the constructor - String key = bs.getName(); - blockStateByIdentifier.put(key, bs); - } - } - - /** - *

Checks whether this block is valid and so can be handled reasonably without error.

- * - *

Note: This will return true even if the block is air. Use {@link MaterialAPI#isAir(Material)} as an - * additional check if this is important to you.

- * - * @param block The block to check. - * @return Returns true if the block is valid. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#isValidBlock(Block)} instead. - */ - @Deprecated - public static boolean isValidBlock(Block block) { - if (block == null) { - return false; - } - if (block.getType() == null) { // Do not remove this, it's not necessarily certain - return false; - } - return LocationAPI.isValidLocation(block.getLocation()); - } - - /** - * Returns a map of a block's relatives. - * - * @param block The block to get the relatives of. - * @param faces An array of the faces, which will be the keys of the returned map. - * @return Returns an immutable map of the block's relatives. - * - * @deprecated Use - * {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getBlockSidesMapped(Block, Collection, boolean)} instead. - */ - @Deprecated - public static Map getBlockSidesMapped(Block block, BlockFace... faces) { - if (faces == null || faces.length < 1) { - return Collections.unmodifiableMap(new EnumMap<>(BlockFace.class)); - } - else { - return getBlockSidesMapped(block, Arrays.asList(faces)); - } - } - - /** - * Returns a map of a block's relatives. - * - * @param block The block to get the relatives of. - * @param faces A collection of the faces, which will be the keys of the returned map. - * @return Returns an immutable map of the block's relatives. - * - * @deprecated Use - * {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getBlockSidesMapped(Block, Collection, boolean)} instead. - */ - @Deprecated - public static Map getBlockSidesMapped(Block block, Collection faces) { - EnumMap results = new EnumMap<>(BlockFace.class); - if (block != null && faces != null) { - faces.forEach(face -> results.put(face, block.getRelative(face))); - } - return Collections.unmodifiableMap(results); - } - - /** - * Returns a map of a block's relatives. - * - * @param block The block to get the relatives of. - * @param faces A collection of the faces, which will be the keys of the returned map. - * @return Returns an immutable map of the block's relatives. - * - * @deprecated Use - * {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getBlockSides(Block, Collection, boolean)} instead. - */ - @Deprecated - public static List getBlockSides(Block block, Collection faces) { - if (block == null || faces == null) { - throw new IllegalArgumentException("One of the args passed was null"); - } - if (faces.isEmpty()) { - return Collections.emptyList(); - } - return faces.stream().map(block::getRelative).collect(Collectors.toList()); - } - - /** - * Returns a map of all the block's relatives. - * - * @param block The block to get all the relatives of. - * @return Returns an immutable map of all the block's relatives. - * - * @deprecated Use - * {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getAllBlockSidesMapped(Block, boolean)} instead. - */ - @Deprecated - public static Map getAllSidesMapped(Block block) { - return getBlockSidesMapped(block, ALL_SIDES); - } - - /** - * Returns a list of all the block's relatives. - * - * @param block The block to get all the relatives of. - * @return Returns an immutable list of all the block's relatives. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getAllBlockSides(Block, boolean)} instead. - */ - @Deprecated - public static List getAllSides(Block block) { - return getBlockSides(block, ALL_SIDES); - } - - /** - * Returns a map of all the block's planar relatives. - * - * @param block The block to get the planar relatives of. - * @return Returns an immutable map of all the block's planar relatives. - * - * @deprecated Use - * {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getPlanarBlockSidesMapped(Block, boolean)} instead. - */ - @Deprecated - public static Map getPlanarSidesMapped(Block block) { - return getBlockSidesMapped(block, PLANAR_SIDES); - } - - /** - * Returns a list of all the block's planar relatives. - * - * @param block The block to get the planar relatives of. - * @return Returns an immutable list of all the block's planar relatives. - * - * @deprecated Use - * {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getPlanarBlockSides(Block, boolean)} instead. - */ - @Deprecated - public static List getPlanarSides(Block block) { - return getBlockSides(block, PLANAR_SIDES); - } - - /** - * Turns once in a clockwise direction. - * - * @param face The starting face, which must exist and be planar. - * @return Returns the next planar face in a clockwise direction. - * - * @exception IllegalArgumentException Throws if the given face is null or non-planar. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#turnClockwise(BlockFace)} instead. - */ - @Deprecated - public static BlockFace turnClockwise(BlockFace face) { - Preconditions.checkArgument(face != null); - Preconditions.checkArgument(PLANAR_SIDES.contains(face)); - switch (face) { - default: - case NORTH: - return BlockFace.EAST; - case EAST: - return BlockFace.SOUTH; - case SOUTH: - return BlockFace.WEST; - case WEST: - return BlockFace.NORTH; - } - } - - /** - * Turns once in a anti-clockwise direction. - * - * @param face The starting face, which must exist and be planar. - * @return Returns the next planar face in a anti-clockwise direction. - * - * @exception IllegalArgumentException Throws if the given face is null or non-planar. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#turnAntiClockwise(BlockFace)} instead. - */ - @Deprecated - public static BlockFace turnAntiClockwise(BlockFace face) { - Preconditions.checkArgument(face != null); - Preconditions.checkArgument(PLANAR_SIDES.contains(face)); - switch (face) { - default: - case NORTH: - return BlockFace.WEST; - case EAST: - return BlockFace.NORTH; - case SOUTH: - return BlockFace.EAST; - case WEST: - return BlockFace.SOUTH; - } - } - - /** - * Gets the {@link BlockFace} this attachable is attached to. This exists as - * {@link org.bukkit.block.data.Directional} has odd behaviour whereby if attached to the top or bottom of a block, - * the direction is the rotation of the block, rather than the attached face. - * - * @param attachable The Switch, which is an instance of {@link BlockData}. So do your own checks beforehand. - * @return Returns the block face the given attachable is attached to, or null. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getAttachedFace(Switch)} instead. - */ - @Deprecated - public static BlockFace getAttachedFace(Switch attachable) { - if (attachable == null) { - return null; - } - switch (attachable.getAttachedFace()) { - case CEILING: - return BlockFace.DOWN; - case FLOOR: - return BlockFace.UP; - case WALL: - return attachable.getFacing().getOppositeFace(); - default: - return null; - } - } - - /** - * Attempts to get the other block of a double chest. - * - * @param block The block that represents the double chest block you already have. - * @return Returns the other block or null if none can be found, or if the given block isn't that of a double chest. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getOtherDoubleChestBlock(Block, boolean)} - * instead. - */ - @Deprecated - public static Block getOtherDoubleChestBlock(Block block) { - if (!isValidBlock(block)) { - return null; - } - Chest chest = chain(() -> (Chest) block.getBlockData()); - if (chest == null) { - return null; - } - switch (chest.getType()) { - case LEFT: // This block is left side - return block.getRelative(turnClockwise(chest.getFacing())); - case RIGHT: - return block.getRelative(turnAntiClockwise(chest.getFacing())); - default: - case SINGLE: - return null; - } - } - - /** - * Creates a {@link BlockIterator} from a block's perspective, which is lacking from its constructors, which are - * more focused on entities. Keep in mind that the first returned block will likely be the given block parameter. - * - * @param block The block to start the iterator from. - * @param face The direction at which the iterator should iterate. - * @param range The distance the iterator should iterate. - * @return Returns a new instance of an BlockIterator. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getBlockIterator(Block, BlockFace, int)} - * instead. - */ - @Deprecated - public static BlockIterator getBlockIterator(Block block, BlockFace face, int range) { - if (!BlockAPI.isValidBlock(block)) { - throw new IllegalArgumentException("Cannot create a block iterator from a null block."); - } - if (face == null || face == BlockFace.SELF) { - throw new IllegalArgumentException("Block iterator requires a valid direction."); - } - if (range <= 0) { - throw new IllegalArgumentException("Block iterator requires a range of 1 or higher."); - } - return new BlockIterator(block.getWorld(), block.getLocation().toVector(), face.getDirection(), 0, range); - } - - /** - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.BlockProperties#setBlockProperty(Block, String, String)} - * instead. - */ - @Deprecated - public static boolean setBlockProperty(Block block, String key, String value) { - //we need this wrapper method to trick the java generics - return innerSetBlockProperty(block, key, value); - } - - // WHY IS THIS PUBLIC IF IT'S INNER? - /** - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.BlockProperties#setBlockProperty(Block, String, String)} - * instead. - */ - @Deprecated - public static > boolean innerSetBlockProperty(Block block, String key, String value) { - @SuppressWarnings("unchecked") - IBlockState state = (IBlockState) blockStateByIdentifier.get(key); - if (state == null) { - return false; - } - Optional opt = state.b(value); - if (!opt.isPresent()) { - return false; - } - V valueToSet = state.b(value).get(); - CraftBlock cb = (CraftBlock) block; - CraftWorld world = (CraftWorld) block.getWorld(); - //no idea what the last integer parameter does, I found 2 and 3 being used in NMS code and stuck to that - world.getHandle().setTypeAndData(cb.getPosition(), cb.getNMS().set( state, valueToSet), 2); - return true; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/ColourAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/ColourAPI.java deleted file mode 100644 index 4b810645..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/ColourAPI.java +++ /dev/null @@ -1,84 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import java.awt.Color; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import net.md_5.bungee.api.ChatColor; -import vg.civcraft.mc.civmodcore.chat.ChatUtils; - -/** - * @deprecated Use {@link ChatUtils} instead. - */ -public class ColourAPI { - - /** - * This is necessary as {@link ChatColor#values()} has all colours and all formats. - * - * @deprecated Use {@link ChatUtils#COLOURS} instead. - */ - @Deprecated - public static final List COLOURS = Collections.unmodifiableList(Arrays.asList( - ChatColor.BLACK, - ChatColor.DARK_BLUE, - ChatColor.DARK_GREEN, - ChatColor.DARK_AQUA, - ChatColor.DARK_RED, - ChatColor.DARK_PURPLE, - ChatColor.GOLD, - ChatColor.GRAY, - ChatColor.DARK_GRAY, - ChatColor.BLUE, - ChatColor.GREEN, - ChatColor.AQUA, - ChatColor.RED, - ChatColor.LIGHT_PURPLE, - ChatColor.YELLOW - )); - - /** - * Converts an RGB value into a Bungee ChatColor. - * - * @param r The red value. - * @param g The green value. - * @param b The blue value. - * @return Returns a valid Bungee ChatColor. - * - * @deprecated Use {@link ChatUtils#fromRGB(byte, byte, byte)} instead. - */ - @Deprecated - public static ChatColor fromRGB(final byte r, final byte g, final byte b) { - return ChatColor.of(new Color(r, g, b)); - } - - /** - * Attempts to collapse an RGB colour to established Minecraft colours. - * - * @param colour The given RGB colour. - * @return Returns the closest Minecraft match, or null. - * - * @deprecated Use {@link ChatUtils#collapseColour(ChatColor)} instead. - */ - @Deprecated - public static ChatColor collapseColour(final ChatColor colour) { - if (colour == null) { - return null; - } - final Color color = colour.getColor(); - ChatColor nearestColour = null; - double nearestDistance = Double.MAX_VALUE; - for (final ChatColor currentColour : COLOURS) { - final Color currentColor = currentColour.getColor(); - final double distance = Math.sqrt( - Math.pow(color.getRed() - currentColor.getRed(), 2) - - Math.pow(color.getGreen() - currentColor.getGreen(), 2) - - Math.pow(color.getBlue() - currentColor.getBlue(), 2)); - if (nearestDistance > distance) { - nearestDistance = distance; - nearestColour = currentColour; - } - } - return nearestColour; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/EnchantAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/EnchantAPI.java deleted file mode 100644 index b458bb80..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/EnchantAPI.java +++ /dev/null @@ -1,155 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import java.util.Map; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; - -/** - * Class of static APIs for Enchantments. - * - * @deprecated Use {@link EnchantUtils} instead. - */ -@Deprecated -public final class EnchantAPI { - - /** - * Determines whether an enchantment is considered safe. - * - * @param enchantment The enchantment to validate. - * @param level The enchantment level to validate. - * @return Returns true if the enchantment is not null, and the level is within the acceptable bounds. - * - * @see Enchantment#getStartLevel() The starting level. A valid level cannot be below this. - * @see Enchantment#getMaxLevel() The maximum level. A valid level cannot be above this. - * - * @deprecated Use {@link EnchantUtils#isSafeEnchantment(Enchantment, int)} instead. - */ - @Deprecated - public static boolean isSafeEnchantment(Enchantment enchantment, int level) { - return enchantment != null && level >= enchantment.getStartLevel() && level <= enchantment.getMaxLevel(); - } - - /** - * Attempts to retrieve an enchantment by its slug, display name, and abbreviation. - * - * @param value The value to search for a matching enchantment by. - * @return Returns a matched enchantment or null. - * - * @deprecated Use {@link EnchantUtils#getEnchantment(String)} instead. - */ - @Deprecated - @SuppressWarnings("deprecation") - public static Enchantment getEnchantment(String value) { - if (Strings.isNullOrEmpty(value)) { - return null; - } - Enchantment enchantment = Enchantment.getByKey(NamespaceAPI.fromString(value)); - if (enchantment != null) { - return enchantment; - } - enchantment = Enchantment.getByName(value.toUpperCase()); - if (enchantment != null) { - return enchantment; - } - EnchantNames.SearchResult search = EnchantNames.findByDisplayName(value); - if (search != null) { - return search.getEnchantment(); - } - search = EnchantNames.findByAbbreviation(value); - if (search != null) { - return search.getEnchantment(); - } - return null; - } - - /** - * Gets the enchantments from an item. - * - * @param item The item to retrieve the enchantments from. - * @return Returns the item's enchantments, which are never null. - * - * @deprecated Use {@link EnchantUtils#getEnchantments(ItemStack)} instead. - */ - @Deprecated - public static Map getEnchantments(ItemStack item) { - Preconditions.checkArgument(ItemAPI.isValidItem(item)); - return item.getEnchantments(); - } - - /** - * Adds a safe enchantment to an item. - * - * @param item The item to add the enchantment to. - * @param enchantment The enchantment to add to the item. - * @param level The level of the enchantment to add to the item. - * @return Returns true if the enchantment was successfully added. - * - * @see EnchantAPI#isSafeEnchantment(Enchantment, int) - * - * @deprecated Use {@link EnchantUtils#addEnchantment(ItemStack, Enchantment, int)} instead. - */ - @Deprecated - public static boolean addEnchantment(ItemStack item, Enchantment enchantment, int level) { - return addEnchantment(item, enchantment, level, true); - } - - /** - * Adds an enchantment to an item. - * - * @param item The item to add the enchantment to. - * @param enchantment The enchantment to add to the item. - * @param level The level of the enchantment to add to the item. - * @param onlyAllowSafeEnchantments Requires enchantments to be safe if set to true. - * @return Returns true if the enchantment was successfully added. - * - * @see EnchantAPI#isSafeEnchantment(Enchantment, int) - * - * @deprecated Use {@link EnchantUtils#addEnchantment(ItemStack, Enchantment, int, boolean)} instead. - */ - @Deprecated - public static boolean addEnchantment(ItemStack item, Enchantment enchantment, int level, - boolean onlyAllowSafeEnchantments) { - Preconditions.checkArgument(ItemAPI.isValidItem(item)); - return ItemAPI.handleItemMeta(item, (ItemMeta meta) -> - meta.addEnchant(enchantment, level, !onlyAllowSafeEnchantments)); - } - - /** - * Removes an enchantment from an item. - * - * @param item The item to remove the enchantment from. - * @param enchant The enchantment to remove from the item. - * @return Returns true if the enchantment was successfully removed. - * - * @deprecated Use {@link EnchantUtils#removeEnchantment(ItemStack, Enchantment)} instead. - */ - @Deprecated - public static boolean removeEnchantment(ItemStack item, Enchantment enchant) { - Preconditions.checkArgument(ItemAPI.isValidItem(item)); - if (enchant == null) { - return true; - } - return ItemAPI.handleItemMeta(item, (ItemMeta meta) -> meta.removeEnchant(enchant)); - } - - /** - * Removes all enchantments from an item. - * - * @param item The item to clear enchantment from. - * - * @deprecated Use {@link EnchantUtils#clearEnchantments(ItemStack)} instead. - */ - @Deprecated - public static void clearEnchantments(ItemStack item) { - Preconditions.checkArgument(ItemAPI.isValidItem(item)); - ItemAPI.handleItemMeta(item, (ItemMeta meta) -> { - meta.getEnchants().forEach((key, value) -> meta.removeEnchant(key)); - return true; - }); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/EnchantNames.java b/src/main/java/vg/civcraft/mc/civmodcore/api/EnchantNames.java deleted file mode 100644 index 56bff356..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/EnchantNames.java +++ /dev/null @@ -1,94 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.base.Strings; -import java.util.Objects; -import org.bukkit.enchantments.Enchantment; -import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; - -/** - * @deprecated Use {@link EnchantUtils} instead. - */ -@Deprecated -public final class EnchantNames { - - @Deprecated - public static SearchResult findByEnchantment(Enchantment enchant) { - if (enchant == null) { - return null; - } - return new SearchResult(enchant, - EnchantUtils.getEnchantAbbreviation(enchant), - EnchantUtils.getEnchantNiceName(enchant)); - } - - @Deprecated - public static SearchResult findByAbbreviation(String abbreviation) { - if (Strings.isNullOrEmpty(abbreviation)) { - return null; - } - final Enchantment found = EnchantUtils.getEnchantment(abbreviation); - if (found == null) { - return null; - } - return new SearchResult(found, abbreviation, EnchantUtils.getEnchantNiceName(found)); - } - - @Deprecated - public static SearchResult findByDisplayName(String displayName) { - if (Strings.isNullOrEmpty(displayName)) { - return null; - } - final Enchantment found = EnchantUtils.getEnchantment(displayName); - if (found == null) { - return null; - } - return new SearchResult(found, EnchantUtils.getEnchantAbbreviation(found), displayName); - } - - /** - * This class represents a data set for a particular enchantment. - */ - @Deprecated - public static final class SearchResult { - - private final Enchantment enchantment; - - private final String abbreviation; - - private final String displayName; - - private SearchResult(Enchantment enchantment, String abbreviation, String displayName) { - this.enchantment = enchantment; - this.abbreviation = abbreviation; - this.displayName = displayName; - } - - /** - * @return Returns the enchantment itself. - */ - public Enchantment getEnchantment() { - return this.enchantment; - } - - /** - * @return Returns the enchantment's official abbreviation. - */ - public String getAbbreviation() { - return this.abbreviation; - } - - /** - * @return Returns the enchantment's official display name. - */ - public String getDisplayName() { - return this.displayName; - } - - @Override - public int hashCode() { - return Objects.hash(this.enchantment, this.displayName, this.abbreviation); - } - - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/EntityAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/EntityAPI.java deleted file mode 100644 index 82d16df1..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/EntityAPI.java +++ /dev/null @@ -1,66 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.base.Strings; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.entities.EntityUtils; - -/** - * Class of static APIs for Entities. - * - * @deprecated Use {@link EntityUtils} instead. - */ -@Deprecated -public final class EntityAPI { - - /** - * Attempts to retrieve an entity type by its slug or id. - * - * @param value The value to search for a matching entity type by. - * @return Returns a matched entity type or null. - * - * @deprecated Use {@link EntityUtils#getEntityType(String)} instead. - */ - @Deprecated - public static EntityType getEntityType(String value) { - if (Strings.isNullOrEmpty(value)) { - return null; - } - try { - return EntityType.valueOf(value.toUpperCase()); - } - catch (Exception ignored) { } - try { - EntityType type = EntityType.fromId(Short.parseShort(value)); - if (type != null) { - return type; - } - } - catch (Exception ignored) { } - return null; - } - - /** - * Checks whether an entity is a player. - * - * @param entity The entity to test. - * @return Returns true if the entity is a player. - * - * @deprecated Use {@link EntityUtils#isPlayer(Entity)} instead. - */ - @Deprecated - public static boolean isPlayer(Entity entity) { - if (entity == null) { - return false; - } - if (entity.getType() != EntityType.PLAYER) { - return false; - } - if (!(entity instanceof Player)) { - return false; - } - return true; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/InventoryAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/InventoryAPI.java deleted file mode 100644 index c1ad9007..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/InventoryAPI.java +++ /dev/null @@ -1,279 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.base.Preconditions; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import org.apache.commons.lang3.ArrayUtils; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.inventory.ClonedInventory; -import vg.civcraft.mc.civmodcore.inventory.InventoryUtils; -import vg.civcraft.mc.civmodcore.util.Iteration; - -/** - * @deprecated Use {@link InventoryUtils} instead. - */ -@Deprecated -public final class InventoryAPI { - - /** - * Tests an inventory to see if it's valid. - * - * @param inventory The inventory to test. - * @return Returns true if it's more likely than not valid. - * - * @deprecated Use {@link InventoryUtils#isValidInventory(Inventory)} instead. - */ - @Deprecated - public static boolean isValidInventory(Inventory inventory) { - if (inventory == null) { - return false; - } - if (inventory.getSize() <= 0) { - return false; - } - return true; - } - - /** - * Get the players viewing an inventory. - * - * @param inventory The inventory to get the viewers of. - * @return Returns a list of players. Returns an empty list if the inventory is null. - * - * @deprecated Use {@link InventoryUtils#isValidInventory(Inventory)} instead. - */ - @Deprecated - public static List getViewingPlayers(Inventory inventory) { - if (!isValidInventory(inventory)) { - return new ArrayList<>(); - } - return inventory.getViewers().stream(). - map((entity) -> EntityAPI.isPlayer(entity) ? (Player) entity : null). - filter(Objects::nonNull). - collect(Collectors.toCollection(ArrayList::new)); - } - - /** - *

Attempts to find the first safe place to put an item.

- * - * @param inventory The inventory to attempt to find a slot in. - * @param item The item to find a place for. - * @return Returns an index of a slot that it's safe to add to. A return value of -1 means no safe place. Even if - * the return value is -1 it may still be possible to add the item stack to the inventory, as this - * function attempts to find the first slot that the given item stack can fit into wholly; that if it can - * technically fit but has to be distributed then there's no "first empty". - * - * @deprecated Use {@link InventoryUtils#firstEmpty(Inventory, ItemStack)} instead. - */ - @Deprecated - public static int firstEmpty(Inventory inventory, ItemStack item) { - if (inventory == null) { - return -1; - } - // If there's a slot free, then just return that. Otherwise if - // the item is invalid, just return whatever slot was returned. - int index = inventory.firstEmpty(); - if (index >= 0 || !ItemAPI.isValidItem(item)) { - return index; - } - // If gets here, then we're certain that there's no stacks free. - // If the amount of the item to add is larger than a stack, then - // it can't be merged with another stack. So just back out. - int remainder = item.getMaxStackSize() - item.getAmount(); - if (remainder <= 0) { - return -1; - } - // Find all items that match with the given item to see if there's - // a stack that can be merged with. If none can be found, back out. - for (Map.Entry entry : inventory.all(item).entrySet()) { - if (entry.getValue().getAmount() <= remainder) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Clears an inventory of items. - * - * @param inventory The inventory to clear of items. - * - * @deprecated Use {@link InventoryUtils#clearInventory(Inventory)} instead. - */ - @Deprecated - public static void clearInventory(Inventory inventory) { - Preconditions.checkArgument(isValidInventory(inventory)); - inventory.setContents(Iteration.fill(inventory.getContents(), null)); - } - - /** - * Checks whether an inventory has more than one viewer. - * - * @param inventory The inventory to check. - * @return Returns true if an inventory has multiple viewers. - * - * @deprecated Use {@link InventoryUtils#hasOtherViewers(Inventory)} instead. - */ - @Deprecated - public static boolean hasOtherViewers(Inventory inventory) { - if (!isValidInventory(inventory)) { - return false; - } - return inventory.getViewers().size() > 1; - } - - /** - * Checks whether a certain amount of slots would be considered a valid chest inventory. - * - * @param slots The slot amount to check. - * @return Returns true if the slot count is or between 1-6 multiples of 9. - * - * @deprecated Use {@link InventoryUtils#isValidChestSize(int)} instead. - */ - @Deprecated - public static boolean isValidChestSize(int slots) { - if (slots <= 0 || slots > 54) { - return false; - } - if ((slots % 9) > 0) { - return false; - } - return true; - } - - /** - * Will safely add a set of items to an inventory. If not all items are added, it's not committed to the inventory. - * - * @param inventory The inventory to add the items to. - * @param items The items to add to the inventory. - * @return Returns true if the items were safely added. - * - * @deprecated Use {@link InventoryUtils#safelyAddItemsToInventory(Inventory, ItemStack[])} instead. - */ - @Deprecated - public static boolean safelyAddItemsToInventory(Inventory inventory, ItemStack[] items) { - Preconditions.checkArgument(isValidInventory(inventory)); - if (ArrayUtils.isEmpty(items)) { - return true; - } - Inventory clone = cloneInventory(inventory); - for (ItemStack itemToAdd : items) { - if (firstEmpty(clone, itemToAdd) < 0) { - return false; - } - if (!clone.addItem(itemToAdd).isEmpty()) { - return false; - } - } - inventory.setContents(clone.getContents()); - return true; - } - - /** - * Will safely remove a set of items from an inventory. If not all items are removed, it's not committed to the - * inventory. - * - * @param inventory The inventory to remove the items from. - * @param items The items to remove to the inventory. - * @return Returns true if the items were safely removed. - * - * @deprecated Use {@link InventoryUtils#safelyRemoveItemsFromInventory(Inventory, ItemStack[])} instead. - */ - @Deprecated - public static boolean safelyRemoveItemsFromInventory(Inventory inventory, ItemStack[] items) { - Preconditions.checkArgument(isValidInventory(inventory)); - if (ArrayUtils.isEmpty(items)) { - return true; - } - Inventory clone = cloneInventory(inventory); - for (ItemStack itemToRemove : items) { - if (!clone.removeItem(itemToRemove).isEmpty()) { - return false; - } - } - inventory.setContents(clone.getContents()); - return true; - } - - /** - * Will safely transact a set of items from one inventory to another inventory. If not all items are transacted, the - * transaction is not committed. - * - * @param from The inventory to move the given items from. - * @param items The items to transact. - * @param to The inventory to move the given items to. - * @return Returns true if the items were successfully transacted. - * - * @deprecated Use {@link InventoryUtils#safelyTransactBetweenInventories(Inventory, Inventory, ItemStack[])} - * instead. - */ - @Deprecated - public static boolean safelyTransactBetweenInventories(Inventory from, ItemStack[] items, Inventory to) { - Preconditions.checkArgument(isValidInventory(from)); - Preconditions.checkArgument(isValidInventory(to)); - if (ArrayUtils.isEmpty(items)) { - return true; - } - Inventory fromClone = cloneInventory(from); - Inventory toClone = cloneInventory(to); - if (!safelyRemoveItemsFromInventory(fromClone, items)) { - return false; - } - if (!safelyAddItemsToInventory(toClone, items)) { - return false; - } - from.setContents(fromClone.getContents()); - to.setContents(toClone.getContents()); - return true; - } - - /** - * Will safely trade items between inventories. If not all items are traded, the trade is cancelled. - * - * @param formerInventory The first inventory. - * @param formerItems The items to trade from the first inventory to give to the second inventory. - * @param latterInventory The second inventory. - * @param latterItems The items to trade from the second inventory to give to the first inventory. - * @return Returns true if the trade succeeded. - * - * @deprecated Use - * {@link InventoryUtils#safelyTradeBetweenInventories(Inventory, Inventory, ItemStack[], ItemStack[])} instead. - */ - @Deprecated - public static boolean safelyTradeBetweenInventories(Inventory formerInventory, ItemStack[] formerItems, - Inventory latterInventory, ItemStack[] latterItems) { - Preconditions.checkArgument(isValidInventory(formerInventory)); - Preconditions.checkArgument(isValidInventory(latterInventory)); - Inventory formerClone = InventoryAPI.cloneInventory(formerInventory); - Inventory latterClone = InventoryAPI.cloneInventory(latterInventory); - if (!safelyRemoveItemsFromInventory(formerClone, formerItems)) { - return false; - } - if (!safelyRemoveItemsFromInventory(latterClone, latterItems)) { - return false; - } - if (!safelyAddItemsToInventory(formerClone, latterItems)) { - return false; - } - if (!safelyAddItemsToInventory(latterClone, formerItems)) { - return false; - } - formerInventory.setContents(formerClone.getContents()); - latterInventory.setContents(latterClone.getContents()); - return true; - } - - /** - * @deprecated Use {@link ClonedInventory#cloneInventory(Inventory)} instead. - */ - @Deprecated - public static Inventory cloneInventory(Inventory inventory) { - return ClonedInventory.cloneInventory(inventory); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/ItemAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/ItemAPI.java deleted file mode 100644 index 96215223..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/ItemAPI.java +++ /dev/null @@ -1,420 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import static vg.civcraft.mc.civmodcore.util.NullCoalescing.castOrNull; - -import com.google.common.base.Objects; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.util.Iteration; - -/** - * Class of static APIs for Items. Replaces ISUtils. - * - * @deprecated Use {@link ItemUtils} instead. - */ -@Deprecated -public final class ItemAPI { - - /** - * Checks if an ItemStack is valid. An ItemStack is considered valid if when added to an inventory, it shows as an - * item with an amount within appropriate bounds. Therefore {@code new ItemStack(Material.AIR)} will not be - * considered valid, nor will {@code new ItemStack(Material.STONE, 80)} - * - * @param item The item to validate. - * @return Returns true if the item is valid. - * - * @deprecated Use {@link ItemUtils#isValidItem(ItemStack)} instead. - */ - @Deprecated - public static boolean isValidItem(ItemStack item) { - if (item == null) { - return false; - } - if (!MaterialAPI.isValidItemMaterial(item.getType())) { - return false; - } - if (!isValidItemAmount(item)) { - return false; - } - return true; - } - - /** - * Checks if an ItemStack has a valid amount. - * - * @param item The item to validate. - * @return Returns true if the item has a valid amount. - * - * @deprecated Use {@link ItemUtils#isValidItemAmount(ItemStack)} instead. - */ - @Deprecated - public static boolean isValidItemAmount(ItemStack item) { - if (item == null) { - return false; - } - if (item.getAmount() <= 0) { - return false; - } - if (item.getAmount() > item.getMaxStackSize()) { - return false; - } - return true; - } - - /** - * Determines whether two item stacks are functionally identical. (Will check both items against the other) - * - * @param former The first item. - * @param latter The second item. - * @return Returns true if both items are equal and not null. - * - * @see ItemStack#equals(Object) - * - * @deprecated Use {@link Objects#equal(Object, Object)} instead. - */ - @Deprecated - public static boolean areItemsEqual(ItemStack former, ItemStack latter) { - if (former != null && former.equals(latter)) { - return true; - } - if (latter != null && latter.equals(former)) { - return true; - } - return false; - } - - /** - * Determines whether two item stacks are similar. (Will check both items against the other) - * - * @param former The first item. - * @param latter The second item. - * @return Returns true if both items are similar and not null. - * - * @see ItemStack#isSimilar(ItemStack) - * - * @deprecated Use {@link ItemUtils#areItemsSimilar(ItemStack, ItemStack)} instead. - */ - @Deprecated - public static boolean areItemsSimilar(ItemStack former, ItemStack latter) { - if (former != null && former.isSimilar(latter)) { - return true; - } - if (latter != null && latter.isSimilar(former)) { - return true; - } - return false; - } - - /** - * Decrements an item's amount, or returns null if the amount reaches zero. - * - * @param item The item to decrement in amount. - * @return Returns the given item with a decremented amount, or null. - * - * @deprecated Use {@link ItemUtils#decrementItem(ItemStack)} instead. - */ - @Deprecated - public static ItemStack decrementItem(ItemStack item) { - if (item == null || item.getAmount() <= 1) { - return null; - } - item.setAmount(item.getAmount() - 1); - return item; - } - - /** - * Normalizes an item. - * - * @param item The item to normalize. - * @return The normalized item. - * - * @deprecated Use {@link ItemUtils#normalizeItem(ItemStack)} instead. - */ - @Deprecated - public static ItemStack normalizeItem(ItemStack item) { - if (item == null) { - return null; - } - item = item.clone(); - item.setAmount(1); - return item; - } - - /** - * Retrieves the ItemMeta from an item. - * - * @param item The item to retrieve meta from. - * @return Returns the item meta, which is never null. - * - * @deprecated Use {@link ItemUtils#getItemMeta(ItemStack)} instead. - */ - @Deprecated - public static ItemMeta getItemMeta(ItemStack item) { - if (item == null) { - throw new IllegalArgumentException("Cannot retrieve the item's meta; the item is null."); - } - ItemMeta meta = item.getItemMeta(); - if (meta == null) { - throw new IllegalArgumentException("Cannot retrieve item meta; it has no meta nor was any generated."); - } - return meta; - } - - /** - * Retrieves the display name from an item. - * - * @param item The item to retrieve the display name from. - * @return Returns the display name of an item. Will return null if there's no display name, or if it's empty. - * - * @deprecated Use {@link ItemUtils#getDisplayName(ItemStack)} instead. - */ - @Deprecated - public static String getDisplayName(ItemStack item) { - ItemMeta meta = getItemMeta(item); - String name = meta.getDisplayName(); - if (StringUtils.isEmpty(name)) { - return null; - } - return name; - } - - /** - * Sets a display name to an item. A null or empty name will remove the display name from the item. - * - * @param item The item to set the display name to. - * @param name The display name to set on the item. - * - * @deprecated Use {@link ItemUtils#setDisplayName(ItemStack, String)} instead. - */ - @Deprecated - public static void setDisplayName(ItemStack item, String name) { - ItemMeta meta = getItemMeta(item); - if (StringUtils.isEmpty(name)) { - meta.setDisplayName(null); - } - else { - meta.setDisplayName(name); - } - item.setItemMeta(meta); - } - - /** - * Retrieves the lore from an item. - * - * @param item The item to retrieve the lore from. - * @return Returns the lore, which is never null. - * - * @deprecated Use {@link ItemUtils#getLore(ItemStack)} instead. - */ - @Deprecated - public static List getLore(ItemStack item) { - ItemMeta meta = getItemMeta(item); - List lore = meta.getLore(); - if (lore == null) { - return new ArrayList<>(); - } - return lore; - } - - /** - * Sets the lore for an item, replacing any lore that may have already been set. - * - * @param item The item to set the lore to. - * @param lines The lore to set to the item. - * - * @see ItemAPI#clearLore(ItemStack) - * - * @deprecated Use {@link ItemUtils#setLore(ItemStack, String...)} instead. - */ - @Deprecated - public static void setLore(ItemStack item, String... lines) { - if (ArrayUtils.isEmpty(lines)) { - clearLore(item); - } - else { - setLore(item, Iteration.collect(ArrayList::new, lines)); - } - } - - /** - * Sets the lore for an item, replacing any lore that may have already been set. - * - * @param item The item to set the lore to. - * @param lines The lore to set to the item. - * - * @see ItemAPI#clearLore(ItemStack) - * - * @deprecated Use {@link ItemUtils#setLore(ItemStack, List)} instead. - */ - @Deprecated - public static void setLore(ItemStack item, List lines) { - ItemMeta meta = getItemMeta(item); - meta.setLore(lines); - item.setItemMeta(meta); - } - - /** - * Appends lore to an item. - * - * @param item The item to append the lore to. - * @param lines The lore to append to the item. - * - * @deprecated Use {@link ItemUtils#addLore(ItemStack, String...)} instead. - */ - @Deprecated - public static void addLore(ItemStack item, String... lines) { - addLore(item, Iteration.collect(ArrayList::new, lines)); - } - - /** - * Appends lore to an item. - * - * @param item The item to append the lore to. - * @param lines The lore to append to the item. - * - * @deprecated Use {@link ItemUtils#addLore(ItemStack, List)} instead. - */ - @Deprecated - public static void addLore(ItemStack item, List lines) { - addLore(item, false, lines); - } - - /** - * Adds lore to an item, either by appending or prepending. - * - * @param item The item to append the lore to. - * @param prepend If set to true, the lore will be prepended instead of appended. - * @param lines The lore to append to the item. - * - * @deprecated Use {@link ItemUtils#addLore(ItemStack, boolean, String...)} instead. - */ - @Deprecated - public static void addLore(ItemStack item, boolean prepend, String... lines) { - addLore(item, prepend, Iteration.collect(ArrayList::new, lines)); - } - - /** - * Adds lore to an item, either by appending or prepending. - * - * @param item The item to append the lore to. - * @param prepend If set to true, the lore will be prepended instead of appended. - * @param lines The lore to append to the item. - * - * @deprecated Use {@link ItemUtils#addLore(ItemStack, boolean, List)} instead. - */ - @Deprecated - public static void addLore(ItemStack item, boolean prepend, List lines) { - if (Iteration.isNullOrEmpty(lines)) { - throw new IllegalArgumentException("Cannot add to the item's lore; the lore is null."); - } - ItemMeta meta = getItemMeta(item); - List lore = meta.getLore(); - if (lore == null) { - lore = new ArrayList<>(); - } - if (prepend) { - Collections.reverse(lines); - for (String line : lines) { - lore.add(0, line); - } - } - else { - lore.addAll(lines); - } - setLore(item, lore); - } - - /** - * Clears the lore from an item. - * - * @param item The item to clear lore of. - * - * @deprecated Use {@link ItemUtils#clearLore(ItemStack)} instead. - */ - @Deprecated - public static void clearLore(ItemStack item) { - setLore(item, (List) null); - } - - /** - * Retrieves the Damageable ItemMeta only if it's relevant to the item. This is necessary because [almost?] every - * ItemMeta implements Damageable.. for some reason. And so this will only return a Damageable instance if the item - * material actually has a maximum durability above zero. - * - * @param item The item to get the Damageable meta from. - * @return Returns an instance of Damageable, or null. - * - * @deprecated Use {@link ItemUtils#getDamageable(ItemStack)} instead. - */ - @Deprecated - public static Damageable getDamageable(ItemStack item) { - if (item == null) { - return null; - } - short maxDurability = item.getType().getMaxDurability(); - if (maxDurability <= 0) { - return null; - } - return castOrNull(Damageable.class, item.getItemMeta()); - } - - /** - * Makes an item glow by adding an enchantment and the flag for hiding enchantments, - * so it has the enchantment glow without an enchantment being visible. Note that this - * does actually apply an enchantment to an item. - * - * @param item Item to apply glow to. - * - * @deprecated Use {@link ItemUtils#addGlow(ItemStack)} instead. - */ - @Deprecated - public static void addGlow(ItemStack item) { - ItemMeta meta = getItemMeta(item); - meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); - item.setItemMeta(meta); - item.addUnsafeEnchantment(Enchantment.DURABILITY, 1); - } - - /** - * Handles an item's metadata. - * - * @param The item meta type, which might not extend ItemMeta (Damageable for example) - * @param item The item to handle the metadata of. - * @param handler The item metadata handler, which should return true if modifications were made. - * @return Returns true if the metadata was successfully handled. - * - * @see ItemStack#getItemMeta() - * - * @deprecated Use {@link ItemUtils#handleItemMeta(ItemStack, Predicate)} instead. - */ - @Deprecated - @SuppressWarnings("unchecked") - public static boolean handleItemMeta(ItemStack item, Predicate handler) { - if (item == null || handler == null) { - return false; - } - T meta; - try { - meta = (T) item.getItemMeta(); - if (meta == null) { - return false; - } - if (handler.test(meta)) { - return item.setItemMeta((ItemMeta) meta); - } - } - catch (ClassCastException ignored) { } - return false; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/ItemNames.java b/src/main/java/vg/civcraft/mc/civmodcore/api/ItemNames.java deleted file mode 100644 index 460d4c0e..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/ItemNames.java +++ /dev/null @@ -1,37 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; - -/** - * @deprecated Use {@link ItemUtils} instead. - */ -@Deprecated -public final class ItemNames { - - /** - * @deprecated Use {@link ItemUtils#getItemName(Material)} instead. - */ - @Deprecated - public static String getItemName(Material material) { - return ItemUtils.getItemName(material); - } - - /** - * @deprecated Use {@link ItemUtils#getItemName(ItemStack)} instead. - */ - @Deprecated - public static String getItemName(ItemStack item) { - return ItemUtils.getItemName(item); - } - - /** - * @deprecated Use {@link ItemUtils#hasDisplayName(ItemStack)} instead. - */ - @Deprecated - public static boolean hasDisplayName(ItemStack item) { - return ItemUtils.hasDisplayName(item); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/LocationAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/LocationAPI.java deleted file mode 100644 index 16b3d11f..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/LocationAPI.java +++ /dev/null @@ -1,156 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import org.bukkit.Location; -import org.bukkit.World; -import vg.civcraft.mc.civmodcore.util.NullCoalescing; - -/** - * Class of utility functions for Locations. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils} instead. - */ -@Deprecated -public final class LocationAPI { - - /** - * Retrieves the world from a location. - * - * @param location The location to retrieve the world from. - * @return Returns the world if loaded, or null. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getLocationWorld(Location)} instead. - */ - @Deprecated - public static World getLocationWorld(Location location) { - if (location == null) { - return null; - } - try { - return location.getWorld(); - } - catch (IllegalArgumentException ignored) { // Will be thrown if the world is not loaded - return null; - } - } - - /** - * Determines whether a location is valid and safe to use. - * - * @param location The location to check. - * @return Returns true if the location exists, is valid, and safe to use. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#isValidLocation(Location)} instead. - */ - @Deprecated - public static boolean isValidLocation(Location location) { - if (location == null) { - return false; - } - if (!location.isWorldLoaded()) { - return false; - } - return true; - } - - /** - * Converts a location into a block location. (Yaw and Pitch values are lost) - * - * @param location The location to convert. - * @return Returns a block location, or null if the given location was null. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getBlockLocation(Location)} instead. - */ - @Deprecated - public static Location getBlockLocation(Location location) { - if (location == null) { - return null; - } - return new Location(getLocationWorld(location), - location.getBlockX(), - location.getBlockY(), - location.getBlockZ()); - } - - /** - * Converts a location into a block's mid point. (Yaw and Pitch values are lost) - * - * @param location The location to convert. - * @return Returns a block's mid point, or null if the given location was null. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#getMidBlockLocation(Location)} instead. - */ - @Deprecated - public static Location getMidBlockLocation(Location location) { - if (location == null) { - return null; - } - return getBlockLocation(location).add(0.5d, 0.5d, 0.5d); - } - - /** - * Determines whether two locations share the same world. - * - * @param former The first location. - * @param latter The second location. - * @return Returns true if the two locations are not null and share the same world. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#doLocationsHaveSameWorld(Location, Location)} - * instead. - */ - @Deprecated - public static boolean areLocationsSameWorld(Location former, Location latter) { - if (former == null || latter == null) { - return false; - } - return NullCoalescing.equalsNotNull(getLocationWorld(former), getLocationWorld(latter)); - } - - /** - * Returns the largest axis distance. - * - * @param latter The first location. - * @param former The second location. - * @param consider2D Whether only the X and Z axis should be considered. (true if yes) - * @return Returns the largest axis distance, or -1 if there's a problem, - * like the two locations being in two different worlds. - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#blockDistance(Location, Location, boolean)} - * instead. - */ - @Deprecated - public static int blockDistance(final Location former, final Location latter, final boolean consider2D) { - if (!LocationAPI.areLocationsSameWorld(former, latter)) { - return -1; - } - final int x = Math.abs(former.getBlockX() - latter.getBlockX()); - final int z = Math.abs(former.getBlockZ() - latter.getBlockZ()); - if (consider2D) { - return Math.max(x, z); - } - else { - final int y = Math.abs(former.getBlockY() - latter.getBlockY()); - return Math.max(x, Math.max(y, z)); - } - } - - /** - * Checks whether a location's Y coordinate is a valid block location. - * - * @param location The location to check. - * @return Returns true if the Y coordinate is a valid block location. (False if given location is null!) - * - * @deprecated Use {@link vg.civcraft.mc.civmodcore.world.WorldUtils#isWithinBounds(Location)} instead. - */ - @Deprecated - public static boolean isWithinBounds(final Location location) { - if (location == null) { - return false; - } - final double y = location.getY(); - if (y < 0 || y >= 256) { - return false; - } - return true; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/MaterialAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/MaterialAPI.java deleted file mode 100644 index 3dae6419..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/MaterialAPI.java +++ /dev/null @@ -1,793 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.base.Strings; -import com.google.common.math.IntMath; -import java.util.ArrayList; -import java.util.List; -import org.bukkit.Material; -import org.bukkit.Tag; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; -import vg.civcraft.mc.civmodcore.inventory.items.MoreTags; -import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; -import vg.civcraft.mc.civmodcore.inventory.items.TreeTypeUtils; - -/** - *

See BukkitReports.

- * - *
    - * - *
  • {@link SpawnEggUtils SpawnEggAPI}
  • - *
  • {@link TreeTypeUtils TreeTypeAPI}
  • - *
- * - * @deprecated Use {@link MaterialUtils}, {@link ItemUtils}, and {@link MoreTags} instead. - */ -@Deprecated -public final class MaterialAPI { - - private static final List hashMaterials = new ArrayList<>(); - - static { - hashMaterials.addAll(Tag.WOOL.getValues()); - hashMaterials.add(Material.BLACK_STAINED_GLASS); - hashMaterials.add(Material.WHITE_STAINED_GLASS); - hashMaterials.add(Material.YELLOW_STAINED_GLASS); - hashMaterials.add(Material.RED_STAINED_GLASS); - hashMaterials.add(Material.LIME_STAINED_GLASS); - hashMaterials.add(Material.GRAY_STAINED_GLASS); - hashMaterials.add(Material.BLUE_STAINED_GLASS); - hashMaterials.add(Material.LIGHT_GRAY_STAINED_GLASS); - hashMaterials.add(Material.LIGHT_BLUE_STAINED_GLASS); - hashMaterials.add(Material.GREEN_STAINED_GLASS); - hashMaterials.add(Material.BROWN_STAINED_GLASS); - hashMaterials.add(Material.PINK_STAINED_GLASS); - hashMaterials.add(Material.PURPLE_STAINED_GLASS); - hashMaterials.add(Material.CYAN_STAINED_GLASS); - hashMaterials.add(Material.MAGENTA_STAINED_GLASS); - hashMaterials.add(Material.ORANGE_STAINED_GLASS); - hashMaterials.add(Material.BLACK_STAINED_GLASS_PANE); - hashMaterials.add(Material.WHITE_STAINED_GLASS_PANE); - hashMaterials.add(Material.YELLOW_STAINED_GLASS_PANE); - hashMaterials.add(Material.RED_STAINED_GLASS_PANE); - hashMaterials.add(Material.LIME_STAINED_GLASS_PANE); - hashMaterials.add(Material.GRAY_STAINED_GLASS_PANE); - hashMaterials.add(Material.BLUE_STAINED_GLASS_PANE); - hashMaterials.add(Material.LIGHT_GRAY_STAINED_GLASS_PANE); - hashMaterials.add(Material.LIGHT_BLUE_STAINED_GLASS_PANE); - hashMaterials.add(Material.GREEN_STAINED_GLASS_PANE); - hashMaterials.add(Material.BROWN_STAINED_GLASS_PANE); - hashMaterials.add(Material.PINK_STAINED_GLASS_PANE); - hashMaterials.add(Material.PURPLE_STAINED_GLASS_PANE); - hashMaterials.add(Material.CYAN_STAINED_GLASS_PANE); - hashMaterials.add(Material.MAGENTA_STAINED_GLASS_PANE); - hashMaterials.add(Material.ORANGE_STAINED_GLASS_PANE); - hashMaterials.add(Material.BLACK_CONCRETE); - hashMaterials.add(Material.WHITE_CONCRETE); - hashMaterials.add(Material.YELLOW_CONCRETE); - hashMaterials.add(Material.RED_CONCRETE); - hashMaterials.add(Material.LIME_CONCRETE); - hashMaterials.add(Material.GRAY_CONCRETE); - hashMaterials.add(Material.BLUE_CONCRETE); - hashMaterials.add(Material.LIGHT_GRAY_CONCRETE); - hashMaterials.add(Material.LIGHT_BLUE_CONCRETE); - hashMaterials.add(Material.GREEN_CONCRETE); - hashMaterials.add(Material.BROWN_CONCRETE); - hashMaterials.add(Material.PINK_CONCRETE); - hashMaterials.add(Material.PURPLE_CONCRETE); - hashMaterials.add(Material.CYAN_CONCRETE); - hashMaterials.add(Material.MAGENTA_CONCRETE); - hashMaterials.add(Material.ORANGE_CONCRETE); - } - - /** - * Checks whether a material would be considered a valid item. - * - * @param material The material to check. - * @return Returns true if the material would be considered a valid item. - * - * @deprecated Use {@link ItemUtils#isValidItemMaterial(Material)} instead. - */ - @Deprecated - public static boolean isValidItemMaterial(Material material) { - if (material == null) { - return false; - } - if (material.isAir()) { - return false; - } - if (!material.isItem()) { - return false; - } - return true; - } - - /** - * Attempts to retrieve a material by its slug. - * - * @param value The value to search for a matching material by. - * @return Returns a matched material or null. - * - * @deprecated Use {@link MaterialUtils#getMaterial(String)} instead. - */ - @Deprecated - public static Material getMaterial(String value) { - if (Strings.isNullOrEmpty(value)) { - return null; - } - return Material.getMaterial(value.toUpperCase()); - } - - /** - * Checks whether a material is air. - * Will also return true if the given material is null. - * - * @param material The material to check. - * @return Returns true if the material is air. - * - * @deprecated Use {@link MaterialUtils#isAir(Material)} instead. - */ - @Deprecated - public static boolean isAir(Material material) { - if (material == null) { - return true; - } - return material.isAir(); - } - - /** - * Checks whether a material is a non-stripped log. - * - * @param material The material to check. - * @return Returns true if the material is a log. - * - * @deprecated Please use {@code MoreTags.LOGS.isTagged(material);} - */ - @Deprecated - public static boolean isLog(Material material) { - if (material == null) { - return false; - } - if (isStrippedLog(material)) { - return true; - } - switch (material) { - case ACACIA_LOG: - case BIRCH_LOG: - case DARK_OAK_LOG: - case JUNGLE_LOG: - case OAK_LOG: - case SPRUCE_LOG: - return true; - default: - return false; - } - } - - /** - * @deprecated Please use {@code Tag.PLANKS.isTagged(material);} - */ - @Deprecated - public static boolean isPlank(Material material) { - return Tag.PLANKS.isTagged(material); - } - - /** - * Checks whether a material is a stripped log or wood plank. - * - * @param material The material to check. - * @return Returns true if the material is a stripped log or wood plank. - * - * @deprecated Please use {@code MoreTags.STRIPPED_ALL.isTagged(material);} - */ - @Deprecated - public static boolean isStripped(Material material) { - if (material == null) { - return false; - } - if (isStrippedLog(material)) { - return true; - } - if (isStrippedPlank(material)) { - return true; - } - return false; - } - - /** - * Checks whether a material is a stripped log. - * - * @param material The material to check. - * @return Returns true if the material is a stripped log. - * - * @deprecated Please use {@code MoreTags.STRIPPED_LOGS.isTagged(material);} - */ - @Deprecated - public static boolean isStrippedLog(Material material) { - if (material == null) { - return false; - } - switch (material) { - case STRIPPED_ACACIA_LOG: - case STRIPPED_BIRCH_LOG: - case STRIPPED_DARK_OAK_LOG: - case STRIPPED_JUNGLE_LOG: - case STRIPPED_OAK_LOG: - case STRIPPED_SPRUCE_LOG: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a stripped plank. - * - * @param material The material to check. - * @return Returns true if the material is a stripped plank. - * - * @deprecated Please use {@code MoreTags.STRIPPED_PLANKS.isTagged(material);} - */ - @Deprecated - public static boolean isStrippedPlank(Material material) { - if (material == null) { - return false; - } - switch (material) { - case STRIPPED_ACACIA_WOOD: - case STRIPPED_BIRCH_WOOD: - case STRIPPED_DARK_OAK_WOOD: - case STRIPPED_JUNGLE_WOOD: - case STRIPPED_OAK_WOOD: - case STRIPPED_SPRUCE_WOOD: - return true; - default: - return false; - } - } - - /** - * Checks whether a material can be placed into a pot. - * - * @param material The material to check. - * @return Returns true if the material can be potted. - * - * @deprecated Please use {@code MoreTags.POTTABLE.isTagged(material);} - */ - @Deprecated - public static boolean isPottable(Material material) { - if (material == null) { - return false; - } - switch (material) { - case ACACIA_SAPLING: - case ALLIUM: - case AZURE_BLUET: - case BAMBOO: - case BIRCH_SAPLING: - case BLUE_ORCHID: - case BROWN_MUSHROOM: - case CACTUS: - case CORNFLOWER: - case CRIMSON_FUNGUS: - case CRIMSON_ROOTS: - case DANDELION: - case DARK_OAK_SAPLING: - case DEAD_BUSH: - case FERN: - case JUNGLE_SAPLING: - case LILY_OF_THE_VALLEY: - case OAK_SAPLING: - case ORANGE_TULIP: - case OXEYE_DAISY: - case PINK_TULIP: - case POPPY: - case RED_MUSHROOM: - case RED_TULIP: - case SPRUCE_SAPLING: - case WARPED_FUNGUS: - case WARPED_ROOTS: - case WHITE_TULIP: - case WITHER_ROSE: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a crop. Something is a crop if it's a plant that can grow, excluding Saplings. - * - * @param material The material to check. - * @return Returns true if the material is a crop. - * - * @see org.bukkit.block.data.Ageable Check if the Block's data is an instance of this Ageable, though be aware that - * {@link Material#FIRE fire} and {@link Material#FROSTED_ICE frosted ice} also implement Ageable. - * - * @deprecated Please use {@code MoreTags.CROPS.isTagged(material);} - */ - @Deprecated - public static boolean isCrop(Material material) { - if (material == null) { - return false; - } - switch (material) { - case BAMBOO: - case BEETROOTS: - case CACTUS: - case CARROTS: - case CHORUS_FLOWER: - case CHORUS_PLANT: - case COCOA: - case KELP: - case MELON_STEM: - case NETHER_WART: - case POTATOES: - case PUMPKIN_STEM: - case SUGAR_CANE: - case SWEET_BERRY_BUSH: - case WHEAT: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a skull/head. - * - * @param material The material to check. - * @return Returns true if the material is a skull/head. - * - * @deprecated Please use {@code MaterialTags.SKULLS.isTagged(material);} - */ - @Deprecated - public static boolean isSkull(Material material) { - if (material == null) { - return false; - } - switch (material) { - case CREEPER_HEAD: - case CREEPER_WALL_HEAD: - case DRAGON_HEAD: - case DRAGON_WALL_HEAD: - case PLAYER_HEAD: - case PLAYER_WALL_HEAD: - case ZOMBIE_HEAD: - case ZOMBIE_WALL_HEAD: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a glass block, coloured or otherwise. - * - * @param material The material to check. - * @return Returns true if the material is a glass block. - * - * @see Tag#IMPERMEABLE This functionally fulfils glass checking, however the name doesn't incidate that the tag - * is specific to glass, thus the switch remains. - * - * @deprecated Please use {@code MaterialTags.GLASS.isTagged(material);} - */ - @Deprecated - public static boolean isGlassBlock(Material material) { - if (material == null) { - return false; - } - switch (material) { - case BLACK_STAINED_GLASS: - case BLUE_STAINED_GLASS: - case BROWN_STAINED_GLASS: - case CYAN_STAINED_GLASS: - case GRAY_STAINED_GLASS: - case GLASS: - case GREEN_STAINED_GLASS: - case LIGHT_BLUE_STAINED_GLASS: - case LIGHT_GRAY_STAINED_GLASS: - case LIME_STAINED_GLASS: - case MAGENTA_STAINED_GLASS: - case ORANGE_STAINED_GLASS: - case PINK_STAINED_GLASS: - case PURPLE_STAINED_GLASS: - case RED_STAINED_GLASS: - case WHITE_STAINED_GLASS: - case YELLOW_STAINED_GLASS: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a glass pane, coloured or otherwise. - * - * @param material The material to check. - * @return Returns true if the material is a glass pane. - * - * @deprecated Please use {@code MaterialTags.GLASS_PANES.isTagged(material);} - */ - @Deprecated - public static boolean isGlassPane(Material material) { - if (material == null) { - return false; - } - switch (material) { - case BLACK_STAINED_GLASS_PANE: - case BLUE_STAINED_GLASS_PANE: - case BROWN_STAINED_GLASS_PANE: - case CYAN_STAINED_GLASS_PANE: - case GRAY_STAINED_GLASS_PANE: - case GLASS_PANE: - case GREEN_STAINED_GLASS_PANE: - case LIGHT_BLUE_STAINED_GLASS_PANE: - case LIGHT_GRAY_STAINED_GLASS_PANE: - case LIME_STAINED_GLASS_PANE: - case MAGENTA_STAINED_GLASS_PANE: - case ORANGE_STAINED_GLASS_PANE: - case PINK_STAINED_GLASS_PANE: - case PURPLE_STAINED_GLASS_PANE: - case RED_STAINED_GLASS_PANE: - case WHITE_STAINED_GLASS_PANE: - case YELLOW_STAINED_GLASS_PANE: - return true; - default: - return false; - } - } - - /** - * @deprecated Please use {@code Tag.DRAGON_IMMUNE.isTagged(material);} - */ - @Deprecated - public static boolean isDragonImmune(Material material) { - return Tag.DRAGON_IMMUNE.isTagged(material); - } - - /** - * @deprecated Please use {@code Tag.WITHER_IMMUNE.isTagged(material);} - */ - @Deprecated - public static boolean isWitherImmune(Material material) { - return Tag.WITHER_IMMUNE.isTagged(material); - } - - /** - * @deprecated Please use {@code Tag.FENCE_GATES.isTagged(material);} - */ - @Deprecated - public static boolean isWoodenFenceGate(Material material) { - return Tag.FENCE_GATES.isTagged(material); - } - - /** - * Checks whether a material is an infested block. This is what used to be referred to as Monster Egg blocks. - * - * @param material The material to check. - * @return Returns true if the material is infested. - * - * @deprecated Please use {@code MaterialTags.INFESTED_BLOCKS.isTagged(material);} - */ - @Deprecated - public static boolean isInfested(Material material) { - if (material == null) { - return false; - } - switch (material) { - case INFESTED_STONE: - case INFESTED_COBBLESTONE: - case INFESTED_STONE_BRICKS: - case INFESTED_MOSSY_STONE_BRICKS: - case INFESTED_CRACKED_STONE_BRICKS: - case INFESTED_CHISELED_STONE_BRICKS: - return true; - default: - return false; - } - } - - /** - * Duplicate of {@link #isWoodenFenceGate(Material)} - * - * @deprecated Please use {@code Tag.FENCE_GATES.isTagged(material);} - */ - @Deprecated - public static boolean isFenceGate(Material material) { - return isWoodenFenceGate(material); - } - - /** - * Checks whether a material is a dirt like block. - * - * @param material The material to check. - * @return Returns true if the material is dirty. - * - * @deprecated Please use {@code MoreTags.DIRT.isTagged(material);} - */ - @Deprecated - public static boolean isDirt(Material material) { - if (material == null) { - return false; - } - switch (material) { - case FARMLAND: - case GRASS_PATH: - case GRASS_BLOCK: - case DIRT: - case COARSE_DIRT: - case PODZOL: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a potion of some sort. - * - * @param material The material to check. - * @return Returns true if the material is a potion. - * - * @deprecated Please use {@code MoreTags.POTIONS.isTagged(material);} - */ - @Deprecated - public static boolean isPotion(Material material) { - if (material == null) { - return false; - } - switch (material) { - case POTION: - case SPLASH_POTION: - case LINGERING_POTION: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of sword. - * - * @param material The material to check. - * @return Returns true if the material is a sword. - * - * @deprecated Please use {@code MaterialTags.SWORDS.isTagged(material);} - */ - @Deprecated - public static boolean isSword(Material material) { - if (material == null) { - return false; - } - switch (material) { - case WOODEN_SWORD: - case STONE_SWORD: - case IRON_SWORD: - case GOLDEN_SWORD: - case DIAMOND_SWORD: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of pick axe. - * - * @param material The material to check. - * @return Returns true if the material is a pick axe. - * - * @deprecated Please use {@code MaterialTags.PICKAXES.isTagged(material);} - */ - @Deprecated - public static boolean isPickaxe(Material material) { - if (material == null) { - return false; - } - switch (material) { - case WOODEN_PICKAXE: - case STONE_PICKAXE: - case IRON_PICKAXE: - case GOLDEN_PICKAXE: - case DIAMOND_PICKAXE: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of axe. - * - * @param material The material to check. - * @return Returns true if the material is a axe. - * - * @deprecated Please use {@code MaterialTags.AXES.isTagged(material);} - */ - @Deprecated - public static boolean isAxe(Material material) { - if (material == null) { - return false; - } - switch (material) { - case WOODEN_AXE: - case STONE_AXE: - case IRON_AXE: - case GOLDEN_AXE: - case DIAMOND_AXE: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of spade. - * - * @param material The material to check. - * @return Returns true if the material is a spade. - * - * @deprecated Please use {@code MaterialTags.SHOVELS.isTagged(material);} - */ - @Deprecated - public static boolean isShovel(Material material) { - if (material == null) { - return false; - } - switch (material) { - case WOODEN_SHOVEL: - case STONE_SHOVEL: - case IRON_SHOVEL: - case GOLDEN_SHOVEL: - case DIAMOND_SHOVEL: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of hoe. - * - * @param material The material to check. - * @return Returns true if the material is a hoe. - * - * @deprecated Please use {@code MaterialTags.HOES.isTagged(material);} - */ - @Deprecated - public static boolean isHoe(Material material) { - if (material == null) { - return false; - } - switch (material) { - case WOODEN_HOE: - case STONE_HOE: - case IRON_HOE: - case GOLDEN_HOE: - case DIAMOND_HOE: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of helmet. - * - * @param material The material to check. - * @return Returns true if the material is a helmet. - * - * @deprecated Please use {@code MaterialTags.HELMETS.isTagged(material);} - */ - @Deprecated - public static boolean isHelmet(Material material) { - if (material == null) { - return false; - } - switch (material) { - case LEATHER_HELMET: - case CHAINMAIL_HELMET: - case IRON_HELMET: - case GOLDEN_HELMET: - case DIAMOND_HELMET: - case TURTLE_HELMET: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of chest plate. - * - * @param material The material to check. - * @return Returns true if the material is a chest plate. - * - * @deprecated Please use {@code MaterialTags.CHESTPLATES.isTagged(material);} - */ - @Deprecated - public static boolean isChestplate(Material material) { - if (material == null) { - return false; - } - switch (material) { - case LEATHER_CHESTPLATE: - case CHAINMAIL_CHESTPLATE: - case IRON_CHESTPLATE: - case GOLDEN_CHESTPLATE: - case DIAMOND_CHESTPLATE: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of leggings. - * - * @param material The material to check. - * @return Returns true if the material is a pair of leggings. - * - * @deprecated Please use {@code MaterialTags.LEGGINGS.isTagged(material);} - */ - @Deprecated - public static boolean areLeggings(Material material) { - if (material == null) { - return false; - } - switch (material) { - case LEATHER_LEGGINGS: - case CHAINMAIL_LEGGINGS: - case IRON_LEGGINGS: - case GOLDEN_LEGGINGS: - case DIAMOND_LEGGINGS: - return true; - default: - return false; - } - } - - /** - * Checks whether a material is a type of boots. - * - * @param material The material to check. - * @return Returns true if the material is a pair of boots. - * - * @deprecated Please use {@code MaterialTags.BOOTS.isTagged(material);} - */ - @Deprecated - public static boolean areBoots(Material material) { - if (material == null) { - return false; - } - switch (material) { - case LEATHER_BOOTS: - case CHAINMAIL_BOOTS: - case IRON_BOOTS: - case GOLDEN_BOOTS: - case DIAMOND_BOOTS: - return true; - default: - return false; - } - } - - /** - * Gets a random material based on the given objects hashcode. - * - * @param object Object to base returned material on - * @return Material hash of the given object - * - * @deprecated Use {@link MaterialUtils#getMaterialHash(Object)} instead. - */ - @Deprecated - public static Material getMaterialHash(Object object) { - if (object == null) { - return hashMaterials.get(0); - } - int index = IntMath.mod(object.hashCode(), hashMaterials.size()); - return hashMaterials.get(index); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/NamespaceAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/NamespaceAPI.java deleted file mode 100644 index 3439f47b..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/NamespaceAPI.java +++ /dev/null @@ -1,91 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.base.Strings; -import org.bukkit.Keyed; -import org.bukkit.NamespacedKey; -import vg.civcraft.mc.civmodcore.util.KeyedUtils; - -/** - * Utility class to make dealing with namespace keys easier. - * - * @deprecated Use {@link KeyedUtils} instead. - */ -@Deprecated -public final class NamespaceAPI { - - /** - * Converts a stringified namespace back into a {@link NamespacedKey}. - * - * @param key The stringified namespace, which MUST be formatted as {@code namespace:name} - * @return Returns a valid {@link NamespacedKey}, or null. - * - * @exception IllegalArgumentException Will throw if the stringified key fails a "[a-z0-9._-]+" check, or if the - * total length is longer than 256. - * - * @deprecated Use {@link KeyedUtils#fromString(String)} instead. - */ - @Deprecated - public static NamespacedKey fromString(String key) { - if (Strings.isNullOrEmpty(key)) { - return null; - } - String[] parts = key.split(":"); - if (parts.length != 2) { - return null; - } - return fromParts(parts[0], parts[1]); - } - - /** - * Converts a namespace and a key into a {@link NamespacedKey}. - * - * @param namespace The namespace name. - * @param key The namespaced key. - * @return Returns a valid {@link NamespacedKey}, or null. - * - * @exception IllegalArgumentException Will throw if either part fails a "[a-z0-9._-]+" check, or if the total - * combined length is longer than 256. - * - * @deprecated Use {@link KeyedUtils#fromParts(String, String)} instead. - */ - @Deprecated - public static NamespacedKey fromParts(String namespace, String key) { - if (Strings.isNullOrEmpty(namespace) || Strings.isNullOrEmpty(key)) { - return null; - } - return new NamespacedKey(namespace, key); - } - - /** - * Converts a {@link NamespacedKey} into a string. - * - * @param key The {@link NamespacedKey} to convert. - * @return Returns the stringified {@link NamespacedKey}, or null. - * - * @deprecated Use {@link KeyedUtils#getString(NamespacedKey)} instead. - */ - @Deprecated - public static String getString(NamespacedKey key) { - if (key == null) { - return null; - } - return key.toString(); - } - - /** - * Retrieves a {@link Keyed}'s {@link NamespacedKey} and converts it to a string. - * - * @param keyed The {@link Keyed} instance. - * @return Returns the stringified {@link Keyed}'s {@link NamespacedKey}, or null. - * - * @deprecated Use {@link KeyedUtils#getString(Keyed)} instead. - */ - @Deprecated - public static String getString(Keyed keyed) { - if (keyed == null) { - return null; - } - return getString(keyed.getKey()); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/PotionAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/PotionAPI.java deleted file mode 100644 index 797f8feb..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/PotionAPI.java +++ /dev/null @@ -1,65 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import java.util.HashMap; -import java.util.Map; -import org.bukkit.potion.PotionEffectType; -import vg.civcraft.mc.civmodcore.inventory.items.PotionUtils; - -/** - * @deprecated Use {@link PotionUtils} instead. - */ -@Deprecated -public final class PotionAPI { - - private PotionAPI() { - - } - - private static Map potionEffectNameMapping; - - - static { - potionEffectNameMapping = new HashMap<>(); - potionEffectNameMapping.put(PotionEffectType.ABSORPTION, "Absorption"); - potionEffectNameMapping.put(PotionEffectType.BAD_OMEN, "Bad Omen"); - potionEffectNameMapping.put(PotionEffectType.BLINDNESS, "Blindness"); - potionEffectNameMapping.put(PotionEffectType.CONDUIT_POWER, "Conduit Power"); - potionEffectNameMapping.put(PotionEffectType.CONFUSION, "Nausea"); - potionEffectNameMapping.put(PotionEffectType.DAMAGE_RESISTANCE, "Resistance"); - potionEffectNameMapping.put(PotionEffectType.DOLPHINS_GRACE, "Dolphin's Grace"); - potionEffectNameMapping.put(PotionEffectType.FAST_DIGGING, "Haste"); - potionEffectNameMapping.put(PotionEffectType.FIRE_RESISTANCE, "Fire Resistance "); - potionEffectNameMapping.put(PotionEffectType.GLOWING, "Glowing"); - potionEffectNameMapping.put(PotionEffectType.HARM, "Instant Damage"); - potionEffectNameMapping.put(PotionEffectType.HEAL, "Instant Health"); - potionEffectNameMapping.put(PotionEffectType.HEALTH_BOOST, "Health Boost"); - potionEffectNameMapping.put(PotionEffectType.HERO_OF_THE_VILLAGE, "Hero of the Village"); - potionEffectNameMapping.put(PotionEffectType.HUNGER, "Hunger"); - potionEffectNameMapping.put(PotionEffectType.INCREASE_DAMAGE, "Strength"); - potionEffectNameMapping.put(PotionEffectType.INVISIBILITY, "Invisibility"); - potionEffectNameMapping.put(PotionEffectType.JUMP, "Jump Boost"); - potionEffectNameMapping.put(PotionEffectType.LEVITATION, "Levitation"); - potionEffectNameMapping.put(PotionEffectType.LUCK, "Luck"); - potionEffectNameMapping.put(PotionEffectType.NIGHT_VISION, "Night Vision"); - potionEffectNameMapping.put(PotionEffectType.POISON, "Poison"); - potionEffectNameMapping.put(PotionEffectType.REGENERATION, "Regeneration"); - potionEffectNameMapping.put(PotionEffectType.SATURATION, "Saturation"); - potionEffectNameMapping.put(PotionEffectType.SLOW, "Slowness"); - potionEffectNameMapping.put(PotionEffectType.SLOW_DIGGING, "Mining Fatigue"); - potionEffectNameMapping.put(PotionEffectType.SLOW_FALLING, "Slow Falling"); - potionEffectNameMapping.put(PotionEffectType.SPEED, "Speed"); - potionEffectNameMapping.put(PotionEffectType.UNLUCK, "Bad Luck"); - potionEffectNameMapping.put(PotionEffectType.WATER_BREATHING, "Haste"); - potionEffectNameMapping.put(PotionEffectType.WEAKNESS, "Weakness"); - potionEffectNameMapping.put(PotionEffectType.WITHER, "Wither"); - } - - /** - * @deprecated Use {@link PotionUtils#getEffectNiceName(PotionEffectType)} instead. - */ - @Deprecated - public static String getNiceName(PotionEffectType pet) { - return potionEffectNameMapping.get(pet); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/PotionNames.java b/src/main/java/vg/civcraft/mc/civmodcore/api/PotionNames.java deleted file mode 100644 index 629ec333..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/PotionNames.java +++ /dev/null @@ -1,203 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.base.Strings; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import vg.civcraft.mc.civmodcore.CivModCorePlugin; -import vg.civcraft.mc.civmodcore.inventory.items.PotionUtils; -import vg.civcraft.mc.civmodcore.util.TextUtil; - -/** - * Class that loads and store potion names. - * - * @deprecated Use {@link PotionUtils} instead. - * */ -@Deprecated -public final class PotionNames { - - private static final Logger LOGGER = LoggerFactory.getLogger(PotionNames.class.getSimpleName()); - - private static final Set POTION_DETAILS = new HashSet<>(); - - /** - * Resets all item names, custom item names included. - * */ - public static void resetPotionNames() { - POTION_DETAILS.clear(); - } - - /** - * Loads item names from configurable files and requests any custom item names programmatically from plugins. - * */ - public static void loadPotionNames() { - resetPotionNames(); - InputStream enchantmentsCSV = CivModCorePlugin.class.getResourceAsStream("/potions.csv"); - if (enchantmentsCSV != null) { - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(enchantmentsCSV)); - String line = reader.readLine(); - while (line != null) { - String[] values = line.split(","); - // If there's not at least three values (slug, abbreviation, display name) then skip - if (values.length != 3) { - LOGGER.warn("[Config] This potion row is corrupted: " + line); - // Go to the next line - line = reader.readLine(); - continue; - } - // If the potion type cannot be found by the given slug, then skip - PotionType type; - try { - type = PotionType.valueOf(values[0]); - } - catch (Exception ignored) { - LOGGER.warn("[Config] Could not find a potion type on this line: " + line); - // Go to the next line - line = reader.readLine(); - continue; - } - // If the line specifies an effect type, then try to find it or skip if not found - PotionEffectType effectType = null; - if (!Strings.isNullOrEmpty(values[1])) { - effectType = PotionEffectType.getByName(values[1]); - if (effectType == null) { - LOGGER.warn("[Config] Could not find potion effect type type on this line: " + line); - // Go to the next line - line = reader.readLine(); - continue; - } - } - // Get the potion's name - String name = values[2]; - if (Strings.isNullOrEmpty(name)) { - LOGGER.warn("[Config] Could not find potion name on this line: " + line); - // Go to the next line - line = reader.readLine(); - continue; - } - // Put the enchantment and name into the system - POTION_DETAILS.add(new SearchResult(type, effectType, name)); - line = reader.readLine(); - } - reader.close(); - } - catch (IOException error) { - LOGGER.warn("[Config] Could not load potions from potions.csv"); - error.printStackTrace(); - } - } - else { - LOGGER.warn("[Config] Could not load potions from potions.csv as the file does not exist."); - } - } - - /** - * Attempts to match a potion type with a set of details. - * - * @param type The potion type to search with. - * @return The potion details, or null. - */ - public static SearchResult findByType(PotionType type) { - if (type == null) { - return null; - } - for (SearchResult details : POTION_DETAILS) { - if (details.type == type) { - return details; - } - } - return null; - } - - /** - * Attempts to match a potion effect type with a set of details. This is a less desirable search as some potion - * do not have an effect. If you're checking with a static value, best to take a look at {@link PotionType}. - * - * @param type The potion effect type to search with. - * @return The potion details, or null. - */ - public static SearchResult findByEffect(PotionEffectType type) { - if (type == null) { - return null; - } - for (SearchResult details : POTION_DETAILS) { - if (details.effectType == type) { - return details; - } - } - return null; - } - - /** - * Attempts to match a potion name with a set of details. - * - * @param name The potion name to search with. - * @return The potion details, or null. - */ - public static SearchResult findByName(String name) { - if (Strings.isNullOrEmpty(name)) { - return null; - } - for (SearchResult details : POTION_DETAILS) { - if (TextUtil.stringEqualsIgnoreCase(details.name, name)) { - return details; - } - } - return null; - } - - /** - * This class represents a data set for a particular enchantment. - */ - public static final class SearchResult { - - private final PotionType type; - - private final PotionEffectType effectType; - - private final String name; - - private SearchResult(PotionType type, PotionEffectType effectType, String name) { - this.type = type; - this.effectType = effectType; - this.name = name; - } - - /** - * @return Returns the potion type. - */ - public PotionType getPotionType() { - return this.type; - } - - /** - * @return Returns the potion's effect type. - */ - public PotionEffectType getEffectType() { - return this.effectType; - } - - /** - * @return Returns the potion's name. - */ - public String getName() { - return this.name; - } - - @Override - public int hashCode() { - return Objects.hash(this.type, this.effectType, this.name); - } - - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/RecipeAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/RecipeAPI.java deleted file mode 100644 index 2ef837a7..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/RecipeAPI.java +++ /dev/null @@ -1,93 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import java.util.Iterator; -import org.bukkit.Bukkit; -import org.bukkit.Keyed; -import org.bukkit.inventory.Recipe; -import vg.civcraft.mc.civmodcore.inventory.RecipeManager; -import vg.civcraft.mc.civmodcore.util.NullCoalescing; - -/** - * Class of utility functions for Recipes. - * - * @deprecated Use {@link RecipeManager} instead. - */ -@Deprecated -public final class RecipeAPI { - - /** - *

Determines whether a recipe matches another recipe.

- * - *

Note: This matcher pretty much assumes that all recipes are keyed. If other kinds of recipes come up that - * aren't keyed, then support here can be added. But until then? /shrug

- * - * @param base The base recipe to base the matching upon. - * @param other The other recipe, the unknown. - * @return Returns true if the other recipe matches the base. - * - * @deprecated Use {@link RecipeManager#matchRecipe(Recipe, Recipe)} instead. - */ - @Deprecated - public static boolean matchRecipe(Recipe base, Recipe other) { - if (base == null) { - return false; - } - if (base instanceof Keyed && other instanceof Keyed) { - return NullCoalescing.equalsNotNull( - ((Keyed) base).getKey(), - ((Keyed) other).getKey()); - } - return false; - } - - /** - *

Registers a recipe to the Bukkit server.

- * - *

This is to prevent thrown exceptions for re-registered recipes. Since registrations often happen within a - * plugin's onEnable() method, an exception here will actually disable the plugin entirely, which is a bit - * disproportionate. You should check the returned boolean to see whether the registration was successful and - * handle that accordingly.

- * - * @param recipe The recipe to register. - * @return Returns true if the recipe was registered. - * - * @deprecated Use {@link RecipeManager#registerRecipe(Recipe)} instead. - */ - @Deprecated - public static boolean registerRecipe(Recipe recipe) { - if (recipe == null) { - return false; - } - try { - return Bukkit.getServer().addRecipe(recipe); - } - catch (Exception exception) { - return false; - } - } - - /** - * Removes a recipe from Bukkit's registered recipes list. - * - * @param recipe The shaped recipe to deregister. - * @return Returns true if the recipe was de-registered, or wasn't ever registered. - * - * @deprecated Use {@link RecipeManager#registerRecipe(Recipe)} instead. - */ - @Deprecated - public static boolean removeRecipe(Recipe recipe) { - if (recipe == null) { - return false; - } - Iterator iterator = Bukkit.getServer().recipeIterator(); - while (iterator.hasNext()) { - if (!matchRecipe(recipe, iterator.next())) { - continue; - } - iterator.remove(); - return true; - } - return true; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/SpawnEggAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/SpawnEggAPI.java deleted file mode 100644 index d4a5f88d..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/SpawnEggAPI.java +++ /dev/null @@ -1,136 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableBiMap; -import javax.annotation.Nullable; -import org.bukkit.Material; -import org.bukkit.entity.EntityType; -import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; - -/** - * Class of static APIs for Spawn Eggs. - * - * @deprecated Use {@link SpawnEggUtils} instead. - */ -@Deprecated -public final class SpawnEggAPI { - - private SpawnEggAPI() {} - - private static final BiMap spawnEggs = ImmutableBiMap.builder(). - put(Material.BAT_SPAWN_EGG, EntityType.BAT). - put(Material.BEE_SPAWN_EGG, EntityType.BEE). - put(Material.BLAZE_SPAWN_EGG, EntityType.BLAZE). - put(Material.CAT_SPAWN_EGG, EntityType.CAT). - put(Material.CAVE_SPIDER_SPAWN_EGG, EntityType.CAVE_SPIDER). - put(Material.CHICKEN_SPAWN_EGG, EntityType.CHICKEN). - put(Material.COD_SPAWN_EGG, EntityType.COD). - put(Material.COW_SPAWN_EGG, EntityType.COW). - put(Material.CREEPER_SPAWN_EGG, EntityType.CREEPER). - put(Material.DOLPHIN_SPAWN_EGG, EntityType.DOLPHIN). - put(Material.DONKEY_SPAWN_EGG, EntityType.DONKEY). - put(Material.DROWNED_SPAWN_EGG, EntityType.DROWNED). - put(Material.ELDER_GUARDIAN_SPAWN_EGG, EntityType.ELDER_GUARDIAN). - put(Material.ENDERMAN_SPAWN_EGG, EntityType.ENDERMAN). - put(Material.ENDERMITE_SPAWN_EGG, EntityType.ENDERMITE). - put(Material.EVOKER_SPAWN_EGG, EntityType.EVOKER). - put(Material.FOX_SPAWN_EGG, EntityType.FOX). - put(Material.GHAST_SPAWN_EGG, EntityType.GHAST). - put(Material.GUARDIAN_SPAWN_EGG, EntityType.GUARDIAN). - put(Material.HOGLIN_SPAWN_EGG, EntityType.HOGLIN). - put(Material.HORSE_SPAWN_EGG, EntityType.HORSE). - put(Material.HUSK_SPAWN_EGG, EntityType.HUSK). - put(Material.LLAMA_SPAWN_EGG, EntityType.LLAMA). - put(Material.MAGMA_CUBE_SPAWN_EGG, EntityType.MAGMA_CUBE). - put(Material.MOOSHROOM_SPAWN_EGG, EntityType.MUSHROOM_COW). - put(Material.MULE_SPAWN_EGG, EntityType.MULE). - put(Material.OCELOT_SPAWN_EGG, EntityType.OCELOT). - put(Material.PANDA_SPAWN_EGG, EntityType.PANDA). - put(Material.PARROT_SPAWN_EGG, EntityType.PARROT). - put(Material.PHANTOM_SPAWN_EGG, EntityType.PHANTOM). - put(Material.PIG_SPAWN_EGG, EntityType.PIG). - put(Material.PIGLIN_SPAWN_EGG, EntityType.PIGLIN). - put(Material.PILLAGER_SPAWN_EGG, EntityType.PILLAGER). - put(Material.POLAR_BEAR_SPAWN_EGG, EntityType.POLAR_BEAR). - put(Material.PUFFERFISH_SPAWN_EGG, EntityType.PUFFERFISH). - put(Material.RABBIT_SPAWN_EGG, EntityType.RABBIT). - put(Material.RAVAGER_SPAWN_EGG, EntityType.RAVAGER). - put(Material.SALMON_SPAWN_EGG, EntityType.SALMON). - put(Material.SHEEP_SPAWN_EGG, EntityType.SHEEP). - put(Material.SHULKER_SPAWN_EGG, EntityType.SHULKER). - put(Material.SILVERFISH_SPAWN_EGG, EntityType.SILVERFISH). - put(Material.SKELETON_HORSE_SPAWN_EGG, EntityType.SKELETON_HORSE). - put(Material.SKELETON_SPAWN_EGG, EntityType.SKELETON). - put(Material.SLIME_SPAWN_EGG, EntityType.SLIME). - put(Material.SPIDER_SPAWN_EGG, EntityType.SPIDER). - put(Material.SQUID_SPAWN_EGG, EntityType.SQUID). - put(Material.STRAY_SPAWN_EGG, EntityType.STRAY). - put(Material.STRIDER_SPAWN_EGG, EntityType.STRIDER). - put(Material.TRADER_LLAMA_SPAWN_EGG, EntityType.TRADER_LLAMA). - put(Material.TROPICAL_FISH_SPAWN_EGG, EntityType.TROPICAL_FISH). - put(Material.TURTLE_SPAWN_EGG, EntityType.TURTLE). - put(Material.VEX_SPAWN_EGG, EntityType.VEX). - put(Material.VILLAGER_SPAWN_EGG, EntityType.VILLAGER). - put(Material.VINDICATOR_SPAWN_EGG, EntityType.VINDICATOR). - put(Material.WANDERING_TRADER_SPAWN_EGG, EntityType.WANDERING_TRADER). - put(Material.WITCH_SPAWN_EGG, EntityType.WITCH). - put(Material.WITHER_SKELETON_SPAWN_EGG, EntityType.WITHER_SKELETON). - put(Material.WOLF_SPAWN_EGG, EntityType.WOLF). - put(Material.ZOGLIN_SPAWN_EGG, EntityType.ZOGLIN). - put(Material.ZOMBIE_HORSE_SPAWN_EGG, EntityType.ZOMBIE_HORSE). - put(Material.ZOMBIE_SPAWN_EGG, EntityType.ZOMBIE). - put(Material.ZOMBIE_VILLAGER_SPAWN_EGG, EntityType.ZOMBIE_VILLAGER). - put(Material.ZOMBIFIED_PIGLIN_SPAWN_EGG, EntityType.ZOMBIFIED_PIGLIN). - build(); - - /** - * Tests if a material is that of a spawn egg. - * - * @param material The material to test. - * @return Returns true if the material is that of a spawn egg. - * - * @deprecated Use {@link SpawnEggUtils#isSpawnEgg(Material)} instead. - */ - @Deprecated - public static boolean isSpawnEgg(Material material) { - if (material == null) { - return false; - } - return spawnEggs.containsKey(material); - } - - /** - * Gets the spawned entity type for a spawn egg. - * - * @param material The material, must be a spawn egg otherwise it's a guaranteed null. - * @return Returns the entity type that will be spawned from the spawn egg, or null. - * - * @deprecated Use {@link SpawnEggUtils#getEntityType(Material)} instead. - */ - @Deprecated - @Nullable - public static EntityType getEntityType(Material material) { - if (material == null) { - return null; - } - return spawnEggs.get(material); - } - - /** - * Gets the spawn egg material from an entity type. - * - * @param entityType The type of entity to match to the spawn egg. - * @return Returns a spawn egg material, or null. - * - * @deprecated Use {@link SpawnEggUtils#getSpawnEgg(EntityType)} instead. - */ - @Deprecated - @Nullable - public static Material getSpawnEgg(EntityType entityType) { - if (entityType == null) { - return null; - } - return spawnEggs.inverse().get(entityType); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/ToolAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/ToolAPI.java deleted file mode 100644 index 077e4a75..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/ToolAPI.java +++ /dev/null @@ -1,48 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import org.bukkit.Material; - -@Deprecated(forRemoval = true) -public final class ToolAPI { - - /** - * @deprecated Use {@link MaterialAPI#isSword(Material)} instead. - */ - @Deprecated - public static boolean isSword(Material material) { - return MaterialAPI.isSword(material); - } - - /** - * @deprecated Use {@link MaterialAPI#isShovel(Material)} instead. - */ - @Deprecated - public static boolean isShovel(Material material) { - return MaterialAPI.isShovel(material); - } - - /** - * @deprecated Use {@link MaterialAPI#isPickaxe(Material)} instead. - */ - @Deprecated - public static boolean isPickaxe(Material material) { - return MaterialAPI.isPickaxe(material); - } - - /** - * @deprecated Use {@link MaterialAPI#isAxe(Material)} instead. - */ - @Deprecated - public static boolean isAxe(Material material) { - return MaterialAPI.isAxe(material); - } - - /** - * @deprecated Use {@link MaterialAPI#isHoe(Material)} instead. - */ - @Deprecated - public static boolean isHoe(Material material) { - return MaterialAPI.isHoe(material); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/TreeTypeAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/TreeTypeAPI.java deleted file mode 100644 index 4a5cde30..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/TreeTypeAPI.java +++ /dev/null @@ -1,121 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import org.bukkit.Material; -import org.bukkit.TreeType; -import vg.civcraft.mc.civmodcore.inventory.items.TreeTypeUtils; - -/** - * @deprecated Use {@link TreeTypeUtils} instead. - */ -@Deprecated -public final class TreeTypeAPI { - - /** - * @deprecated Use {@link TreeTypeUtils#getMatchingTreeType(Material)} instead. - */ - @Deprecated - public static TreeType getMatchingTreeType(Material material) { - if (material == null) { - return null; - } - switch (material) { - case ACACIA_SAPLING: - case ACACIA_WOOD: - case ACACIA_LOG: - case ACACIA_LEAVES: - case STRIPPED_ACACIA_LOG: - case STRIPPED_ACACIA_WOOD: - return TreeType.ACACIA; - case BIRCH_SAPLING: - case BIRCH_WOOD: - case BIRCH_LOG: - case BIRCH_LEAVES: - case STRIPPED_BIRCH_LOG: - case STRIPPED_BIRCH_WOOD: - return TreeType.BIRCH; - case OAK_SAPLING: - case OAK_WOOD: - case OAK_LOG: - case OAK_LEAVES: - case STRIPPED_OAK_LOG: - case STRIPPED_OAK_WOOD: - return TreeType.TREE; - case JUNGLE_SAPLING: - case JUNGLE_WOOD: - case JUNGLE_LOG: - case JUNGLE_LEAVES: - case STRIPPED_JUNGLE_LOG: - case STRIPPED_JUNGLE_WOOD: - return TreeType.JUNGLE; - case DARK_OAK_SAPLING: - case DARK_OAK_WOOD: - case DARK_OAK_LOG: - case DARK_OAK_LEAVES: - case STRIPPED_DARK_OAK_LOG: - case STRIPPED_DARK_OAK_WOOD: - return TreeType.DARK_OAK; - case SPRUCE_SAPLING: - case SPRUCE_WOOD: - case SPRUCE_LOG: - case SPRUCE_LEAVES: - case STRIPPED_SPRUCE_LOG: - case STRIPPED_SPRUCE_WOOD: - return TreeType.REDWOOD; - case CHORUS_FLOWER: - case CHORUS_PLANT: - return TreeType.CHORUS_PLANT; - case RED_MUSHROOM: - case RED_MUSHROOM_BLOCK: - return TreeType.RED_MUSHROOM; - case BROWN_MUSHROOM: - case BROWN_MUSHROOM_BLOCK: - return TreeType.BROWN_MUSHROOM; - case COCOA: - return TreeType.COCOA_TREE; - default: - return null; - } - } - - /** - * @deprecated Use {@link TreeTypeUtils#getMatchingSapling(TreeType)} instead. - */ - @Deprecated - public static Material getMatchingSapling(TreeType type) { - if (type == null) { - return null; - } - switch(type) { - case ACACIA: - return Material.ACACIA_SAPLING; - case BIG_TREE: - case TREE: - case SWAMP: - return Material.OAK_SAPLING; - case BIRCH: - case TALL_BIRCH: - return Material.BIRCH_SAPLING; - case BROWN_MUSHROOM: - return Material.BROWN_MUSHROOM; - case CHORUS_PLANT: - return Material.CHORUS_PLANT; - case COCOA_TREE: - return Material.COCOA; - case DARK_OAK: - return Material.DARK_OAK_SAPLING; - case JUNGLE: - case SMALL_JUNGLE: - case JUNGLE_BUSH: - return Material.JUNGLE_SAPLING; - case MEGA_REDWOOD: - case REDWOOD: - case TALL_REDWOOD: - return Material.SPRUCE_SAPLING; - case RED_MUSHROOM: - return Material.RED_MUSHROOM; - default: - return null; - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/WorldAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/api/WorldAPI.java deleted file mode 100644 index 3cac1652..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/WorldAPI.java +++ /dev/null @@ -1,90 +0,0 @@ -package vg.civcraft.mc.civmodcore.api; - -import java.util.Objects; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import vg.civcraft.mc.civmodcore.world.WorldUtils; - -/** - * Class of utility functions for Worlds. - * - * @deprecated Use {@link WorldUtils} instead. - */ -@Deprecated -public final class WorldAPI { - - /** - * Determines if a world is currently loaded. - * - * @param world World to test. - * @return Returns true if the world is loaded. - * - * @deprecated Use {@link WorldUtils#isWorldLoaded(World)} instead. - */ - @Deprecated - public static boolean isWorldLoaded(World world) { - if (world == null) { - return false; - } - // Same method in Location.isWorldLoaded() - return Bukkit.getWorld(world.getUID()) != null; - } - - /** - * Determines if a chunk is loaded in an efficient manner without loading any chunks. - * - * @param world The world the target chunk is located within. - * @param x The (CHUNK) X coordinate. - * @param z The (CHUNK) Z coordinate. - * @return Returns true if the chunk is loaded. - * - * @deprecated Use {@link WorldUtils#isChunkLoaded(World, int, int)} instead. - */ - @Deprecated - public static boolean isChunkLoaded(World world, int x, int z) { - if (!isWorldLoaded(world)) { - return false; - } - return world.isChunkLoaded(x, z); - } - - /** - * Retrieves a chunk only if it's loaded. - * - * @param world The world the target chunk is located within. - * @param x The (CHUNK) X coordinate. - * @param z The (CHUNK) Z coordinate. - * @return Returns the loaded chunk, or null. - * - * @deprecated Use {@link WorldUtils#getLoadedChunk(World, int, int)} instead. - */ - @Deprecated - public static Chunk getLoadedChunk(final World world, final int x, final int z) { - if (!isChunkLoaded(world, x, z)) { - return null; - } - return world.getChunkAt(x, z); - } - - /** - * Determines if a block is loaded by nature of whether the chunk it's in is loaded. - * - * @param location The block location. - * @return Returns true if the block is laoded. - * - * @deprecated Use {@link WorldUtils#isBlockLoaded(Location)} instead. - */ - @Deprecated - public static boolean isBlockLoaded(Location location) { - if (!LocationAPI.isValidLocation(location)) { - return false; - } - World world = Objects.requireNonNull(location.getWorld()); - int chunkX = location.getBlockX() >> 4; - int chunkZ = location.getBlockZ() >> 4; - return world.isChunkLoaded(chunkX, chunkZ); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/api/package-info.java b/src/main/java/vg/civcraft/mc/civmodcore/api/package-info.java deleted file mode 100644 index 41b27c19..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/api/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * This package was created as a sort of island of tranquility for standardised, standalone functions... however it's - * gotten a little unwieldy and so the plan is to deprecate and disperse the functionality to more sensible locations. - * - * The plan is to remove the "api" package completely. - */ -package vg.civcraft.mc.civmodcore.api; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java index f87f8585..ce349e21 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java @@ -1,19 +1,36 @@ package vg.civcraft.mc.civmodcore.chat; -import com.google.common.base.Strings; +import io.papermc.paper.adventure.PaperAdventure; import java.awt.Color; import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.craftbukkit.v1_17_R1.util.CraftChatMessage; +import org.jetbrains.annotations.Contract; +@UtilityClass public final class ChatUtils { /** * This is necessary as {@link ChatColor#values()} has all colours and all formats. + * + * @deprecated Use {@link NamedTextColor} instead. */ - @SuppressWarnings("deprecation") + @Deprecated public static final List COLOURS = List.of( ChatColor.BLACK, ChatColor.DARK_BLUE, @@ -38,7 +55,11 @@ public final class ChatUtils { * @param g The green value. * @param b The blue value. * @return Returns a valid Bungee ChatColor. + * + * @deprecated Use {@link net.kyori.adventure.text.format.TextColor#color(int, int, int)} instead. */ + @Nonnull + @Deprecated public static ChatColor fromRGB(final byte r, final byte g, final byte b) { return ChatColor.of(new Color(r, g, b)); } @@ -49,7 +70,9 @@ public static ChatColor fromRGB(final byte r, final byte g, final byte b) { * @param colour The given RGB colour. * @return Returns the closest Minecraft match, or null. */ - public static ChatColor collapseColour(final ChatColor colour) { + @Contract("!null -> !null") + @Nullable + public static ChatColor collapseColour(@Nullable final ChatColor colour) { if (colour == null) { return null; } @@ -74,23 +97,47 @@ public static ChatColor collapseColour(final ChatColor colour) { // Color parsing // -------------------------------------------- // - public static String parseColor(String string) { + /** + * @deprecated Please use MiniMessage instead. + * Read More. + */ + @Nonnull + @Deprecated + public static String parseColor(@Nonnull String string) { string = parseColorAmp(string); string = parseColorAcc(string); string = parseColorTags(string); return string; } - public static String parseColorAmp(String string) { + /** + * @deprecated Please use MiniMessage instead. + * Read More. + */ + @Nonnull + @Deprecated + public static String parseColorAmp(@Nonnull String string) { string = string.replace("&&", "&"); return ChatColor.translateAlternateColorCodes('&', string); } - public static String parseColorAcc(String string) { + /** + * @deprecated Please use MiniMessage instead. + * Read More. + */ + @Nonnull + @Deprecated + public static String parseColorAcc(@Nonnull String string) { return ChatColor.translateAlternateColorCodes('`', string); } - public static String parseColorTags(String string) { + /** + * @deprecated Please use MiniMessage instead. + * Read More. + */ + @Nonnull + @Deprecated + public static String parseColorTags(@Nonnull String string) { return string .replace("", ChatColor.BLACK.toString()) .replace("", ChatColor.DARK_BLUE.toString()) @@ -151,73 +198,185 @@ public static String parseColorTags(String string) { * * @param component The component to test if null or empty. * @return Returns true if the component is null or has no visible content. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. */ - public static boolean isNullOrEmpty(final BaseComponent component) { - if (component == null) { + public static boolean isNullOrEmpty(@Nullable final Component component) { + if (component == null || component == Component.empty()) { return true; } - final TextComponent text = fromLegacyText(component.toPlainText()); - return Strings.isNullOrEmpty(text.toPlainText()); + return StringUtils.isBlank(PlainTextComponentSerializer.plainText().serialize(component)); } /** - *

Converts a string containing Minecraft's legacy colour codes into a text component.

+ *

Determines whether a given base component is null or empty.

* - *

Note: This does not work on Civ's colour code equivalents, make sure to parse those before using this.

+ *

This is determined by converting the component into plain text, so a non-null component filled with + * nothing but colour codes and hover text will likely return true.

* - * @param text The legacy text to parse. - * @return Returns a text component of the legacy text. + * @param component The component to test if null or empty. + * @return Returns true if the component is null or has no visible content. */ - public static TextComponent fromLegacyText(final String text) { - if (Strings.isNullOrEmpty(text)) { - return new TextComponent(text); + public static boolean isBaseComponent(@Nullable final Component component) { + if (component == null) { + return false; } - return new TextComponent(TextComponent.fromLegacyText(text, ChatColor.RESET)); - } - - /** - * This is an easy way to create a text component when all you want to do is colour it. - * - * @param value The value of the text. (Objects will be stringified) - * @param formats The colour formats. - * @return Returns the created component, so you can do more stuff to it. - */ - public static TextComponent textComponent(final Object value, final ChatColor... formats) { - final TextComponent component = new TextComponent(value == null ? "" : value.toString()); - if (!ArrayUtils.isEmpty(formats)) { - for (final ChatColor format : formats) { - if (format == null) { - //continue; - } - else if (format.getColor() != null) { - component.setColor(format); - } - else if (format == ChatColor.RESET) { - component.setColor(format); - component.setBold(false); - component.setItalic(false); - component.setUnderlined(false); - component.setStrikethrough(false); - component.setObfuscated(false); - } - else if (format == ChatColor.BOLD) { - component.setBold(true); - } - else if (format == ChatColor.ITALIC) { - component.setItalic(true); - } - else if (format == ChatColor.UNDERLINE) { - component.setUnderlined(true); - } - else if (format == ChatColor.STRIKETHROUGH) { - component.setStrikethrough(true); - } - else if (format == ChatColor.MAGIC) { - component.setObfuscated(true); - } - } + return (!(component instanceof TextComponent textComponent) || StringUtils.isEmpty(textComponent.content())) + && !component.children().isEmpty() + && component.clickEvent() == null + && component.hoverEvent() == null + && !component.hasStyling(); + } + + private static final Map NORMALISED_DECORATION_MAP = + Map.of(TextDecoration.ITALIC, TextDecoration.State.FALSE); + + /** + * Checks whether a given component is the result of {@link #normaliseComponent(Component...)} or + * {@link #normaliseComponent(List)}. + * + * @param component The component to check. + * @return Returns true if the given component is "normalised." + */ + public static boolean isNormalisedComponent(@Nullable final Component component) { + if (!(component instanceof final TextComponent textComponent)) { + return false; + } + return StringUtils.isEmpty(textComponent.content()) + && !component.children().isEmpty() + && component.clickEvent() == null + && component.hoverEvent() == null + && Objects.equals(component.color(), NamedTextColor.WHITE) + && Objects.equals(component.decorations(), NORMALISED_DECORATION_MAP); + } + + /** + * Wraps a component or series of components into a "normalised" display component, meaning that the text is + * white and non-italic by default. + * + * @param components The component / components to wrap. + * @return Returns the normalised component, or empty if no components are passed. + */ + @Nonnull + public static Component normaliseComponent(final Component... components) { + if (ArrayUtils.isEmpty(components)) { + return Component.empty(); + } + return Component.text() + .color(NamedTextColor.WHITE) + .decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE) + .append(components) + .build(); + } + + /** + * Wraps a series of components into a "normalised" display component, meaning that the text is white and + * non-italic by default. + * + * @param components The components to wrap. + * @return Returns the normalised component, or empty if no components are passed. + */ + @Nonnull + public static Component normaliseComponent(@Nullable final List components) { + if (CollectionUtils.isEmpty(components)) { + return Component.empty(); + } + return Component.text() + .color(NamedTextColor.WHITE) + .decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE) + .append(components) + .build(); + } + + /** + * This will also happily translate any translatable components. + * + * @param component The component to stringify. + * @return Returns a stringified version of the given component. + */ + @Nonnull + public static String stringify(@Nullable final Component component) { + return component == null || component == Component.empty() ? "" : + CraftChatMessage.fromComponent(PaperAdventure.asVanilla(component)); + } + + /** + * Upgrades a legacy string (eg: §6Hello, World!) to a Kyori component. + * + * @param string The string to convert into a component. + * @return Returns a new component, or null if the given string was null. + */ + @Contract("!null -> !null") + @Nullable + public static Component upgradeLegacyString(@Nullable final String string) { + return string == null ? null : string.isEmpty() ? Component.empty() : + LegacyComponentSerializer.legacySection().deserialize(string); + } + + /** + * @return Generates a new text component that's specifically NOT italicised. Use this for item names and + * lore. + */ + @Nonnull + public static TextComponent newComponent() { + return newComponent(""); + } + + /** + * Generates a new text component that's specifically NOT italicised. Use this for item names and lore. + * + * @param content The text content for the component. + * @return Returns the generated text component. + */ + @Nonnull + public static TextComponent newComponent(final String content) { + return Component.text(Objects.requireNonNull(content)) + .decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE); + } + + /** + * Clones a component. + * + * @param component The component to clone. + * @return Returns a clone of the given component. + * + * @deprecated Kyori components are immutable, so any methods that offer to update the component will actually + * instantiate a new component with the modification, thus making this utility unnecessary, if not + * downright inefficient. + */ + @Contract("!null -> !null") + @Nullable + @Deprecated(forRemoval = true) + public static Component cloneComponent(@Nullable final Component component) { + return component == null ? null : component.style(component.style()); + } + + /** + * Determines whether two given components are equal to each other. + * + * @param former The left hand side component. + * @param latter The right hand side component. + * @return Returns whether the two given components are equal. + */ + public static boolean areComponentsEqual(@Nullable final Component former, + @Nullable final Component latter) { + if (Objects.equals(former, latter)) { + return true; + } + if (former == null || latter == null) { + return false; + } + if (StringUtils.equals( + MiniMessage.get().serialize(former), + MiniMessage.get().serialize(latter))) { + return true; + } + if (StringUtils.equals( + LegacyComponentSerializer.legacyAmpersand().serialize(former), + LegacyComponentSerializer.legacyAmpersand().serialize(latter))) { + return true; } - return component; + return false; } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chat/Componentify.java b/src/main/java/vg/civcraft/mc/civmodcore/chat/Componentify.java new file mode 100644 index 00000000..8a5b9855 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/chat/Componentify.java @@ -0,0 +1,69 @@ +package vg.civcraft.mc.civmodcore.chat; + +import javax.annotation.Nonnull; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Location; + +@UtilityClass +public final class Componentify { + + private static Component INTERNAL_addLocationWorld(final Location location) { + if (location.isWorldLoaded()) { + return Component.text(location.getWorld().getName()) + .hoverEvent(HoverEvent.showText(Component.text("World name"))); + } + else { + return Component.text("") + .color(NamedTextColor.RED) + .hoverEvent(HoverEvent.showText(Component.text("World not specified / loaded"))); + } + } + + public static Component fullLocation(@Nonnull final Location location) { + final var component = Component.text(); + component.append(INTERNAL_addLocationWorld(location)); + component.append(Component.space()); + component.append(Component.text(location.getX()) + .color(NamedTextColor.RED) + .hoverEvent(HoverEvent.showText(Component.text("X")))); + component.append(Component.space()); + component.append(Component.text(location.getY()) + .color(NamedTextColor.GREEN) + .hoverEvent(HoverEvent.showText(Component.text("Y")))); + component.append(Component.space()); + component.append(Component.text(location.getZ()) + .color(NamedTextColor.BLUE) + .hoverEvent(HoverEvent.showText(Component.text("Z")))); + component.append(Component.space()); + component.append(Component.text(location.getYaw()) + .color(NamedTextColor.GOLD) + .hoverEvent(HoverEvent.showText(Component.text("Yaw")))); + component.append(Component.space()); + component.append(Component.text(location.getPitch()) + .color(NamedTextColor.AQUA) + .hoverEvent(HoverEvent.showText(Component.text("Pitch")))); + return component.build(); + } + + public static Component blockLocation(@Nonnull final Location location) { + final var component = Component.text(); + component.append(INTERNAL_addLocationWorld(location)); + component.append(Component.space()); + component.append(Component.text(location.getBlockX()) + .color(NamedTextColor.RED) + .hoverEvent(HoverEvent.showText(Component.text("Block X")))); + component.append(Component.space()); + component.append(Component.text(location.getBlockY()) + .color(NamedTextColor.GREEN) + .hoverEvent(HoverEvent.showText(Component.text("Block X")))); + component.append(Component.space()); + component.append(Component.text(location.getBlockZ()) + .color(NamedTextColor.BLUE) + .hoverEvent(HoverEvent.showText(Component.text("Block X")))); + return component.build(); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chat/dialog/DialogManager.java b/src/main/java/vg/civcraft/mc/civmodcore/chat/dialog/DialogManager.java index 56dab136..7ddf7daf 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/chat/dialog/DialogManager.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/chat/dialog/DialogManager.java @@ -18,7 +18,7 @@ public class DialogManager implements Listener { public static final DialogManager INSTANCE = new DialogManager(); private static final Map DIALOGS = new TreeMap<>(); - + private DialogManager() {} public static Dialog getDialog(final UUID player) { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/ChatListener.java b/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/ChatListener.java deleted file mode 100644 index 596783bd..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/ChatListener.java +++ /dev/null @@ -1,36 +0,0 @@ -package vg.civcraft.mc.civmodcore.chatDialog; - -import java.util.Collections; -import java.util.List; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.server.TabCompleteEvent; - -@Deprecated -public class ChatListener implements Listener { - - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) - public void tabComplete(TabCompleteEvent e) { - if (!(e.getSender() instanceof Player)) { - return; - } - Dialog dia = DialogManager.getDialog((Player) e.getSender()); - if (dia != null) { - String[] split = e.getBuffer().split(" "); - List complet = dia.onTabComplete(split.length > 0 ? split[split.length - 1] : "", split); - if (complet == null) { - complet = Collections.emptyList(); - } - e.setCompletions(complet); - } - } - - @EventHandler - public void logoff(PlayerQuitEvent e) { - DialogManager.forceEndDialog(e.getPlayer()); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/Dialog.java b/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/Dialog.java deleted file mode 100644 index 9aa61ed7..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/Dialog.java +++ /dev/null @@ -1,67 +0,0 @@ -package vg.civcraft.mc.civmodcore.chatDialog; - -import java.util.List; -import org.bukkit.Bukkit; -import org.bukkit.conversations.Conversation; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.ConversationFactory; -import org.bukkit.conversations.Prompt; -import org.bukkit.conversations.StringPrompt; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -/** - * @deprecated Use {@link vg.civcraft.mc.civmodcore.chat.dialog.Dialog} instead. - */ -@Deprecated -public abstract class Dialog { - - protected Player player; - - private Conversation convo; - - public Dialog(Player player, JavaPlugin plugin) { - this(player, plugin, null); - } - - public Dialog(Player player, JavaPlugin plugin, final String toDisplay) { - DialogManager.registerDialog(player, this); - this.player = player; - - Bukkit.getScheduler().runTask(plugin, (Runnable) player::closeInventory); - - convo = new ConversationFactory(plugin).withModality(false).withLocalEcho(false) - .withFirstPrompt(new StringPrompt() { - - @Override - public String getPromptText(ConversationContext arg0) { - if (toDisplay != null) { - return toDisplay; - } - return ""; - } - - @Override - public Prompt acceptInput(ConversationContext arg0, String arg1) { - onReply(arg1.split(" ")); - return Prompt.END_OF_CONVERSATION; - } - - }).buildConversation(player); - - convo.begin(); - } - - public abstract void onReply(String[] message); - - public abstract List onTabComplete(String wordCompleted, String[] fullMessage); - - public Player getPlayer() { - return player; - } - - public void end() { - convo.abandon(); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/DialogManager.java b/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/DialogManager.java deleted file mode 100644 index 9cbcc598..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/DialogManager.java +++ /dev/null @@ -1,45 +0,0 @@ -package vg.civcraft.mc.civmodcore.chatDialog; - -import java.util.Map; -import java.util.TreeMap; -import java.util.UUID; -import org.bukkit.entity.Player; - -/** - * @deprecated Use {@link vg.civcraft.mc.civmodcore.chat.dialog.DialogManager} instead. - */ -@Deprecated -public class DialogManager { - - private static Map dialogs = new TreeMap<>(); - - private DialogManager() {} - - public static Dialog getDialog(Player p) { - return getDialog(p.getUniqueId()); - } - - public static Dialog getDialog(UUID uuid) { - return dialogs.get(uuid); - } - - public static void registerDialog(Player p, Dialog dialog) { - Dialog current = dialogs.get(p.getUniqueId()); - if (current != null) { - current.end(); - } - dialogs.put(p.getUniqueId(), dialog); - } - - public static void forceEndDialog(Player p) { - forceEndDialog(p.getUniqueId()); - } - - public static void forceEndDialog(UUID uuid) { - Dialog dia = dialogs.remove(uuid); - if (dia != null) { - dia.end(); - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/LDialog.java b/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/LDialog.java deleted file mode 100644 index 8dd875dd..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/LDialog.java +++ /dev/null @@ -1,36 +0,0 @@ -package vg.civcraft.mc.civmodcore.chatDialog; - -import java.util.Collections; -import java.util.List; -import java.util.function.Consumer; -import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.CivModCorePlugin; - -/** - * @deprecated Create an anonymous instance of {@link vg.civcraft.mc.civmodcore.chat.dialog.Dialog} instead. - */ -@Deprecated -public class LDialog extends Dialog { - - private Consumer replyFunction; - - public LDialog(Player player, Consumer replyFunction) { - this(player, replyFunction, null); - } - - public LDialog(Player player, Consumer replyFunction, String msgToShow) { - super(player, CivModCorePlugin.getInstance(), msgToShow); - this.replyFunction = replyFunction; - } - - @Override - public void onReply(String[] message) { - replyFunction.accept(String.join(" ", message)); - } - - @Override - public List onTabComplete(String wordCompleted, String[] fullMessage) { - return Collections.emptyList(); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/package-info.java b/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/package-info.java deleted file mode 100644 index 2ee0705f..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/chatDialog/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * The goal with this package is to relocate it to "chat.dialog" - */ -package vg.civcraft.mc.civmodcore.chatDialog; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/AikarCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/command/AikarCommand.java deleted file mode 100644 index 7c0ec819..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/AikarCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import co.aikar.commands.BaseCommand; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Class that represents a compatible command for {@link AikarCommandManager}. - */ -public abstract class AikarCommand extends BaseCommand { - - /** - *

This annotation, when used on a method on an {@link AikarCommand} extending class will be automatically - * registered as a tab completion.

- * - *

The return type of the method MUST, MUST be {@code List}, otherwise it will fail.

- * - *

The method is permitted to have 0-1 parameters, but that parameter MUST, MUST be of type - * {@code BukkitCommandCompletionContext}, otherwise it will fail.

- */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public @interface TabComplete { - String value(); - boolean async() default false; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/AikarCommandManager.java b/src/main/java/vg/civcraft/mc/civmodcore/command/AikarCommandManager.java deleted file mode 100644 index 27890ad4..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/AikarCommandManager.java +++ /dev/null @@ -1,267 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import static vg.civcraft.mc.civmodcore.command.AikarCommand.TabComplete; - -import co.aikar.commands.BukkitCommandCompletionContext; -import co.aikar.commands.BukkitCommandExecutionContext; -import co.aikar.commands.BukkitCommandManager; -import co.aikar.commands.CommandCompletions; -import co.aikar.commands.CommandCompletions.CommandCompletionHandler; -import co.aikar.commands.CommandContexts; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.collect.Maps; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import vg.civcraft.mc.civmodcore.ACivMod; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; - -/** - * Command registration class wrapper around {@link BukkitCommandManager}. - */ -public class AikarCommandManager { - - private final ACivMod plugin; - - private CustomBukkitManager manager; - - /** - * Creates a new command manager for Aikar based commands and tab completions. - * - * @param plugin The plugin to bind this manager to. - */ - public AikarCommandManager(ACivMod plugin) { - this(plugin, true); - } - - /** - * Creates a new command manager for Aikar based commands and tab completions. - * - * @param plugin The plugin to bind this manager to. - * @param autoInit If true will automatically initialise and register all commands and completions as defined within - * {@link AikarCommandManager#registerCommands()} and - * {@link AikarCommandManager#registerCompletions(CommandCompletions)}. - */ - public AikarCommandManager(ACivMod plugin, boolean autoInit) { - this.plugin = plugin; - if (autoInit) { - init(); - } - } - - /** - * @deprecated Use {@link #init()} instead as it's more indicative of what's happening. - */ - @Deprecated - public final void register() { - init(); - } - - /** - * Will initialise the manager and register both commands and completions. You should only really use this if - * you've used {@link AikarCommandManager#reset()} or both {@link AikarCommandManager#deregisterCommands()} and - * {@link AikarCommandManager#deregisterCompletions()}, otherwise there may be issues. - */ - public final void init() { - this.manager = new CustomBukkitManager(plugin); - registerCommands(); - registerCompletions(this.manager.getCommandCompletions()); - registerContexts(this.manager.getCommandContexts()); - } - - /** - * This is called as part of {@link AikarCommandManager#register()} and should be overridden by an extending class - * to register all (or as many) commands at once. - */ - public void registerCommands() { } - - /** - * This is called as part of {@link AikarCommandManager#register()} and should be overridden by an extending class - * to register all (or as many) completions at once, though make sure to call super. - * - * @param completions The completion manager is given. It is the same manager that can be reached via - * {@link CustomBukkitManager#getCommandCompletions()}. - */ - public void registerCompletions(CommandCompletions completions) { - completions.registerAsyncCompletion("allplayers", context -> - Arrays.stream(Bukkit.getOfflinePlayers()) - .map(OfflinePlayer::getName) - .filter(name -> StringUtils.startsWithIgnoreCase(name, context.getInput())) - .collect(Collectors.toCollection(ArrayList::new))); - completions.registerAsyncCompletion("materials", context -> - Arrays.stream(Material.values()) - .map(Enum::name) - .filter((name) -> StringUtils.startsWithIgnoreCase(name, context.getInput())) - .collect(Collectors.toCollection(ArrayList::new))); - completions.registerAsyncCompletion("itemMaterials", context -> - Arrays.stream(Material.values()) - .filter(ItemUtils::isValidItemMaterial) - .map(Enum::name) - .filter((name) -> StringUtils.startsWithIgnoreCase(name, context.getInput())) - .collect(Collectors.toCollection(ArrayList::new))); - } - - /** - * This is called as part of {@link AikarCommandManager#register()} and should be overridden by an extending class - * to register all (or as many) contexts at once. - * - * @param contexts The context manager is given. It is the same manager that can be reached via - * {@link CustomBukkitManager#getCommandContexts()}. - */ - public void registerContexts(CommandContexts contexts) { } - - /** - * Registers a new command and any attached tab completions. - * - * @param command The command instance to register. - */ - public final void registerCommand(AikarCommand command) { - Preconditions.checkArgument(command != null, "Could not register that command: the command was null."); - this.manager.registerCommand(command); - this.plugin.info("Command [" + command.getClass().getSimpleName() + "] registered."); - for (Map.Entry entry : getTabCompletions(command.getClass()).entrySet()) { - if (entry.getValue().async()) { - this.manager.getCommandCompletions().registerAsyncCompletion(entry.getValue().value(), (context) -> - runCommandCompletion(context, command, entry.getValue().value(), entry.getKey())); - } - else { - this.manager.getCommandCompletions().registerCompletion(entry.getValue().value(), (context) -> - runCommandCompletion(context, command, entry.getValue().value(), entry.getKey())); - } - this.plugin.info("Command Completer [" + entry.getValue().value() + "] registered."); - } - } - - /** - * Deregisters a command and any attached tab completions. - * - * @param command The command instance to register. - */ - @SuppressWarnings("unchecked") - public final void deregisterCommand(AikarCommand command) { - Preconditions.checkArgument(command != null, "Could not deregister that command: the command was null."); - this.manager.unregisterCommand(command); - this.plugin.info("Command [" + command.getClass().getSimpleName() + "] deregistered."); - Map> internal; - try { - internal = (HashMap>) - FieldUtils.readField(this.manager.getCommandCompletions(), "completionMap", true); - } - catch (Exception exception) { - throw new UnsupportedOperationException("Could not get internal completion map.", exception); - } - for (TabComplete complete : getTabCompletions(command.getClass()).values()) { - internal.remove(complete.value().toLowerCase(Locale.ENGLISH)); - this.plugin.info("Command Completer [" + complete.value() + "] deregistered."); - } - } - - /** - * Deregisters all commands. - */ - public final void deregisterCommands() { - this.manager.unregisterCommands(); - } - - /** - * Deregisters all command completions. - */ - public final void deregisterCompletions() { - this.manager.unregisterCompletions(); - } - - /** - * Resets the manager, resetting all commands and completions. - */ - public final void reset() { - this.manager.unregisterCommands(); - this.manager.unregisterCompletions(); - this.manager = null; - } - - /** - * Retrieves the internal manager this class wraps. - * - * @return Returns the internal manager. - */ - public final CustomBukkitManager getInternalManager() { - return this.manager; - } - - // ------------------------------------------------------------ - // Utilities - // ------------------------------------------------------------ - - @SuppressWarnings("unchecked") - private List runCommandCompletion(BukkitCommandCompletionContext context, AikarCommand command, - String id, Method method) { - try { - method.setAccessible(true); - switch (method.getParameterCount()) { - case 0: - return (List) method.invoke(command); - case 1: - return (List) method.invoke(command, context); - default: - throw new UnsupportedOperationException("Unsupported number of parameters."); - } - } - catch (Exception exception) { - this.plugin.warning("Could not tab complete [@" + id + "]: an error with the handler!", exception); - return Collections.emptyList(); - } - } - - private static Map getTabCompletions(Class clazz) { - Map completions = Maps.newHashMap(); - if (clazz == null) { - return completions; - } - for (Method method : clazz.getDeclaredMethods()) { - if (!Modifier.isPublic(method.getModifiers())) { - continue; - } - if (!List.class.isAssignableFrom(method.getReturnType())) { - continue; - } - if (method.getParameterCount() > 1) { - continue; - } - TabComplete tabComplete = method.getAnnotation(TabComplete.class); - if (tabComplete == null) { - continue; - } - if (Strings.isNullOrEmpty(tabComplete.value())) { - continue; - } - completions.put(method, tabComplete); - } - return completions; - } - - public static class CustomBukkitManager extends BukkitCommandManager { - - public CustomBukkitManager(ACivMod plugin) { - super(plugin); - } - - public void unregisterCompletions() { - this.completions = null; - } - - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/CivCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/command/CivCommand.java deleted file mode 100644 index 0074b407..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/CivCommand.java +++ /dev/null @@ -1,14 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface CivCommand { - - String id(); - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/CivConfigAnnotationProcessor.java b/src/main/java/vg/civcraft/mc/civmodcore/command/CivConfigAnnotationProcessor.java deleted file mode 100644 index 4edfc902..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/CivConfigAnnotationProcessor.java +++ /dev/null @@ -1,103 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.util.HashSet; -import java.util.Set; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.Filer; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.util.Elements; -import javax.tools.Diagnostic.Kind; -import javax.tools.FileObject; -import javax.tools.StandardLocation; - -@SupportedSourceVersion(value=SourceVersion.RELEASE_8) -@SupportedAnnotationTypes("vg.civcraft.mc.civmodcore.command.CivCommand") -public class CivConfigAnnotationProcessor extends AbstractProcessor { - - public static final String fileLocation = "META-INF/civmodcore/civconfig"; - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - if (roundEnv.processingOver()) { - return false; - } - Set pluginClasses = new HashSet<>(); - Elements elements = processingEnv.getElementUtils(); - for (Element element : roundEnv.getElementsAnnotatedWith(CivCommand.class)) { - CivCommand annot = element.getAnnotation(CivCommand.class); - if (annot == null) { - // ???? - continue; - } - if (!validConstructor(element)) { - return true; - } - pluginClasses.add(elements.getBinaryName((TypeElement) element).toString()); - } - // load existing - Filer filer = processingEnv.getFiler(); - try { - FileObject file = filer.getResource(StandardLocation.CLASS_OUTPUT, "", fileLocation); - BufferedReader reader = new BufferedReader(new InputStreamReader(file.openInputStream(), "UTF-8")); - String line; - while ((line = reader.readLine()) != null) { - pluginClasses.add(line); - } - reader.close(); - } catch (java.nio.file.NoSuchFileException | FileNotFoundException x) { - // doesn't exist yet, that's fine - } catch (IOException x) { - processingEnv.getMessager().printMessage(Kind.ERROR, - "Failed to load existing service definition files: " + x); - } - - // write back - try { - FileObject f = filer.createResource(StandardLocation.CLASS_OUTPUT, "", fileLocation); - PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(f.openOutputStream(), "UTF-8")); - processingEnv.getMessager().printMessage(Kind.NOTE, "Writing " + f.getName()); - for (String value : pluginClasses) { - printWriter.println(value); - } - printWriter.close(); - } catch (IOException x) { - processingEnv.getMessager().printMessage(Kind.ERROR, "Failed to write service definition files: " + x); - } - return true; - } - - private boolean validConstructor(Element el) { - for (Element subelement : el.getEnclosedElements()) { - if (subelement.getKind() == ElementKind.CONSTRUCTOR) { - if (!subelement.getModifiers().contains(Modifier.PUBLIC)) { - processingEnv.getMessager().printMessage(Kind.ERROR, - "Invalid constructor visibility for plugin " + subelement.toString()); - return false; - } - ExecutableType mirror = (ExecutableType) subelement.asType(); - if (!mirror.getParameterTypes().isEmpty()) { - processingEnv.getMessager().printMessage(Kind.ERROR, - "Invalid constructor for plugin, taking arguments is not allowed: " + subelement.toString()); - return false; - } - } - } - return true; - } - -} - diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/Command.java b/src/main/java/vg/civcraft/mc/civmodcore/command/Command.java deleted file mode 100644 index 5f74e99e..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/Command.java +++ /dev/null @@ -1,39 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import java.util.List; -import org.bukkit.command.CommandSender; -import vg.civcraft.mc.civmodcore.ratelimiting.RateLimiter; - -public interface Command { - - boolean execute(CommandSender sender, String[] args); - - List tabComplete(CommandSender sender, String[] args); - - String getName(); - - String getDescription(); - - String getUsage(); - - String getIdentifier(); - - int getMaxArguments(); - - int getMinArguments(); - - void postSetup(); - - boolean getSenderMustBePlayer(); - - boolean getErrorOnTooManyArgs(); - - void setSender(CommandSender sender); - - void setArgs(String[] args); - - RateLimiter getRateLimiter(); - - RateLimiter getTabCompletionRateLimiter(); - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/CommandHandler.java b/src/main/java/vg/civcraft/mc/civmodcore/command/CommandHandler.java deleted file mode 100644 index 9b1c3233..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/CommandHandler.java +++ /dev/null @@ -1,109 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.ratelimiting.RateLimiter; -import vg.civcraft.mc.civmodcore.util.TextUtil; - -@Deprecated -public abstract class CommandHandler { - - private static final String cmdMustBePlayer = "This command can only be used by in-game players."; - private static final String cmdRateLimited = "You have run this command too often and have to wait before running it again."; - - public Map commands = new HashMap<>(); - - public abstract void registerCommands(); - - protected void addCommands(Command command) { - commands.put(command.getIdentifier().toLowerCase(), command); - } - - public boolean execute(CommandSender sender, org.bukkit.command.Command cmd, String[] args) { - if (commands.containsKey(cmd.getName().toLowerCase())) { - Command command = commands.get(cmd.getName().toLowerCase()); - boolean isPlayer = sender instanceof Player; - if (command.getSenderMustBePlayer() && !isPlayer) { - sender.sendMessage(TextUtil.parse(cmdMustBePlayer)); - return true; - } - if (args.length < command.getMinArguments() - || (command.getErrorOnTooManyArgs() && args.length > command.getMaxArguments())) { - helpPlayer(command, sender); - return true; - } - RateLimiter limiter = command.getRateLimiter(); - if (limiter != null && isPlayer) { - if (!limiter.pullToken((Player) sender)) { - sender.sendMessage(TextUtil.parse(cmdRateLimited)); - return true; - } - } - command.setSender(sender); - command.setArgs(args); - command.execute(sender, args); - } - else { - sender.sendMessage("Command was registered in plugin.yml, but not registered in command handler, tell a dev about this"); - } - return true; - } - - public List complete(CommandSender sender, org.bukkit.command.Command cmd, String[] args) { - if (commands.containsKey(cmd.getName().toLowerCase())) { - Command command = commands.get(cmd.getName().toLowerCase()); - boolean isPlayer = sender instanceof Player; - if (command.getSenderMustBePlayer() && !isPlayer) { - sender.sendMessage(TextUtil.parse(cmdMustBePlayer)); - return null; - } - RateLimiter limiter = command.getTabCompletionRateLimiter(); - if (limiter != null && isPlayer) { - if (!limiter.pullToken((Player) sender)) { - sender.sendMessage(TextUtil.parse(cmdRateLimited)); - return null; - } - } - - command.setSender(sender); - command.setArgs(args); - List completes = command.tabComplete(sender, args); - String completeArg; - if (args.length == 0) { - completeArg = ""; - } else { - completeArg = args[args.length - 1].toLowerCase(); - } - if (completes == null) { - completes = new ArrayList<>(); - for (Player p : Bukkit.getOnlinePlayers()) { - if (p.getName().toLowerCase().startsWith(completeArg)) { - completes.add(p.getName()); - } - } - return completes; - } else { - return completes; - } - } - return null; - } - - protected void helpPlayer(Command command, CommandSender sender) { - sender.sendMessage(new StringBuilder().append(ChatColor.RED + "Command: ").append(command.getName()).toString()); - sender.sendMessage(new StringBuilder().append(ChatColor.RED + "Description: ").append(command.getDescription()) - .toString()); - sender.sendMessage(new StringBuilder().append(ChatColor.RED + "Usage: ").append(command.getUsage()).toString()); - } - - public void reset() { - this.commands.clear(); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/MailBoxAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/command/MailBoxAPI.java deleted file mode 100644 index 41c86cb9..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/MailBoxAPI.java +++ /dev/null @@ -1,56 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import com.google.common.base.Preconditions; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; -import vg.civcraft.mc.civmodcore.playersettings.impl.collection.ListSetting; - -public class MailBoxAPI { - - private static ListSetting mail; - - /** - * Internal setup, should only be called when CivModCore is enabling - */ - public static void setup() { - if (mail != null) { - throw new IllegalStateException("Was already registed"); - } - /*mail = new ListSetting<>(CivModCorePlugin.getInstance(), "Mail box", "cmcMailBox", - new ItemStack(Material.STICK), null, String.class, false); */ - PlayerSettingAPI.registerSetting(mail, null); - } - - /** - * Adds a new messages to the players mail box - * @param player UUID of the player to send message to - * @param msg Message to add - */ - public static void addMail(UUID player, String msg) { - Preconditions.checkNotNull(player, "Player may not be null"); - Preconditions.checkNotNull(msg, "Message to add may not be null"); - mail.addElement(player, msg); - } - - /** - * Gets all pending messages in a players mail box - * @param player Player to mail box of - * @return Messages in the players mail box - */ - public static List getMail(UUID player) { - Preconditions.checkNotNull(player, "Player may not be null"); - return mail.getValue(player); - } - - /** - * Clears all mail a player has - * @param player - */ - public static void clearMail(UUID player) { - Preconditions.checkNotNull(player, "Player may not be null"); - mail.setValue(player, new ArrayList<>()); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/PlayerCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/command/PlayerCommand.java deleted file mode 100644 index 69c71660..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/PlayerCommand.java +++ /dev/null @@ -1,171 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.ratelimiting.RateLimiter; -import vg.civcraft.mc.civmodcore.ratelimiting.RateLimiting; -import vg.civcraft.mc.civmodcore.util.TextUtil; - -@Deprecated -public abstract class PlayerCommand implements Command { - - protected String name = ""; - protected String description = ""; - private String usage = ""; - protected String identifier = ""; - protected int min = 0; - protected int max = 0; - protected boolean senderMustBePlayer = false; - protected boolean errorOnTooManyArgs = true; - protected CommandSender sender; - protected String[] args; - protected RateLimiter rateLimiter; - protected RateLimiter tabCompletionRateLimiter; - - public PlayerCommand(String name) { - this.name = name; - } - - @Override - @Deprecated - public String getName() { - return name; - } - - @Override - public String getDescription() { - return description; - } - - @Override - public String getUsage() { - return usage; - } - - @Override - public String getIdentifier() { - return identifier; - } - - @Override - public int getMinArguments() { - return min; - } - - @Override - public int getMaxArguments() { - return max; - } - - @Override - public boolean getSenderMustBePlayer() { - return senderMustBePlayer; - } - - @Override - public boolean getErrorOnTooManyArgs() { - return this.errorOnTooManyArgs; - } - - @Override - public void setSender(CommandSender sender) { - this.sender = sender; - } - - @Override - @Deprecated - public void setArgs(String[] args) { - this.args = args; - } - - @Deprecated - public String[] getArgs() { - return args; - } - - @Override - public void postSetup() { - PluginCommand cmd = Bukkit.getPluginCommand(identifier); - if (cmd != null) { - cmd.setDescription(this.description); - cmd.setUsage(this.usage); - } - } - - public void setName(String name) { - this.name = name; - } - - public void setDescription(String description) { - this.description = description; - postSetup(); - } - - public void setUsage(String usage) { - this.usage = usage; - postSetup(); - } - - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - public void setArguments(int min, int max) { - this.min = min; - this.max = max; - } - - public boolean sendPlayerMessage(Player p, String m, boolean flag) { - p.sendMessage(m); - return flag; - } - - public void setSenderMustBePlayer(boolean senderMustBePlayer) { - this.senderMustBePlayer = senderMustBePlayer; - } - - public void setErrorOnTooManyArgs(boolean errorOnTooManyArgs) { - this.errorOnTooManyArgs = errorOnTooManyArgs; - } - - public void setRateLimitingBehavior(int limit, int refillAmount, int refillIntervallInSeconds) { - this.rateLimiter = RateLimiting.createRateLimiter("COMMAND_" + identifier, - limit, limit, refillAmount, ((long) refillIntervallInSeconds) * 1000); - } - - public void setTabCompletionRateLimitingBehavior(int limit, int refillAmount, int refillIntervallInSeconds) { - this.tabCompletionRateLimiter = RateLimiting.createRateLimiter("COMMAND_" + identifier, - limit, limit, refillAmount, ((long) refillIntervallInSeconds) * 1000); - } - - public RateLimiter getRateLimiter() { - return rateLimiter; - } - - public RateLimiter getTabCompletionRateLimiter() { - return tabCompletionRateLimiter; - } - - public Player player() { - return (Player) sender; - } - - public void msg(String msg) { - sender.sendMessage(parse(msg)); - } - - public void msg(String msg, Object... args) { - sender.sendMessage(parse(msg, args)); - } - - public String parse(String text) { - return TextUtil.parse(text); - } - - public String parse(String text, Object... args) { - return TextUtil.parse(text, args); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/StandaloneCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/command/StandaloneCommand.java deleted file mode 100644 index 25cb2e3d..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/StandaloneCommand.java +++ /dev/null @@ -1,112 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.ratelimiting.RateLimiter; - -public abstract class StandaloneCommand { - - protected int minArgs = 0; - protected int maxArgs = Integer.MAX_VALUE; - protected boolean mustBePlayer = false; - protected boolean mustBeConsole = false; - protected RateLimiter rateLimiter; - protected RateLimiter tabCompletionRateLimiter; - - public abstract boolean execute(CommandSender sender, String[] args); - - public abstract List tabComplete(CommandSender sender, String[] args); - - public String getIdentifier() { - Class pluginClass = this.getClass(); - CivCommand annot = pluginClass.getAnnotation(CivCommand.class); - if (annot == null) { - return null; - } - return annot.id(); - } - - boolean isRateLimitedToExecute(Player p) { - if (rateLimiter == null) { - return false; - } - return rateLimiter.pullToken(p); - } - - boolean isRateLimitedToTabComplete(Player p) { - if (tabCompletionRateLimiter == null) { - return false; - } - return tabCompletionRateLimiter.pullToken(p); - } - - boolean hasTooManyArgs(int argLength) { - return argLength > maxArgs; - } - - boolean hasTooFewArgs(int argLength) { - return argLength < minArgs; - } - - void setMaxArgs(int maxArgs) { - this.maxArgs = maxArgs; - } - - void setMinArgs(int minArgs) { - this.minArgs = minArgs; - } - - void setRateLimiter(RateLimiter limiter) { - this.rateLimiter = limiter; - } - - void setTabCompletionRateLimiter(RateLimiter limiter) { - this.tabCompletionRateLimiter = limiter; - } - - void setSenderMustBePlayer(boolean mustBePlayer) { - this.mustBePlayer = mustBePlayer; - } - - void setSenderMustBeConsole(boolean mustBeConsole) { - this.mustBeConsole = mustBeConsole; - } - - boolean canBeRunByPlayers() { - return !mustBeConsole; - } - - boolean canBeRunByConsole() { - return !mustBePlayer; - } - - protected static List doTabComplete(String arg, Collection candidates, boolean caseSensitive) { - return doTabComplete(arg, candidates, s -> s ,caseSensitive); - } - - protected static List doTabComplete(String arg, Collection suppliers, Function function, boolean caseSensitive) { - List result = new ArrayList<>(); - if (!caseSensitive) { - arg = arg.toLowerCase(); - } - for(T supplier : suppliers) { - String candidate = function.apply(supplier); - boolean matches; - if (caseSensitive) { - matches = candidate.startsWith(arg); - } - else { - matches = candidate.toLowerCase().startsWith(arg); - } - if (matches) { - result.add(candidate); - } - } - return result; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/StandaloneCommandHandler.java b/src/main/java/vg/civcraft/mc/civmodcore/command/StandaloneCommandHandler.java deleted file mode 100644 index 391e1772..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/StandaloneCommandHandler.java +++ /dev/null @@ -1,294 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.SimpleCommandMap; -import org.bukkit.entity.Player; -import org.bukkit.plugin.InvalidDescriptionException; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.SimplePluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.plugin.java.JavaPluginLoader; -import vg.civcraft.mc.civmodcore.ratelimiting.RateLimiter; -import vg.civcraft.mc.civmodcore.ratelimiting.RateLimiting; -import vg.civcraft.mc.civmodcore.util.ConfigParsing; - -public class StandaloneCommandHandler { - - private JavaPlugin plugin; - private Map commands; - - public StandaloneCommandHandler(JavaPlugin plugin) { - this.plugin = plugin; - this.commands = new HashMap<>(); - loadAll(); - } - - public void registerCommand(StandaloneCommand command) { - String id = command.getIdentifier(); - if (id == null) { - plugin.getLogger() - .warning("Could not register command " + command.getClass().getName() + ". Identifier was null"); - return; - } - // we dont do alias resolving, Bukkit will do that for us - commands.put(id.toLowerCase(), command); - } - - public boolean executeCommand(CommandSender sender, org.bukkit.command.Command cmd, String[] args) { - StandaloneCommand command = commands.get(cmd.getName().toLowerCase()); - if (command == null) { - plugin.getLogger() - .warning("Could not execute command " + cmd.getName() + ", no implementation was provided"); - sender.sendMessage(ChatColor.RED + "Command not available"); - return true; - } - if (command.hasTooManyArgs(args.length)) { - sender.sendMessage(ChatColor.RED + "You provided too many arguments"); - helpPlayer(command, sender); - return false; - } - if (command.hasTooFewArgs(args.length)) { - sender.sendMessage(ChatColor.RED + "You provided too few arguments"); - helpPlayer(command, sender); - return false; - } - if (sender instanceof Player) { - if (!command.canBeRunByPlayers()) { - sender.sendMessage(ChatColor.RED + "This command can only be run from console"); - return true; - } - if (command.isRateLimitedToExecute((Player) sender)) { - sender.sendMessage( - ChatColor.RED + "You are rate limited and have to wait before running this command again"); - return true; - } - } else { - // console - if (!command.canBeRunByConsole()) { - sender.sendMessage(ChatColor.RED + "This command can only be run by players"); - return true; - } - } - boolean worked = command.execute(sender, args); - if (!worked) { - helpPlayer(command, sender); - } - return worked; - } - - public List tabCompleteCommand(CommandSender sender, org.bukkit.command.Command cmd, String[] args) { - StandaloneCommand command = commands.get(cmd.getName().toLowerCase()); - if (command == null) { - plugin.getLogger().warning( - "Could not tab complete command " + cmd.getName() + ", no implementation was provided"); - return Collections.emptyList(); - } - if (sender instanceof Player) { - if (!command.canBeRunByPlayers()) { - sender.sendMessage(ChatColor.RED + "This command can only be used from console"); - return Collections.emptyList(); - } - if (command.isRateLimitedToTabComplete((Player) sender)) { - sender.sendMessage(ChatColor.RED - + "You are rate limited and have to wait before tab completing this command again"); - return Collections.emptyList(); - } - } else { - // console - if (!command.canBeRunByConsole()) { - sender.sendMessage(ChatColor.RED + "This command can only be used by players"); - return Collections.emptyList(); - } - } - return command.tabComplete(sender, args); - } - - protected void helpPlayer(StandaloneCommand sCommand, CommandSender sender) { - org.bukkit.command.Command bukCom = getBukkitCommand(sCommand); - if (bukCom == null) { - sender.sendMessage(ChatColor.RED + "No help available for command " + sCommand.getIdentifier()); - return; - } - sender.sendMessage(ChatColor.RED + "Command: " + sCommand.getIdentifier()); - sender.sendMessage(ChatColor.RED + "Description: " + bukCom.getDescription()); - sender.sendMessage(ChatColor.RED + "Usage: " + bukCom.getUsage()); - } - - public org.bukkit.command.Command getBukkitCommand(StandaloneCommand command) { - SimpleCommandMap commandMap; - try { - Field mapField = SimplePluginManager.class.getDeclaredField("commandMap"); - mapField.setAccessible(true); - commandMap = (SimpleCommandMap) mapField.get(Bukkit.getPluginManager()); - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - plugin.getLogger().severe("Failed to retrieve command map field " + e.toString()); - return null; - } - return (org.bukkit.command.Command) commandMap.getCommand(command.getIdentifier()); - } - - private void loadAll() { - File file = getPluginJar(); - if (file == null) { - return; - } - @SuppressWarnings("deprecation") - JavaPluginLoader pluginLoader = new JavaPluginLoader(Bukkit.getServer()); - PluginDescriptionFile pluginYml; - try { - pluginYml = pluginLoader.getPluginDescription(file); - } catch (InvalidDescriptionException e1) { - plugin.getLogger().severe("Plugin " + plugin.getName() + " had invalid plugin.yml"); - return; - } - try (JarFile jar = new JarFile(file)) { - JarEntry entry = jar.getJarEntry(CivConfigAnnotationProcessor.fileLocation); - if (entry == null) { - // doesn't exist, that's fine - return; - } - try (InputStream stream = jar.getInputStream(entry); - InputStreamReader reader = new InputStreamReader(stream); - BufferedReader buffer = new BufferedReader(reader)) { - String line; - while ((line = buffer.readLine()) != null) { - loadCommand(line, pluginYml); - } - } - } catch (IOException e) { - plugin.getLogger().severe("Failed to load plugin.yml: " + e.toString()); - } - } - - private File getPluginJar() { - try { - Method method = JavaPlugin.class.getDeclaredMethod("getFile"); - method.setAccessible(true); - return (File) method.invoke(plugin); - } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - plugin.getLogger().severe("Failed to retrieve plugin file: " + e.toString()); - return null; - } - } - - private void loadCommand(String classPath, PluginDescriptionFile pluginYml) { - Class commandClass; - try { - commandClass = Class.forName(classPath); - } catch (ClassNotFoundException e) { - plugin.getLogger().warning("Attempted to load command " + classPath + ", but it could not be found"); - return; - } - StandaloneCommand command; - try { - command = (StandaloneCommand) commandClass.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException e) { - plugin.getLogger().warning("Error occured when loading command " + classPath + ": " + e.toString()); - return; - } - Object commandSection = pluginYml.getCommands().get(command.getIdentifier().toLowerCase()); - if (commandSection == null) { - plugin.getLogger().warning("No command with the identifier " + command.getIdentifier() - + " could be found in the plugin.yml. Command will be unavailable"); - return; - } - @SuppressWarnings("unchecked") - Map commandMap = (Map) commandSection; - command.setRateLimiter( - parseRateLimiter(command.getIdentifier() + "-ratelimit", commandMap.get("rate-limiter"))); - command.setTabCompletionRateLimiter( - parseRateLimiter(command.getIdentifier() + "-tabratelimit", commandMap.get("tab-rate-limiter"))); - Boolean playerOnly = attemptBoolean(commandMap.get("player-only")); - if (playerOnly != null) { - command.setSenderMustBePlayer(playerOnly); - } - Boolean consoleOnly = attemptBoolean(commandMap.get("console-only")); - if (consoleOnly != null) { - command.setSenderMustBeConsole(consoleOnly); - if (consoleOnly && playerOnly != null && playerOnly) { - plugin.getLogger().severe("Command " + command.getIdentifier() - + " is simultaneously console only and player only. It can not be run"); - } - } - Integer minArgs = attemptInteger(commandMap.get("min-args")); - if (minArgs != null) { - command.setMinArgs(minArgs); - } - Integer maxArgs = attemptInteger(commandMap.get("max-args")); - if (maxArgs != null) { - command.setMaxArgs(maxArgs); - } - this.commands.put(command.getIdentifier().toLowerCase(), command); - } - - private RateLimiter parseRateLimiter(String name, Object o) { - if (o == null) { - return null; - } - if (o instanceof Map) { - @SuppressWarnings("unchecked") - Map map = (Map) o; - if (!(map.containsKey("capacity") && map.containsKey("amount") && map.containsKey("interval"))) { - plugin.getLogger().severe("Incomplete rate limiting configuration for command " + name); - return null; - } - Integer capacity = attemptInteger(map.get("capacity")); - if (capacity == null) { - plugin.getLogger().severe("No capacity provided for rate limiting configuration for " + name); - return null; - } - Integer refillAmount = attemptInteger(map.get("amount")); - if (refillAmount == null) { - plugin.getLogger().severe("No amount provided for rate limiting configuration for " + name); - return null; - } - long interval = ConfigParsing.parseTime(String.valueOf(map.get("interval"))); - if (interval == 0) { - plugin.getLogger().severe("No interval provided for rate limiting configuration for " + name); - return null; - } - // multiply by 50 to convert ticks into ms - return RateLimiting.createRateLimiter("command-" + name, capacity, capacity, refillAmount, interval * 50); - } - return null; - } - - public void reset() { - this.commands.clear(); - } - - // if only java generics allowed instanceof... - - private Integer attemptInteger(Object o) { - if (o instanceof Integer) { - return (Integer) o; - } - return null; - } - - private Boolean attemptBoolean(Object o) { - if (o instanceof Boolean) { - return (Boolean) o; - } - return null; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/command/Trie.java b/src/main/java/vg/civcraft/mc/civmodcore/command/Trie.java deleted file mode 100644 index 8e2770e6..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/command/Trie.java +++ /dev/null @@ -1,109 +0,0 @@ -package vg.civcraft.mc.civmodcore.command; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.commons.lang3.StringUtils; - -public final class Trie { - - private Map children; - private String word; - // instead of storing only the suffix and re-concatenating the original word for - // every lookup we always store the full word and depth as a relative offset - private int depth; - //whether this non-leaf also represents the end of a word - private boolean isEnd; - - public static Trie getNewTrie() { - Trie trie = new Trie("", 0); - trie.children = new HashMap<>(); - return trie; - } - - private Trie(String word, int depth) { - this.word = word; - this.depth = depth; - this.isEnd = false; - } - - public boolean isLeaf() { - return children == null; - } - - public void insert(String wordToInsert) { - if (isLeaf()) { - if (word.equals(wordToInsert)) { - return; - } - children = new HashMap<>(); - // insert current suffix - if (word.length() > depth) { - children.put(word.charAt(depth), new Trie(word, depth + 1)); - this.word = word.substring(0, depth); - } - else { - isEnd = true; - } - } - Trie targetNode = children.computeIfAbsent(wordToInsert.charAt(depth), c -> new Trie(wordToInsert, depth + 1)); - targetNode.insert(wordToInsert); - } - - public List match(String prefix) { - List result = new ArrayList<>(); - matchWord(prefix, result); - return result; - } - - public List complete(String [] args) { - String full = String.join(" ", args); - List matches = match(full); - if (args.length < 2) { - return matches; - } - int elementsToRemove = args.length - 1; - for (int i = 0; i < args.length; i++) { - String mod = matches.get(i); - int startingSpot = StringUtils.ordinalIndexOf(mod, " ", elementsToRemove) + 1; - matches.set(i, mod.substring(startingSpot)); - } - return matches; - } - - private void matchWord(String wordToMatch, List result) { - if (isLeaf()) { - if (wordToMatch.length() <= this.word.length()) { - result.add(word); - return; - } - if (wordToMatch.length() > this.word.length()) { - //we can not be a prefix if we are shorter - return; - } - for(int i = depth; i < this.word.length(); i++) { - if (wordToMatch.charAt(i) != this.word.charAt(i)) { - return; - } - } - result.add(this.word); - } else { - if (isEnd) { - result.add(this.word); - } - if (wordToMatch.length() <= depth) { - //valid prefix from here on and deeper, so deep search and add everything below - for(Trie subTrie : children.values()) { - subTrie.matchWord(wordToMatch, result); - } - return; - } - Trie deeperNode = children.get(wordToMatch.charAt(depth)); - if (deeperNode != null) { - deeperNode.matchWord(wordToMatch, result); - } - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java b/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java new file mode 100644 index 00000000..bb204017 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java @@ -0,0 +1,218 @@ +package vg.civcraft.mc.civmodcore.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.BukkitCommandCompletionContext; +import co.aikar.commands.BukkitCommandExecutionContext; +import co.aikar.commands.BukkitCommandManager; +import co.aikar.commands.CommandCompletions; +import co.aikar.commands.CommandCompletions.CommandCompletionHandler; +import co.aikar.commands.CommandContexts; +import com.google.common.base.Strings; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.plugin.Plugin; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; + +/** + * Command registration class wrapper around {@link BukkitCommandManager}. + */ +public class CommandManager extends BukkitCommandManager { + + private final CivLogger logger; + + /** + * Creates a new command manager for Aikar based commands and tab completions. + * + * @param plugin The plugin to bind this manager to. + */ + public CommandManager(@Nonnull final Plugin plugin) { + super(Objects.requireNonNull(plugin)); + this.logger = CivLogger.getLogger(plugin.getClass(), getClass()); + } + + /** + * Will initialise the manager and register both commands and completions. You should only really use this if + * you've used {@link CommandManager#reset()} or both {@link #unregisterCommands()} and + * {@link #unregisterCompletions()}, otherwise there may be issues. + */ + public final void init() { + registerCommands(); + registerCompletions(getCommandCompletions()); + registerContexts(getCommandContexts()); + } + + /** + * This is called as part of {@link CommandManager#init()} and should be overridden by an extending class to + * register all (or as many) commands at once. + */ + public void registerCommands() { } + + /** + * This is called as part of {@link CommandManager#init()} and should be overridden by an extending class to + * register all (or as many) completions at once, though make sure to call super. + * + * @param completions The completion manager is given. It is the same manager that can be reached via + * {@link #getCommandCompletions()}. + */ + public void registerCompletions(@Nonnull final CommandCompletions completions) { + completions.registerCompletion("none", (context) -> Collections.emptyList()); + completions.registerAsyncCompletion("allplayers", (context) -> + Arrays.stream(Bukkit.getOfflinePlayers()) + .map(OfflinePlayer::getName) + .toList()); + completions.registerAsyncCompletion("materials", (context) -> + Arrays.stream(Material.values()) + .map(Enum::name) + .toList()); + completions.registerAsyncCompletion("itemMaterials", (context) -> + Arrays.stream(Material.values()) + .filter(ItemUtils::isValidItemMaterial) + .map(Enum::name) + .toList()); + } + + /** + * This is called as part of {@link CommandManager#init()} and should be overridden by an extending class + * to register all (or as many) contexts at once. + * + * @param contexts The context manager is given. It is the same manager that can be reached via + * {@link #getCommandContexts()}. + */ + public void registerContexts(@Nonnull final CommandContexts contexts) { } + + /** + * Registers a new command and any attached tab completions. + * + * @param command The command instance to register. + * @param forceReplace Whether to force replace any existing command. + */ + @Override + public final void registerCommand(@Nonnull final BaseCommand command, final boolean forceReplace) { + super.registerCommand(Objects.requireNonNull(command), forceReplace); + this.logger.info("Command [" + command.getClass().getSimpleName() + "] registered."); + getTabCompletions(command.getClass()).forEach((method, annotation) -> { + if (annotation.async()) { + getCommandCompletions().registerAsyncCompletion(annotation.value(), (context) -> + runCommandCompletion(context, command, annotation.value(), method)); + } + else { + getCommandCompletions().registerCompletion(annotation.value(), (context) -> + runCommandCompletion(context, command, annotation.value(), method)); + } + this.logger.info("Command Completer [" + annotation.value() + "] registered."); + }); + } + + /** + * Deregisters a command and any attached tab completions. + * + * @param command The command instance to register. + */ + @SuppressWarnings("unchecked") + @Override + public final void unregisterCommand(@Nonnull final BaseCommand command) { + super.unregisterCommand(Objects.requireNonNull(command)); + this.logger.info("Command [" + command.getClass().getSimpleName() + "] unregistered."); + final Map> internal; + try { + internal = (HashMap>) + FieldUtils.readField(getCommandCompletions(), "completionMap", true); + } + catch (final Throwable exception) { + throw new UnsupportedOperationException("Could not get internal completion map.", exception); + } + for (final TabComplete complete : getTabCompletions(command.getClass()).values()) { + internal.remove(complete.value().toLowerCase(Locale.ENGLISH)); + this.logger.info("Command Completer [" + complete.value() + "] unregistered."); + } + } + + /** + * Resets all command completions. + */ + public final void unregisterCompletions() { + this.completions = null; + } + + /** + * Resets the manager, resetting all commands and completions. + */ + public final void reset() { + unregisterCommands(); + unregisterCompletions(); + } + + // ------------------------------------------------------------ + // Tab Completions + // ------------------------------------------------------------ + + @SuppressWarnings("unchecked") + private List runCommandCompletion(final BukkitCommandCompletionContext context, + final BaseCommand command, + final String id, + final Method method) { + try { + method.setAccessible(true); + return switch (method.getParameterCount()) { + case 0 -> (List) method.invoke(command); + case 1 -> (List) method.invoke(command, context); + default -> throw new UnsupportedOperationException("Unsupported number of parameters."); + }; + } + catch (final Throwable exception) { + this.logger.log(Level.WARNING, + "Could not tab complete [@" + id + "]: an error with the handler!", exception); + return Collections.emptyList(); + } + } + + private static Map getTabCompletions(final Class clazz) { + final var completions = new HashMap(); + if (clazz == null) { + return completions; + } + for (final Method method : clazz.getDeclaredMethods()) { + if (!Modifier.isPublic(method.getModifiers())) { + continue; + } + if (!List.class.isAssignableFrom(method.getReturnType())) { + continue; + } + // TODO add a generic type check here when possible + switch (method.getParameterCount()) { + case 0: + break; + case 1: + if (BukkitCommandCompletionContext.class.isAssignableFrom(method.getParameterTypes()[0])) { + break; + } + default: + continue; + } + final TabComplete tabComplete = method.getAnnotation(TabComplete.class); + if (tabComplete == null) { + continue; + } + if (Strings.isNullOrEmpty(tabComplete.value())) { + continue; + } + completions.put(method, tabComplete); + } + return completions; + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/commands/NamedCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/commands/NamedCommand.java new file mode 100644 index 00000000..4b64c1dd --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/commands/NamedCommand.java @@ -0,0 +1,17 @@ +package vg.civcraft.mc.civmodcore.commands; + +import co.aikar.commands.BaseCommand; +import java.util.Objects; +import javax.annotation.Nonnull; + +/** + * This class should be used when you can't use {@link co.aikar.commands.annotation.CommandAlias}. + */ +public abstract class NamedCommand extends BaseCommand { + + @SuppressWarnings("deprecation") + public NamedCommand(@Nonnull final String command) { + super(Objects.requireNonNull(command)); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/commands/TabComplete.java b/src/main/java/vg/civcraft/mc/civmodcore/commands/TabComplete.java new file mode 100644 index 00000000..3568f0a2 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/commands/TabComplete.java @@ -0,0 +1,24 @@ +package vg.civcraft.mc.civmodcore.commands; + +import co.aikar.commands.BaseCommand; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

This annotation, when used on a method on an {@link BaseCommand} extending class will be automatically + * registered as a tab completion.

+ * + *

The return type of the method MUST, MUST be {@code List}, otherwise it will fail.

+ * + *

The method is permitted to have 0-1 parameters, but that parameter MUST, MUST be of type + * {@code BukkitCommandCompletionContext}, otherwise it will fail.

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface TabComplete { + String value(); + + boolean async() default false; +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/ConfigParsing.java b/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigHelper.java similarity index 56% rename from src/main/java/vg/civcraft/mc/civmodcore/util/ConfigParsing.java rename to src/main/java/vg/civcraft/mc/civmodcore/config/ConfigHelper.java index 5ee5e53b..9f26a833 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/ConfigParsing.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigHelper.java @@ -1,13 +1,17 @@ -package vg.civcraft.mc.civmodcore.util; +package vg.civcraft.mc.civmodcore.config; import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; import java.util.logging.Logger; -import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.NonNull; +import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Location; @@ -26,29 +30,109 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; -import vg.civcraft.mc.civmodcore.areas.EllipseArea; -import vg.civcraft.mc.civmodcore.areas.GlobalYLimitedArea; -import vg.civcraft.mc.civmodcore.areas.IArea; -import vg.civcraft.mc.civmodcore.areas.RectangleArea; -import vg.civcraft.mc.civmodcore.itemHandling.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; +import vg.civcraft.mc.civmodcore.inventory.items.ItemMap; +import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; +import vg.civcraft.mc.civmodcore.world.model.EllipseArea; +import vg.civcraft.mc.civmodcore.world.model.GlobalYLimitedArea; +import vg.civcraft.mc.civmodcore.world.model.IArea; +import vg.civcraft.mc.civmodcore.world.model.RectangleArea; -public class ConfigParsing { +@UtilityClass +public final class ConfigHelper { - private static final Logger log = Bukkit.getLogger(); + private static final Logger LOGGER = Bukkit.getLogger(); /** - * Creates an itemmap containing all the items listed in the given config + * Retrieves the configuration section at the given key on the given configuration section. + * + * @param config The config to get the keyed section from. + * @param key The key of the section to retrieve. + * @return Returns the configuration section at the given key, or returns a new, empty section. + */ + @Nonnull + public static ConfigurationSection getSection(@NonNull final ConfigurationSection config, + @NonNull final String key) { + ConfigurationSection found = config.getConfigurationSection(key); + if (found == null) { + found = config.createSection(key); + } + return found; + } + + /** + * Retrieves a string list from a given config section. If the keyed value is a standalone string instead of a + * list, that value will be converted to a list. + * + * @param config The config section to retrieve the list from. + * @param key The key to get the list of. + * @return Returns a list of strings, which is never null. + */ + @Nonnull + public static List getStringList(@NonNull final ConfigurationSection config, + @NonNull final String key) { + if (config.isString(key)) { + final var list = new ArrayList(1); + list.add(config.getString(key)); + return list; + } + return config.getStringList(key); + } + + /** + * Attempts to retrieve a list from a config section. + * + * @param The type to parse the list into. + * @param config The config section. + * @param key The key of the list. + * @param parser The parser to convert the string value into the correct type. + * @return Returns a list, or null. + */ + @Nonnull + public static List parseList(@NonNull final ConfigurationSection config, + @NonNull final String key, + @NonNull final Function parser) { + if (!config.isList(key)) { + return new ArrayList<>(0); + } + final var entries = getStringList(config, key); + final var result = new ArrayList(entries.size()); + for (final String entry : entries) { + final T item = parser.apply(entry); + if (item != null) { + result.add(item); + } + } + return result; + } + + /** + * Attempts to retrieve a list of materials from a config section. + * + * @param config The config section. + * @param key The key of the list. + * @return Returns a list of materials, or null. + */ + @Nonnull + public static List parseMaterialList(@Nonnull final ConfigurationSection config, + @Nonnull final String key) { + return parseList(config, key, MaterialUtils::getMaterial); + } + + /** + * Creates an item map containing all the items listed in the given config * section * * @param config ConfigurationSection to parse the items from * @return The item map created */ - public static ItemMap parseItemMap(ConfigurationSection config) { - ItemMap result = new ItemMap(); + @Nonnull + public static ItemMap parseItemMap(@Nullable final ConfigurationSection config) { + final var result = new ItemMap(); if (config == null) { return result; } - for (String key : config.getKeys(false)) { + for (final String key : config.getKeys(false)) { ConfigurationSection current = config.getConfigurationSection(key); ItemMap partMap = parseItemMapDirectly(current); result.merge(partMap); @@ -56,7 +140,8 @@ public static ItemMap parseItemMap(ConfigurationSection config) { return result; } - public static ItemMap parseItemMapDirectly(ConfigurationSection current) { + @Nonnull + public static ItemMap parseItemMapDirectly(@Nullable final ConfigurationSection current) { ItemMap im = new ItemMap(); if (current == null) { return im; @@ -68,17 +153,17 @@ public static ItemMap parseItemMapDirectly(ConfigurationSection current) { m = null; } finally { if (m == null) { - log.severe("Failed to find material " + current.getString("material") + " in section " + current.getCurrentPath()); + LOGGER.severe("Failed to find material " + current.getString("material") + " in section " + current.getCurrentPath()); return im; } } ItemStack toAdd = new ItemStack(m); if (current.isInt("durability")) { - log.warning("Item durability as specified at " + current.getCurrentPath() + " is no longer supported"); + LOGGER.warning("Item durability as specified at " + current.getCurrentPath() + " is no longer supported"); } ItemMeta meta = toAdd.getItemMeta(); if (meta == null) { - log.severe("No item meta found for" + current.getCurrentPath()); + LOGGER.severe("No item meta found for" + current.getCurrentPath()); } else { String name = current.getString("name"); if (name != null) { @@ -101,7 +186,7 @@ public static ItemMap parseItemMapDirectly(ConfigurationSection current) { ConfigurationSection enchantConfig = current.getConfigurationSection("enchants") .getConfigurationSection(enchantKey); if (!enchantConfig.isString("enchant")) { - log.warning("No enchant specified for enchantment entry at " + enchantConfig.getCurrentPath() + LOGGER.warning("No enchant specified for enchantment entry at " + enchantConfig.getCurrentPath() + ". Entry was ignored"); continue; } @@ -109,7 +194,7 @@ public static ItemMap parseItemMapDirectly(ConfigurationSection current) { enchant = Enchantment .getByKey(NamespacedKey.minecraft((enchantConfig.getString("enchant").toLowerCase()))); if (enchant == null) { - log.severe("Failed to parse enchantment " + enchantConfig.getString("enchant") + LOGGER.severe("Failed to parse enchantment " + enchantConfig.getString("enchant") + ", the entry was ignored"); continue; } @@ -147,14 +232,14 @@ public static ItemMap parseItemMapDirectly(ConfigurationSection current) { ConfigurationSection currentStoredEnchantSection = storedEnchantSection .getConfigurationSection(sEKey); if (currentStoredEnchantSection != null) { - Enchantment enchant = parseEnchantment(currentStoredEnchantSection, "enchant"); + Enchantment enchant = EnchantUtils.getEnchantment(currentStoredEnchantSection.getString("enchant")); int level = currentStoredEnchantSection.getInt("level", 1); if (enchant != null) { enchantMeta.addStoredEnchant(enchant, level, true); } else { - log.severe("Failed to parse enchantment at " + currentStoredEnchantSection.getCurrentPath() - + ", it was not applied"); + LOGGER.severe("Failed to parse enchantment at " + currentStoredEnchantSection.getCurrentPath() + + ", it was not applied"); } } } @@ -168,7 +253,7 @@ public static ItemMap parseItemMapDirectly(ConfigurationSection current) { try { potType = PotionType.valueOf(potion.getString("type", "AWKWARD")); } catch (IllegalArgumentException e) { - log.warning("Expected potion type at " + potion.getCurrentPath() + ", but " + LOGGER.warning("Expected potion type at " + potion.getCurrentPath() + ", but " + potion.getString("type") + " is not a valid potion type"); potType = PotionType.AWKWARD; } @@ -199,12 +284,13 @@ public static ItemMap parseItemMapDirectly(ConfigurationSection current) { im.addItemStack(toAdd); return im; } - - public static int parseTimeAsTicks(String arg) { + + public static int parseTimeAsTicks(@Nonnull final String arg) { return (int) (parseTime(arg, TimeUnit.MILLISECONDS) / 50L); } - public static long parseTime(String arg, TimeUnit unit) { + public static long parseTime(@Nonnull final String arg, + @Nonnull final TimeUnit unit) { long millis = parseTime(arg); return unit.convert(millis, TimeUnit.MILLISECONDS); } @@ -224,7 +310,7 @@ public static long parseTime(String arg, TimeUnit unit) { * @param input Parsed string containing the time format * @return How many milliseconds the given time value is */ - public static long parseTime(String input) { + public static long parseTime(@Nonnull String input) { input = input.replace(" ", "").replace(",", "").toLowerCase(); long result = 0; try { @@ -244,63 +330,65 @@ public static long parseTime(String input) { duration = Long.parseLong(numberSuffix); } switch (typeSuffix) { - case "ms": - case "milli": - case "millis": - result += duration; - break; - case "s": // seconds - case "sec": - case "second": - case "seconds": - result += TimeUnit.SECONDS.toMillis(duration); - break; - case "m": // minutes - case "min": - case "minute": - case "minutes": - result += TimeUnit.MINUTES.toMillis(duration); - break; - case "h": // hours - case "hour": - case "hours": - result += TimeUnit.HOURS.toMillis(duration); - break; - case "d": // days - case "day": - case "days": - result += TimeUnit.DAYS.toMillis(duration); - break; - case "w": // weeks - case "week": - case "weeks": - result += TimeUnit.DAYS.toMillis(duration * 7); - break; - case "month": // weeks - case "months": - result += TimeUnit.DAYS.toMillis(duration * 30); - break; - case "y": - case "year": - case "years": - result += TimeUnit.DAYS.toMillis(duration * 365); - break; - case "never": - case "inf": - case "infinite": - case "perm": - case "perma": - case "forever": - // 1000 years counts as perma - result += TimeUnit.DAYS.toMillis(365 * 1000); - default: - // just ignore it + case "ms": + case "milli": + case "millis": + result += duration; + break; + case "s": // seconds + case "sec": + case "second": + case "seconds": + result += TimeUnit.SECONDS.toMillis(duration); + break; + case "m": // minutes + case "min": + case "minute": + case "minutes": + result += TimeUnit.MINUTES.toMillis(duration); + break; + case "h": // hours + case "hour": + case "hours": + result += TimeUnit.HOURS.toMillis(duration); + break; + case "d": // days + case "day": + case "days": + result += TimeUnit.DAYS.toMillis(duration); + break; + case "w": // weeks + case "week": + case "weeks": + result += TimeUnit.DAYS.toMillis(duration * 7); + break; + case "month": // weeks + case "months": + result += TimeUnit.DAYS.toMillis(duration * 30); + break; + case "y": + case "year": + case "years": + result += TimeUnit.DAYS.toMillis(duration * 365); + break; + case "never": + case "inf": + case "infinite": + case "perm": + case "perma": + case "forever": + // 1000 years counts as perma + result += TimeUnit.DAYS.toMillis(365 * 1000); + default: + // just ignore it } } return result; } - private static String getSuffix(String arg, Predicate selector) { + @Nonnull + private static String getSuffix(@Nonnull final String arg, + @Nonnull final Predicate selector) { StringBuilder number = new StringBuilder(); for (int i = arg.length() - 1; i >= 0; i--) { if (selector.test(arg.charAt(i))) { @@ -318,20 +406,21 @@ private static String getSuffix(String arg, Predicate selector) { * @param configurationSection ConfigurationSection to parse the effect from * @return The potion effect parsed */ - public static List parsePotionEffects(ConfigurationSection configurationSection) { + @Nonnull + public static List parsePotionEffects(@Nullable final ConfigurationSection configurationSection) { List potionEffects = Lists.newArrayList(); if (configurationSection != null) { for (String name : configurationSection.getKeys(false)) { ConfigurationSection configEffect = configurationSection.getConfigurationSection(name); String type = configEffect.getString("type"); if (type == null) { - log.severe("Expected potion type to be specified, but found no \"type\" option at " + LOGGER.severe("Expected potion type to be specified, but found no \"type\" option at " + configEffect.getCurrentPath()); continue; } PotionEffectType effect = PotionEffectType.getByName(type); if (effect == null) { - log.severe("Expected potion type to be specified at " + configEffect.getCurrentPath() + LOGGER.severe("Expected potion type to be specified at " + configEffect.getCurrentPath() + " but found " + type + " which is no valid type"); } int duration = configEffect.getInt("duration", 200); @@ -342,26 +431,27 @@ public static List parsePotionEffects(ConfigurationSection configu return potionEffects; } - public static IArea parseArea(ConfigurationSection config) { + @Nullable + public static IArea parseArea(@Nullable final ConfigurationSection config) { if (config == null) { - log.warning("Tried to parse area on null section"); + LOGGER.warning("Tried to parse area on null section"); return null; } String type = config.getString("type"); if (type == null) { - log.warning("Found no area type at " + config.getCurrentPath()); + LOGGER.warning("Found no area type at " + config.getCurrentPath()); return null; } int lowerYBound = config.getInt("lowerYBound", 0); int upperYBound = config.getInt("upperYBound", 255); String worldName = config.getString("world"); if (worldName == null) { - log.warning("Found no world specified for area at " + config.getCurrentPath()); + LOGGER.warning("Found no world specified for area at " + config.getCurrentPath()); return null; } World world = Bukkit.getWorld(worldName); if (world == null) { - log.warning("Found no world with name " + worldName + " as specified at " + config.getCurrentPath()); + LOGGER.warning("Found no world with name " + worldName + " as specified at " + config.getCurrentPath()); return null; } Location center = null; @@ -378,48 +468,48 @@ public static IArea parseArea(ConfigurationSection config) { int zSize = config.getInt("zSize", -1); IArea area = null; switch (type) { - case "GLOBAL": - area = new GlobalYLimitedArea(lowerYBound, upperYBound, world); - break; - case "ELLIPSE": - if (center == null) { - log.warning("Found no center for area at " + config.getCurrentPath()); - return null; - } - if (xSize == -1) { - log.warning("Found no xSize for area at " + config.getCurrentPath()); - return null; - } - if (zSize == -1) { - log.warning("Found no zSize for area at " + config.getCurrentPath()); - return null; - } - area = new EllipseArea(lowerYBound, upperYBound, center, xSize, zSize); - break; - case "RECTANGLE": - if (center == null) { - log.warning("Found no center for area at " + config.getCurrentPath()); - return null; - } - if (xSize == -1) { - log.warning("Found no xSize for area at " + config.getCurrentPath()); - return null; - } - if (zSize == -1) { - log.warning("Found no zSize for area at " + config.getCurrentPath()); - return null; - } - area = new RectangleArea(lowerYBound, upperYBound, center, xSize, zSize); - break; - default: - log.warning("Invalid area type " + type + " at " + config.getCurrentPath()); + case "GLOBAL": + area = new GlobalYLimitedArea(lowerYBound, upperYBound, world); + break; + case "ELLIPSE": + if (center == null) { + LOGGER.warning("Found no center for area at " + config.getCurrentPath()); + return null; + } + if (xSize == -1) { + LOGGER.warning("Found no xSize for area at " + config.getCurrentPath()); + return null; + } + if (zSize == -1) { + LOGGER.warning("Found no zSize for area at " + config.getCurrentPath()); + return null; + } + area = new EllipseArea(lowerYBound, upperYBound, center, xSize, zSize); + break; + case "RECTANGLE": + if (center == null) { + LOGGER.warning("Found no center for area at " + config.getCurrentPath()); + return null; + } + if (xSize == -1) { + LOGGER.warning("Found no xSize for area at " + config.getCurrentPath()); + return null; + } + if (zSize == -1) { + LOGGER.warning("Found no zSize for area at " + config.getCurrentPath()); + return null; + } + area = new RectangleArea(lowerYBound, upperYBound, center, xSize, zSize); + break; + default: + LOGGER.warning("Invalid area type " + type + " at " + config.getCurrentPath()); } return area; } /** * Parses a section which contains key-value mappings of a type to another type - * + * * @param Key type * @param Value type * @param parent Configuration section containing the section with the values @@ -429,8 +519,12 @@ public static IArea parseArea(ConfigurationSection config) { * @param valueConverter Converts strings to type V * @param mapToUse The map to place parsed keys and values. */ - public static void parseKeyValueMap(ConfigurationSection parent, String identifier, Logger logger, - Function keyConverter, Function valueConverter, Map mapToUse) { + public static void parseKeyValueMap(@Nonnull final ConfigurationSection parent, + @Nonnull final String identifier, + @Nonnull final Logger logger, + @Nonnull final Function keyConverter, + @Nonnull final Function valueConverter, + @Nonnull final Map mapToUse) { if (!parent.isConfigurationSection(identifier)) { return; } @@ -453,20 +547,15 @@ public static void parseKeyValueMap(ConfigurationSection parent, String i mapToUse.put(keyinstance, value); } } - - public static Enchantment parseEnchantment(ConfigurationSection config, String key) { - if (!config.isString(key)) { - return null; - } - String val = config.getString(key); - if (!Pattern.matches("[a-z0-9/._-]+", val)) { - return null; - } - NamespacedKey nsKey = NamespacedKey.minecraft(val); - if (nsKey == null) { - return null; - } - return Enchantment.getByKey(nsKey); + + /** + * @deprecated Use {@link EnchantUtils#getEnchantment(String)} instead. + */ + @Nullable + @Deprecated(forRemoval = true) + public static Enchantment parseEnchantment(@Nonnull final ConfigurationSection config, + @Nonnull final String key) { + return EnchantUtils.getEnchantment(config.getString(key)); } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigParser.java b/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigParser.java new file mode 100644 index 00000000..901f7f07 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigParser.java @@ -0,0 +1,82 @@ +package vg.civcraft.mc.civmodcore.config; + +import java.util.logging.Logger; +import org.bukkit.ChatColor; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.Plugin; + +/** + * This is a config parsing class intended to make handling configs a little easier, and automatically parse commonly + * seen things within civ configs. + */ +public abstract class ConfigParser { + + protected final Plugin plugin; + protected final Logger logger; + + private boolean debug; + private boolean logReplies; + + public ConfigParser(final Plugin plugin) { + this.plugin = plugin; + this.logger = plugin.getLogger(); + } + + /** + * Parses this civ plugin's config. It will also save a default config (if one is not already present) and reload + * the config, so there's no need to do so yourself beforehand. + * + * @return Returns true if the config was successfully parsed. + */ + public final boolean parse() { + this.logger.info(ChatColor.BLUE + "Parsing config."); + this.plugin.saveDefaultConfig(); + FileConfiguration config = this.plugin.getConfig(); + // Parse debug value + this.debug = config.getBoolean("debug", false); + this.logger.info("Debug mode: " + (this.debug ? "enabled" : "disabled")); + // Parse reply logging value + this.logReplies = config.getBoolean("logReplies", false); + this.logger.info("Logging replies: " + (this.logReplies ? "enabled" : "disabled")); + // Allow child class parsing + final boolean worked = parseInternal(config); + if (worked) { + this.logger.info(ChatColor.BLUE + "Config parsed."); + } + else { + this.logger.warning("Failed to parse config!"); + } + return worked; + } + + /** + * An internal parser method intended to be overridden by child classes. + * + * @param config The root config section. + * @return Return true if the + */ + protected abstract boolean parseInternal(final ConfigurationSection config); + + /** + * This should reset all config values back to their defaults. Child classes should override this if they parse + * additional values that should be reset. + */ + public void reset() { + this.debug = false; + this.logReplies = false; + } + + // ------------------------------------------------------------ // + // Getters + // ------------------------------------------------------------ // + + public final boolean isDebugEnabled() { + return this.debug; + } + + public final boolean logReplies() { + return this.logReplies; + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigSection.java b/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigSection.java new file mode 100644 index 00000000..6e0053c3 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/config/ConfigSection.java @@ -0,0 +1,24 @@ +package vg.civcraft.mc.civmodcore.config; + +import java.util.Map; +import javax.annotation.Nonnull; +import org.bukkit.configuration.MemoryConfiguration; +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +/** + * Class is intended to be used by implementations of {@link ConfigurationSerializable} during the deserialization + * process. + */ +public class ConfigSection extends MemoryConfiguration { + + /** + * @param data The data to create a new ConfigSection from. + * @return Returns a new ConfigSection. + */ + public static ConfigSection fromData(@Nonnull final Map data) { + final var section = new ConfigSection(); + section.map.putAll(data); + return section; + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/dao/ConnectionPool.java b/src/main/java/vg/civcraft/mc/civmodcore/dao/ConnectionPool.java index e163d494..6b9098ba 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/dao/ConnectionPool.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/dao/ConnectionPool.java @@ -1,13 +1,15 @@ package vg.civcraft.mc.civmodcore.dao; -import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import java.util.Objects; import java.util.logging.Logger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.bukkit.Bukkit; /** @@ -20,7 +22,6 @@ public class ConnectionPool { private static final Logger LOGGER = Bukkit.getLogger(); private final DatabaseCredentials credentials; - private HikariDataSource datasource; /** @@ -29,23 +30,23 @@ public class ConnectionPool { * * @param credentials The credentials to connect with. */ - public ConnectionPool(DatabaseCredentials credentials) { - Preconditions.checkArgument(credentials != null, + public ConnectionPool(@Nonnull final DatabaseCredentials credentials) { + this.credentials = Objects.requireNonNull(credentials, "Cannot create a ConnectionPool with a null set of credentials."); - this.credentials = credentials; HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:" + credentials.getDriver() + "://" + credentials.getHostname() + ":" + - credentials.getPort() + "/" + credentials.getDatabase()); - config.setConnectionTimeout(credentials.getConnectionTimeout()); - config.setIdleTimeout(credentials.getIdleTimeout()); - config.setMaxLifetime(credentials.getMaxLifetime()); - config.setMaximumPoolSize(credentials.getPoolSize()); - config.setUsername(credentials.getUsername()); - if (!Strings.isNullOrEmpty(credentials.getPassword())) { - config.setPassword(credentials.getPassword()); + config.setJdbcUrl("jdbc:" + credentials.driver() + "://" + credentials.host() + ":" + + credentials.port() + "/" + credentials.database()); + config.setConnectionTimeout(credentials.connectionTimeout()); + config.setIdleTimeout(credentials.idleTimeout()); + config.setMaxLifetime(credentials.maxLifetime()); + config.setMaximumPoolSize(credentials.poolSize()); + config.setUsername(credentials.username()); + if (!Strings.isNullOrEmpty(credentials.password())) { + config.setPassword(credentials.password()); } this.datasource = new HikariDataSource(config); - try (Connection connection = getConnection(); Statement statement = connection.createStatement();) { + try (final Connection connection = getConnection(); + final Statement statement = connection.createStatement()) { statement.executeQuery("SELECT 1;"); LOGGER.info("Successfully connected to the database."); } @@ -72,40 +73,26 @@ public ConnectionPool(DatabaseCredentials credentials) { * @param idleTimeout The longest a connection can sit idle before recycling (10 minutes recommended, check dbms) * @param maxLifetime The longest a connection can exist in total. (2 hours recommended, check dbms) */ - public ConnectionPool(String user, String pass, String host, int port, String driver, String database, - int poolSize, long connectionTimeout, long idleTimeout, long maxLifetime) { + public ConnectionPool(final String user, + final String pass, + final String host, + final int port, + final String driver, + final String database, + final int poolSize, + final long connectionTimeout, + final long idleTimeout, + final long maxLifetime) { this(new DatabaseCredentials(user, pass, host, port, driver, database, poolSize, connectionTimeout, idleTimeout, maxLifetime)); } - /** - * Legacy support constructor to create a connection pool. - * - * @param logger The logger that would be logged to. This can now be null since ConnectionPool now - * uses its own logger. - * @param user The SQL user to connect as. - * @param pass The SQL user's password. - * @param host The hostname of the database. - * @param port The port to connect via. - * @param database The specific database to create and modify tables in. - * @param poolSize The maximum size of the connection pool (under 10 recommended) - * @param connectionTimeout The longest a query can run until it times out (1-5 seconds recommended) - * @param idleTimeout The longest a connection can sit idle before recycling (10 minutes recommended, check dbms) - * @param maxLifetime The longest a connection can exist in total. (2 hours recommended, check dbms) - * - * @deprecated This is deprecated as it insists on a logger and does not allow you to specify a jdbc driver. - */ - @Deprecated - public ConnectionPool(Logger logger, String user, String pass, String host, int port, String database, - int poolSize, long connectionTimeout, long idleTimeout, long maxLifetime) { - this(user, pass, host, port, "mysql", database, poolSize, connectionTimeout, idleTimeout, maxLifetime); - } - /** * Gets the credentials used for this ConnectionPool. * * @return Returns the credentials being used. */ + @Nonnull public DatabaseCredentials getCredentials() { return this.credentials; } @@ -116,6 +103,7 @@ public DatabaseCredentials getCredentials() { * @return A new Connection * @throws SQLException */ + @Nonnull public Connection getConnection() throws SQLException { available(); return this.datasource.getConnection(); @@ -148,8 +136,9 @@ public void available() throws SQLException { * * @return DataSource being used */ + @Nullable HikariDataSource getHikariDataSource() { - return datasource; + return this.datasource; } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/dao/DatabaseCredentials.java b/src/main/java/vg/civcraft/mc/civmodcore/dao/DatabaseCredentials.java index ffb1bdc6..85707ef2 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/dao/DatabaseCredentials.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/dao/DatabaseCredentials.java @@ -2,90 +2,39 @@ import java.util.HashMap; import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.apache.commons.collections4.MapUtils; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.util.NumberConversions; -import vg.civcraft.mc.civmodcore.util.MoreMapUtils; +import vg.civcraft.mc.civmodcore.utilities.MoreMapUtils; /** * This is a data class representing a set of credentials used for connecting to databases. * * @author Protonull */ -public class DatabaseCredentials implements ConfigurationSerializable { - - private final String user; - private final String pass; - private final String host; - private final int port; - private final String driver; - private final String database; - private final int poolSize; - private final long connectionTimeout; - private final long idleTimeout; - private final long maxLifetime; - - public DatabaseCredentials(String user, String pass, String host, int port, String driver, String database, - int poolSize, long connectionTimeout, long idleTimeout, long maxLifetime) { - this.user = user; - this.pass = pass; - this.host = host; - this.port = port; - this.driver = driver; - this.database = database; - this.poolSize = poolSize; - this.connectionTimeout = connectionTimeout; - this.idleTimeout = idleTimeout; - this.maxLifetime = maxLifetime; - } - - public final String getUsername() { - return this.user; - } - - public final String getPassword() { - return this.pass; - } - - public final String getHostname() { - return this.host; - } - - public final int getPort() { - return this.port; - } - - public final String getDriver() { - return this.driver; - } - - public final String getDatabase() { - return this.database; - } - - public final int getPoolSize() { - return this.poolSize; - } - - public final long getConnectionTimeout() { - return this.connectionTimeout; - } - - public final long getIdleTimeout() { - return this.idleTimeout; - } - - public final long getMaxLifetime() { - return this.maxLifetime; - } - +public record DatabaseCredentials(String username, + String password, + String host, + int port, + String driver, + String database, + int poolSize, + long connectionTimeout, + long idleTimeout, + long maxLifetime) + implements ConfigurationSerializable { + + @Nonnull @Override - public final Map serialize() { - Map data = new HashMap<>(); - data.put("username", this.user); - data.put("password", this.pass); + public Map serialize() { + final var data = new HashMap(10); + data.put("username", this.username); + data.put("password", this.password); data.put("hostname", this.host); data.put("port", this.port); + data.put("driver", this.driver); data.put("database", this.database); data.put("poolSize", this.poolSize); data.put("connectionTimeout", this.connectionTimeout); @@ -94,26 +43,32 @@ public final Map serialize() { return data; } - // This must be kept compatible with ManagedDatasource's deserialization - public static DatabaseCredentials deserialize(Map data) { + @Nullable + public static DatabaseCredentials deserialize(@Nonnull final Map data) { if (MapUtils.isEmpty(data)) { return null; } - String user = MoreMapUtils.attemptGet(data, "root", "username", "user"); - String pass = MoreMapUtils.attemptGet(data, "", "password", "pass"); - String host = MoreMapUtils.attemptGet(data, "localhost", "hostname", "host"); - int port = MoreMapUtils.attemptGet(data, NumberConversions::toInt, 3306, "port"); - String driver = MoreMapUtils.attemptGet(data, "mysql", "driver"); - String database = MoreMapUtils.attemptGet(data, "minecraft", "database", "dbname", "db"); - int poolSize = MoreMapUtils.attemptGet(data, NumberConversions::toInt, 10, "poolSize", "poolsize"); - long connectionTimeout = MoreMapUtils.attemptGet(data, NumberConversions::toLong, 10_000L, - "connectionTimeout", "connection_timeout"); - long idleTimeout = MoreMapUtils.attemptGet(data, NumberConversions::toLong, 600_000L, - "idleTimeout", "idle_timeout"); - long maxLifetime = MoreMapUtils.attemptGet(data, NumberConversions::toLong, 7_200_000L, - "maxLifetime", "max_lifetime"); - return new DatabaseCredentials(user, pass, host, port, driver, database, - poolSize, connectionTimeout, idleTimeout, maxLifetime); + return new DatabaseCredentials( + MoreMapUtils.attemptGet(data, "root", + "username", "user"), // keys + MoreMapUtils.attemptGet(data, "", + "password", "pass"), // keys + MoreMapUtils.attemptGet(data, "localhost", + "hostname", "host"), // keys + MoreMapUtils.attemptGet(data, NumberConversions::toInt, 3306, + "port"), // keys + MoreMapUtils.attemptGet(data, "mysql", + "driver"), // keys + MoreMapUtils.attemptGet(data, "minecraft", + "database", "dbname", "db"), // keys + MoreMapUtils.attemptGet(data, NumberConversions::toInt, 10, + "poolSize", "poolsize"), // keys + MoreMapUtils.attemptGet(data, NumberConversions::toLong, 10_000L, + "connectionTimeout", "connection_timeout"), // keys + MoreMapUtils.attemptGet(data, NumberConversions::toLong, 600_000L, + "idleTimeout", "idle_timeout"), // keys + MoreMapUtils.attemptGet(data, NumberConversions::toLong, 7_200_000L, + "maxLifetime", "max_lifetime")); // keys } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/dao/DatabaseMigration.java b/src/main/java/vg/civcraft/mc/civmodcore/dao/DatabaseMigration.java new file mode 100644 index 00000000..852aabcc --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/dao/DatabaseMigration.java @@ -0,0 +1,56 @@ +package vg.civcraft.mc.civmodcore.dao; + +import java.sql.SQLException; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.ArrayUtils; + +/** + * Interface to allow for more object-oriented based migration. I've noticed as plugins get older, their DAOs get + * larger and larger to accommodate more and more migrations. This interface allows migrations to be split up into + * individually functional classes with the convention of NEVER using plugin APIs: that anything the migration needs + * to do should be encapsulated within the migration class. + */ +public interface DatabaseMigration { + + /** + * @return Returns this migration's id -- 0, 1, 2, etc, must be unique. + */ + int getMigrationId(); + + /** + * @return Returns whether errors in this migration should be ignored. + */ + default boolean shouldIgnoreErrors() { + return false; + } + + /** + * @return Returns this migration's queries. Each query will be run in sequences. Must not be null or empty! + */ + @Nonnull + String[] getMigrationQueries(); + + /** + * An optional callback that'll run after the migration has completed. + * + * @return Returns whether the callback completed successfully. + */ + default boolean migrationCallback(@Nonnull final ManagedDatasource datasource) throws SQLException { + return true; + } + + /** + * @param datasource The datasource to register this migration to. + */ + default void registerMigration(@Nonnull final ManagedDatasource datasource) { + final var queries = getMigrationQueries(); + if (ArrayUtils.isEmpty(queries)) { + throw new IllegalArgumentException("Migration queries cannot be null or empty!"); + } + datasource.registerMigration(getMigrationId(), + shouldIgnoreErrors(), + () -> migrationCallback(datasource), + queries); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/dao/ManagedDatasource.java b/src/main/java/vg/civcraft/mc/civmodcore/dao/ManagedDatasource.java index e867fb2e..61429ec3 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/dao/ManagedDatasource.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/dao/ManagedDatasource.java @@ -1,7 +1,5 @@ package vg.civcraft.mc.civmodcore.dao; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -9,11 +7,8 @@ import java.sql.SQLWarning; import java.sql.Statement; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.NavigableMap; -import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -21,14 +16,12 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.logging.Level; -import java.util.logging.Logger; -import org.apache.commons.collections4.MapUtils; -import org.bukkit.Bukkit; -import org.bukkit.configuration.serialization.ConfigurationSerializable; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.bukkit.plugin.Plugin; import vg.civcraft.mc.civmodcore.ACivMod; -import vg.civcraft.mc.civmodcore.util.MoreCollectionUtils; -import vg.civcraft.mc.civmodcore.util.MoreMapUtils; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; +import vg.civcraft.mc.civmodcore.utilities.MoreCollectionUtils; /** * Plugins should replace their custom Database handlers with an instance of ManagedDatasource. @@ -71,67 +64,77 @@ * * @author ProgrammerDan (refactored by Protonull) */ -public class ManagedDatasource implements ConfigurationSerializable { +public class ManagedDatasource { - private static final Logger LOGGER = Bukkit.getLogger(); + private static final String CREATE_MIGRATIONS_TABLE = """ + CREATE TABLE IF NOT EXISTS managed_plugin_data ( + managed_id BIGINT NOT NULL AUTO_INCREMENT, + plugin_name VARCHAR(120) NOT NULL, + management_began TIMESTAMP NOT NULL DEFAULT NOW(), + current_migration_number INT NOT NULL, + last_migration TIMESTAMP, + CONSTRAINT pk_managed_plugin_data PRIMARY KEY (managed_id), + CONSTRAINT uniq_managed_plugin UNIQUE (plugin_name), + INDEX idx_managed_plugin USING BTREE (plugin_name) + ); + """; - private static final String CREATE_MIGRATIONS_TABLE = "CREATE TABLE IF NOT EXISTS managed_plugin_data (" - + "managed_id BIGINT NOT NULL AUTO_INCREMENT, " + "plugin_name VARCHAR(120) NOT NULL, " - + "management_began TIMESTAMP NOT NULL DEFAULT NOW(), " + "current_migration_number INT NOT NULL, " - + "last_migration TIMESTAMP, " + "CONSTRAINT pk_managed_plugin_data PRIMARY KEY (managed_id), " - + "CONSTRAINT uniq_managed_plugin UNIQUE (plugin_name), " - + "INDEX idx_managed_plugin USING BTREE (plugin_name)" + ");"; + private static final String CREATE_LOCK_TABLE = """ + CREATE TABLE IF NOT EXISTS managed_plugin_locks ( + plugin_name VARCHAR(120) NOT NULL, + lock_time TIMESTAMP NOT NULL DEFAULT NOW(), + CONSTRAINT pk_managed_plugin_locks PRIMARY KEY (plugin_name) + ); + """; - private static final String CREATE_LOCK_TABLE = "CREATE TABLE IF NOT EXISTS managed_plugin_locks (" - + "plugin_name VARCHAR(120) NOT NULL, " + "lock_time TIMESTAMP NOT NULL DEFAULT NOW(), " - + "CONSTRAINT pk_managed_plugin_locks PRIMARY KEY (plugin_name)" + ");"; + private static final String CHECK_LAST_MIGRATION = """ + SELECT current_migration_number FROM managed_plugin_data WHERE plugin_name = ?; + """; - private static final String CHECK_LAST_MIGRATION = "SELECT current_migration_number FROM managed_plugin_data " - + "WHERE plugin_name = ?;"; + private static final String RECORD_MIGRATION = """ + INSERT INTO managed_plugin_data + (plugin_name, current_migration_number, last_migration) + VALUES + (?, ?, NOW()) + ON DUPLICATE KEY UPDATE + plugin_name = VALUES(plugin_name), + current_migration_number = VALUES(current_migration_number), + last_migration = VALUES(last_migration); + """; - private static final String RECORD_MIGRATION = "INSERT INTO managed_plugin_data " - + "(plugin_name, current_migration_number, last_migration) " - + "VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE plugin_name = VALUES(plugin_name), " - + "current_migration_number = VALUES(current_migration_number), " - + "last_migration = VALUES(last_migration);"; + private static final String CLEANUP_LOCK_TABLE = """ + DELETE FROM managed_plugin_locks WHERE lock_time <= TIMESTAMPADD(HOUR, -8, NOW()); + """; - private static final String CLEANUP_LOCK_TABLE = "DELETE FROM managed_plugin_locks " - + "WHERE lock_time <= TIMESTAMPADD(HOUR, -8, NOW());"; + private static final String ACQUIRE_LOCK = """ + INSERT IGNORE INTO managed_plugin_locks (plugin_name) VALUES (?); + """; - private static final String ACQUIRE_LOCK = "INSERT IGNORE INTO managed_plugin_locks (plugin_name) VALUES (?);"; - - private static final String RELEASE_LOCK = "DELETE FROM managed_plugin_locks WHERE plugin_name = ?;"; + private static final String RELEASE_LOCK = """ + DELETE FROM managed_plugin_locks WHERE plugin_name = ?; + """; private static final long MAX_WAIT_FOR_LOCK = 600000L; - private static final long WAIT_PERIOD = 500L; + private final CivLogger logger; private final Plugin plugin; - private final DatabaseCredentials credentials; private final ConnectionPool connections; private final ExecutorService postExecutor; private final TreeMap migrations; private int firstMigration; private int lastMigration; - /** - * See {@link #ManagedDatasource(ACivMod, String, String, String, int, String, String, int, long, long, long)} for - * more details. - * - * @param plugin The civ plugin whose database is being managed. - * @param user The SQL user to connect as. - * @param pass The SQL user's password. - * @param host The hostname of the database. - * @param port The port to connect via. - * @param database The specific database to create and modify tables in. - * @param poolSize The maximum size of the connection pool (under 10 recommended) - * @param connectionTimeout The longest a query can run until it times out (1-5 seconds recommended) - * @param idleTimeout The longest a connection can sit idle before recycling (10 minutes recommended, check dbms) - * @param maxLifetime The longest a connection can exist in total. (2 hours recommended, check dbms) - */ - public ManagedDatasource(ACivMod plugin, String user, String pass, String host, int port, String database, - int poolSize, long connectionTimeout, long idleTimeout, long maxLifetime) { - this(plugin, user, pass, host, port, "mysql", database, poolSize, connectionTimeout, idleTimeout, maxLifetime); + private ManagedDatasource(final CivLogger logger, + final ACivMod plugin, + final ConnectionPool connections) { + this.logger = logger; + this.plugin = plugin; + this.connections = connections; + this.postExecutor = Executors.newSingleThreadExecutor(); + this.migrations = new TreeMap<>(); + this.firstMigration = Integer.MAX_VALUE; + this.lastMigration = Integer.MIN_VALUE; } /** @@ -174,55 +177,36 @@ public ManagedDatasource(ACivMod plugin, String user, String pass, String host, * docs for any further questions. * * @param plugin The plugin whose database is being managed. - * @param user The SQL user to connect as. - * @param pass The SQL user's password. - * @param host The hostname of the database. - * @param port The port to connect via. - * @param driver The jdbc driver to use to connect to the database. - * @param database The specific database to create and modify tables in. - * @param poolSize The maximum size of the connection pool (under 10 recommended) - * @param connectionTimeout The longest a query can run until it times out (1-5 seconds recommended) - * @param idleTimeout The longest a connection can sit idle before recycling (10 minutes recommended, check dbms) - * @param maxLifetime The longest a connection can exist in total. (2 hours recommended, check dbms) - */ - public ManagedDatasource(ACivMod plugin, String user, String pass, String host, int port, String driver, - String database, int poolSize, long connectionTimeout, long idleTimeout, - long maxLifetime) { - this(plugin, new DatabaseCredentials(user, pass, host, port, driver, database, - poolSize, connectionTimeout, idleTimeout, maxLifetime)); - } - - /** - * Create a new ManagedDatasource. - * - * @param plugin The plugin whose database is being managed. * @param credentials The credentials to connect to the database with. + * @return Returns */ - public ManagedDatasource(ACivMod plugin, DatabaseCredentials credentials) { - Preconditions.checkArgument(plugin != null && plugin.isEnabled()); - Preconditions.checkArgument(credentials != null); - this.plugin = plugin; - this.credentials = credentials; - LOGGER.info(String.format("Connecting to %s@%s:%s using %s",credentials.getDatabase(), - credentials.getHostname(), credentials.getPort(), credentials.getUsername())); - this.connections = new ConnectionPool(credentials); - this.postExecutor = Executors.newSingleThreadExecutor(); - this.migrations = new TreeMap<>(); - this.firstMigration = Integer.MAX_VALUE; - this.lastMigration = Integer.MIN_VALUE; - try (Connection connection = connections.getConnection();) { - try (Statement statement = connection.createStatement();) { + @Nullable + public static ManagedDatasource construct(@Nonnull final ACivMod plugin, + @Nullable final DatabaseCredentials credentials) { + final var logger = CivLogger.getLogger(plugin.getClass(), ManagedDatasource.class); + if (credentials == null) { + logger.warning("You must pass in a set of credentials"); + return null; + } + final var connections = new ConnectionPool(credentials); + logger.info(String.format("Connecting to %s@%s:%s using %s",credentials.database(), + credentials.host(), credentials.port(), credentials.username())); + try (final Connection connection = connections.getConnection()) { + try (final Statement statement = connection.createStatement()) { statement.executeUpdate(ManagedDatasource.CREATE_MIGRATIONS_TABLE); } - try (Statement statement = connection.createStatement();) { + try (final Statement statement = connection.createStatement()) { statement.executeUpdate(ManagedDatasource.CREATE_LOCK_TABLE); } } - catch (SQLException ignored) { - LOGGER.severe("Failed to prepare migrations table or register this plugin to it."); - LOGGER.log(Level.SEVERE, "Assuming you provided proper database credentials this is most likely happening, because " + - "your mysql install is outdated. We recommend using MariaDB or at least the latest mysql version", ignored); + catch (final SQLException exception) { + logger.severe("Failed to prepare migrations table or register this plugin to it."); + logger.log(Level.SEVERE, "Assuming you provided proper database credentials this is most likely " + + "happening, because your mysql install is outdated. We recommend using MariaDB or at least the " + + "latest mysql version.", exception); + return null; } + return new ManagedDatasource(logger, plugin, connections); } /** @@ -234,7 +218,9 @@ public ManagedDatasource(ACivMod plugin, DatabaseCredentials credentials) { * @param ignoreErrors Indicates if errors in this migration should be ignored. * @param queries The queries to run, in sequence. */ - public void registerMigration(int id, boolean ignoreErrors, String... queries) { + public void registerMigration(final int id, + final boolean ignoreErrors, + final String... queries) { registerMigration(id, ignoreErrors, null, queries); } @@ -248,13 +234,16 @@ public void registerMigration(int id, boolean ignoreErrors, String... queries) { * @param callback An optional callback that'll run after the migration has completed. * @param queries The queries to run, in sequence. */ - public void registerMigration(int id, boolean ignoreErrors, Callable callback, String... queries) { + public void registerMigration(final int id, + final boolean ignoreErrors, + final Callable callback, + final String... queries) { this.migrations.put(id, new Migration(ignoreErrors, callback, queries)); - if (id > lastMigration) { - lastMigration = id; + if (id > this.lastMigration) { + this.lastMigration = id; } - if (id < firstMigration) { - firstMigration = id; + if (id < this.firstMigration) { + this.firstMigration = id; } } @@ -280,51 +269,51 @@ public boolean updateDatabase() { try { checkWaitLock(); } - catch (SQLException exception) { - LOGGER.log(Level.SEVERE, "An uncorrectable SQL error was encountered!", exception); + catch (final SQLException exception) { + this.logger.log(Level.SEVERE, "An uncorrectable SQL error was encountered!", exception); return false; } - catch (TimeoutException exception) { - LOGGER.log(Level.SEVERE, "Unable to acquire a lock!", exception); + catch (final TimeoutException exception) { + this.logger.log(Level.SEVERE, "Unable to acquire a lock!", exception); return false; } // Now check update level, etc. - int currentLevel = migrations.firstKey() - 1; - try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement(CHECK_LAST_MIGRATION);) { + int currentLevel = this.migrations.firstKey() - 1; + try (final Connection connection = getConnection(); + final PreparedStatement statement = connection.prepareStatement(CHECK_LAST_MIGRATION)) { statement.setString(1, plugin.getName()); - try (ResultSet set = statement.executeQuery();) { + try (final ResultSet set = statement.executeQuery()) { if (set.next()) { currentLevel = set.getInt(1); } // else we aren't tracked yet! } } - catch (SQLException e) { - LOGGER.log(Level.SEVERE, "Unable to check last migration!", e); + catch (final SQLException exception) { + this.logger.log(Level.SEVERE, "Unable to check last migration!", exception); releaseLock(); return false; } - NavigableMap newApply = migrations.tailMap(currentLevel, false); + final NavigableMap newApply = this.migrations.tailMap(currentLevel, false); try { if (newApply.size() > 0) { - LOGGER.info(String.format("%s database is behind, %s migrations found", - plugin.getName(), newApply.size())); + this.logger.info(String.format("%s database is behind, %s migrations found", + this.plugin.getName(), newApply.size())); if (doMigrations(newApply)) { - LOGGER.info(plugin.getName() + " fully migrated."); + this.logger.info(this.plugin.getName() + " fully migrated."); } else { - LOGGER.warning(plugin.getName() + " failed to apply updates."); + this.logger.warning(this.plugin.getName() + " failed to apply updates."); return false; } } else { - LOGGER.info(plugin.getName() + " database is up to date."); + this.logger.info(this.plugin.getName() + " database is up to date."); } return true; } - catch (Exception exception) { - LOGGER.warning(plugin.getName() + " failed to apply updates for some reason..."); - LOGGER.log(Level.WARNING, "Full exception: ", exception); + catch (final Throwable exception) { + this.logger.warning(this.plugin.getName() + " failed to apply updates for some reason..."); + this.logger.log(Level.WARNING, "Full exception: ", exception); return false; } finally { @@ -332,60 +321,63 @@ public boolean updateDatabase() { } } - private boolean doMigrations(NavigableMap migrations) { + private boolean doMigrations(final NavigableMap migrations) { try { - for (Integer id : migrations.keySet()) { - LOGGER.info("Migration " + id + " ] Applying"); - Migration migration = migrations.get(id); + for (final Integer id : migrations.keySet()) { + this.logger.info("Migration " + id + " ] Applying"); + final Migration migration = migrations.get(id); if (migration == null) { continue; // huh? } if (doMigration(id, migration.migrations, migration.ignoreErrors, migration.postMigration)) { - LOGGER.info("Migration " + id + " ] Successful"); - try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement(RECORD_MIGRATION);) { - statement.setString(1, plugin.getName()); + this.logger.info("Migration " + id + " ] Successful"); + try (final Connection connection = getConnection(); + final PreparedStatement statement = connection.prepareStatement(RECORD_MIGRATION)) { + statement.setString(1, this.plugin.getName()); statement.setInt(2, id); if (statement.executeUpdate() < 1) { - LOGGER.warning("Might not have recorded migration " + id + " occurrence successfully."); + this.logger.warning("Might not have recorded migration " + id + " occurrence successfully."); } } - catch (SQLException exception) { - LOGGER.warning("Failed to record migration " + id + " occurrence successfully."); - LOGGER.log(Level.SEVERE, "Full Error: ", exception); + catch (final SQLException exception) { + this.logger.warning("Failed to record migration " + id + " occurrence successfully."); + this.logger.log(Level.SEVERE, "Full Error: ", exception); return false; } } else { - LOGGER.info("Migration " + id + " ] Failed"); + this.logger.info("Migration " + id + " ] Failed"); return false; } } return true; } - catch (Exception exception) { - LOGGER.log(Level.SEVERE, "Unexpected failure during migrations", exception); + catch (final Throwable exception) { + this.logger.log(Level.SEVERE, "Unexpected failure during migrations", exception); return false; } } - private boolean doMigration(Integer migration, List queries, boolean ignoreErrors, Callable post) { - try (Connection connection = getConnection();) { - for (String query : queries) { - try (Statement statement = connection.createStatement();) { + private boolean doMigration(final Integer migration, + final List queries, + final boolean ignoreErrors, + final Callable post) { + try (final Connection connection = getConnection()) { + for (final String query : queries) { + try (final Statement statement = connection.createStatement()) { statement.executeUpdate(query); if (!ignoreErrors) { // if we ignore errors we totally ignore warnings. SQLWarning warning = statement.getWarnings(); while (warning != null) { - LOGGER.warning("Migration " + migration + " ] Warning: " + warning.getMessage()); + this.logger.warning("Migration " + migration + " ] Warning: " + warning.getMessage()); // TODO: add verbose check warning = warning.getNextWarning(); } } } - catch (SQLException exception) { + catch (final SQLException exception) { if (ignoreErrors) { - LOGGER.warning("Migration " + migration + " ] Ignoring error: " + exception.getMessage()); + this.logger.warning("Migration " + migration + " ] Ignoring error: " + exception.getMessage()); } else { throw exception; @@ -393,40 +385,40 @@ private boolean doMigration(Integer migration, List queries, boolean ign } } } - catch (SQLException exception) { + catch (final SQLException exception) { if (ignoreErrors) { - LOGGER.warning("Migration " + migration + " ] Ignoring error: " + exception.getMessage()); + this.logger.warning("Migration " + migration + " ] Ignoring error: " + exception.getMessage()); } else { - LOGGER.warning("Migration " + migration + " ] Failed migration: " + exception.getMessage()); - LOGGER.log(Level.SEVERE, "Full Error: ", exception); + this.logger.warning("Migration " + migration + " ] Failed migration: " + exception.getMessage()); + this.logger.log(Level.SEVERE, "Full Error: ", exception); return false; } } if (post != null) { - Future doing = postExecutor.submit(post); + final Future doing = postExecutor.submit(post); try { if (doing.get()) { - LOGGER.info("Migration " + migration + " ] Post Call Complete"); + this.logger.info("Migration " + migration + " ] Post Call Complete"); } else { if (ignoreErrors) { - LOGGER.warning("Migration " + migration + " ] Post Call indicated failure; ignored."); + this.logger.warning("Migration " + migration + " ] Post Call indicated failure; ignored."); } else { - LOGGER.severe("Migration " + migration + " ] Post Call failed!"); + this.logger.severe("Migration " + migration + " ] Post Call failed!"); return false; } } } - catch (Exception exception) { + catch (final Throwable exception) { if (ignoreErrors) { - LOGGER.warning("Migration " + migration + " ] Post Call indicated failure; ignored: " + + this.logger.warning("Migration " + migration + " ] Post Call indicated failure; ignored: " + exception.getMessage()); } else { - LOGGER.severe("Migration " + migration + " ] Post Call failed!"); - LOGGER.log(Level.SEVERE, "Full Error: ", exception); + this.logger.severe("Migration " + migration + " ] Post Call failed!"); + this.logger.log(Level.SEVERE, "Full Error: ", exception); return false; } } @@ -440,14 +432,14 @@ private boolean doMigration(Integer migration, List queries, boolean ign * @return Returns true if the plugin has an entry in the migrations table; false for any other outcome. */ public boolean isManaged() { - try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement(CHECK_LAST_MIGRATION);) { + try (final Connection connection = getConnection(); + final PreparedStatement statement = connection.prepareStatement(CHECK_LAST_MIGRATION)) { statement.setString(1, plugin.getName()); - try (ResultSet set = statement.executeQuery();) { + try (final ResultSet set = statement.executeQuery()) { return set.next(); } } - catch (SQLException e) { + catch (final SQLException ignored) { return false; } } @@ -469,27 +461,28 @@ public boolean isManaged() { */ private boolean checkWaitLock() throws TimeoutException, SQLException { /* First, cleanup old locks if any */ - try (Connection connection = getConnection(); Statement cleanup = connection.createStatement();) { + try (final Connection connection = getConnection(); + final Statement cleanup = connection.createStatement()) { cleanup.executeUpdate(CLEANUP_LOCK_TABLE); } - catch (SQLException exception) { - LOGGER.severe("Unable to cleanup old locks, error encountered!"); + catch (final SQLException exception) { + this.logger.severe("Unable to cleanup old locks, error encountered!"); throw exception; } /* Now get our own lock */ long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < MAX_WAIT_FOR_LOCK) { - try (Connection connection = getConnection(); - PreparedStatement tryAcquire = connection.prepareStatement(ACQUIRE_LOCK);) { - tryAcquire.setString(1, plugin.getName()); + try (final Connection connection = getConnection(); + final PreparedStatement tryAcquire = connection.prepareStatement(ACQUIRE_LOCK)) { + tryAcquire.setString(1, this.plugin.getName()); int hasLock = tryAcquire.executeUpdate(); if (hasLock > 0) { - LOGGER.info("Lock acquired, proceeding."); + this.logger.info("Lock acquired, proceeding."); return true; } } - catch (SQLException failToAcquire) { - LOGGER.severe("Unable to acquire a lock, error encountered!"); + catch (final SQLException failToAcquire) { + this.logger.severe("Unable to acquire a lock, error encountered!"); // let the exception continue so we return right away; only errors we'd encounter here are terminal. throw failToAcquire; } @@ -499,7 +492,7 @@ private boolean checkWaitLock() throws TimeoutException, SQLException { try { Thread.sleep(WAIT_PERIOD); } - catch (InterruptedException ignored) { + catch (final InterruptedException ignored) { // Someone wants us to check right away. } } @@ -507,19 +500,19 @@ private boolean checkWaitLock() throws TimeoutException, SQLException { } private void releaseLock() { - try (Connection connection = getConnection(); - PreparedStatement release = connection.prepareStatement(RELEASE_LOCK);) { + try (final Connection connection = getConnection(); + final PreparedStatement release = connection.prepareStatement(RELEASE_LOCK)) { release.setString(1, plugin.getName()); int releaseLock = release.executeUpdate(); if (releaseLock < 1) { - LOGGER.warning("Attempted to release a lock, already released."); + this.logger.warning("Attempted to release a lock, already released."); } else { - LOGGER.info("Lock released."); + this.logger.info("Lock released."); } } - catch (SQLException exception) { - LOGGER.log(Level.WARNING, "Attempted to release lock; failed. This may interrupt startup for other " + + catch (final SQLException exception) { + this.logger.log(Level.WARNING, "Attempted to release lock; failed. This may interrupt startup for other " + "servers working against this database.", exception); } } @@ -533,7 +526,7 @@ private void releaseLock() { * @throws SQLException If the pool has gone away, database is not connected, or other error has occurred. */ public Connection getConnection() throws SQLException { - return connections.getConnection(); + return this.connections.getConnection(); } /** @@ -542,48 +535,13 @@ public Connection getConnection() throws SQLException { * @throws SQLException Something went horribly wrong. */ public void close() throws SQLException { - connections.close(); + this.connections.close(); } - private static class Migration { - public List migrations; - public boolean ignoreErrors; - public Callable postMigration; + private static record Migration(boolean ignoreErrors, Callable postMigration, List migrations) { public Migration(boolean ignoreErrors, Callable postMigration, String... migrations) { - this.migrations = MoreCollectionUtils.collect(ArrayList::new, migrations); - this.ignoreErrors = ignoreErrors; - this.postMigration = postMigration; - } - } - - @Override - public Map serialize() { - Map data = new HashMap<>(); - data.put("plugin", this.plugin.getName()); - data.putAll(this.credentials.serialize()); - return data; - } - - public static ManagedDatasource deserialize(Map data) { - if (MapUtils.isEmpty(data)) { - LOGGER.info("Database not defined."); - return null; - } - String pluginName = MoreMapUtils.attemptGet(data, "", "plugin"); - if (Strings.isNullOrEmpty(pluginName)) { - LOGGER.warning("Config defined ManagedDatasource did not specify a plugin, which is required."); - return null; - } - Plugin plugin = Bukkit.getPluginManager().getPlugin(pluginName); - if (plugin == null || !plugin.isEnabled()) { - LOGGER.warning("Config defined ManagedDatasource did not specify a loaded plugin, is it correct?"); - return null; - } - if (!ACivMod.class.isAssignableFrom(plugin.getClass())) { - LOGGER.warning("ManagedDatasource only supports ACivMod plugins."); - return null; + this(ignoreErrors, postMigration, MoreCollectionUtils.collect(ArrayList::new, migrations)); } - return new ManagedDatasource((ACivMod) plugin, Objects.requireNonNull(DatabaseCredentials.deserialize(data))); } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java index 0621b2d2..129b5d96 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java @@ -1,13 +1,16 @@ package vg.civcraft.mc.civmodcore.entities; import com.google.common.base.Strings; -import org.bukkit.entity.Entity; +import javax.annotation.Nonnull; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; /** * Class of static APIs for Entities. */ +@UtilityClass public final class EntityUtils { /** @@ -24,34 +27,23 @@ public static EntityType getEntityType(final String value) { try { return EntityType.valueOf(value.toUpperCase()); } - catch (Exception ignored) { } + catch (final Throwable ignored) { } try { final EntityType type = EntityType.fromId(Short.parseShort(value)); if (type != null) { return type; } } - catch (final Exception ignored) { } + catch (final Throwable ignored) { } return null; } /** - * Checks whether an entity is a player. - * - * @param entity The entity to test. - * @return Returns true if the entity is a player. + * @param entityType The entity type to translate. + * @return Returns a translatable component based on the given entity type. */ - public static boolean isPlayer(final Entity entity) { - if (entity == null) { - return false; - } - if (entity.getType() != EntityType.PLAYER) { - return false; - } - if (!(entity instanceof Player)) { - return false; - } - return true; + public static TranslatableComponent asTranslatable(@Nonnull final EntityType entityType) { + return Component.translatable(entityType.translationKey()); } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java index 8354095f..79c37d80 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java @@ -5,6 +5,8 @@ import java.util.ListIterator; import java.util.Spliterator; import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -13,9 +15,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import vg.civcraft.mc.civmodcore.util.MoreArrayUtils; +import vg.civcraft.mc.civmodcore.utilities.MoreArrayUtils; /** * Wrapper for cloned inventories intended to ensure that ClonedInventories aren't themselves cloned. @@ -58,15 +58,15 @@ public HashMap addItem(final ItemStack... items) throws Ille return this.inventory.addItem(items); } - @NotNull + @Nonnull @Override public HashMap removeItem(final ItemStack... items) throws IllegalArgumentException { return this.inventory.removeItem(items); } - @NotNull + @Nonnull @Override - public HashMap removeItemAnySlot(@NotNull ItemStack... items) throws IllegalArgumentException { + public HashMap removeItemAnySlot(@Nonnull ItemStack... items) throws IllegalArgumentException { return this.inventory.removeItemAnySlot(items); } @@ -91,7 +91,7 @@ public void setStorageContents(final ItemStack[] items) throws IllegalArgumentEx } @Override - public boolean contains(@NotNull final Material material) throws IllegalArgumentException { + public boolean contains(@Nonnull final Material material) throws IllegalArgumentException { return this.inventory.contains(material); } @@ -101,7 +101,7 @@ public boolean contains(final ItemStack item) { } @Override - public boolean contains(@NotNull final Material material, final int amount) throws IllegalArgumentException { + public boolean contains(@Nonnull final Material material, final int amount) throws IllegalArgumentException { return this.inventory.contains(material, amount); } @@ -116,7 +116,7 @@ public boolean containsAtLeast(final ItemStack item, final int amount) { } @Override - public HashMap all(@NotNull final Material material) throws IllegalArgumentException { + public HashMap all(@Nonnull final Material material) throws IllegalArgumentException { return this.inventory.all(material); } @@ -126,12 +126,12 @@ public boolean containsAtLeast(final ItemStack item, final int amount) { } @Override - public int first(@NotNull final Material material) throws IllegalArgumentException { + public int first(@Nonnull final Material material) throws IllegalArgumentException { return this.inventory.first(material); } @Override - public int first(@NotNull final ItemStack item) { + public int first(@Nonnull final ItemStack item) { return this.inventory.first(item); } @@ -146,12 +146,12 @@ public boolean isEmpty() { } @Override - public void remove(@NotNull final Material material) throws IllegalArgumentException { + public void remove(@Nonnull final Material material) throws IllegalArgumentException { this.inventory.remove(material); } @Override - public void remove(@NotNull final ItemStack item) { + public void remove(@Nonnull final ItemStack item) { this.inventory.remove(item); } @@ -165,6 +165,11 @@ public void clear() { this.inventory.clear(); } + @Override + public int close() { + return this.inventory.close(); + } + @Override public List getViewers() { return this.inventory.getViewers(); @@ -186,7 +191,7 @@ public InventoryHolder getHolder(boolean useSnapshot) { return this.inventory.getHolder(useSnapshot); } - @NotNull + @Nonnull @Override public ListIterator iterator() { return this.inventory.iterator(); @@ -231,10 +236,24 @@ public Inventory getInventory() { * @return Returns a clone of the given inventory. */ public static ClonedInventory cloneInventory(final Inventory inventory) { + return cloneInventory(inventory, false); + } + + /** + *

Clones the given inventory for the purpose of test manipulating its contents.

+ * + *

Note: Do not type check the inventory, it's JUST a contents copy within an inventory wrapper to provide the + * relevant and useful methods.

+ * + * @param inventory The inventory to clone. + * @param forceClone Determines whether the inventory should be cloned even if it's an already cloned inventory. + * @return Returns a clone of the given inventory. + */ + public static ClonedInventory cloneInventory(final Inventory inventory, final boolean forceClone) { if (inventory == null) { return null; } - if (inventory instanceof ClonedInventory) { + if (!forceClone && inventory instanceof ClonedInventory) { return (ClonedInventory) inventory; } Inventory clone; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java index 094adfe7..2f1749f1 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java @@ -2,32 +2,39 @@ import com.google.common.base.Preconditions; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; import org.apache.commons.lang3.ArrayUtils; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.util.MoreArrayUtils; +@UtilityClass public final class InventoryUtils { + public static final int CHEST_1_ROW = 9; + public static final int CHEST_2_ROWS = 9 * 2; + public static final int CHEST_3_ROWS = 9 * 3; + public static final int CHEST_4_ROWS = 9 * 4; + public static final int CHEST_5_ROWS = 9 * 5; + public static final int CHEST_6_ROWS = 9 * 6; + /** * Tests an inventory to see if it's valid. * * @param inventory The inventory to test. * @return Returns true if it's more likely than not valid. */ - public static boolean isValidInventory(final Inventory inventory) { - if (inventory == null) { - return false; - } - if (inventory.getSize() <= 0) { - return false; - } - return true; + public static boolean isValidInventory(@Nullable final Inventory inventory) { + return inventory != null + && inventory.getSize() > 0; } /** @@ -35,10 +42,13 @@ public static boolean isValidInventory(final Inventory inventory) { * * @param inventory The inventory to get the viewers of. * @return Returns a list of players. Returns an empty list if the inventory is null. + * + * @deprecated Use Java 16's instanceof pattern matching instead. */ - public static List getViewingPlayers(final Inventory inventory) { + @Deprecated + public static List getViewingPlayers(@Nullable final Inventory inventory) { if (!isValidInventory(inventory)) { - return new ArrayList<>(); + return new ArrayList<>(0); } return inventory.getViewers().stream() .filter(entity -> entity instanceof Player) @@ -52,15 +62,15 @@ public static List getViewingPlayers(final Inventory inventory) { * @param inventory The inventory to attempt to find a slot in. * @param item The item to find a place for. * @return Returns an index of a slot that it's safe to add to. A return value of -1 means no safe place. Even if - * the return value is -1 it may still be possible to add the item stack to the inventory, as this - * function attempts to find the first slot that the given item stack can fit into wholly; that if it can - * technically fit but has to be distributed then there's no "first empty". + * the return value is -1 it may still be possible to add the item stack to the inventory, as this + * function attempts to find the first slot that the given item stack can fit into wholly; that if it can + * technically fit but has to be distributed then there's no "first empty". */ - public static int firstEmpty(final Inventory inventory, final ItemStack item) { + public static int firstEmpty(@Nullable final Inventory inventory, final ItemStack item) { if (inventory == null) { return -1; } - // If there's a slot free, then just return that. Otherwise if + // If there's a slot free, then just return that. Otherwise, if // the item is invalid, just return whatever slot was returned. final int index = inventory.firstEmpty(); if (index >= 0 || !ItemUtils.isValidItem(item)) { @@ -88,9 +98,10 @@ public static int firstEmpty(final Inventory inventory, final ItemStack item) { * * @param inventory The inventory to clear of items. */ - public static void clearInventory(final Inventory inventory) { - Preconditions.checkArgument(isValidInventory(inventory)); - inventory.setContents(MoreArrayUtils.fill(inventory.getContents(), null)); + public static void clearInventory(@Nonnull final Inventory inventory) { + final ItemStack[] contents = inventory.getContents(); + Arrays.fill(contents, new ItemStack(Material.AIR)); + inventory.setContents(contents); } /** @@ -99,11 +110,8 @@ public static void clearInventory(final Inventory inventory) { * @param inventory The inventory to check. * @return Returns true if an inventory has multiple viewers. */ - public static boolean hasOtherViewers(final Inventory inventory) { - if (!isValidInventory(inventory)) { - return false; - } - return inventory.getViewers().size() > 1; + public static boolean hasOtherViewers(@Nullable final Inventory inventory) { + return inventory != null && inventory.getViewers().size() > 1; } /** @@ -113,13 +121,9 @@ public static boolean hasOtherViewers(final Inventory inventory) { * @return Returns true if the slot count is or between 1-6 multiples of 9. */ public static boolean isValidChestSize(final int slots) { - if (slots <= 0 || slots > 54) { - return false; - } - if ((slots % 9) > 0) { - return false; - } - return true; + return slots > 0 + && slots <= 54 + && (slots % 9) == 0; } /** diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/RecipeManager.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/RecipeManager.java index 3fb24f73..37b18290 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/RecipeManager.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/RecipeManager.java @@ -1,14 +1,15 @@ package vg.civcraft.mc.civmodcore.inventory; -import java.util.Iterator; +import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; import org.bukkit.Keyed; import org.bukkit.inventory.Recipe; -import vg.civcraft.mc.civmodcore.util.NullUtils; +import vg.civcraft.mc.civmodcore.utilities.NullUtils; /** * Class of utility functions for Recipes. */ +@UtilityClass public final class RecipeManager { /** @@ -22,13 +23,13 @@ public final class RecipeManager { * @return Returns true if the other recipe matches the base. */ public static boolean matchRecipe(final Recipe base, final Recipe other) { - if (base == null) { - return false; + if (base == other) { + return true; } - if (base instanceof Keyed && other instanceof Keyed) { + if (base instanceof Keyed baseKeyed && other instanceof Keyed otherKeyed) { return NullUtils.equalsNotNull( - ((Keyed) base).getKey(), - ((Keyed) other).getKey()); + baseKeyed.getKey(), + otherKeyed.getKey()); } return false; } @@ -51,7 +52,7 @@ public static boolean registerRecipe(final Recipe recipe) { try { return Bukkit.getServer().addRecipe(recipe); } - catch (Exception exception) { + catch (final Throwable exception) { return false; } } @@ -66,13 +67,11 @@ public static boolean removeRecipe(final Recipe recipe) { if (recipe == null) { return false; } - final Iterator iterator = Bukkit.getServer().recipeIterator(); - while (iterator.hasNext()) { - if (!matchRecipe(recipe, iterator.next())) { - continue; + for (final var iterator = Bukkit.getServer().recipeIterator(); iterator.hasNext();) { + if (matchRecipe(recipe, iterator.next())) { + iterator.remove(); + break; } - iterator.remove(); - return true; } return true; } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/AnimatedClickable.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/AnimatedClickable.java similarity index 96% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/AnimatedClickable.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/AnimatedClickable.java index 94d7c529..cccc7c32 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/AnimatedClickable.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/AnimatedClickable.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui; +package vg.civcraft.mc.civmodcore.inventory.gui; import java.util.List; import org.bukkit.entity.Player; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/Clickable.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/Clickable.java similarity index 92% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/Clickable.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/Clickable.java index 4b353a98..f1ad4fcb 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/Clickable.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/Clickable.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui; +package vg.civcraft.mc.civmodcore.inventory.gui; import org.bukkit.inventory.ItemStack; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/ClickableInventory.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/ClickableInventory.java similarity index 92% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/ClickableInventory.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/ClickableInventory.java index 3b484327..89ebf0cd 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/ClickableInventory.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/ClickableInventory.java @@ -1,9 +1,8 @@ -package vg.civcraft.mc.civmodcore.inventorygui; +package vg.civcraft.mc.civmodcore.inventory.gui; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.logging.Logger; import org.bukkit.Bukkit; @@ -14,6 +13,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitTask; +import vg.civcraft.mc.civmodcore.CivModCorePlugin; /** * Represents an inventory filled with Clickables. Whenever one of those is @@ -172,13 +172,32 @@ public Inventory getInventory() { /** * Shows a player the inventory of this instance with all of its clickables + * Same as calling {@code showInventory(p, true)} * * @param p Player to show the inventory to */ public void showInventory(Player p) { + showInventory(p, true); + } + + /** + * Shows a player the inventory of this instance with all of its clickables + * Same as calling {@code showInventory(p, true)} + * + * @param p Player to show the inventory to + * @param eventSafe should be true if called in any code triggered by an InventoryEvent + */ + public void showInventory(Player p, boolean eventSafe) { if (p != null) { - p.openInventory(inventory); - openInventories.put(p.getUniqueId(), this); + if (eventSafe) { + Bukkit.getScheduler().runTask(CivModCorePlugin.getInstance(), () -> { + p.openInventory(inventory); + openInventories.put(p.getUniqueId(), this); + }); + } else { + p.openInventory(inventory); + openInventories.put(p.getUniqueId(), this); + } applyCooldown(p); } } @@ -188,7 +207,7 @@ public void showInventory(Player p) { * currently open and syncs it with the internal representation. */ public void updateInventory() { - for (Map.Entry c : openInventories.entrySet()) { + for (var c : new ArrayList<>(openInventories.entrySet())) { if (c.getValue() == this) { Player p = Bukkit.getPlayer(c.getKey()); p.updateInventory(); diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/ClickableInventoryListener.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/ClickableInventoryListener.java similarity index 74% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/ClickableInventoryListener.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/ClickableInventoryListener.java index f0b915c8..34896a75 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/ClickableInventoryListener.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/ClickableInventoryListener.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui; +package vg.civcraft.mc.civmodcore.inventory.gui; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -6,7 +6,6 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.player.PlayerQuitEvent; -import vg.civcraft.mc.civmodcore.entities.EntityUtils; /** * The listener which makes ClickableInventories work. To use this either register it as a listener in your plugin or @@ -18,10 +17,9 @@ public class ClickableInventoryListener implements Listener { @EventHandler public void inventoryClick(InventoryClickEvent event) { - if (!EntityUtils.isPlayer(event.getWhoClicked())) { + if (!(event.getWhoClicked() instanceof Player player)) { return; } - Player player = (Player) event.getWhoClicked(); ClickableInventory inventory = ClickableInventory.getOpenInventory(player); if (inventory != null) { event.setCancelled(true); // always cancel first to prevent dupes @@ -31,10 +29,10 @@ public void inventoryClick(InventoryClickEvent event) { @EventHandler public void inventoryClose(InventoryCloseEvent event) { - if (!EntityUtils.isPlayer(event.getPlayer())) { // Despite the name, it's not necessarily a player + if (!(event.getPlayer() instanceof Player player)) { // Despite the name, it's not necessarily a player return; } - ClickableInventory.inventoryWasClosed((Player) event.getPlayer()); + ClickableInventory.inventoryWasClosed(player); } @EventHandler diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/DecorationStack.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/DecorationStack.java similarity index 93% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/DecorationStack.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/DecorationStack.java index c7426ff3..e8ef3cf9 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/DecorationStack.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/DecorationStack.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui; +package vg.civcraft.mc.civmodcore.inventory.gui; import org.bukkit.Material; import org.bukkit.entity.Player; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/IClickable.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/IClickable.java new file mode 100644 index 00000000..90fc82ad --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/IClickable.java @@ -0,0 +1,130 @@ +package vg.civcraft.mc.civmodcore.inventory.gui; + +import javax.annotation.Nonnull; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public abstract class IClickable { + + /** + * @return Which item stack represents this clickable when it is initially loaded into the inventory. + */ + @Nonnull + public abstract ItemStack getItemStack(); + + /** + * Called when this instance is added to an inventory so it can do something if + * desired + * + * @param inventory Inventory it was added to + * @param slot Slot in which it was added + */ + public abstract void addedToInventory(@Nonnull final ClickableInventory inventory, final int slot); + + /** + * General method called whenever this clickable is clicked with a type that did + * not a have a special implementation provided + * + * @param clicker Player who clicked + */ + protected abstract void clicked(@Nonnull final Player clicker); + + /** + * Called when a player double clicks this clickable, overwrite to define + * special behavior for this + * + * @param clicker Player who clicked + */ + protected void onDoubleClick(@Nonnull final Player clicker) { + clicked(clicker); + } + + /** + * Called when a player drops one of this clickable (in default keybinds + * pressing Q while hovering the slot), overwrite to define special behavior for + * this + * + * @param clicker Player who clicked + */ + protected void onDrop(@Nonnull final Player clicker) { + clicked(clicker); + } + + /** + * Called when a player drops this clickable stack (in default keybinds pressing + * Q while holding CTRL and hovering the slot), overwrite to define special + * behavior for this + * + * @param clicker Player who clicked + */ + protected void onControlDrop(@Nonnull final Player clicker) { + clicked(clicker); + } + + /** + * Called when a player left clicks this clickable, overwrite to define + * special behavior for this + * + * @param clicker Player who clicked + */ + protected void onLeftClick(@Nonnull final Player clicker) { + clicked(clicker); + } + + /** + * Called when a player right clicks this clickable, overwrite to define + * special behavior for this + * + * @param clicker Player who clicked + */ + protected void onRightClick(@Nonnull final Player clicker) { + clicked(clicker); + } + + /** + * Called when a player middle (mouse wheell) clicks this clickable, overwrite to define + * special behavior for this + * + * @param clicker Player who clicked + */ + protected void onMiddleClick(@Nonnull final Player clicker) { + clicked(clicker); + } + + /** + * Called when a player left clicks this clickable while holding shift, overwrite to define + * special behavior for this + * + * @param clicker Player who clicked + */ + protected void onShiftLeftClick(@Nonnull final Player clicker) { + clicked(clicker); + } + + /** + * Called when a player right clicks this clickable while holding shift, overwrite to define + * special behavior for this + * + * @param clicker Player who clicked + */ + protected void onShiftRightClick(@Nonnull final Player clicker) { + clicked(clicker); + } + + public void handleClick(@Nonnull final Player clicker, + @Nonnull final ClickType type) { + switch (type) { + case CONTROL_DROP -> onControlDrop(clicker); + case DOUBLE_CLICK -> onDoubleClick(clicker); + case DROP -> onDrop(clicker); + case LEFT -> onLeftClick(clicker); + case MIDDLE -> onMiddleClick(clicker); + case RIGHT -> onRightClick(clicker); + case SHIFT_LEFT -> onShiftLeftClick(clicker); + case SHIFT_RIGHT -> onShiftRightClick(clicker); + default -> clicked(clicker); + } + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/LClickable.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/LClickable.java similarity index 96% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/LClickable.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/LClickable.java index 317ee467..a2bfa5af 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/LClickable.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/LClickable.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui; +package vg.civcraft.mc.civmodcore.inventory.gui; import java.util.function.Consumer; import org.bukkit.Material; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/MultiPageView.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/MultiPageView.java similarity index 98% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/MultiPageView.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/MultiPageView.java index 957acf04..1bc31196 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/MultiPageView.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/MultiPageView.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui; +package vg.civcraft.mc.civmodcore.inventory.gui; import java.util.List; import org.bukkit.ChatColor; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MenuPagination.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MenuPagination.java new file mode 100644 index 00000000..78e04292 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MenuPagination.java @@ -0,0 +1,241 @@ +package vg.civcraft.mc.civmodcore.inventory.gui.canvas; + +import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.apache.commons.collections4.CollectionUtils; +import org.bukkit.entity.Player; +import org.ipvp.canvas.slot.SlotSettings; +import org.ipvp.canvas.type.AbstractCivMenu; + +/** + * Better version of {@link org.ipvp.canvas.paginate.PaginatedMenuBuilder} as it allows you to apply pagination to an + * already existing menu, rather than using a builder to spawn a bunch of new menu instances that'll just be used by a + * single player before promptly discarded. + */ +public final class MenuPagination { + + private final AbstractCivMenu menu; + private final Map playerPages; + private final List menuBar; + private final ElementSupplier supplier; + private final int buttonSlots; + + private MenuPagination(final AbstractCivMenu menu, + final ElementSupplier supplier, + final List menuBar) { + this.menu = Objects.requireNonNull(menu, "Menu cannot be null"); + final int rows = menu.getDimensions().getRows(); + if (rows < 2 || rows > 5) { + throw new IllegalArgumentException("Row count must be between 2 and 5 inclusively"); + } + this.playerPages = new Object2ObjectAVLTreeMap<>(); + this.menuBar = List.copyOf(menuBar); + if (menuBar.size() > 9) { + throw new IllegalArgumentException("Bar cannot contain more than 9 buttons"); + } + this.supplier = Objects.requireNonNull(supplier, "Element supplier cannot be null"); + this.buttonSlots = (rows * 9) - 9; + + MenuUtils.overrideOpenHandler(menu, (viewer, opened) -> { + final var pager = INTERNAL_getPlayerPager(viewer); + pager.pageNumber = 0; + pager.elements = this.supplier.generate(viewer, opened); + INTERNAL_applyPlayerPage(viewer, opened, INTERNAL_getPlayerPager(viewer)); + }, true); + + MenuUtils.overrideUpdateHandler(menu, (viewer, updated) -> { + INTERNAL_applyPlayerPage(viewer, updated, INTERNAL_getPlayerPager(viewer)); + }, true); + + menu.setCallCloseHandlerRegardless(true); + MenuUtils.overrideCloseHandler(menu, (viewer, ignored) -> { + this.playerPages.remove(viewer.getUniqueId()); + }, true); + + for (int i = 0; i < this.buttonSlots; i++) { + menu.getSlot(i).setClickHandler((viewer, click) -> { + final var pager = INTERNAL_getPlayerPager(viewer); + final int buttonIndex = INTERNAL_calculateElementIndex(pager, click.getClickedSlot().getIndex()); + if (buttonIndex >= pager.elements.size()) { + return; + } + final var handler = pager.elements.get(buttonIndex).getClickHandler(); + if (handler == null) { + return; + } + handler.click(viewer, click); + }); + } + + for (int i = 0; i < 9; i++) { + final var slotIndex = INTERNAL_calculateBarIndex(i); + if (i >= this.menuBar.size()) { + menu.getSlot(slotIndex).setSettings(MenuUtils.createEmptySlot()); + break; + } + final var barElement = this.menuBar.get(i); + if (barElement == null) { + menu.getSlot(slotIndex).setSettings(MenuUtils.createEmptySlot()); + continue; + } + menu.getSlot(slotIndex).setSettings(barElement); + } + } + + /** + * @param viewer The viewer to send to the next page. + */ + public void nextPage(final Player viewer) { + final var pager = INTERNAL_getPlayerPager(viewer); + pager.pageNumber++; + this.menu.update(viewer); + } + + /** + * @param viewer The viewer to send to the previous page. + */ + public void previousPage(final Player viewer) { + final var pager = INTERNAL_getPlayerPager(viewer); + pager.pageNumber--; + this.menu.update(viewer); + } + + /** + * @param viewer The viewer to check if they have a next page. + * @return Returns whether the viewer has a next page. + */ + public boolean hasNextPage(final Player viewer) { + final var pager = INTERNAL_getPlayerPager(viewer); + final int elementsSize = CollectionUtils.size(pager.elements); + final int totalPages = elementsSize / this.buttonSlots; + if (totalPages == 0) { + return false; + } + return pager.pageNumber >= totalPages; + } + + /** + * @param viewer The viewer to check if they have a previous page. + * @return Returns whether the viewer has a previous page. + */ + public boolean hasPreviousPage(final Player viewer) { + final var pager = INTERNAL_getPlayerPager(viewer); + final int elementsSize = CollectionUtils.size(pager.elements); + final int totalPages = elementsSize / this.buttonSlots; + if (totalPages == 0) { + return false; + } + return pager.pageNumber < totalPages; + } + + protected void INTERNAL_applyPlayerPage(final Player viewer, + final AbstractCivMenu menu, + final INTERNAL_PlayerPager pager) { + pager.pageNumber = Math.max(pager.pageNumber, 0); + pager.pageNumber = Math.min(pager.pageNumber, pager.elements.size() / this.buttonSlots); + for (int i = 0; i < this.buttonSlots; i++) { + final int buttonIndex = INTERNAL_calculateElementIndex(pager, i); + if (buttonIndex >= pager.elements.size()) { + menu.getSlot(i).setRawItem(viewer, null); + continue; + } + final var template = pager.elements.get(buttonIndex).getItemTemplate(); + if (template == null) { + menu.getSlot(i).setRawItem(viewer, null); + continue; + } + menu.getSlot(i).setRawItem(viewer, template.getItem(viewer)); + } + } + + protected INTERNAL_PlayerPager INTERNAL_getPlayerPager(final Player viewer) { + return this.playerPages.computeIfAbsent(viewer.getUniqueId(), (uuid) -> new INTERNAL_PlayerPager()); + } + + protected int INTERNAL_calculateElementIndex(final INTERNAL_PlayerPager pager, final int slot) { + return (pager.pageNumber * this.buttonSlots) + slot; + } + + protected int INTERNAL_calculateBarIndex(final int slot) { + return this.buttonSlots + slot; + } + + protected static class INTERNAL_PlayerPager { + private int pageNumber; + private List elements; + } + + @FunctionalInterface + public interface ElementSupplier { + List generate(Player viewer, AbstractCivMenu menu); + } + + /** + * @return Returns a new pagination builder. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private AbstractCivMenu menu; + private ElementSupplier supplier; + private final SlotSettings[] menuBar; + + Builder() { + this.menuBar = new SlotSettings[] { + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot(), + MenuUtils.createEmptySlot() + }; + } + + /** + * @param menu The menu to paginate. + * @return Returns this builder. + */ + public Builder setMenu(final AbstractCivMenu menu) { + this.menu = menu; + return this; + } + + /** + * @param supplier Supplier method to return all the elements to be paginated. + * @return Returns this builder. + */ + public Builder setElementSupplier(final ElementSupplier supplier) { + this.supplier = supplier; + return this; + } + + /** + * @param index The index of the menu bar element from 0-8 inclusively. + * @param element The menu bar element to add. + * @return Returns this builder. + */ + public Builder setMenuBarElement(final int index, final SlotSettings element) { + this.menuBar[index] = element == null ? MenuUtils.createEmptySlot() : element; + return this; + } + + /** + * @return Returns a constructed menu pagination based on this builder. + */ + public MenuPagination build() { + return new MenuPagination(this.menu, this.supplier, Arrays.asList(this.menuBar)); + } + + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MenuUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MenuUtils.java new file mode 100644 index 00000000..8fc3eb71 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MenuUtils.java @@ -0,0 +1,104 @@ +package vg.civcraft.mc.civmodcore.inventory.gui.canvas; + +import lombok.experimental.UtilityClass; +import org.ipvp.canvas.Menu; +import org.ipvp.canvas.slot.SlotSettings; +import org.ipvp.canvas.type.AbstractCivMenu; +import org.ipvp.canvas.type.OpenHandler; +import org.ipvp.canvas.type.UpdateHandler; + +@UtilityClass +public final class MenuUtils { + + /** + * @return Returns an empty, unmodifiable slot. + */ + public static SlotSettings createEmptySlot() { + return SlotSettings.builder().build(); + } + + /** + * Overrides a given menu's open event handler, in lieu of just replacing it, meaning that you can add handlers to + * a menu without fear of removing any previously added functionality. + * + * @param menu The menu to add the handler for. + * @param handler The handler to add. + * @param callSuperBefore Whether or not to call the existing handler before or after the given handler. + */ + public static void overrideOpenHandler(final AbstractCivMenu menu, + final OpenHandler handler, + final boolean callSuperBefore) { + final var findExisting = menu.getOpenHandler(); + if (findExisting.isEmpty()) { + menu.setOpenHandler(handler); + return; + } + final var existing = findExisting.get(); + menu.setOpenHandler((viewer, openedMenu) -> { + if (callSuperBefore) { + existing.handle(viewer, openedMenu); + } + handler.handle(viewer, openedMenu); + if (!callSuperBefore) { + existing.handle(viewer, openedMenu); + } + }); + } + + /** + * Overrides a given menu's update event handler, in lieu of just replacing it, meaning that you can add handlers + * to a menu without fear of removing any previously added functionality. + * + * @param menu The menu to add the handler for. + * @param handler The handler to add. + * @param callSuperBefore Whether or not to call the existing handler before or after the given handler. + */ + public static void overrideUpdateHandler(final AbstractCivMenu menu, + final UpdateHandler handler, + final boolean callSuperBefore) { + final var findExisting = menu.getUpdateHandler(); + if (findExisting.isEmpty()) { + menu.setUpdateHandler(handler); + return; + } + final var existing = findExisting.get(); + menu.setUpdateHandler((viewer, openedMenu) -> { + if (callSuperBefore) { + existing.handle(viewer, openedMenu); + } + handler.handle(viewer, openedMenu); + if (!callSuperBefore) { + existing.handle(viewer, openedMenu); + } + }); + } + + /** + * Overrides a given menu's close event handler, in lieu of just replacing it, meaning that you can add handlers to + * a menu without fear of removing any previously added functionality. + * + * @param menu The menu to add the handler for. + * @param handler The handler to add. + * @param callSuperBefore Whether or not to call the existing handler before or after the given handler. + */ + public static void overrideCloseHandler(final AbstractCivMenu menu, + final Menu.CloseHandler handler, + final boolean callSuperBefore) { + final var findExisting = menu.getCloseHandler(); + if (findExisting.isEmpty()) { + menu.setCloseHandler(handler); + return; + } + final var existing = findExisting.get(); + menu.setCloseHandler((viewer, openedMenu) -> { + if (callSuperBefore) { + existing.close(viewer, openedMenu); + } + handler.close(viewer, openedMenu); + if (!callSuperBefore) { + existing.close(viewer, openedMenu); + } + }); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MultiPageCanvas.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MultiPageCanvas.java new file mode 100644 index 00000000..0b3375db --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/canvas/MultiPageCanvas.java @@ -0,0 +1,180 @@ +package vg.civcraft.mc.civmodcore.inventory.gui.canvas; + +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.ipvp.canvas.slot.SlotSettings; +import org.ipvp.canvas.type.AbstractCivMenu; +import org.ipvp.canvas.type.CivChestMenu; +import vg.civcraft.mc.civmodcore.inventory.InventoryUtils; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +/** + * Utility to automate creating views, which have multiple pages and automatically adjust their size + * + */ +public class MultiPageCanvas { + + private final Player viewer; + private final Component title; + private final List slots; + private final boolean adjustSize; + private final SlotSettings[] extraMenuItems; + + private AbstractCivMenu window; + private int currentPage; + + public MultiPageCanvas(@Nonnull final Player viewer, + @Nullable final Component title, + @Nonnull final List slots, + final boolean adjustSize) { + this.viewer = Objects.requireNonNull(viewer); + this.title = title; + this.slots = List.copyOf(slots); + this.adjustSize = adjustSize; + this.extraMenuItems = new SlotSettings[7]; + this.currentPage = 0; + } + + private void INTERNAL_constructInventory() { + // Clamp the total page within the valid range + if (this.currentPage < 0) { + this.currentPage = 0; + } + final int totalPages = INTERNAL_calculateTotalPages(); + if (this.currentPage >= totalPages) { + this.currentPage = totalPages - 1; + } + // Update the window instance if necessary + final int totalRows = INTERNAL_calculateTotalRows(); + if (this.window == null) { + this.window = new CivChestMenu(this.title, totalRows); + } + else { + this.window.clear(); + if (this.adjustSize && totalRows != this.window.getDimensions().getRows()) { + this.window = new CivChestMenu(this.title, totalRows); + } + } + // Add all content slots + final int contentOffset = this.currentPage * InventoryUtils.CHEST_5_ROWS; + final int contentLength = Math.min(this.slots.size() - contentOffset, InventoryUtils.CHEST_5_ROWS); + for (int i = 0; i < contentLength; i++) { + this.window.getSlot(i).setSettings(this.slots.get(contentOffset + i)); + } + final int totalSlots = this.window.getDimensions().getArea(); + // Back button + if (this.currentPage > 0) { + this.window.getSlot(totalSlots - 10).setSettings(SlotSettings.builder() + .itemTemplate((ignored) -> { + final var item = new ItemStack(Material.ARROW); + ItemUtils.handleItemMeta(item, (ItemMeta meta) -> { + meta.displayName(Component.text() + .color(NamedTextColor.GOLD) + .content("Go to previous page") + .build()); + return true; + }); + return item; + }) + .clickHandler((clicker, click) -> { + if (MultiPageCanvas.this.currentPage > 0) { + MultiPageCanvas.this.currentPage--; + } + showScreen(); + }) + .build()); + } + // Next button + if (this.currentPage < totalPages) { + this.window.getSlot(totalSlots - 1).setSettings(SlotSettings.builder() + .itemTemplate((ignored) -> { + final var item = new ItemStack(Material.ARROW); + ItemUtils.handleItemMeta(item, (ItemMeta meta) -> { + meta.displayName(Component.text() + .color(NamedTextColor.GOLD) + .content("Go to next page") + .build()); + return true; + }); + return item; + }) + .clickHandler((clicker, click) -> { + if (MultiPageCanvas.this.currentPage < totalPages) { + MultiPageCanvas.this.currentPage++; + } + showScreen(); + }) + .build()); + } + // Other menu items + for (int i = 0; i < this.extraMenuItems.length; i++) { + final SlotSettings slot = this.extraMenuItems[i]; + if (slot == null) { + continue; + } + this.window.getSlot(totalSlots - 9 + i).setSettings(slot); + } + } + + protected int INTERNAL_calculateTotalPages() { + final int size = this.slots.size(); + int pages = size / InventoryUtils.CHEST_5_ROWS; + if ((size % InventoryUtils.CHEST_5_ROWS) > 0) { + pages++; + } + return Math.max(pages, 0); + } + + private int INTERNAL_calculateTotalRows() { + if (!this.adjustSize) { + return 6; + } + final int clicks = this.slots.size(); + if (clicks >= InventoryUtils.CHEST_5_ROWS) { + return 6; + } + final int rows = (clicks / 9) + ((clicks % 9) > 0 ? 1 : 0); + return Math.max(rows, 2); // Always have at least 2 rows + } + + /** + * Shows the current page + */ + public void showScreen() { + INTERNAL_constructInventory(); + this.window.open(this.viewer); + } + + /** + * Allows setting a menu slot at the bottom of the GUI. Will not update the inventory, you'll need to do that + * manually with {@link #showScreen()}. + * + * @param slot The slot to set. + * @param index The index of the slot. Must be between 0 and 6 inclusively. + */ + public void setMenuSlot(@Nullable final SlotSettings slot, final int index) { + if (index < 0 || index > 6) { + throw new IllegalArgumentException("Index for MultiPageView menu must be between 0 and 6"); + } + this.extraMenuItems[index] = slot; + } + + /** + * Forcibly changes the page of this view. Will not update the inventory, you'll need to do that manually with + * {@link #showScreen()}. + * + * @param page The page to set. + */ + public void setPage(final int page) { + this.currentPage = page; + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ComponableInventory.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ComponableInventory.java similarity index 85% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ComponableInventory.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ComponableInventory.java index 40ba6269..ec66d6a7 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ComponableInventory.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ComponableInventory.java @@ -1,9 +1,9 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; +package vg.civcraft.mc.civmodcore.inventory.gui.components; import java.util.List; import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.inventorygui.ClickableInventory; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; public class ComponableInventory extends ComponableSection { @@ -43,7 +43,7 @@ public void setName(String name) { } @Override - void updateComponent(InventoryComponent component) { + protected void updateComponent(InventoryComponent component) { // copy of the implementation from ComponableSection, except that we also mirror // changes through to the ClickableInventory int offSet = 0; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ComponableSection.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ComponableSection.java similarity index 93% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ComponableSection.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ComponableSection.java index 7b9b5d38..3c9d557e 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ComponableSection.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ComponableSection.java @@ -1,9 +1,9 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; +package vg.civcraft.mc.civmodcore.inventory.gui.components; import java.util.ArrayList; import java.util.List; import java.util.function.IntPredicate; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; public class ComponableSection extends InventoryComponent { @@ -58,7 +58,7 @@ public void removeComponent(InventoryComponent component) { * Updates the displayed clickables for one specific contained component * @param component Component to update */ - void updateComponent(InventoryComponent component) { + protected void updateComponent(InventoryComponent component) { int offSet = 0; component.rebuild(); List componentContent = component.getContent(); @@ -84,7 +84,7 @@ public void clear() { } @Override - void rebuild() { + protected void rebuild() { // we use lists instead of maps here, because we expect the amount of components // to be very low, rarely to never above 5 List> builds = new ArrayList<>(); diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ContentAligner.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ContentAligner.java similarity index 54% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ContentAligner.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ContentAligner.java index cec3f530..f31d315c 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ContentAligner.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ContentAligner.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; +package vg.civcraft.mc.civmodcore.inventory.gui.components; public interface ContentAligner { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ContentAligners.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ContentAligners.java similarity index 90% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ContentAligners.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ContentAligners.java index fbadfffd..1ec06b41 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/ContentAligners.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/ContentAligners.java @@ -1,7 +1,9 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; +package vg.civcraft.mc.civmodcore.inventory.gui.components; import java.util.function.Function; +import lombok.experimental.UtilityClass; +@UtilityClass public class ContentAligners { public static ContentAligner getLeftAligned() { @@ -48,7 +50,7 @@ public static ContentAligner getCenteredInOrder(int contentAmount, int windowSiz if (contentAmount % rowLength == 0) { offset = 1; } - int lastElementLastCompleteRow = ((contentAmount / rowLength) + offset) * rowLength - 1; + int lastElementLastCompleteRow = (((contentAmount / rowLength) + offset) * rowLength - 1) + rowLength; return new Counter(i -> { // just increment until we reach the last element in the last full row @@ -56,10 +58,10 @@ public static ContentAligner getCenteredInOrder(int contentAmount, int windowSiz return i + 1; } else { // jump to offset start of last row - int lengthLastRow = contentAmount - lastElementLastCompleteRow - 1; + int lengthLastRow = (contentAmount + rowLength) - lastElementLastCompleteRow - 1; int emptySlots = rowLength - lengthLastRow; int leftOffset = Math.max(1, emptySlots / 2); - return i + leftOffset; + return (i + 1) + leftOffset; } }, defaultNum); } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/InventoryComponent.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/InventoryComponent.java similarity index 86% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/InventoryComponent.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/InventoryComponent.java index deaa50d9..9c4e38e5 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/InventoryComponent.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/InventoryComponent.java @@ -1,9 +1,9 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; +package vg.civcraft.mc.civmodcore.inventory.gui.components; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; public abstract class InventoryComponent { @@ -29,8 +29,8 @@ public InventoryComponent(int size) { public int getSize() { return size; } - - abstract void rebuild(); + + protected abstract void rebuild(); List getContent() { return Collections.unmodifiableList(content); diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/Scrollbar.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/Scrollbar.java similarity index 94% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/Scrollbar.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/Scrollbar.java index 8183e488..8f5a6cc4 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/Scrollbar.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/Scrollbar.java @@ -1,11 +1,11 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; +package vg.civcraft.mc.civmodcore.inventory.gui.components; import java.util.ArrayList; import java.util.List; import org.bukkit.ChatColor; import org.bukkit.Material; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; -import vg.civcraft.mc.civmodcore.inventorygui.LClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.LClickable; public class Scrollbar extends InventoryComponent { @@ -91,7 +91,7 @@ public void setPage(int page) { } @Override - void rebuild() { + protected void rebuild() { int size = getSize(); int contentIndex = scrollOffset * page; // subtract offset created through the next/previous button diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/SlotPredicates.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/SlotPredicates.java new file mode 100644 index 00000000..3a8faba7 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/SlotPredicates.java @@ -0,0 +1,79 @@ +package vg.civcraft.mc.civmodcore.inventory.gui.components; + +import java.util.function.IntPredicate; +import lombok.experimental.UtilityClass; + +@UtilityClass +public final class SlotPredicates { + + private static final int ROW_LENGTH = 9; + + public static IntPredicate slots(int count) { + Counter counter = new Counter(count); + return counter::isValid; + } + + public static IntPredicate rows(int height) { + return rows(height, 9); + } + + public static IntPredicate rows(int height, int rowLength) { + return slots(height * rowLength); + } + + public static IntPredicate offsetRectangle(int height, int width, int y, int x, + int rowLength) { + OffsetSlotter slotter = new OffsetSlotter(y, x, height, width, rowLength); + return slotter::isValid; + } + + public static IntPredicate offsetRectangle(int height, int width, int y, int x) { + return offsetRectangle(height, width, y, x, ROW_LENGTH); + } + + public static IntPredicate rectangle(int height, int width, int rowLength) { + return offsetRectangle(height, width, 0, 0, rowLength); + } + + public static IntPredicate rectangle(int height, int width) { + return rectangle(height, width, ROW_LENGTH); + } + + private static class OffsetSlotter { + + private int lowerHorizontalBound; + private int lowerVerticalBound; + private int upperHorizontalBound; + private int upperVerticalBound; + private int rowLength; + + OffsetSlotter(int y, int x, int height, int width, int rowLength) { + this.lowerHorizontalBound = x; + this.upperHorizontalBound = x + width - 1; + this.lowerVerticalBound = y; + this.upperVerticalBound = y + height - 1; + this.rowLength = rowLength; + } + + boolean isValid(int index) { + int row = index / rowLength; + int column = index % rowLength; + return column >= lowerHorizontalBound && column <= upperHorizontalBound && row >= lowerVerticalBound + && row <= upperVerticalBound; + } + + } + + private static class Counter { + private int count; + + Counter(int count) { + this.count = count; + } + + public boolean isValid(int index) { + return count-- > 0; + } + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/StaticDisplaySection.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/StaticDisplaySection.java similarity index 83% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/StaticDisplaySection.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/StaticDisplaySection.java index 6ec66d49..a5e87883 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/StaticDisplaySection.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/StaticDisplaySection.java @@ -1,6 +1,6 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; +package vg.civcraft.mc.civmodcore.inventory.gui.components; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; public class StaticDisplaySection extends InventoryComponent { private int additionCounter; @@ -34,7 +34,7 @@ public void set(IClickable click, int slot) { } @Override - void rebuild() { + protected void rebuild() { //NO OP } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/impl/CommonGUIs.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/impl/CommonGUIs.java similarity index 59% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/impl/CommonGUIs.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/impl/CommonGUIs.java index 4b64c24d..6fd0b4b4 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/impl/CommonGUIs.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/impl/CommonGUIs.java @@ -1,11 +1,13 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components.impl; +package vg.civcraft.mc.civmodcore.inventory.gui.components.impl; +import lombok.experimental.UtilityClass; import org.bukkit.Material; -import vg.civcraft.mc.civmodcore.inventorygui.LClickable; -import vg.civcraft.mc.civmodcore.inventorygui.components.ComponableSection; -import vg.civcraft.mc.civmodcore.inventorygui.components.SlotPredicates; -import vg.civcraft.mc.civmodcore.inventorygui.components.StaticDisplaySection; +import vg.civcraft.mc.civmodcore.inventory.gui.LClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ComponableSection; +import vg.civcraft.mc.civmodcore.inventory.gui.components.SlotPredicates; +import vg.civcraft.mc.civmodcore.inventory.gui.components.StaticDisplaySection; +@UtilityClass public class CommonGUIs { public static ComponableSection genConfirmationGUI(int rows, int columns, Runnable yesFunc, String yesText, Runnable noFunc, diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/history/HistoryItem.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/history/HistoryItem.java new file mode 100644 index 00000000..37680fbe --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/history/HistoryItem.java @@ -0,0 +1,7 @@ +package vg.civcraft.mc.civmodcore.inventory.gui.history; + +public interface HistoryItem { + + void setStateTo(); + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/history/HistoryTracker.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/history/HistoryTracker.java similarity index 96% rename from src/main/java/vg/civcraft/mc/civmodcore/inventorygui/history/HistoryTracker.java rename to src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/history/HistoryTracker.java index 919ad7ec..d065b334 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/history/HistoryTracker.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/history/HistoryTracker.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.inventorygui.history; +package vg.civcraft.mc.civmodcore.inventory.gui.history; import java.util.LinkedList; import java.util.ListIterator; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java index 1f82abab..3264efa2 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java @@ -1,129 +1,71 @@ package vg.civcraft.mc.civmodcore.inventory.items; -import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; import java.io.File; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import org.apache.commons.collections4.CollectionUtils; -import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import vg.civcraft.mc.civmodcore.CivModCorePlugin; -import vg.civcraft.mc.civmodcore.util.Chainer; -import vg.civcraft.mc.civmodcore.util.KeyedUtils; +import vg.civcraft.mc.civmodcore.chat.ChatUtils; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; +import vg.civcraft.mc.civmodcore.utilities.KeyedUtils; /** * Class of static utilities for Enchantments. */ +@UtilityClass public final class EnchantUtils { - private static final BiMap ENCHANT_NAMES = ImmutableBiMap.builder() - // Beta 1.9 - .put(Enchantment.DAMAGE_ALL, "Sharpness") - .put(Enchantment.DAMAGE_ARTHROPODS, "Bane of Arthropods") - .put(Enchantment.DAMAGE_UNDEAD, "Smite") - .put(Enchantment.DIG_SPEED, "Efficiency") - .put(Enchantment.DURABILITY, "Unbreaking") - .put(Enchantment.FIRE_ASPECT, "Fire Aspect") - .put(Enchantment.KNOCKBACK, "Knockback") - .put(Enchantment.LOOT_BONUS_BLOCKS, "Fortune") - .put(Enchantment.LOOT_BONUS_MOBS, "Looting") - .put(Enchantment.OXYGEN, "Respiration") - .put(Enchantment.PROTECTION_ENVIRONMENTAL, "Protection") - .put(Enchantment.PROTECTION_EXPLOSIONS, "Blast Protection") - .put(Enchantment.PROTECTION_FALL, "Feather Falling") - .put(Enchantment.PROTECTION_FIRE, "Fire Protection") - .put(Enchantment.PROTECTION_PROJECTILE, "Projectile Protection") - .put(Enchantment.SILK_TOUCH, "Silk Touch") - .put(Enchantment.WATER_WORKER, "Aqua Affinity") - // 1.1 - .put(Enchantment.ARROW_DAMAGE, "Power") - .put(Enchantment.ARROW_FIRE, "Flame") - .put(Enchantment.ARROW_INFINITE, "Infinity") - .put(Enchantment.ARROW_KNOCKBACK, "Punch") - // 1.4.6 - .put(Enchantment.THORNS, "Thorns") - // 1.7.2 - .put(Enchantment.LUCK, "Luck of the Sea") - .put(Enchantment.LURE, "Lure") - // 1.8 - .put(Enchantment.DEPTH_STRIDER, "Depth Strider") - // 1.9 - .put(Enchantment.FROST_WALKER, "Frost Walker") - .put(Enchantment.MENDING, "Mending") - // 1.11 - .put(Enchantment.BINDING_CURSE, "Curse of Binding") - .put(Enchantment.VANISHING_CURSE, "Curse of Vanishing") - // 1.11.1 - .put(Enchantment.SWEEPING_EDGE, "Sweeping Edge") - // 1.13 - .put(Enchantment.CHANNELING, "Channeling") - .put(Enchantment.IMPALING, "Impaling") - .put(Enchantment.LOYALTY, "Loyalty") - .put(Enchantment.RIPTIDE, "Riptide") - // 1.14 - .put(Enchantment.MULTISHOT, "Multishot") - .put(Enchantment.PIERCING, "Piercing") - .put(Enchantment.QUICK_CHARGE, "Quick Charge") - // 1.16 - .put(Enchantment.SOUL_SPEED, "Soul Speed") - .build(); - - private static final BiMap ENCHANT_ABBR = HashBiMap.create(ENCHANT_NAMES.size()); - - static { - // Determine if there's any enchants missing names - final Set missing = new HashSet<>(); - CollectionUtils.addAll(missing, Enchantment.values()); - missing.removeIf(ENCHANT_NAMES::containsKey); - if (!missing.isEmpty()) { - //noinspection deprecation - Bukkit.getLogger().warning("[EnchantUtils] The following enchants are lacking names: " + - missing.stream().map(Enchantment::getName).collect(Collectors.joining(",")) + "."); - } - } + private static final BiMap ENCHANT_ABBR = HashBiMap.create(Enchantment.values().length); /** * Loads enchantment names and initials from the config. */ - public static void loadEnchantAbbreviations(final CivModCorePlugin plugin) { + public static void loadEnchantAbbreviations(@Nonnull final CivModCorePlugin plugin) { + final var logger = CivLogger.getLogger(EnchantUtils.class); ENCHANT_ABBR.clear(); - final File enchantsFile = plugin.getResourceFile("enchants.yml"); + final File enchantsFile = plugin.getDataFile("enchants.yml"); final YamlConfiguration enchantsConfig = YamlConfiguration.loadConfiguration(enchantsFile); for (final String key : enchantsConfig.getKeys(false)) { if (Strings.isNullOrEmpty(key)) { - plugin.warning("[EnchantUtils] Enchantment key was empty."); + logger.warning("Enchantment key was empty."); continue; } final Enchantment enchant = EnchantUtils.getEnchantment(key); if (enchant == null) { - plugin.warning("[EnchantUtils] Could not find enchantment: " + key); + logger.warning("Could not find enchantment: " + key); return; } final String abbreviation = enchantsConfig.getString(key); if (Strings.isNullOrEmpty(abbreviation)) { - plugin.warning("[EnchantUtils] Abbreviation for [" + key + "] was empty."); + logger.warning("Abbreviation for [" + key + "] was empty."); continue; } ENCHANT_ABBR.put(enchant, abbreviation); } - plugin.info("[EnchantUtils] Loaded a total of " + ENCHANT_ABBR.size() + " abbreviations from enchants.yml"); + logger.info("Loaded a total of " + ENCHANT_ABBR.size() + " abbreviations from enchants.yml"); // Determine if there's any enchants missing abbreviations final Set missing = new HashSet<>(); CollectionUtils.addAll(missing, Enchantment.values()); missing.removeIf(ENCHANT_ABBR::containsKey); if (!missing.isEmpty()) { //noinspection deprecation - plugin.warning("[EnchantUtils] The following enchants are missing from enchants.yml: " + + logger.warning("The following enchants are missing from enchants.yml: " + missing.stream().map(Enchantment::getName).collect(Collectors.joining(",")) + "."); } } @@ -134,23 +76,27 @@ public static void loadEnchantAbbreviations(final CivModCorePlugin plugin) { * @param value The value to search for a matching enchantment by. * @return Returns a matched enchantment or null. */ - public static Enchantment getEnchantment(final String value) { + @Nullable + @SuppressWarnings("deprecation") + public static Enchantment getEnchantment(@Nullable final String value) { if (Strings.isNullOrEmpty(value)) { return null; } - Enchantment enchantment = Enchantment.getByKey(KeyedUtils.fromString(value)); - if (enchantment != null) { - return enchantment; - } - //noinspection deprecation - enchantment = Enchantment.getByName(value.toUpperCase()); - if (enchantment != null) { - return enchantment; + Enchantment enchantment; + // From NamespacedKey + final var enchantmentKey = KeyedUtils.fromString(value); + if (enchantmentKey != null) { + enchantment = Enchantment.getByKey(enchantmentKey); + if (enchantment != null) { + return enchantment; + } } - enchantment = ENCHANT_NAMES.inverse().get(value); + // From Name + enchantment = Enchantment.getByName(value.toUpperCase()); // deprecated if (enchantment != null) { return enchantment; } + // From Abbreviation enchantment = ENCHANT_ABBR.inverse().get(value); if (enchantment != null) { return enchantment; @@ -158,11 +104,33 @@ public static Enchantment getEnchantment(final String value) { return null; } - public static String getEnchantNiceName(final Enchantment enchant) { - return ENCHANT_NAMES.get(enchant); + /** + * @param enchant The enchantment to get a translatable component for. + * @return Returns a translatable component for the given enchantment. + */ + @Nonnull + public static TranslatableComponent asTranslatable(@Nonnull final Enchantment enchant) { + return Component.translatable(enchant.translationKey()); } - public static String getEnchantAbbreviation(final Enchantment enchant) { + /** + * @param enchant The enchantment to get the name of. + * @return Returns the name of the enchantment, or null. + * + * @deprecated Use {@link #asTranslatable(Enchantment)} instead. + */ + @Nullable + @Deprecated + public static String getEnchantNiceName(@Nullable final Enchantment enchant) { + return enchant == null ? null : ChatUtils.stringify(asTranslatable(enchant)); + } + + /** + * @param enchant The enchantment to get the abbreviation of. + * @return Returns the abbreviation of the enchantment, or null. + */ + @Nullable + public static String getEnchantAbbreviation(@Nullable final Enchantment enchant) { return ENCHANT_ABBR.get(enchant); } @@ -176,8 +144,10 @@ public static String getEnchantAbbreviation(final Enchantment enchant) { * @see Enchantment#getStartLevel() The starting level. A valid level cannot be below this. * @see Enchantment#getMaxLevel() The maximum level. A valid level cannot be above this. */ - public static boolean isSafeEnchantment(final Enchantment enchantment, final int level) { - return enchantment != null && level >= enchantment.getStartLevel() && level <= enchantment.getMaxLevel(); + public static boolean isSafeEnchantment(@Nullable final Enchantment enchantment, final int level) { + return enchantment != null + && level >= enchantment.getStartLevel() + && level <= enchantment.getMaxLevel(); } /** @@ -186,8 +156,9 @@ public static boolean isSafeEnchantment(final Enchantment enchantment, final int * @param item The item to retrieve the enchantments from. * @return Returns the item's enchantments, which are never null. */ - public static Map getEnchantments(final ItemStack item) { - return Chainer.from(item).then(ItemStack::getEnchantments).getOrGenerate(HashMap::new); + @Nonnull + public static Map getEnchantments(@Nullable final ItemStack item) { + return item == null ? ImmutableMap.of() : item.getEnchantments(); } /** @@ -200,7 +171,9 @@ public static Map getEnchantments(final ItemStack item) { * * @see EnchantUtils#isSafeEnchantment(Enchantment, int) */ - public static boolean addEnchantment(final ItemStack item, final Enchantment enchantment, final int level) { + public static boolean addEnchantment(@Nonnull final ItemStack item, + @Nonnull final Enchantment enchantment, + final int level) { return addEnchantment(item, enchantment, level, true); } @@ -215,12 +188,11 @@ public static boolean addEnchantment(final ItemStack item, final Enchantment enc * * @see EnchantUtils#isSafeEnchantment(Enchantment, int) */ - public static boolean addEnchantment(final ItemStack item, - final Enchantment enchantment, + public static boolean addEnchantment(@Nonnull final ItemStack item, + @Nonnull final Enchantment enchantment, final int level, final boolean onlyAllowSafeEnchantments) { - Preconditions.checkArgument(ItemUtils.isValidItem(item)); - return ItemUtils.handleItemMeta(item, (ItemMeta meta) -> + return ItemUtils.handleItemMeta(Objects.requireNonNull(item), (ItemMeta meta) -> meta.addEnchant(enchantment, level, !onlyAllowSafeEnchantments)); } @@ -231,12 +203,11 @@ public static boolean addEnchantment(final ItemStack item, * @param enchant The enchantment to remove from the item. * @return Returns true if the enchantment was successfully removed. */ - public static boolean removeEnchantment(final ItemStack item, final Enchantment enchant) { - Preconditions.checkArgument(ItemUtils.isValidItem(item)); - if (enchant == null) { - return true; - } - return ItemUtils.handleItemMeta(item, (ItemMeta meta) -> meta.removeEnchant(enchant)); + public static boolean removeEnchantment(@Nonnull final ItemStack item, + @Nullable final Enchantment enchant) { + return enchant == null + || ItemUtils.handleItemMeta(Objects.requireNonNull(item), + (ItemMeta meta) -> meta.removeEnchant(enchant)); } /** @@ -244,9 +215,8 @@ public static boolean removeEnchantment(final ItemStack item, final Enchantment * * @param item The item to clear enchantment from. */ - public static void clearEnchantments(final ItemStack item) { - Preconditions.checkArgument(ItemUtils.isValidItem(item)); - ItemUtils.handleItemMeta(item, (ItemMeta meta) -> { + public static void clearEnchantments(@Nonnull final ItemStack item) { + ItemUtils.handleItemMeta(Objects.requireNonNull(item), (ItemMeta meta) -> { meta.getEnchants().forEach((key, value) -> meta.removeEnchant(key)); return true; }); diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemBuilder.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemBuilder.java new file mode 100644 index 00000000..e1de2451 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemBuilder.java @@ -0,0 +1,88 @@ +package vg.civcraft.mc.civmodcore.inventory.items; + +import java.util.Objects; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class ItemBuilder { + + private Material material; + private ItemMeta meta; + private int amount; + + private ItemBuilder() { + this.amount = 1; + } + + /** + * @param material The material to set for this item. + * @return Returns this builder. + * + * @throws IllegalArgumentException Throws an IAE if the given material fails am + * {@link ItemUtils#isValidItemMaterial(Material)} test. + */ + public ItemBuilder material(@Nonnull final Material material) { + if (!ItemUtils.isValidItemMaterial(material)) { + throw new IllegalArgumentException("That is not a valid item material!"); + } + this.material = material; + return this; + } + + /** + * @param The type to cast the item meta to. + * @param handler The item meta handler. + * @return Returns this builder. + * + * @throws NullPointerException Throws an NPE if a new item meta needs to be created, but the new meta is null. + * @throws ClassCastException Throws an CCE if the item meta cannot be cast to the inferred type. + */ + @SuppressWarnings("unchecked") + public ItemBuilder meta(@Nonnull final Consumer handler) { + if (this.meta == null || !Bukkit.getItemFactory().isApplicable(this.meta, this.material)) { + this.meta = Objects.requireNonNull( + Bukkit.getItemFactory().getItemMeta(this.material), + "Tried to create an item meta for [" + this.material + "] but it returned null!"); + } + handler.accept((T) this.meta); + return this; + } + + /** + * @param amount The amount to set for this item. + * @return Returns this builder. + * + * @throws IllegalArgumentException Throws an IAE if the given amount is less than or equal to zero. + */ + public ItemBuilder amount(final int amount) { + if (amount <= 0) { + throw new IllegalArgumentException("Item amount cannot be less than or equal to zero!"); + } + this.amount = amount; + return this; + } + + /** + * @return Returns a new ItemStack based on the builder. + */ + public ItemStack build() { + final var item = new ItemStack(this.material, this.amount); + item.setItemMeta(this.meta); + return item; + } + + /** + * Creates a new builder with the given material. + * + * @param material The material to set for the builder. + * @return Returns a new builder. + */ + public static ItemBuilder builder(@Nonnull final Material material) { + return new ItemBuilder().material(material); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemFactory.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemFactory.java new file mode 100644 index 00000000..aaf11332 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemFactory.java @@ -0,0 +1,70 @@ +package vg.civcraft.mc.civmodcore.inventory.items; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import javax.annotation.Nonnull; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * Utility class that provides a means to construct {@link CraftItemStack CraftItemStacks} in lieu of + * {@link ItemStack#ItemStack(Material, int) new ItemStack(Material, Int)}. The reason being that + * {@link CraftItemStack CraftItemStacks} interact more efficiently with NMS than {@link ItemStack} does as the latter + * is entirely abstract in nature. + */ +@UtilityClass +public class ItemFactory { + + private static final Constructor CRAFT_ITEM_STACK_CONSTRUCTOR; + + static { + final Constructor constructor; + try { + constructor = CraftItemStack.class.getDeclaredConstructor( + Material.class, Integer.TYPE, Short.TYPE, ItemMeta.class); + constructor.setAccessible(true); + } + catch (final NoSuchMethodException exception) { + throw new IllegalStateException(exception); + } + CRAFT_ITEM_STACK_CONSTRUCTOR = constructor; + } + + /** + * Creates a new {@link CraftItemStack} based on a given material. + * + * @param material The material of the item. + * @return Returns a new instance of {@link CraftItemStack}. + */ + public static ItemStack createItemStack(@Nonnull final Material material) { + return createItemStack(material, 1); + } + + /** + * Creates a new {@link CraftItemStack} based on a given material. + * + * @param material The material of the item. + * @param amount The item amount. + * @return Returns a new instance of {@link CraftItemStack}. + */ + public static ItemStack createItemStack(@Nonnull final Material material, final int amount) { + if (material == null || !material.isItem()) { // Ignore highlighter + throw new IllegalArgumentException("Material must be a valid item material!"); + } + if (amount <= 0 || amount > material.getMaxStackSize()) { + throw new IllegalArgumentException("Must have a valid item amount"); + } + try { + return CRAFT_ITEM_STACK_CONSTRUCTOR.newInstance(material, amount, + (short) 0, Bukkit.getItemFactory().getItemMeta(material)); + } + catch (final InvocationTargetException | InstantiationException | IllegalAccessException exception) { + throw new RuntimeException("Could not construct item stack", exception); + } + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemMap.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemMap.java new file mode 100644 index 00000000..f596aacf --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemMap.java @@ -0,0 +1,753 @@ +package vg.civcraft.mc.civmodcore.inventory.items; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagByte; +import net.minecraft.nbt.NBTTagByteArray; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagDouble; +import net.minecraft.nbt.NBTTagFloat; +import net.minecraft.nbt.NBTTagInt; +import net.minecraft.nbt.NBTTagIntArray; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagLong; +import net.minecraft.nbt.NBTTagShort; +import net.minecraft.nbt.NBTTagString; +import org.apache.commons.collections4.CollectionUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.MemorySection; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.Repairable; +import vg.civcraft.mc.civmodcore.nbt.NBTSerialization; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; +import vg.civcraft.mc.civmodcore.utilities.MoreMath; + +/** + * Allows the storage and comparison of item stacks while ignoring their maximum possible stack sizes. This offers + * various tools to compare inventories, to store recipe costs or to specify setup costs. Take great care when dealing + * with item stacks with negative amounts, while this implementation should be consistent even with negative values, + * they create possibly unexpected results. For example an empty inventory/item map will seem to contain items when + * compared to a map containing negative values. Additionally this implementation allows durability "wild cards", if + * you specify -1 as durability it will count as any given durability. When working with multiple ItemMaps this will + * only work if all methods are executed on the instance containing items with a durability of -1. + * + * TODO: ItemMap is troubling because it manipulates and searches for items in ways that are not friendly to modern + * ways of cataloging items. For example, Bastion materials are all custom items, but they're only custom in the + * sense that they are named and lored.. but an Energizer will match with any other emerald unless you use any of + * the weirdly specific methods to weed out custom items. I think it would be better to refactor ItemMap to + * specifically support custom items, but I definitely need help in that regard. Or we can just keep it around + * for a little longer since they work fine enough and aren't in critical updates. + */ +public class ItemMap { + + private static final CivLogger LOGGER = CivLogger.getLogger(ItemMap.class); + + private final Object2IntMap items; + private int totalItems; + + /** + * Empty constructor to create empty item map + */ + public ItemMap() { + this.items = new Object2IntOpenHashMap<>(0); + this.items.defaultReturnValue(0); + this.totalItems = 0; + } + + /** + * Constructor to create an ItemMap based on a single ItemStack + * + * @param item ItemStack to start with + */ + public ItemMap(final ItemStack item) { + this.items = new Object2IntOpenHashMap<>(1); + this.items.defaultReturnValue(0); + this.totalItems = 0; + addItemStack(item); + } + + /** + * Constructor to create an item map based on a collection of ItemStacks + * + * @param stacks Stacks to add to the map + */ + public ItemMap(final Collection stacks) { + this.items = new Object2IntOpenHashMap<>(stacks.size()); + this.items.defaultReturnValue(0); + addAll(stacks); + } + + /** + * Constructor to create an item map based on the content of an inventory. The ItemMap will not be in sync with the + * inventory, it will only update if it's explicitly told to do so. + * + * @param inventory Inventory to base the item map on + */ + public ItemMap(final Inventory inventory) { + this.items = new Object2IntOpenHashMap<>(inventory.getSize()); + this.items.defaultReturnValue(0); + this.totalItems = 0; + update(inventory); + } + + private static ItemStack INTERNAL_createKey(ItemStack item) { + item = item.asOne(); // this also clones the stack + ItemUtils.handleItemMeta(item, (Repairable meta) -> { + meta.setRepairCost(0); + return true; + }); + return item; + } + + + + + //getAmount + //getTotalItemAmount + //getTotalUniqueItemAmount + //getStacksByMaterial + //getStacksByMaterialEnchant + //getStacksByLore + //getEntrySet + //getItemStackRepresentation + //getLoredItemCountRepresentation + + //addAll + //addItemStack + //addItemAmount + //addToInventory + //addToEntrySet + + //removeItemStack + //removeItemStackCompletely + //removeSafelyFrom + + //fitsIn + //isContainedIn + //containedExactlyIn + //getMultiplesContainedIn + //multiplyContent + + //createMapConformCopy + //enrichWithNBT + //clone + //merge + //update + + + + + /** + * Clones the given item stack, sets its amount to one and checks whether a stack equaling the created one exists + * in the item map. If yes the amount of the given stack (before the amount was set to 1) will be added to the + * current amount in the item map, if not a new entry in the map with the correct amount will be created. + * + * @param input ItemStack to insert + */ + public void addItemStack(final ItemStack input) { + if (!ItemUtils.isValidItemIgnoringAmount(input)) { + return; + } + this.items.computeInt(INTERNAL_createKey(input), (key, amount) -> + amount == null ? input.getAmount() : amount + input.getAmount()); + this.totalItems += input.getAmount(); + } + + /** + * Adds all the items contained in this instance to the given inventory + * + * @param inventory Inventory to add items to + */ + public void addToInventory(Inventory inventory) { + for (ItemStack is : getItemStackRepresentation()) { + inventory.addItem(is); + } + } + + /** + * Removes the given ItemStack from this map. Only the amount of the given ItemStack will be removed, not all of + * them. If the amount of the given item stack is bigger than the existing ones in this map, not more than the + * amount in this map will be removed + * + * @param input ItemStack to remove + */ + public void removeItemStack(final ItemStack input) { + if (input.getAmount() <= 0) { + return; + } + final ItemStack key = INTERNAL_createKey(input); + if (key == null) { + return; + } + this.items.computeIntIfPresent(key, (_key, amount) -> (amount -= input.getAmount()) <= 0 ? null : amount); + } + + /** + * Completely removes the given item stack of this item map, completely independent of its amount. + * + * @param input ItemStack to remove + */ + public void removeItemStackCompletely(final ItemStack input) { + final ItemStack key = INTERNAL_createKey(input); + if (key != null) { + this.items.removeInt(key); + } + } + + @Override + public int hashCode() { + return this.items.hashCode(); + } + + /** + * Adds all the stacks given in the collection to this map + * + * @param stacks Stacks to add + */ + public void addAll(Collection stacks) { + for (ItemStack is : stacks) { + if (is != null) { + addItemStack(is); + } + } + } + + /** + * Merges the given item map into this instance + * + * @param im ItemMap to merge + */ + public void merge(ItemMap im) { + for (Entry entry : im.getEntrySet()) { + addItemAmount(entry.getKey(), entry.getValue()); + } + } + + public void update(final Inventory inventory) { + this.items.clear(); + this.totalItems = 0; + for (int i = 0; i < inventory.getSize(); i++) { + final ItemStack item = inventory.getItem(i); + if (item != null) { + addItemStack(item); + } + } + } + + public void addEntrySet(Set> entries) { + for (Entry entry : entries) { + addItemAmount(entry.getKey(), entry.getValue()); + } + } + + /** + * Utility method, which has the amount of items to add as parameter. + * + * @param input ItemStack to sort into the map + * @param amount Amount associated with the given ItemStack + */ + public void addItemAmount(ItemStack input, int amount) { + ItemStack copy = INTERNAL_createKey(input); + if (copy == null) { + return; + } + copy.setAmount(amount); + addItemStack(copy); + } + + /** + * Gets a submap of this instance which contains all stacks with the same material as the given one and their + * respective amounts. + * + * @param m Material to search for + * @return New ItemMap with all ItemStack and their amount whose material + * matches the given one + */ + public ItemMap getStacksByMaterial(Material m) { + ItemMap result = new ItemMap(); + for (ItemStack is : items.keySet()) { + if (is.getType() == m) { + result.addItemAmount(is.clone(), items.get(is)); + } + } + return result; + } + + public ItemMap getStacksByMaterial(ItemStack is) { + return getStacksByMaterial(is.getType()); + } + + /** + * Gets a submap of this instance which contains all stacks with the same material and enchants as the given one + * and their respective amounts. + * + * @param m Material to search for + * @param enchants Enchants to search for + * @return New ItemMap with all ItemStack and their amount whose material and enchants matches the given one + */ + public ItemMap getStacksByMaterialEnchants(Material m, Map enchants) { + ItemMap result = new ItemMap(); + for (ItemStack is : items.keySet()) { + if (is.getType() == m && is.getItemMeta() != null && is.getItemMeta().getEnchants().equals(enchants)) { + result.addItemAmount(is.clone(), items.get(is)); + } + } + return result; + } + + public ItemMap getStacksByMaterialEnchants(ItemStack is) { + if (is.getItemMeta() != null) { + return getStacksByMaterialEnchants(is.getType(), is.getItemMeta().getEnchants()); + } else { + return getStacksByMaterialEnchants(is.getType(), new HashMap<>()); + } + } + + /** + * Gets a submap of this instance which contains all stacks with the same lore as the given and their respective + * amount. + * + * @param lore Lore to search for + * @return New ItemMap with all ItemStacks and their amount whose lore matches the given one + */ + public ItemMap getStacksByLore(final List lore) { + final boolean gaveLore = CollectionUtils.isNotEmpty(lore); + final ItemMap result = new ItemMap(); + for (final ItemStack key : this.items.keySet()) { + if (!key.hasItemMeta()) { + continue; + } + final var keyMeta = key.getItemMeta(); + if (gaveLore != keyMeta.hasLore()) { + continue; + } + final var keyLore = keyMeta.getLore(); + if (!Objects.equals(lore, keyLore)) { + continue; + } + result.addItemAmount(key.clone(), this.items.getInt(key)); + } + return result; + } + + /** + * Gets how many items of the given stack are in this map. Be aware that if a stack doesnt equal with the given one, + * for example because of mismatched NBT tags, it wont be included in the result + * + * @param is Exact ItemStack to search for + * @return amount of items like the given stack in this map + */ + public int getAmount(ItemStack is) { + ItemMap matSubMap = getStacksByMaterial(is); + int amount = 0; + for (Entry entry : matSubMap.getEntrySet()) { + ItemStack current = entry.getKey(); + if (MetaUtils.areMetasEqual(is.getItemMeta(), current.getItemMeta())) { + amount += entry.getValue(); + } + } + return amount; + } + + /** + * @return How many items are stored in this map total + */ + public int getTotalItemAmount() { + return totalItems; + } + + /** + * @return How many unique items are stored in this map + */ + public int getTotalUniqueItemAmount() { + return items.keySet().size(); + } + + @SuppressWarnings("deprecation") + public Set> getEntrySet() { + return this.items.entrySet(); + } + + /** + * Checks whether an inventory contains exactly what's described in this ItemMap + * + * @param i Inventory to compare + * @return True if the inventory is identical with this instance, false if not + */ + public boolean containedExactlyIn(Inventory i) { + ItemMap invMap = new ItemMap(i); + for (Entry entry : getEntrySet()) { + if (!entry.getValue().equals(invMap.getAmount(entry.getKey()))) { + return false; + } + } + for (ItemStack is : i.getContents()) { + if (is == null) { + continue; + } + if (getStacksByMaterial(is).getTotalUniqueItemAmount() == 0) { + return false; + } + } + return true; + } + + /** + * Checks whether this instance is completely contained in the given inventory, which means every stack in this + * instance is also in the given inventory and the amount in the given inventory is either the same or bigger as in + * this instance + * + * @param i inventory to check + * @return true if this instance is completely contained in the given inventory, false if not + */ + public boolean isContainedIn(Inventory i) { + ItemMap invMap = new ItemMap(i); + for (Entry entry : getEntrySet()) { + if (entry.getValue() > invMap.getAmount(entry.getKey())) { + return false; + } + } + return true; + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + for (ItemStack is : getItemStackRepresentation()) { + res.append(is.toString()).append(";"); + } + return res.toString(); + } + + /** + * Checks how often this ItemMap is contained in the given ItemMap or how often this ItemMap could be removed from + * the given one before creating negative stacks + * + * @param i ItemMap to check + * @return How often this map is contained in the given one or Integer.MAX_VALUE if this instance is empty + */ + public int getMultiplesContainedIn(Inventory i) { + ItemMap invMap = new ItemMap(i); + int res = Integer.MAX_VALUE; + for (Entry entry : getEntrySet()) { + int pulledAmount = invMap.getAmount(entry.getKey()); + int multiples = pulledAmount / entry.getValue(); + res = Math.min(res, multiples); + } + return res; + } + + /** + * Multiplies the whole content of this instance by the given multiplier + * + * @param multiplier Multiplier to scale the amount of the contained items with + */ + public void multiplyContent(double multiplier) { + totalItems = 0; + for (Entry entry : getEntrySet()) { + items.put(entry.getKey(), (int) (entry.getValue() * multiplier)); + totalItems += (int) (entry.getValue() * multiplier); + } + } + + /** + * Turns this item map into a list of ItemStacks, with amounts that do not surpass the maximum allowed stack size + * for each ItemStack + * + * @return List of stacksize conform ItemStacks + */ + public List getItemStackRepresentation() { + List result = new ArrayList<>(); + for (Entry entry : getEntrySet()) { + ItemStack is = entry.getKey(); + Integer amount = entry.getValue(); + while (amount != 0) { + ItemStack toAdd = is.clone(); + int addAmount = Math.min(amount, is.getMaxStackSize()); + toAdd.setAmount(addAmount); + // log.info("Adding {0} as ItemStack", toAdd.toString()); + result.add(toAdd); + amount -= addAmount; + } + } + return result; + } + + /** + * Clones this map + */ + @Override + public ItemMap clone() { + ItemMap clone = new ItemMap(); + for (Entry entry : getEntrySet()) { + clone.addItemAmount(entry.getKey(), entry.getValue()); + } + return clone; + } + + /** + * Checks whether this instance would completly fit into the given inventory + * + * @param i Inventory to check + * @return True if this ItemMap's item representation would completly fit in the inventory, false if not + */ + public boolean fitsIn(Inventory i) { + int size; + if (i instanceof PlayerInventory) { + size = 36; + } else { + size = i.getSize(); + } + ItemMap invCopy = new ItemMap(); + for (ItemStack is : i.getStorageContents()) { + invCopy.addItemStack(is); + } + ItemMap instanceCopy = this.clone(); + instanceCopy.merge(invCopy); + return instanceCopy.getItemStackRepresentation().size() <= size; + } + + /** + * Instead of converting into many stacks of maximum size, this creates a stack with an amount of one for each + * entry and adds the total item amount and stack count as lore, which is needed to display larger ItemMaps in + * inventories + * + * @return UI representation of large ItemMap + */ + public List getLoredItemCountRepresentation() { + Set> entrySet = getEntrySet(); + List items = new ArrayList<>(entrySet.size()); + for (Entry entry : entrySet) { + ItemStack is = entry.getKey().clone(); + ItemUtils.addLore(is, ChatColor.GOLD + "Total item count: " + entry.getValue()); + if (entry.getValue() > entry.getKey().getType().getMaxStackSize()) { + int stacks = entry.getValue() / is.getType().getMaxStackSize(); + int extra = entry.getValue() % is.getType().getMaxStackSize(); + StringBuilder out = new StringBuilder(ChatColor.GOLD.toString()); + if (stacks != 0) { + out.append(stacks).append(" stack").append(stacks == 1 ? "" : "s"); + } + if (extra != 0) { + out.append(" and ").append(extra); + out.append(" item").append(extra == 1 ? "" : "s"); + } + ItemUtils.addLore(is, out.toString()); + } + items.add(is); + } + return items; + } + + /** + * Attempts to remove the content of this ItemMap from the given inventory. If it fails to find all the required + * items it will stop and return false + * + * @param i Inventory to remove from + * @return True if everything was successfully removed, false if not + */ + public boolean removeSafelyFrom(Inventory i) { + for (Entry entry : getEntrySet()) { + int amountToRemove = entry.getValue(); + ItemStack is = entry.getKey(); + for (ItemStack inventoryStack : i.getStorageContents()) { + if (inventoryStack == null) { + continue; + } + if (inventoryStack.getType() == is.getType()) { + ItemMap compareMap = new ItemMap(inventoryStack); + int removeAmount = Math.min(amountToRemove, compareMap.getAmount(is)); + if (removeAmount != 0) { + ItemStack cloneStack = inventoryStack.clone(); + cloneStack.setAmount(removeAmount); + if (!i.removeItem(cloneStack).isEmpty()) { + return false; + } else { + amountToRemove -= removeAmount; + if (amountToRemove <= 0) { + break; + } + } + } + } + } + if (amountToRemove > 0) { + if (i instanceof PlayerInventory pInv) { + ItemStack offHand = pInv.getItemInOffHand(); + if (offHand == null) { + return false; + } + if (offHand.getType() == is.getType()) { + ItemMap compareMap = new ItemMap(offHand); + int removeAmount = Math.min(amountToRemove, compareMap.getAmount(is)); + int updatedCount = Math.max(0, offHand.getAmount() - removeAmount); + amountToRemove -= removeAmount; + if (updatedCount == 0) { + pInv.setItemInOffHand(null); + } else { + offHand.setAmount(updatedCount); + } + } + + } + if (amountToRemove > 0) { + return false; + } + } + } + return true; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ItemMap im) { + if (im.getTotalItemAmount() == getTotalItemAmount()) { + return im.getEntrySet().equals(getEntrySet()); + } + } + return false; + } + + /** + * Utility to add NBT tags to an item and produce a custom stack size + * + * @param item Template Bukkit ItemStack + * @param amount Output Stack Size + * @param map Java Maps and Lists representing NBT data + * @return Cloned ItemStack with amount set to amt and NBT set to map. + */ + public static ItemStack enrichWithNBT(ItemStack item, int amount, Map map) { + LOGGER.fine("Received request to enrich " + item.toString()); + item = item.clone(); + item.setAmount(MoreMath.clamp(amount, 1, item.getMaxStackSize())); + item = NBTSerialization.processItem(item, (nbt) -> mapToNBT(nbt, map)); + return item; + } + + @SuppressWarnings("unchecked") + public static NBTTagCompound mapToNBT(NBTTagCompound base, Map map) { + LOGGER.fine("Representing map --> NBTTagCompound"); + if (map == null || base == null) { + return base; + } + for (Map.Entry entry : map.entrySet()) { + Object object = entry.getValue(); + if (object instanceof Map) { + LOGGER.fine("Adding map at key " + entry.getKey()); + base.set(entry.getKey(), mapToNBT(new NBTTagCompound(), (Map) object)); + } else if (object instanceof MemorySection) { + LOGGER.fine("Adding map from MemorySection at key " + entry.getKey()); + base.set(entry.getKey(), mapToNBT(new NBTTagCompound(), ((MemorySection) object).getValues(true))); + } else if (object instanceof List) { + LOGGER.fine("Adding list at key " + entry.getKey()); + base.set(entry.getKey(), listToNBT(new NBTTagList(), (List) object)); + } else if (object instanceof String) { + LOGGER.fine("Adding String " + object + " at key " + entry.getKey()); + base.setString(entry.getKey(), (String) object); + } else if (object instanceof Double) { + LOGGER.fine("Adding Double " + object + " at key " + entry.getKey()); + base.setDouble(entry.getKey(), (Double) object); + } else if (object instanceof Float) { + LOGGER.fine("Adding Float " + object + " at key " + entry.getKey()); + base.setFloat(entry.getKey(), (Float) object); + } else if (object instanceof Boolean) { + LOGGER.fine("Adding Boolean " + object + " at key " + entry.getKey()); + base.setBoolean(entry.getKey(), (Boolean) object); + } else if (object instanceof Byte) { + LOGGER.fine("Adding Byte " + object + " at key " + entry.getKey()); + base.setByte(entry.getKey(), (Byte) object); + } else if (object instanceof Short) { + LOGGER.fine("Adding Byte " + object + " at key " + entry.getKey()); + base.setShort(entry.getKey(), (Short) object); + } else if (object instanceof Integer) { + LOGGER.fine("Adding Integer " + object + " at key " + entry.getKey()); + base.setInt(entry.getKey(), (Integer) object); + } else if (object instanceof Long) { + LOGGER.fine("Adding Long " + object + " at key " + entry.getKey()); + base.setLong(entry.getKey(), (Long) object); + } else if (object instanceof byte[]) { + LOGGER.fine("Adding bytearray at key " + entry.getKey()); + base.setByteArray(entry.getKey(), (byte[]) object); + } else if (object instanceof int[]) { + LOGGER.fine("Adding intarray at key " + entry.getKey()); + base.setIntArray(entry.getKey(), (int[]) object); + } else if (object instanceof UUID) { + LOGGER.fine("Adding UUID " + object + " at key " + entry.getKey()); + base.a(entry.getKey(), (UUID) object); + } else if (object instanceof NBTBase) { + LOGGER.fine("Adding nbtobject at key " + entry.getKey()); + base.set(entry.getKey(), (NBTBase) object); + } else { + LOGGER.warning("Unrecognized entry in map-->NBT: " + object.toString()); + } + } + return base; + } + + @SuppressWarnings("unchecked") + public static NBTTagList listToNBT(NBTTagList base, List list) { + LOGGER.fine("Representing list --> NBTTagList"); + if (list == null || base == null) { + return base; + } + for (Object object : list) { + if (object instanceof Map) { + LOGGER.fine("Adding map to list"); + base.add(mapToNBT(new NBTTagCompound(), (Map) object)); + } else if (object instanceof MemorySection) { + LOGGER.fine("Adding map from MemorySection to list"); + base.add(mapToNBT(new NBTTagCompound(), ((MemorySection) object).getValues(true))); + } else if (object instanceof List) { + LOGGER.fine("Adding list to list"); + base.add(listToNBT(new NBTTagList(), (List) object)); + } else if (object instanceof String) { + LOGGER.fine("Adding string " + object + " to list"); + base.add(NBTTagString.a((String) object)); + } else if (object instanceof Double) { + LOGGER.fine("Adding double " + object + " to list"); + base.add(NBTTagDouble.a((Double) object)); + } else if (object instanceof Float) { + LOGGER.fine("Adding float " + object + " to list"); + base.add(NBTTagFloat.a((Float) object)); + } else if (object instanceof Byte) { + LOGGER.fine("Adding byte " + object + " to list"); + base.add(NBTTagByte.a((Byte) object)); + } else if (object instanceof Short) { + LOGGER.fine("Adding short " + object + " to list"); + base.add(NBTTagShort.a((Short) object)); + } else if (object instanceof Integer) { + LOGGER.fine("Adding integer " + object + " to list"); + base.add(NBTTagInt.a((Integer) object)); + } else if (object instanceof Long) { + LOGGER.fine("Adding long " + object + " to list"); + base.add(NBTTagLong.a((Long) object)); + } else if (object instanceof byte[]) { + LOGGER.fine("Adding byte array to list"); + base.add(new NBTTagByteArray((byte[]) object)); + } else if (object instanceof int[]) { + LOGGER.fine("Adding int array to list"); + base.add(new NBTTagIntArray((int[]) object)); + } else if (object instanceof NBTBase) { + LOGGER.fine("Adding nbt object to list"); + base.add((NBTBase) object); + } else { + LOGGER.warning("Unrecognized entry in list-->NBT: " + base); + } + } + return base; + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemUtils.java index 744acf48..97152be0 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/ItemUtils.java @@ -1,66 +1,37 @@ package vg.civcraft.mc.civmodcore.inventory.items; -import com.google.common.base.Strings; -import java.io.File; import java.util.ArrayList; -import java.util.EnumMap; -import java.util.HashSet; +import java.util.Arrays; import java.util.List; -import java.util.Set; +import java.util.Objects; import java.util.function.Predicate; -import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import org.bukkit.Material; -import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; -import vg.civcraft.mc.civmodcore.CivModCorePlugin; +import org.jetbrains.annotations.Contract; import vg.civcraft.mc.civmodcore.chat.ChatUtils; -import vg.civcraft.mc.civmodcore.util.Chainer; /** * Class of static APIs for Items. Replaces ISUtils. */ +@UtilityClass public final class ItemUtils { - private static final EnumMap MATERIAL_NAMES = new EnumMap<>(Material.class); - /** - * Loads item names from configurable files and requests any custom item names programmatically from plugins. - * - * @param plugin The CivModCore instance plugin. + * @param item The item to get a translatable component for. + * @return Returns a translatable component of the given item. */ - public static void loadItemNames(final CivModCorePlugin plugin) { - MATERIAL_NAMES.clear(); - final File materialsFile = plugin.getResourceFile("materials.yml"); - final YamlConfiguration materialsConfig = YamlConfiguration.loadConfiguration(materialsFile); - for (final String key : materialsConfig.getKeys(false)) { - if (Strings.isNullOrEmpty(key)) { - plugin.warning("[ItemUtils] Material key was empty."); - continue; - } - final Material material = Material.getMaterial(key); - if (material == null) { - plugin.warning("[ItemUtils] Could not find material: " + key); - return; - } - final String name = materialsConfig.getString(key); - if (Strings.isNullOrEmpty(name)) { - plugin.warning("[ItemUtils] Name for [" + key + "] was empty."); - continue; - } - MATERIAL_NAMES.put(material, ChatUtils.parseColor(name)); - } - plugin.info("[ItemUtils] Loaded a total of " + MATERIAL_NAMES.size() + " item names from materials.yml"); - // Determine if there's any materials missing - final Set missing = new HashSet<>(); - CollectionUtils.addAll(missing, Material.values()); - missing.removeIf(MATERIAL_NAMES::containsKey); - if (!missing.isEmpty()) { - plugin.warning("[ItemUtils] The following materials are missing from materials.yml: " + - missing.stream().map(Enum::name).collect(Collectors.joining(",")) + "."); - } + @Nonnull + public static TranslatableComponent asTranslatable(@Nonnull final ItemStack item) { + return Component.translatable(item.translationKey()); } /** @@ -68,12 +39,13 @@ public static void loadItemNames(final CivModCorePlugin plugin) { * * @param material The material to get the name of. * @return Returns the material name. + * + * @deprecated Use {@link MaterialUtils#asTranslatable(Material)} instead. */ - public static String getItemName(final Material material) { - if (material == null) { - throw new IllegalArgumentException("Cannot retrieve name of invalid material."); - } - return MATERIAL_NAMES.computeIfAbsent(material, (_material) -> material.name()); + @Deprecated + @Nonnull + public static String getItemName(@Nonnull final Material material) { + return ChatUtils.stringify(MaterialUtils.asTranslatable(Objects.requireNonNull(material))); } /** @@ -81,9 +53,13 @@ public static String getItemName(final Material material) { * * @param item The item to get the name of. * @return Returns the item's name. + * + * @deprecated Use {@link #asTranslatable(ItemStack)} instead. */ - public static String getItemName(final ItemStack item) { - return getItemName(Chainer.from(item).then(ItemStack::getType).get()); + @Deprecated + @Nullable + public static String getItemName(@Nullable final ItemStack item) { + return item == null ? null : ChatUtils.stringify(asTranslatable(item)); } /** @@ -94,17 +70,26 @@ public static String getItemName(final ItemStack item) { * @param item The item to validate. * @return Returns true if the item is valid. */ - public static boolean isValidItem(final ItemStack item) { - if (item == null) { - return false; - } - if (!isValidItemMaterial(item.getType())) { - return false; - } - if (!isValidItemAmount(item)) { - return false; - } - return true; + public static boolean isValidItem(@Nullable final ItemStack item) { + return item != null + && isValidItemMaterial(item.getType()) + && item.getAmount() > 0 + && item.getAmount() <= item.getMaxStackSize(); + } + + /** + * Checks if an ItemStack is valid instance in that it's non-null, has a valid item material, and has a positive + * item amount. This differs from {@link #isValidItem(ItemStack)} in that the maximum stack size isn't considered, + * so an item considered valid by this method may not be considered valid by {@link #isValidItem(ItemStack)}, thus + * may not appear correctly in inventories. + * + * @param item The item to validate. + * @return Returns true if the item is valid. + */ + public static boolean isValidItemIgnoringAmount(@Nullable final ItemStack item) { + return item != null + && isValidItemMaterial(item.getType()) + && item.getAmount() > 0; } /** @@ -113,17 +98,10 @@ public static boolean isValidItem(final ItemStack item) { * @param item The item to validate. * @return Returns true if the item has a valid amount. */ - public static boolean isValidItemAmount(ItemStack item) { - if (item == null) { - return false; - } - if (item.getAmount() <= 0) { - return false; - } - if (item.getAmount() > item.getMaxStackSize()) { - return false; - } - return true; + public static boolean isValidItemAmount(@Nullable final ItemStack item) { + return item != null + && item.getAmount() > 0 + && item.getAmount() <= item.getMaxStackSize(); } /** @@ -132,36 +110,73 @@ public static boolean isValidItemAmount(ItemStack item) { * @param material The material to check. * @return Returns true if the material would be considered a valid item. */ - public static boolean isValidItemMaterial(final Material material) { - if (material == null) { - return false; - } - if (material.isAir()) { - return false; - } - if (!material.isItem()) { - return false; - } - return true; + public static boolean isValidItemMaterial(@Nullable final Material material) { + return material != null + /** Add any null-returns in {@link CraftItemFactory#getItemMeta(Material, org.bukkit.craftbukkit.v1_17_R1.inventory.CraftMetaItem)} */ + && material != Material.AIR + && material.isItem(); } /** - * Determines whether two item stacks are similar. (Will check both items against the other) + * Determines whether two item stacks are functionally identical. * * @param former The first item. * @param latter The second item. - * @return Returns true if both items are similar and not null. + * @return Returns true if both items are functionally identical. * * @see ItemStack#isSimilar(ItemStack) */ - public static boolean areItemsSimilar(final ItemStack former, final ItemStack latter) { - if (former != null && former.isSimilar(latter)) { + public static boolean areItemsEqual(@Nullable final ItemStack former, + @Nullable final ItemStack latter) { + if (former == latter) { return true; } - if (latter != null && latter.isSimilar(former)) { + return (former != null && latter != null) + && former.getAmount() == latter.getAmount() + && areItemsSimilar(former, latter); + } + + /** + * Determines whether two item stacks are similar. + * + * @param former The first item. + * @param latter The second item. + * @return Returns true if both items are similar. + * + * @see ItemStack#isSimilar(ItemStack) + */ + public static boolean areItemsSimilar(@Nullable final ItemStack former, + @Nullable final ItemStack latter) { + if (former == latter) { return true; } - return false; + if ((former == null || latter == null) + || former.getType() != latter.getType() + || former.hasItemMeta() != latter.hasItemMeta()) { + return false; + } + return MetaUtils.areMetasEqual(former.getItemMeta(), latter.getItemMeta()); + } + + /** + * Returns the NMS version of a given item, preferring the item's craft handle but will fall back upon creating an + * NMS copy. + * + * @param item The item to get the NMS version of. + * @return The NMS version, either handle or copy. + */ + @Contract("!null -> !null") + @Nullable + public static net.minecraft.world.item.ItemStack getNMSItemStack(@Nullable final ItemStack item) { + if (item == null) { + return null; + } + if (item instanceof CraftItemStack craftItem) { + if (craftItem.handle != null) { + return craftItem.handle; + } + } + return CraftItemStack.asNMSCopy(item); } /** @@ -170,12 +185,9 @@ public static boolean areItemsSimilar(final ItemStack former, final ItemStack la * @param item The item to decrement in amount. * @return Returns the given item with a decremented amount, or null. */ - public static ItemStack decrementItem(final ItemStack item) { - if (item == null || item.getAmount() <= 1) { - return null; - } - item.setAmount(item.getAmount() - 1); - return item; + @Nullable + public static ItemStack decrementItem(@Nullable final ItemStack item) { + return item == null ? null : item.subtract().getAmount() <= 0 ? null : item; } /** @@ -184,13 +196,10 @@ public static ItemStack decrementItem(final ItemStack item) { * @param item The item to normalize. * @return The normalized item. */ - public static ItemStack normalizeItem(ItemStack item) { - if (item == null) { - return null; - } - item = item.clone(); - item.setAmount(1); - return item; + @Contract("!null -> !null") + @Nullable + public static ItemStack normalizeItem(@Nullable final ItemStack item) { + return item == null ? null : item.clone().asOne(); } /** @@ -199,8 +208,9 @@ public static ItemStack normalizeItem(ItemStack item) { * @param item The item to retrieve meta from. * @return Returns the item meta. */ - public static ItemMeta getItemMeta(final ItemStack item) { - return Chainer.from(item).then(ItemStack::getItemMeta).get(); + @Nullable + public static ItemMeta getItemMeta(@Nullable final ItemStack item) { + return item == null ? null : item.getItemMeta(); } /** @@ -209,8 +219,9 @@ public static ItemMeta getItemMeta(final ItemStack item) { * @param item The item to check the display name of. * @return Returns true if the item has a display name. */ - public static boolean hasDisplayName(final ItemStack item) { - return Strings.isNullOrEmpty(getDisplayName(item)); + public static boolean hasDisplayName(@Nullable final ItemStack item) { + final var meta = getItemMeta(item); + return meta != null && meta.hasDisplayName(); } /** @@ -219,8 +230,10 @@ public static boolean hasDisplayName(final ItemStack item) { * @param item The item to retrieve the display name from. * @return Returns the display name of an item. */ - public static String getDisplayName(final ItemStack item) { - return Chainer.from(getItemMeta(item)).then(ItemMeta::getDisplayName).get(); + @Nullable + public static Component getComponentDisplayName(@Nullable final ItemStack item) { + final var meta = getItemMeta(item); + return meta == null ? null : meta.displayName(); } /** @@ -231,17 +244,11 @@ public static String getDisplayName(final ItemStack item) { * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void setDisplayName(final ItemStack item, final String name) { - final ItemMeta meta = getItemMeta(item); - if (meta == null) { - throw new IllegalArgumentException("Cannot set that display name: item has no meta."); - } - if (Strings.isNullOrEmpty(name)) { - meta.setDisplayName(null); - } - else { - meta.setDisplayName(name); - } + public static void setComponentDisplayName(@Nonnull final ItemStack item, + @Nullable final Component name) { + final var meta = Objects.requireNonNull(getItemMeta(item), + "Cannot set that display name: item has no meta."); + meta.displayName(name); item.setItemMeta(meta); } @@ -251,8 +258,10 @@ public static void setDisplayName(final ItemStack item, final String name) { * @param item The item to retrieve the lore from. * @return Returns the lore, which is never null. */ - public static List getLore(final ItemStack item) { - return MetaUtils.getLore(getItemMeta(item)); + @Nonnull + public static List getComponentLore(@Nullable final ItemStack item) { + final var meta = getItemMeta(item); + return meta == null ? new ArrayList<>(0) : MetaUtils.getComponentLore(meta); } /** @@ -265,10 +274,9 @@ public static List getLore(final ItemStack item) { * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void setLore(final ItemStack item, final String... lines) { - final List lore = new ArrayList<>(); - CollectionUtils.addAll(lore, lines); - setLore(item, lore); + public static void setComponentLore(@Nonnull final ItemStack item, + @Nullable final Component... lines) { + setComponentLore(item, lines == null ? null : Arrays.asList(lines)); } /** @@ -281,25 +289,23 @@ public static void setLore(final ItemStack item, final String... lines) { * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void setLore(final ItemStack item, final List lines) { - final ItemMeta meta = getItemMeta(item); - if (meta == null) { - throw new IllegalArgumentException("Cannot set that lore: item has no meta."); - } - meta.setLore(lines); + public static void setComponentLore(@Nonnull final ItemStack item, + @Nullable final List lines) { + final var meta = Objects.requireNonNull(getItemMeta(item), + "Cannot set that lore: item has no meta."); + MetaUtils.setComponentLore(meta, lines); item.setItemMeta(meta); } /** - * Appends lore to an item. + * Clears the lore from an item. * - * @param item The item to append the lore to. - * @param lines The lore to append to the item. + * @param item The item to clear lore of. * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void addLore(final ItemStack item, final String... lines) { - addLore(item, false, lines); + public static void clearLore(@Nonnull final ItemStack item) { + setComponentLore(item, (List) null); } /** @@ -310,24 +316,22 @@ public static void addLore(final ItemStack item, final String... lines) { * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void addLore(final ItemStack item, final List lines) { - addLore(item, false, lines); + public static void addComponentLore(@Nonnull final ItemStack item, + @Nullable final Component... lines) { + addComponentLore(item, false, lines); } /** - * Adds lore to an item, either by appending or prepending. + * Appends lore to an item. * * @param item The item to append the lore to. - * @param prepend If set to true, the lore will be prepended instead of appended. * @param lines The lore to append to the item. * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void addLore(final ItemStack item, final boolean prepend, final String... lines) { - handleItemMeta(item, (ItemMeta meta) -> { - MetaUtils.addLore(meta, prepend, lines); - return true; - }); + public static void addComponentLore(@Nonnull final ItemStack item, + @Nullable final List lines) { + addComponentLore(item, false, lines); } /** @@ -339,60 +343,61 @@ public static void addLore(final ItemStack item, final boolean prepend, final St * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void addLore(final ItemStack item, final boolean prepend, final List lines) { - handleItemMeta(item, (ItemMeta meta) -> { - MetaUtils.addLore(meta, prepend, lines); - return true; - }); + public static void addComponentLore(@Nonnull final ItemStack item, + final boolean prepend, + @Nullable final Component... lines) { + addComponentLore(item, prepend, lines == null ? null : Arrays.asList(lines)); } /** - * Clears the lore from an item. + * Adds lore to an item, either by appending or prepending. * - * @param item The item to clear lore of. + * @param item The item to append the lore to. + * @param prepend If set to true, the lore will be prepended instead of appended. + * @param lines The lore to append to the item. * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void clearLore(final ItemStack item) { - setLore(item, (List) null); + public static void addComponentLore(@Nonnull final ItemStack item, + final boolean prepend, + @Nullable final List lines) { + final var meta = Objects.requireNonNull(getItemMeta(item), + "Cannot add that lore: item has no meta."); + MetaUtils.addComponentLore(meta, prepend, lines); + item.setItemMeta(meta); } /** * Retrieves the Damageable ItemMeta only if it's relevant to the item. This is necessary because [almost?] every - * ItemMeta implements Damageable.. for some reason. And so this will only return a Damageable instance if the item - * material actually has a maximum durability above zero. + * ItemMeta implements Damageable... for some reason. And so this will only return a Damageable instance if the + * item material actually has a maximum durability above zero. * * @param item The item to get the Damageable meta from. * @return Returns an instance of Damageable, or null. */ - public static Damageable getDamageable(final ItemStack item) { + @Nullable + public static Damageable getDamageable(@Nullable final ItemStack item) { if (item == null) { return null; } final Material material = item.getType(); - if (!isValidItemMaterial(material)) { - return null; - } - if (material.getMaxDurability() <= 0) { - return null; - } - final ItemMeta meta = item.getItemMeta(); - if (!(meta instanceof Damageable)) { - return null; + if (isValidItemMaterial(material) + && material.getMaxDurability() > 0 + && getItemMeta(item) instanceof Damageable damageable) { + return damageable; } - return (Damageable) meta; + return null; } /** - * Makes an item glow by adding an enchantment and the flag for hiding enchantments, - * so it has the enchantment glow without an enchantment being visible. Note that this - * does actually apply an enchantment to an item. + * Makes an item glow by adding an enchantment and the flag for hiding enchantments, so it has the enchantment glow + * without an enchantment being visible. Note that this does actually apply an enchantment to an item. * * @param item Item to apply glow to. * * @throws IllegalArgumentException Throws when the given item has no meta. */ - public static void addGlow(final ItemStack item) { + public static void addGlow(@Nullable final ItemStack item) { handleItemMeta(item, (ItemMeta meta) -> { MetaUtils.addGlow(meta); return true; @@ -409,8 +414,10 @@ public static void addGlow(final ItemStack item) { * * @see ItemStack#getItemMeta() */ + @Contract("null, _ -> false; _, null -> false") @SuppressWarnings("unchecked") - public static boolean handleItemMeta(final ItemStack item, final Predicate handler) { + public static boolean handleItemMeta(@Nullable final ItemStack item, + @Nullable final Predicate handler) { if (item == null || handler == null) { return false; } @@ -423,8 +430,180 @@ public static boolean handleItemMeta(final ItemStack item, final Predicate getLore(@Nullable final ItemStack item) { + final var meta = getItemMeta(item); + return meta == null ? new ArrayList<>(0) : MetaUtils.getLore(meta); + } + + /** + * Sets the lore for an item, replacing any lore that may have already been set. + * + * @param item The item to set the lore to. + * @param lines The lore to set to the item. + * + * @see ItemUtils#clearLore(ItemStack) + * + * @throws IllegalArgumentException Throws when the given item has no meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. Use + * {@link #setComponentLore(ItemStack, Component...)} instead. + */ + @Deprecated + public static void setLore(@Nonnull final ItemStack item, + @Nullable final String... lines) { + setLore(item, lines == null ? null : Arrays.asList(lines)); + } + + /** + * Sets the lore for an item, replacing any lore that may have already been set. + * + * @param item The item to set the lore to. + * @param lines The lore to set to the item. + * + * @see ItemUtils#clearLore(ItemStack) + * + * @throws IllegalArgumentException Throws when the given item has no meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. Use + * {@link #setComponentLore(ItemStack, List)} instead. + */ + @Deprecated + public static void setLore(@Nonnull final ItemStack item, + @Nullable final List lines) { + final var meta = Objects.requireNonNull(getItemMeta(item), + "Cannot set that lore: item has no meta."); + meta.setLore(lines); + item.setItemMeta(meta); + } + + /** + * Appends lore to an item. + * + * @param item The item to append the lore to. + * @param lines The lore to append to the item. + * + * @throws IllegalArgumentException Throws when the given item has no meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. Use + * {@link #addComponentLore(ItemStack, Component...)} instead. + */ + @Deprecated + public static void addLore(@Nonnull final ItemStack item, + @Nullable final String... lines) { + addLore(item, false, lines); + } + + /** + * Appends lore to an item. + * + * @param item The item to append the lore to. + * @param lines The lore to append to the item. + * + * @throws IllegalArgumentException Throws when the given item has no meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. Use + * {@link #addComponentLore(ItemStack, List)} instead. + */ + @Deprecated + public static void addLore(@Nonnull final ItemStack item, + @Nullable final List lines) { + addLore(item, false, lines); + } + + /** + * Adds lore to an item, either by appending or prepending. + * + * @param item The item to append the lore to. + * @param prepend If set to true, the lore will be prepended instead of appended. + * @param lines The lore to append to the item. + * + * @throws IllegalArgumentException Throws when the given item has no meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. Use + * {@link #addComponentLore(ItemStack, boolean, Component...)} instead. + */ + @Deprecated + public static void addLore(@Nonnull final ItemStack item, + final boolean prepend, + @Nullable final String... lines) { + addLore(item, prepend, lines == null ? null : Arrays.asList(lines)); + } + + /** + * Adds lore to an item, either by appending or prepending. + * + * @param item The item to append the lore to. + * @param prepend If set to true, the lore will be prepended instead of appended. + * @param lines The lore to append to the item. + * + * @throws IllegalArgumentException Throws when the given item has no meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. Use + * {@link #addComponentLore(ItemStack, boolean, List)} instead. + */ + @Deprecated + public static void addLore(@Nonnull final ItemStack item, + final boolean prepend, + @Nullable final List lines) { + final var meta = Objects.requireNonNull(getItemMeta(item), + "Cannot add that lore: item has no meta."); + MetaUtils.addLore(meta, prepend, lines); + item.setItemMeta(meta); + } + } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java index 4d951c88..f0774a24 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java @@ -1,12 +1,18 @@ package vg.civcraft.mc.civmodcore.inventory.items; import com.destroystokyo.paper.MaterialTags; -import com.google.common.base.Strings; import com.google.common.math.IntMath; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import org.apache.commons.lang3.StringUtils; import org.bukkit.Material; import org.bukkit.Tag; +import org.bukkit.inventory.ItemStack; /** *

See BukkitReports.

@@ -18,6 +24,7 @@ *
  • {@link TreeTypeUtils TreeTypeUtils}
  • * */ +@UtilityClass public final class MaterialUtils { private static final List HASH_MATERIALS = new ArrayList<>() {{ @@ -33,11 +40,21 @@ public final class MaterialUtils { * @param value The value to search for a matching material by. * @return Returns a matched material or null. */ - public static Material getMaterial(final String value) { - if (Strings.isNullOrEmpty(value)) { - return null; - } - return Material.getMaterial(value.toUpperCase()); + @Nullable + public static Material getMaterial(@Nullable final String value) { + return StringUtils.isEmpty(value) ? null : Material.getMaterial(value.toUpperCase()); + } + + /** + * {@link ItemUtils#asTranslatable(ItemStack)} is preferable since that will return a translatable based on + * additional item data, such as Potion becoming Potion of Water Breathing or Potion of Regeneration. + * + * @param material The material to translate. + * @return Returns a translatable component based on the given material. + */ + @Nonnull + public static TranslatableComponent asTranslatable(@Nonnull final Material material) { + return Component.translatable(material.translationKey()); } /** @@ -47,11 +64,8 @@ public static Material getMaterial(final String value) { * @param material The material to check. * @return Returns true if the material is air. */ - public static boolean isAir(final Material material) { - if (material == null) { - return true; - } - return material.isAir(); + public static boolean isAir(@Nullable final Material material) { + return material == null || material.isAir(); } /** @@ -60,7 +74,8 @@ public static boolean isAir(final Material material) { * @param object Object to base returned material on * @return Material hash of the given object */ - public static Material getMaterialHash(final Object object) { + @Nonnull + public static Material getMaterialHash(@Nullable final Object object) { if (object == null) { return HASH_MATERIALS.get(0); } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MetaUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MetaUtils.java index 0533bfc6..df12e064 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MetaUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MetaUtils.java @@ -1,24 +1,80 @@ package vg.civcraft.mc.civmodcore.inventory.items; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.function.Predicate; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.ArrayUtils; +import org.bukkit.Bukkit; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.chat.ChatUtils; /** - * Class of static utilities for when you already have an instance of {@link ItemMeta}, such as - * inside of {@link ItemUtils#handleItemMeta(ItemStack, Predicate)}'s handler, thus all of the - * methods defined below will assume the presence of a valid meta instance. + * Class of static utilities for when you already have an instance of {@link ItemMeta}, such as inside of + * {@link ItemUtils#handleItemMeta(ItemStack, Predicate)}'s handler, thus all the methods defined below will assume the + * presence of a valid meta instance. */ +@UtilityClass public final class MetaUtils { + /** + * Determines whether two item metas are functionally identical. + * + * @param former The first item meta. + * @param latter The second item meta. + * @return Returns true if both item metas are functionally identical. + */ + public static boolean areMetasEqual(@Nullable final ItemMeta former, + @Nullable final ItemMeta latter) { + if (former == latter) { + return true; + } + if (former == null || latter == null) { + return false; + } + // Create a version of the items that do not have display names or lore, since those are the pain points + final ItemMeta fakeFormer = former.clone(); + final ItemMeta fakeLatter = latter.clone(); + fakeFormer.displayName(null); + fakeFormer.lore(null); + fakeLatter.displayName(null); + fakeLatter.lore(null); + if (!Bukkit.getItemFactory().equals(fakeFormer, fakeLatter)) { + return false; + } + // And compare the display name and lore manually + if (former.hasDisplayName() != latter.hasDisplayName()) { + return false; + } + if (former.hasDisplayName()) { + if (!ChatUtils.areComponentsEqual( + former.displayName(), + latter.displayName())) { + return false; + } + } + if (former.hasLore() != latter.hasLore()) { + return false; + } + if (former.hasLore()) { + if (!Objects.equals( + getComponentLore(former), + getComponentLore(latter))) { + return false; + } + } + return true; + } + /** * Retrieves the lore from a given item meta. * @@ -26,69 +82,214 @@ public final class MetaUtils { * @return Returns the lore, which is never null. */ @Nonnull - public static List getLore(@Nonnull final ItemMeta meta) { - final List lore = meta.getLore(); + public static List getComponentLore(@Nonnull final ItemMeta meta) { + final List lore = meta.lore(); if (lore == null) { - return new ArrayList<>(); + return new ArrayList<>(0); } return lore; } /** - * Appends lore to an item. + * Sets the lore to a given item meta. * - * @param meta The item to append the lore to. - * @param lines The lore to append to the item. + * @param meta The meta to set the lore to. + * @param lines The lore lines to set. */ - public static void addLore(@Nonnull final ItemMeta meta, final String... lines) { - addLore(meta, false, lines); + public static void setComponentLore(@Nonnull final ItemMeta meta, + @Nullable final Component... lines) { + setComponentLore(meta, lines == null ? null : Arrays.asList(lines)); } /** - * Appends lore to an item. + * Sets the lore to a given item meta. * - * @param meta The item to append the lore to. - * @param lines The lore to append to the item. + * @param meta The meta to set the lore to. + * @param lines The lore lines to set. */ - public static void addLore(@Nonnull final ItemMeta meta, final List lines) { - addLore(meta, false, lines); + public static void setComponentLore(@Nonnull final ItemMeta meta, + @Nullable List lines) { + if (lines == null) { + clearLore(meta); + return; + } + lines = new ArrayList<>(lines); + lines.removeIf(Objects::isNull); + meta.lore(lines); + } + + /** + * Clears the lore from an item meta. + * + * @param meta The item meta to clear the lore of. + */ + public static void clearLore(@Nonnull final ItemMeta meta) { + meta.lore(null); + } + + /** + * Appends lore to an item meta. + * + * @param meta The item meta to append the lore to. + * @param lines The lore to append to the item meta. + */ + public static void addComponentLore(@Nonnull final ItemMeta meta, + @Nullable final Component... lines) { + addComponentLore(meta, false, lines); + } + + /** + * Appends lore to an item meta. + * + * @param meta The item meta to append the lore to. + * @param lines The lore to append to the item meta. + */ + public static void addComponentLore(@Nonnull final ItemMeta meta, + @Nullable final List lines) { + addComponentLore(meta, false, lines); + } + + /** + * Adds lore to an item meta, either by appending or prepending. + * + * @param meta The item meta to append the lore to. + * @param prepend If set to true, the lore will be prepended instead of appended. + * @param lines The lore to append to the item meta. + */ + public static void addComponentLore(@Nonnull final ItemMeta meta, + final boolean prepend, + @Nullable final Component... lines) { + addComponentLore(meta, prepend, lines == null ? null : Arrays.asList(lines)); } /** - * Adds lore to an item, either by appending or prepending. + * Adds lore to an item meta, either by appending or prepending. * - * @param meta The item to append the lore to. + * @param meta The item meta to append the lore to. * @param prepend If set to true, the lore will be prepended instead of appended. - * @param lines The lore to append to the item. + * @param lines The lore to append to the item meta. */ - public static void addLore(@Nonnull final ItemMeta meta, final boolean prepend, final String... lines) { - if (ArrayUtils.isEmpty(lines)) { + public static void addComponentLore(@Nonnull final ItemMeta meta, + final boolean prepend, + @Nullable List lines) { + if (CollectionUtils.isEmpty(lines)) { return; } - final List lore = getLore(meta); + lines = new ArrayList<>(lines); + lines.removeIf(Objects::isNull); + final List lore = getComponentLore(meta); if (prepend) { - ArrayUtils.reverse(lines); - for (final String line : lines) { + Collections.reverse(lines); + for (final Component line : lines) { lore.add(0, line); } } else { - CollectionUtils.addAll(lore, lines); + lore.addAll(lines); } - meta.setLore(lore); + meta.lore(lore); + } + + /** + * Makes an item glow by adding an enchantment and the flag for hiding enchantments, so it has the enchantment glow + * without an enchantment being visible. Note that this does actually apply an enchantment to an item. + * + * @param meta Item meta to apply glow to. + */ + public static void addGlow(@Nonnull final ItemMeta meta) { + meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + meta.addEnchant(Enchantment.DURABILITY, 1, true); // true = ignoreLevelRestriction + } + + // ------------------------------------------------------------ + // Deprecated Functions + // ------------------------------------------------------------ + + /** + * Retrieves the lore from a given item meta. + * + * @param meta The item meta to retrieve the lore from. + * @return Returns the lore, which is never null. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. + * Use {@link #getComponentLore(ItemMeta)} instead. + */ + @Deprecated + @Nonnull + public static List getLore(@Nonnull final ItemMeta meta) { + final List lore = meta.getLore(); + if (lore == null) { + return new ArrayList<>(0); + } + return lore; + } + + /** + * Appends lore to an item meta. + * + * @param meta The item meta to append the lore to. + * @param lines The lore to append to the item meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. + * Use {@link #addComponentLore(ItemMeta, Component...)} instead. + */ + @Deprecated + public static void addLore(@Nonnull final ItemMeta meta, + @Nullable final String... lines) { + addLore(meta, false, lines); + } + + /** + * Appends lore to an item meta. + * + * @param meta The item meta to append the lore to. + * @param lines The lore to append to the item meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. + * Use {@link #addComponentLore(ItemMeta, List)} instead. + */ + @Deprecated + public static void addLore(@Nonnull final ItemMeta meta, + @Nullable final List lines) { + addLore(meta, false, lines); + } + + /** + * Adds lore to an item meta, either by appending or prepending. + * + * @param meta The item meta to append the lore to. + * @param prepend If set to true, the lore will be prepended instead of appended. + * @param lines The lore to append to the item meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. + * Use {@link #addComponentLore(ItemMeta, boolean, Component...)} instead. + */ + @Deprecated + public static void addLore(@Nonnull final ItemMeta meta, + final boolean prepend, + @Nullable final String... lines) { + addLore(meta, prepend, lines == null ? null : Arrays.asList(lines)); } /** - * Adds lore to an item, either by appending or prepending. + * Adds lore to an item meta, either by appending or prepending. * - * @param meta The item to append the lore to. + * @param meta The item meta to append the lore to. * @param prepend If set to true, the lore will be prepended instead of appended. - * @param lines The lore to append to the item. + * @param lines The lore to append to the item meta. + * + * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. + * Use {@link #addComponentLore(ItemMeta, boolean, List)} instead. */ - public static void addLore(@Nonnull final ItemMeta meta, final boolean prepend, final List lines) { + @Deprecated + public static void addLore(@Nonnull final ItemMeta meta, + final boolean prepend, + @Nullable List lines) { if (CollectionUtils.isEmpty(lines)) { return; } + lines = new ArrayList<>(lines); + lines.removeIf(Objects::isNull); final List lore = getLore(meta); if (prepend) { Collections.reverse(lines); @@ -102,25 +303,4 @@ public static void addLore(@Nonnull final ItemMeta meta, final boolean prepend, meta.setLore(lore); } - /** - * Clears the lore from an item. - * - * @param meta The item meta to clear the lore of. - */ - public static void clearLore(@Nonnull final ItemMeta meta) { - meta.setLore(null); - } - - /** - * Makes an item glow by adding an enchantment and the flag for hiding enchantments, - * so it has the enchantment glow without an enchantment being visible. Note that this - * does actually apply an enchantment to an item. - * - * @param meta Item meta to apply glow to. - */ - public static void addGlow(@Nonnull final ItemMeta meta) { - meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); - meta.addEnchant(Enchantment.DURABILITY, 1, true); // true = ignoreLevelRestriction - } - } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MoreTags.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MoreTags.java index dd427662..df6b4e78 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MoreTags.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MoreTags.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; import org.apache.commons.collections4.CollectionUtils; import org.bukkit.Bukkit; import org.bukkit.Keyed; @@ -14,11 +15,13 @@ import org.bukkit.NamespacedKey; import org.bukkit.Tag; import org.bukkit.block.data.Ageable; -import vg.civcraft.mc.civmodcore.util.KeyedUtils; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; +import vg.civcraft.mc.civmodcore.utilities.KeyedUtils; /** * Fills in the gaps between {@link Tag} and {@link MaterialTags}. */ +@UtilityClass public final class MoreTags { /** @@ -64,7 +67,7 @@ public final class MoreTags { public static final Tag DIRT = new BetterTag<>("dirt", ImmutableSet.builder() .add(Material.FARMLAND) - .add(Material.GRASS_PATH) + .add(Material.DIRT_PATH) .add(Material.GRASS_BLOCK) .add(Material.DIRT) .add(Material.COARSE_DIRT) @@ -108,7 +111,10 @@ public final class MoreTags { .add(Material.PUMPKIN_STEM) .add(Material.SUGAR_CANE) .add(Material.SWEET_BERRY_BUSH) + .add(Material.TWISTING_VINES) + .add(Material.WEEPING_VINES) .add(Material.WHEAT) + .add(Material.CAVE_VINES) .build()); public static final Tag POTTABLE = new BetterTag<>("pottable", @@ -175,6 +181,80 @@ public final class MoreTags { .add(Material.NETHERITE_SCRAP) .build()); + public static final Tag LIGHTABLE_CANDLES = new BetterTag<>("lightable_candles", + ImmutableSet.builder() + .add(Material.CANDLE) + .add(Material.CANDLE_CAKE) + .add(Material.CYAN_CANDLE) + .add(Material.CYAN_CANDLE_CAKE) + .add(Material.BLACK_CANDLE) + .add(Material.BLACK_CANDLE_CAKE) + .add(Material.BLUE_CANDLE) + .add(Material.BLUE_CANDLE_CAKE) + .add(Material.BROWN_CANDLE) + .add(Material.BROWN_CANDLE_CAKE) + .add(Material.GRAY_CANDLE) + .add(Material.GRAY_CANDLE_CAKE) + .add(Material.GREEN_CANDLE) + .add(Material.GREEN_CANDLE_CAKE) + .add(Material.LIGHT_BLUE_CANDLE) + .add(Material.LIGHT_BLUE_CANDLE_CAKE) + .add(Material.LIGHT_GRAY_CANDLE) + .add(Material.LIGHT_GRAY_CANDLE_CAKE) + .add(Material.LIME_CANDLE) + .add(Material.LIME_CANDLE_CAKE) + .add(Material.MAGENTA_CANDLE) + .add(Material.MAGENTA_CANDLE_CAKE) + .add(Material.ORANGE_CANDLE) + .add(Material.ORANGE_CANDLE_CAKE) + .add(Material.PINK_CANDLE) + .add(Material.PINK_CANDLE_CAKE) + .add(Material.PURPLE_CANDLE) + .add(Material.PURPLE_CANDLE_CAKE) + .add(Material.RED_CANDLE) + .add(Material.RED_CANDLE_CAKE) + .add(Material.WHITE_CANDLE) + .add(Material.WHITE_CANDLE_CAKE) + .add(Material.YELLOW_CANDLE) + .add(Material.YELLOW_CANDLE_CAKE) + .build()); + + public static final Tag COPPER_BLOCKS = new BetterTag<>("copper_blocks", + ImmutableSet.builder() + .add(Material.COPPER_BLOCK) + .add(Material.EXPOSED_COPPER) + .add(Material.WEATHERED_COPPER) + .add(Material.OXIDIZED_COPPER) + .add(Material.CUT_COPPER) + .add(Material.EXPOSED_CUT_COPPER) + .add(Material.WEATHERED_CUT_COPPER) + .add(Material.OXIDIZED_CUT_COPPER) + .add(Material.CUT_COPPER_STAIRS) + .add(Material.EXPOSED_CUT_COPPER_STAIRS) + .add(Material.WEATHERED_CUT_COPPER_STAIRS) + .add(Material.OXIDIZED_CUT_COPPER_STAIRS) + .add(Material.CUT_COPPER_SLAB) + .add(Material.EXPOSED_CUT_COPPER_SLAB) + .add(Material.WEATHERED_CUT_COPPER_SLAB) + .add(Material.OXIDIZED_CUT_COPPER_SLAB) + .add(Material.WAXED_COPPER_BLOCK) + .add(Material.WAXED_EXPOSED_COPPER) + .add(Material.WAXED_WEATHERED_COPPER) + .add(Material.WAXED_OXIDIZED_COPPER) + .add(Material.WAXED_CUT_COPPER) + .add(Material.WAXED_EXPOSED_CUT_COPPER) + .add(Material.WAXED_WEATHERED_CUT_COPPER) + .add(Material.WAXED_OXIDIZED_CUT_COPPER) + .add(Material.WAXED_CUT_COPPER_STAIRS) + .add(Material.WAXED_EXPOSED_CUT_COPPER_STAIRS) + .add(Material.WAXED_WEATHERED_CUT_COPPER_STAIRS) + .add(Material.WAXED_OXIDIZED_CUT_COPPER_STAIRS) + .add(Material.WAXED_CUT_COPPER_SLAB) + .add(Material.WAXED_EXPOSED_CUT_COPPER_SLAB) + .add(Material.WAXED_WEATHERED_CUT_COPPER_SLAB) + .add(Material.WAXED_OXIDIZED_CUT_COPPER_SLAB) + .build()); + // ------------------------------------------------------------ // Better Tag class to allow for easy Tag creation. // ------------------------------------------------------------ @@ -214,6 +294,7 @@ public NamespacedKey getKey() { // ------------------------------------------------------------ public static void init() { + final var logger = CivLogger.getLogger(MoreTags.class); // Determine if there's any crops missing { final Set missing = new HashSet<>(); @@ -224,7 +305,7 @@ public static void init() { missing.removeIf(Tag.FIRE::isTagged); missing.removeIf(CROPS::isTagged); if (!missing.isEmpty()) { - Bukkit.getLogger().warning("[MoreTags] The following crops are missing: " + + logger.warning("The following crops are missing: " + missing.stream().map(Material::name).collect(Collectors.joining(",")) + "."); } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/PotionUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/PotionUtils.java index 3791f3bc..1b270fb2 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/PotionUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/PotionUtils.java @@ -1,116 +1,103 @@ package vg.civcraft.mc.civmodcore.inventory.items; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; -import org.bukkit.Bukkit; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.minecraft.world.effect.MobEffectList; +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; +import vg.civcraft.mc.civmodcore.chat.ChatUtils; +@UtilityClass public final class PotionUtils { - private static final EnumMap POTIONS = new EnumMap<>(PotionType.class) {{ - put(PotionType.UNCRAFTABLE, "Uncraftable Potion"); - put(PotionType.WATER, "Water Bottle"); - put(PotionType.MUNDANE, "Mundane Potion"); - put(PotionType.THICK, "Thick Potion"); - put(PotionType.AWKWARD, "Awkward Potion"); - put(PotionType.FIRE_RESISTANCE, "Potion of Fire Resistance"); - put(PotionType.SPEED, "Potion of Swiftness"); - put(PotionType.SLOWNESS, "Potion of Slowness"); - put(PotionType.INSTANT_HEAL, "Potion of Healing"); - put(PotionType.INSTANT_DAMAGE, "Potion of Harming"); - put(PotionType.POISON, "Potion of Poison"); - put(PotionType.REGEN, "Potion of Regeneration"); - put(PotionType.STRENGTH, "Potion of Strength"); - put(PotionType.WEAKNESS, "Potion of Weakness"); - put(PotionType.LUCK, "Potion of Luck"); - // 1.4.2 - put(PotionType.NIGHT_VISION, "Potion of Night Vision"); - put(PotionType.INVISIBILITY, "Potion of Invisibility"); - // 1.7.2 - put(PotionType.WATER_BREATHING, "Potion of Water Breathing"); - // 1.8 - put(PotionType.JUMP, "Potion of Leaping"); - // 1.13 - put(PotionType.TURTLE_MASTER, "Potion of the Turtle Master"); - put(PotionType.SLOW_FALLING, "Potion of Slow Falling"); - }}; + private static final Map, TranslatableComponent> POTION_TRANSLATIONS = new Object2ObjectAVLTreeMap<>(); - private static final Map EFFECTS = new HashMap<>() {{ - // Beta 1.8 - put(PotionEffectType.BLINDNESS, "Blindness"); - put(PotionEffectType.CONFUSION, "Nausea"); - put(PotionEffectType.DAMAGE_RESISTANCE, "Resistance"); - put(PotionEffectType.FAST_DIGGING, "Haste"); - put(PotionEffectType.FIRE_RESISTANCE, "Fire Resistance"); - put(PotionEffectType.HARM, "Instant Damage"); - put(PotionEffectType.HEAL, "Instant Health"); - put(PotionEffectType.HUNGER, "Hunger"); - put(PotionEffectType.INCREASE_DAMAGE, "Strength"); - put(PotionEffectType.INVISIBILITY, "Invisibility"); - put(PotionEffectType.JUMP, "Jump Boost"); - put(PotionEffectType.NIGHT_VISION, "Night Vision"); - put(PotionEffectType.POISON, "Poison"); - put(PotionEffectType.REGENERATION, "Regeneration"); - put(PotionEffectType.SLOW, "Slowness"); - put(PotionEffectType.SLOW_DIGGING, "Mining Fatigue"); - put(PotionEffectType.SPEED, "Speed"); - put(PotionEffectType.WATER_BREATHING, "Water Breathing"); - put(PotionEffectType.WEAKNESS, "Weakness"); - // 1.4.2 - put(PotionEffectType.WITHER, "Wither"); - // 1.6.1 - put(PotionEffectType.ABSORPTION, "Absorption"); - put(PotionEffectType.HEALTH_BOOST, "Health Boost"); - put(PotionEffectType.SATURATION, "Saturation"); - // 1.9 - put(PotionEffectType.GLOWING, "Glowing"); - put(PotionEffectType.LEVITATION, "Levitation"); - put(PotionEffectType.LUCK, "Luck"); - put(PotionEffectType.UNLUCK, "Bad Luck"); - // 1.13 - put(PotionEffectType.CONDUIT_POWER, "Conduit Power"); - put(PotionEffectType.DOLPHINS_GRACE, "Dolphin's Grace"); - put(PotionEffectType.SLOW_FALLING, "Slow Falling"); - // 1.14 - put(PotionEffectType.BAD_OMEN, "Bad Omen"); - put(PotionEffectType.HERO_OF_THE_VILLAGE, "Hero of the Village"); - }}; + /** + * @param type The potion type to get a translatable component for. + * @return Returns a translatable component for the given potion type. + */ + @Nonnull + public static TranslatableComponent asTranslatable(@Nonnull final PotionType type) { + return asTranslatable(Material.POTION, type); + } - public static void init() { - // Determine if there's any missing potion types - { - final Set missing = new HashSet<>(); - CollectionUtils.addAll(missing, PotionType.values()); - missing.removeIf(POTIONS::containsKey); - if (!missing.isEmpty()) { - Bukkit.getLogger().warning("[PotionUtils] The following potion types are missing: " + - missing.stream().map(Enum::name).collect(Collectors.joining(",")) + "."); - } - } - // Determine if there's any missing potion effects - { - final Set missing = new HashSet<>(); - CollectionUtils.addAll(missing, PotionEffectType.values()); - missing.removeIf(EFFECTS::containsKey); - if (!missing.isEmpty()) { - Bukkit.getLogger().warning("[PotionUtils] The following potion effects are missing: " + - missing.stream().map(PotionEffectType::getName).collect(Collectors.joining(",")) + "."); - } + /** + * @param material The potion kind. Must comply with {@link MoreTags#POTIONS}. + * @param type The potion type to get a translatable component for. + * @return Returns a translatable component for the given potion type. + */ + @Nonnull + public static TranslatableComponent asTranslatable(@Nonnull final Material material, + @Nonnull final PotionType type) { + if (!MoreTags.POTIONS.isTagged(material)) { + throw new IllegalArgumentException("That is not a recognised potion material! [" + material.name() + "]"); } + return POTION_TRANSLATIONS.computeIfAbsent(Pair.of(material, type), (_key) -> { + final var item = new ItemStack(material); + ItemUtils.handleItemMeta(item, (PotionMeta meta) -> { + meta.setBasePotionData(new PotionData(type, false, false)); + return true; + }); + return Component.translatable(item.translationKey()); + }); + } + + /** + * @param effect The potion effect to get a translatable component for. + * @return Returns a translatable component for the given potion effect. + */ + @SuppressWarnings("deprecation") + @Nonnull + public static TranslatableComponent asTranslatable(@Nonnull final PotionEffectType effect) { + final MobEffectList mojang = MobEffectList.fromId(effect.getId()); + assert mojang != null; + final String key = mojang.c(); // Gets the translation key + // If the obfuscation has changed, look for a method on MobEffectList like: + // + // protected String b() { + // if (this.d == null) { + // this.d = SystemUtils.a("effect", IRegistry.V.getKey(this)); + // } + // return this.d; + // } + // + // then use whatever method calls that method + return Component.translatable(key); } - public static String getPotionNiceName(final PotionType potion) { - return POTIONS.get(potion); + /** + * @param potion The potion type to get the name of. + * @return Returns the name of the potion, or null. + * + * @deprecated Use {@link #asTranslatable(PotionType)} or + * {@link #asTranslatable(Material, PotionType)} instead. + */ + @Deprecated + @Nullable + public static String getPotionNiceName(@Nullable final PotionType potion) { + return potion == null ? null : ChatUtils.stringify(asTranslatable(potion)); } - public static String getEffectNiceName(final PotionEffectType effect) { - return EFFECTS.get(effect); + /** + * @param effect The potion effect to get the name of. + * @return Returns the name of the potion effect, or null. + * + * @deprecated Use {@link #asTranslatable(PotionEffectType)} instead. + */ + @Deprecated + @Nullable + public static String getEffectNiceName(@Nullable final PotionEffectType effect) { + return effect == null ? null : ChatUtils.stringify(asTranslatable(effect)); } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/SpawnEggUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/SpawnEggUtils.java index 54b00fac..0434341e 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/SpawnEggUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/SpawnEggUtils.java @@ -6,89 +6,96 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; +import lombok.experimental.UtilityClass; import org.apache.commons.collections4.CollectionUtils; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.EntityType; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; /** * Class of static APIs for Spawn Eggs. */ +@UtilityClass public final class SpawnEggUtils { - private static final BiMap SPAWN_EGGS = ImmutableBiMap.builder(). - put(Material.BAT_SPAWN_EGG, EntityType.BAT). - put(Material.BEE_SPAWN_EGG, EntityType.BEE). - put(Material.BLAZE_SPAWN_EGG, EntityType.BLAZE). - put(Material.CAT_SPAWN_EGG, EntityType.CAT). - put(Material.CAVE_SPIDER_SPAWN_EGG, EntityType.CAVE_SPIDER). - put(Material.CHICKEN_SPAWN_EGG, EntityType.CHICKEN). - put(Material.COD_SPAWN_EGG, EntityType.COD). - put(Material.COW_SPAWN_EGG, EntityType.COW). - put(Material.CREEPER_SPAWN_EGG, EntityType.CREEPER). - put(Material.DOLPHIN_SPAWN_EGG, EntityType.DOLPHIN). - put(Material.DONKEY_SPAWN_EGG, EntityType.DONKEY). - put(Material.DROWNED_SPAWN_EGG, EntityType.DROWNED). - put(Material.ELDER_GUARDIAN_SPAWN_EGG, EntityType.ELDER_GUARDIAN). - put(Material.ENDERMAN_SPAWN_EGG, EntityType.ENDERMAN). - put(Material.ENDERMITE_SPAWN_EGG, EntityType.ENDERMITE). - put(Material.EVOKER_SPAWN_EGG, EntityType.EVOKER). - put(Material.FOX_SPAWN_EGG, EntityType.FOX). - put(Material.GHAST_SPAWN_EGG, EntityType.GHAST). - put(Material.GUARDIAN_SPAWN_EGG, EntityType.GUARDIAN). - put(Material.HOGLIN_SPAWN_EGG, EntityType.HOGLIN). - put(Material.HORSE_SPAWN_EGG, EntityType.HORSE). - put(Material.HUSK_SPAWN_EGG, EntityType.HUSK). - put(Material.LLAMA_SPAWN_EGG, EntityType.LLAMA). - put(Material.MAGMA_CUBE_SPAWN_EGG, EntityType.MAGMA_CUBE). - put(Material.MOOSHROOM_SPAWN_EGG, EntityType.MUSHROOM_COW). - put(Material.MULE_SPAWN_EGG, EntityType.MULE). - put(Material.OCELOT_SPAWN_EGG, EntityType.OCELOT). - put(Material.PANDA_SPAWN_EGG, EntityType.PANDA). - put(Material.PARROT_SPAWN_EGG, EntityType.PARROT). - put(Material.PHANTOM_SPAWN_EGG, EntityType.PHANTOM). - put(Material.PIG_SPAWN_EGG, EntityType.PIG). - put(Material.PIGLIN_SPAWN_EGG, EntityType.PIGLIN). - put(Material.PILLAGER_SPAWN_EGG, EntityType.PILLAGER). - put(Material.POLAR_BEAR_SPAWN_EGG, EntityType.POLAR_BEAR). - put(Material.PUFFERFISH_SPAWN_EGG, EntityType.PUFFERFISH). - put(Material.RABBIT_SPAWN_EGG, EntityType.RABBIT). - put(Material.RAVAGER_SPAWN_EGG, EntityType.RAVAGER). - put(Material.SALMON_SPAWN_EGG, EntityType.SALMON). - put(Material.SHEEP_SPAWN_EGG, EntityType.SHEEP). - put(Material.SHULKER_SPAWN_EGG, EntityType.SHULKER). - put(Material.SILVERFISH_SPAWN_EGG, EntityType.SILVERFISH). - put(Material.SKELETON_HORSE_SPAWN_EGG, EntityType.SKELETON_HORSE). - put(Material.SKELETON_SPAWN_EGG, EntityType.SKELETON). - put(Material.SLIME_SPAWN_EGG, EntityType.SLIME). - put(Material.SPIDER_SPAWN_EGG, EntityType.SPIDER). - put(Material.SQUID_SPAWN_EGG, EntityType.SQUID). - put(Material.STRAY_SPAWN_EGG, EntityType.STRAY). - put(Material.STRIDER_SPAWN_EGG, EntityType.STRIDER). - put(Material.TRADER_LLAMA_SPAWN_EGG, EntityType.TRADER_LLAMA). - put(Material.TROPICAL_FISH_SPAWN_EGG, EntityType.TROPICAL_FISH). - put(Material.TURTLE_SPAWN_EGG, EntityType.TURTLE). - put(Material.VEX_SPAWN_EGG, EntityType.VEX). - put(Material.VILLAGER_SPAWN_EGG, EntityType.VILLAGER). - put(Material.VINDICATOR_SPAWN_EGG, EntityType.VINDICATOR). - put(Material.WANDERING_TRADER_SPAWN_EGG, EntityType.WANDERING_TRADER). - put(Material.WITCH_SPAWN_EGG, EntityType.WITCH). - put(Material.WITHER_SKELETON_SPAWN_EGG, EntityType.WITHER_SKELETON). - put(Material.WOLF_SPAWN_EGG, EntityType.WOLF). - put(Material.ZOGLIN_SPAWN_EGG, EntityType.ZOGLIN). - put(Material.ZOMBIE_SPAWN_EGG, EntityType.ZOMBIE). - put(Material.ZOMBIE_HORSE_SPAWN_EGG, EntityType.ZOMBIE_HORSE). - put(Material.ZOMBIFIED_PIGLIN_SPAWN_EGG, EntityType.ZOMBIFIED_PIGLIN). - put(Material.ZOMBIE_VILLAGER_SPAWN_EGG, EntityType.ZOMBIE_VILLAGER). - build(); + private static final BiMap SPAWN_EGGS = ImmutableBiMap.builder() + .put(Material.AXOLOTL_SPAWN_EGG, EntityType.AXOLOTL) + .put(Material.BAT_SPAWN_EGG, EntityType.BAT) + .put(Material.BEE_SPAWN_EGG, EntityType.BEE) + .put(Material.BLAZE_SPAWN_EGG, EntityType.BLAZE) + .put(Material.CAT_SPAWN_EGG, EntityType.CAT) + .put(Material.CAVE_SPIDER_SPAWN_EGG, EntityType.CAVE_SPIDER) + .put(Material.CHICKEN_SPAWN_EGG, EntityType.CHICKEN) + .put(Material.COD_SPAWN_EGG, EntityType.COD) + .put(Material.COW_SPAWN_EGG, EntityType.COW) + .put(Material.CREEPER_SPAWN_EGG, EntityType.CREEPER) + .put(Material.DOLPHIN_SPAWN_EGG, EntityType.DOLPHIN) + .put(Material.DONKEY_SPAWN_EGG, EntityType.DONKEY) + .put(Material.DROWNED_SPAWN_EGG, EntityType.DROWNED) + .put(Material.ELDER_GUARDIAN_SPAWN_EGG, EntityType.ELDER_GUARDIAN) + .put(Material.ENDERMAN_SPAWN_EGG, EntityType.ENDERMAN) + .put(Material.ENDERMITE_SPAWN_EGG, EntityType.ENDERMITE) + .put(Material.EVOKER_SPAWN_EGG, EntityType.EVOKER) + .put(Material.FOX_SPAWN_EGG, EntityType.FOX) + .put(Material.GHAST_SPAWN_EGG, EntityType.GHAST) + .put(Material.GLOW_SQUID_SPAWN_EGG, EntityType.GLOW_SQUID) + .put(Material.GOAT_SPAWN_EGG, EntityType.GOAT) + .put(Material.GUARDIAN_SPAWN_EGG, EntityType.GUARDIAN) + .put(Material.HOGLIN_SPAWN_EGG, EntityType.HOGLIN) + .put(Material.HORSE_SPAWN_EGG, EntityType.HORSE) + .put(Material.HUSK_SPAWN_EGG, EntityType.HUSK) + .put(Material.LLAMA_SPAWN_EGG, EntityType.LLAMA) + .put(Material.MAGMA_CUBE_SPAWN_EGG, EntityType.MAGMA_CUBE) + .put(Material.MOOSHROOM_SPAWN_EGG, EntityType.MUSHROOM_COW) + .put(Material.MULE_SPAWN_EGG, EntityType.MULE) + .put(Material.OCELOT_SPAWN_EGG, EntityType.OCELOT) + .put(Material.PANDA_SPAWN_EGG, EntityType.PANDA) + .put(Material.PARROT_SPAWN_EGG, EntityType.PARROT) + .put(Material.PHANTOM_SPAWN_EGG, EntityType.PHANTOM) + .put(Material.PIG_SPAWN_EGG, EntityType.PIG) + .put(Material.PIGLIN_BRUTE_SPAWN_EGG, EntityType.PIGLIN_BRUTE) + .put(Material.PIGLIN_SPAWN_EGG, EntityType.PIGLIN) + .put(Material.PILLAGER_SPAWN_EGG, EntityType.PILLAGER) + .put(Material.POLAR_BEAR_SPAWN_EGG, EntityType.POLAR_BEAR) + .put(Material.PUFFERFISH_SPAWN_EGG, EntityType.PUFFERFISH) + .put(Material.RABBIT_SPAWN_EGG, EntityType.RABBIT) + .put(Material.RAVAGER_SPAWN_EGG, EntityType.RAVAGER) + .put(Material.SALMON_SPAWN_EGG, EntityType.SALMON) + .put(Material.SHEEP_SPAWN_EGG, EntityType.SHEEP) + .put(Material.SHULKER_SPAWN_EGG, EntityType.SHULKER) + .put(Material.SILVERFISH_SPAWN_EGG, EntityType.SILVERFISH) + .put(Material.SKELETON_HORSE_SPAWN_EGG, EntityType.SKELETON_HORSE) + .put(Material.SKELETON_SPAWN_EGG, EntityType.SKELETON) + .put(Material.SLIME_SPAWN_EGG, EntityType.SLIME) + .put(Material.SPIDER_SPAWN_EGG, EntityType.SPIDER) + .put(Material.SQUID_SPAWN_EGG, EntityType.SQUID) + .put(Material.STRAY_SPAWN_EGG, EntityType.STRAY) + .put(Material.STRIDER_SPAWN_EGG, EntityType.STRIDER) + .put(Material.TRADER_LLAMA_SPAWN_EGG, EntityType.TRADER_LLAMA) + .put(Material.TROPICAL_FISH_SPAWN_EGG, EntityType.TROPICAL_FISH) + .put(Material.TURTLE_SPAWN_EGG, EntityType.TURTLE) + .put(Material.VEX_SPAWN_EGG, EntityType.VEX) + .put(Material.VILLAGER_SPAWN_EGG, EntityType.VILLAGER) + .put(Material.VINDICATOR_SPAWN_EGG, EntityType.VINDICATOR) + .put(Material.WANDERING_TRADER_SPAWN_EGG, EntityType.WANDERING_TRADER) + .put(Material.WITCH_SPAWN_EGG, EntityType.WITCH) + .put(Material.WITHER_SKELETON_SPAWN_EGG, EntityType.WITHER_SKELETON) + .put(Material.WOLF_SPAWN_EGG, EntityType.WOLF) + .put(Material.ZOGLIN_SPAWN_EGG, EntityType.ZOGLIN) + .put(Material.ZOMBIE_SPAWN_EGG, EntityType.ZOMBIE) + .put(Material.ZOMBIE_HORSE_SPAWN_EGG, EntityType.ZOMBIE_HORSE) + .put(Material.ZOMBIFIED_PIGLIN_SPAWN_EGG, EntityType.ZOMBIFIED_PIGLIN) + .put(Material.ZOMBIE_VILLAGER_SPAWN_EGG, EntityType.ZOMBIE_VILLAGER) + .build(); public static void init() { + final var logger = CivLogger.getLogger(SpawnEggUtils.class); // Determine if there's any enchants missing names final Set missing = new HashSet<>(); CollectionUtils.addAll(missing, Material.values()); missing.removeIf(material -> !material.name().endsWith("_SPAWN_EGG") || SPAWN_EGGS.containsKey(material)); if (!missing.isEmpty()) { - Bukkit.getLogger().warning("[SpawnEggUtils] The following spawn eggs are missing: " + + logger.warning("The following spawn eggs are missing: " + missing.stream().map(Enum::name).collect(Collectors.joining(",")) + "."); } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/TreeTypeUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/TreeTypeUtils.java index 2b6d1bab..96841d0e 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/TreeTypeUtils.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/TreeTypeUtils.java @@ -5,11 +5,13 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import lombok.experimental.UtilityClass; import org.apache.commons.collections4.CollectionUtils; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.TreeType; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; +@UtilityClass public final class TreeTypeUtils { private static final Map TREE_MATERIALS = ImmutableMap.builder() @@ -72,6 +74,10 @@ public final class TreeTypeUtils { // Crimson Fungus .put(Material.WARPED_FUNGUS, TreeType.WARPED_FUNGUS) .put(Material.WARPED_STEM, TreeType.CRIMSON_FUNGUS) + //Azalea + .put(Material.FLOWERING_AZALEA, TreeType.AZALEA) + .put(Material.AZALEA_LEAVES, TreeType.AZALEA) + .put(Material.FLOWERING_AZALEA_LEAVES, TreeType.AZALEA) .build(); private static final Map SAPLING_MATERIALS = ImmutableMap.builder() @@ -106,9 +112,12 @@ public final class TreeTypeUtils { .put(TreeType.CRIMSON_FUNGUS, Material.CRIMSON_FUNGUS) // Crimson Fungus .put(TreeType.WARPED_FUNGUS, Material.WARPED_FUNGUS) + // Azalea + .put(TreeType.AZALEA, Material.FLOWERING_AZALEA) .build(); public static void init() { + final var logger = CivLogger.getLogger(TreeTypeUtils.class); // Determine if there's any tree types missing { final Set missing = new HashSet<>(); @@ -118,7 +127,7 @@ public static void init() { CollectionUtils.addAll(missing, TreeType.values()); missing.removeIf(type -> exclude.contains(type) || TREE_MATERIALS.containsValue(type)); if (!missing.isEmpty()) { - Bukkit.getLogger().warning("[TreeTypeUtils] The following tree types are missing: " + + logger.warning("The following tree types are missing: " + missing.stream().map(Enum::name).collect(Collectors.joining(",")) + "."); } } @@ -128,13 +137,12 @@ public static void init() { CollectionUtils.addAll(missing, TreeType.values()); missing.removeIf(SAPLING_MATERIALS::containsKey); if (!missing.isEmpty()) { - Bukkit.getLogger().warning("[TreeTypeUtils] The following sapling types are missing: " + + logger.warning("The following sapling types are missing: " + missing.stream().map(Enum::name).collect(Collectors.joining(",")) + "."); } } } - public static TreeType getMatchingTreeType(final Material material) { return TREE_MATERIALS.get(material); } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/IClickable.java b/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/IClickable.java deleted file mode 100644 index f74e1ec2..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/IClickable.java +++ /dev/null @@ -1,153 +0,0 @@ -package vg.civcraft.mc.civmodcore.inventorygui; - -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.inventory.ItemStack; - -public abstract class IClickable { - - /** - * General method called whenever this clickable is clicked with a type that did - * not a have a special implementation provided - * - * @param p Player who clicked - */ - protected abstract void clicked(Player p); - - /** - * Called when a player double clicks this clickable, overwrite to define - * special behavior for this - * - * @param p Player who clicked - */ - protected void onDoubleClick(Player p) { - clicked(p); - } - - /** - * Called when a player drops one of this clickable (in default keybinds - * pressing Q while hovering the slot), overwrite to define special behavior for - * this - * - * @param p Player who clicked - */ - protected void onDrop(Player p) { - clicked(p); - } - - /** - * Called when a player drops this clickable stack (in default keybinds pressing - * Q while holding CTRL and hovering the slot), overwrite to define special - * behavior for this - * - * @param p Player who clicked - */ - protected void onControlDrop(Player p) { - clicked(p); - } - - /** - * Called when a player left clicks this clickable, overwrite to define - * special behavior for this - * - * @param p Player who clicked - */ - protected void onLeftClick(Player p) { - clicked(p); - } - - /** - * Called when a player right clicks this clickable, overwrite to define - * special behavior for this - * - * @param p Player who clicked - */ - protected void onRightClick(Player p) { - clicked(p); - } - - /** - * Called when a player middle (mouse wheell) clicks this clickable, overwrite to define - * special behavior for this - * - * @param p Player who clicked - */ - protected void onMiddleClick(Player p) { - clicked(p); - } - - /** - * Called when a player left clicks this clickable while holding shift, overwrite to define - * special behavior for this - * - * @param p Player who clicked - */ - protected void onShiftLeftClick(Player p) { - clicked(p); - } - - /** - * Called when a player right clicks this clickable while holding shift, overwrite to define - * special behavior for this - * - * @param p Player who clicked - */ - protected void onShiftRightClick(Player p) { - clicked(p); - } - - public void handleClick(Player p, ClickType type) { - switch (type) { - case CONTROL_DROP: - onControlDrop(p); - break; - case DOUBLE_CLICK: - onDoubleClick(p); - break; - case DROP: - onDrop(p); - break; - case LEFT: - onLeftClick(p); - break; - case MIDDLE: - onMiddleClick(p); - break; - case RIGHT: - onRightClick(p); - break; - case SHIFT_LEFT: - onShiftLeftClick(p); - break; - case SHIFT_RIGHT: - onShiftRightClick(p); - break; - case CREATIVE: - case UNKNOWN: - case WINDOW_BORDER_LEFT: - case WINDOW_BORDER_RIGHT: - case NUMBER_KEY: - clicked(p); - break; - default: - clicked(p); - break; - } - } - - /** - * @return Which item stack represents this clickable when it is initially - * loaded into the inventory - */ - public abstract ItemStack getItemStack(); - - /** - * Called when this instance is added to an inventory so it can do something if - * desired - * - * @param inv Inventory it was added to - * @param slot Slot in which it was added - */ - public abstract void addedToInventory(ClickableInventory inv, int slot); - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/SlotPredicates.java b/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/SlotPredicates.java deleted file mode 100644 index 3922234f..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/components/SlotPredicates.java +++ /dev/null @@ -1,80 +0,0 @@ -package vg.civcraft.mc.civmodcore.inventorygui.components; - -import java.util.function.IntPredicate; - -public final class SlotPredicates { - - private static final int ROW_LENGTH = 9; - - private SlotPredicates() { - } - - public static IntPredicate slots(int count) { - Counter counter = new Counter(count); - return counter::isValid; - } - - public static IntPredicate rows(int rows) { - return rows(rows, 9); - } - - public static IntPredicate rows(int rows, int rowLength) { - return slots(rows * rowLength); - } - - public static IntPredicate offsetRectangle(int rows, int columns, int verticalOffset, int horizontalOffset, - int rowLength) { - OffsetSlotter slotter = new OffsetSlotter(verticalOffset, horizontalOffset, rows, columns, rowLength); - return slotter::isValid; - } - - public static IntPredicate offsetRectangle(int rows, int columns, int verticalOffset, int horizontalOffset) { - return offsetRectangle(rows, columns, verticalOffset, horizontalOffset, ROW_LENGTH); - } - - public static IntPredicate rectangle(int rows, int columns, int rowLength) { - return offsetRectangle(rows, columns, 0, 0, rowLength); - } - - public static IntPredicate rectangle(int rows, int columns) { - return rectangle(rows, columns, ROW_LENGTH); - } - - private static class OffsetSlotter { - - private int lowerHorizontalBound; - private int lowerVerticalBound; - private int upperHorizontalBound; - private int upperVerticalBound; - private int rowLength; - - OffsetSlotter(int offSetRow, int offSetColumn, int sizeRows, int sizeColumns, int rowLength) { - this.lowerHorizontalBound = offSetColumn; - this.upperHorizontalBound = offSetColumn + sizeColumns - 1; - this.lowerVerticalBound = offSetRow; - this.upperVerticalBound = offSetRow + sizeRows - 1; - this.rowLength = rowLength; - } - - boolean isValid(int index) { - int row = index / rowLength; - int column = index % rowLength; - return column >= lowerHorizontalBound && column <= upperHorizontalBound && row >= lowerVerticalBound - && row <= upperVerticalBound; - } - - } - - private static class Counter { - private int count; - - Counter(int count) { - this.count = count; - } - - public boolean isValid(int index) { - return count-- > 0; - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/history/HistoryItem.java b/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/history/HistoryItem.java deleted file mode 100644 index 98941363..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/history/HistoryItem.java +++ /dev/null @@ -1,7 +0,0 @@ -package vg.civcraft.mc.civmodcore.inventorygui.history; - -public interface HistoryItem { - - void setStateTo(); - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/paged/PagedGUI.java b/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/paged/PagedGUI.java deleted file mode 100644 index a53d3f50..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/paged/PagedGUI.java +++ /dev/null @@ -1,356 +0,0 @@ -package vg.civcraft.mc.civmodcore.inventorygui.paged; - -import com.google.common.base.Preconditions; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.Stack; -import java.util.UUID; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryView; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitTask; -import vg.civcraft.mc.civmodcore.CivModCorePlugin; -import vg.civcraft.mc.civmodcore.inventory.InventoryUtils; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.inventorygui.ClickableInventory; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; - -/** - * Class that represents a proverbial slideshow of pages that can be switched to efficiently. - * - * You could also use {@link vg.civcraft.mc.civmodcore.inventorygui.MultiPageView} - */ -public final class PagedGUI { - - private final int slots; - - private final UUID uuid; - - private final ClickableInventory inventory; - - private final Stack history; - - private Page currentPage; - - /** - * Creates a paged GUI based on an amount of slots and a name. - * - * @param slots The amount of slots, which must pass a {@link InventoryUtils#isValidChestSize(int)} test. - * @param name The name for the GUI, which should be relevant across each page as it cannot be changed. - */ - public PagedGUI(int slots, String name) { - Preconditions.checkArgument(InventoryUtils.isValidChestSize(slots)); - this.slots = slots; - this.uuid = UUID.randomUUID(); - this.inventory = new LegacyInventory(slots, name); - this.history = new Stack<>(); - this.currentPage = null; - } - - /** - * Resets the paged GUI if there's one or fewer viewers. - * - * @return Returns true if the paged GUI was reset. - */ - public boolean reset() { - if (InventoryUtils.hasOtherViewers(this.inventory.getInventory())) { - return false; - } - InventoryUtils.clearInventory(this.inventory.getInventory()); - this.history.clear(); - this.currentPage = null; - return true; - } - - /** - *

    Creates a new page to be used for this paged GUI.

    - * - *

    Note: The returned page will be forever bound to this paged GUI, do not try to use it for another.

    - * - * @return Returns a new page for this GUI. - */ - public Page createPage() { - return new Page(); - } - - /** - * Presents this paged GUI to a player. - * - * @param player The player to present this paged GUI to. - * @return Returns true if the player was successfully presented with this paged GUI. - */ - public boolean showGUI(Player player) { - Preconditions.checkArgument(player != null); - InventoryView view = player.openInventory(getInventory()); - if (view == null) { - return false; - } - PagedGUIManager.GUIs.put(getInventory(), this); - return true; - } - - /** - *

    Handler for when this paged GUI has been clicked.

    - * - *

    Note: Only {@link PagedGUIManager#onInventoryClick(InventoryClickEvent)} should call this method.

    - * - * @param slot The slot of the button that has been clicked. - * @param clicker The player who clicked the button. - */ - void clicked(int slot, Player clicker, ClickType clickType) { - if (this.currentPage == null) { - return; - } - IClickable button = this.currentPage.getButton(slot); - if (button == null) { - return; - } - button.handleClick(clicker, clickType); - } - - /** - * @return Returns how many slots this GUI has. - */ - public int getSlotCount() { - return this.slots; - } - - /** - * @return Returns the underlying inventory storing the items representing buttons. - */ - public Inventory getInventory() { - return this.inventory.getInventory(); - } - - /** - * Clears this GUI's page lineage, making the current page the root page. - */ - public void clearPageLineage() { - this.history.clear(); - } - - /** - * Gets the current page. - * - * @return Returns the current page, which may be null. - */ - public Page getCurrentPage() { - return this.currentPage; - } - - /** - * Sets the current page. Will add the previous current page to the lineage. - * - * @param page The page to set, which cannot be null. - */ - public void setCurrentPage(Page page) { - setCurrentPage(page, true); - } - - /** - * Sets the current page. - * - * @param page The page to set, which cannot be null. - * @param addPreviousToHistory If true will add the previous current page to the lineage. - */ - public void setCurrentPage(Page page, boolean addPreviousToHistory) { - Preconditions.checkArgument(page != null && page.getGUI() == this); - Page previousPage = this.currentPage; - if (previousPage == page) { - return; - } - this.currentPage = page; - if (previousPage != null) { - if (addPreviousToHistory) { - this.history.push(previousPage); - } - previousPage.hidden(); - } - page.showGUI(); - updateGUI(); - } - - /** - * Goes to the previous page within the lineage. If no lineage can be found then the GUI is emptied. - */ - public void goToPreviousPage() { - if (!this.history.isEmpty()) { - Page page = this.history.pop(); - if (page != null) { - setCurrentPage(page, false); - return; - } - } - this.currentPage = null; - InventoryUtils.clearInventory(this.inventory.getInventory()); - updateGUI(); - } - - private void updateGUI() { - Bukkit.getScheduler().scheduleSyncDelayedTask(CivModCorePlugin.getInstance(), this.inventory::updateInventory); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (!(other instanceof PagedGUI)) { - return false; - } - if (hashCode() != other.hashCode()) { - return false; - } - return true; - } - - @Override - public int hashCode() { - return Objects.hash(this.uuid, this.inventory); - } - - /** - * Class that represents a page of a gui, a proverbial slide of a slideshow. - */ - public final class Page { - - private final IClickable[] items; - - private final Set tasks; - - private Page() { - this.items = new IClickable[slots]; - this.tasks = new HashSet<>(); - } - - private void showGUI() { - for (int i = 0; i < slots; i++) { - IClickable clickable = this.items[i]; - if (clickable == null) { - inventory.setItem(null, i); - continue; - } - ItemStack button = clickable.getItemStack(); - if (!ItemUtils.isValidItem(button)) { - inventory.setItem(null, i); - continue; - } - inventory.setItem(button, i); - clickable.addedToInventory(inventory, i); - } - } - - private void hidden() { - this.tasks.forEach(BukkitTask::cancel); - this.tasks.clear(); - } - - /** - * Resets this page to if it had just been created. - */ - public void reset() { - Arrays.fill(this.items, null); - hidden(); - } - - /** - * Gets the particular GUI this page is bound to. Do not attempt to use this page for another GUI, it will fail. - * - * @return Returns the GUI this page is bound to. - */ - public PagedGUI getGUI() { - return PagedGUI.this; - } - - /** - * Checks whether this page is currently being displayed. - * - * @return Returns true if this page is currently being displayed. - */ - public boolean isCurrentPage() { - return currentPage == this; - } - - /** - * Gets a button (a clickable) for a particular index. - * - * @param index The index to get the button for. - * @return Returns the button at the given index, or null. - */ - public IClickable getButton(int index) { - return this.items[index]; - } - - /** - * Sets a button (a clickable) to a particular index. - * - * @param index The index to save the button to. - * @param button The button to save. - */ - public void setButton(int index, IClickable button) { - this.items[index] = button; - } - - /** - * Adds a task to this page. This feature pretty much exists to support - * {@link vg.civcraft.mc.civmodcore.inventorygui.AnimatedClickable AnimatedClickable}. - * - * @param task The task to add. - */ - public void addTask(BukkitTask task) { - Preconditions.checkArgument(task != null); - Preconditions.checkArgument(!task.isCancelled()); - Preconditions.checkArgument(task.isSync()); - this.tasks.add(task); - } - - /** - * Removes a task from the page and will be cancelled in the process. - * - * @param task The task to remove and cancel. - */ - public void removeTask(BukkitTask task) { - Preconditions.checkArgument(task != null); - task.cancel(); - this.tasks.remove(task); - } - - } - - /** - * This class is just a ClickableInventory to - */ - public class LegacyInventory extends ClickableInventory { - - public LegacyInventory(int size, String name) { - super(size, name); - } - - @Override - public void registerTask(BukkitTask task) { - Preconditions.checkArgument(currentPage != null); - currentPage.addTask(task); - } - - @Override - @Deprecated - public void showInventory(Player p) { - throw new UnsupportedOperationException(); - } - - @Override - public void updateInventory() { - for (Player viewer : InventoryUtils.getViewingPlayers(getInventory())) { - viewer.updateInventory(); - } - } - - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/paged/PagedGUIManager.java b/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/paged/PagedGUIManager.java deleted file mode 100644 index 3d53d50d..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/inventorygui/paged/PagedGUIManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package vg.civcraft.mc.civmodcore.inventorygui.paged; - -import java.util.HashMap; -import java.util.Map; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.inventory.Inventory; -import vg.civcraft.mc.civmodcore.entities.EntityUtils; -import vg.civcraft.mc.civmodcore.inventory.InventoryUtils; - -public final class PagedGUIManager implements Listener { - - static final Map GUIs = new HashMap<>(); - - public static void closeAllGUIs() { - for (Map.Entry entry : GUIs.entrySet()) { - for (Player player : InventoryUtils.getViewingPlayers(entry.getKey())) { - player.closeInventory(); - } - entry.getValue().reset(); - } - GUIs.clear(); - } - - @EventHandler - public void onInventoryClose(InventoryCloseEvent event) { - GUIs.computeIfPresent(event.getInventory(), (k, gui) -> gui.reset() ? null : gui); - } - - @EventHandler - public void onInventoryClick(InventoryClickEvent event) { - if (!EntityUtils.isPlayer(event.getWhoClicked())) { - return; - } - Player viewer = (Player) event.getWhoClicked(); - PagedGUI gui = GUIs.get(event.getInventory()); - if (gui == null) { - return; - } - switch (event.getAction()) { - // Disable unknown actions - default: - case UNKNOWN: - // These events are cursed. There's no way to know where the items are moving to or from, just cancel. - case COLLECT_TO_CURSOR: - case MOVE_TO_OTHER_INVENTORY: - event.setCancelled(true); - return; - // Leave these be as they aren't dangerous. Cloning a stack is an OP feature and clones to your cursor - case NOTHING: - case CLONE_STACK: - return; - // Allow the following in context to the player's inventory, but not when interacting with the GUI - case PICKUP_ONE: - case PICKUP_SOME: - case PICKUP_HALF: - case PICKUP_ALL: - case PLACE_ONE: - case PLACE_SOME: - case PLACE_ALL: - case SWAP_WITH_CURSOR: - case DROP_ONE_SLOT: - case DROP_ALL_SLOT: - case DROP_ONE_CURSOR: - case DROP_ALL_CURSOR: - case HOTBAR_MOVE_AND_READD: - case HOTBAR_SWAP: { - event.setCancelled(true); - gui.clicked(event.getSlot(), viewer, event.getClick()); - } - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/itemHandling/ItemMap.java b/src/main/java/vg/civcraft/mc/civmodcore/itemHandling/ItemMap.java deleted file mode 100644 index fb6be0b6..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/itemHandling/ItemMap.java +++ /dev/null @@ -1,635 +0,0 @@ -package vg.civcraft.mc.civmodcore.itemHandling; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.logging.Logger; -import net.minecraft.server.v1_16_R3.NBTTagCompound; -import net.minecraft.server.v1_16_R3.NBTTagList; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; - -/** - * Allows the storage and comparison of itemstacks while ignoring their maximum - * possible stack sizes. This offers various tools to compare inventories, to - * store recipe costs or to specify setupcosts. Take great care when dealing - * with itemstacks with negative amounnts, while this implementation should be - * consistent even with negative values, they create possibly unexpected - * results. For example an empty inventory/itemmap will seem to contain items - * when compared to a map containing negative values. Additionally this - * implementation allows durability "wild cards", if you specify -1 as - * durability it will count as any given durability. When working with multiple - * ItemMaps this will only work if all methods are executed on the instance - * containing items with a durability of -1. - */ -public class ItemMap { - - private static final Logger log = Bukkit.getLogger(); - - private HashMap items; - - private int totalItems; - - /** - * Empty constructor to create empty item map - */ - public ItemMap() { - items = new HashMap<>(); - totalItems = 0; - } - - /** - * Constructor to create an item map based on the content of an inventory. The - * ItemMap will not be in sync with the inventory, it will only update if it's - * explicitly told to do so - * - * @param inv Inventory to base the item map on - */ - public ItemMap(Inventory inv) { - totalItems = 0; - update(inv); - } - - /** - * Constructor to create an ItemMap based on a single ItemStack - * - * @param is ItemStack to start with - */ - public ItemMap(ItemStack is) { - items = new HashMap<>(); - totalItems = 0; - addItemStack(is); - } - - /** - * Constructor to create an item map based on a collection of ItemStacks - * - * @param stacks Stacks to add to the map - */ - public ItemMap(Collection stacks) { - items = new HashMap<>(); - addAll(stacks); - } - - /** - * Clones the given itemstack, sets its amount to one and checks whether a stack - * equaling the created one exists in the item map. If yes the amount of the - * given stack (before the amount was set to 1) will be added to the current - * amount in the item map, if not a new entry in the map with the correct amount - * will be created - * - * @param input ItemStack to insert - */ - public void addItemStack(ItemStack input) { - if (input != null) { - // log().info("Adding {0} as ItemStack", input.toString()); - ItemStack is = createMapConformCopy(input); - // log().info(" Conform Copy: {0}", is.toString()); - if (is == null) { - return; - } - Integer i; - if ((i = items.get(is)) != null) { - items.put(is, i + input.getAmount()); - } else { - items.put(is, input.getAmount()); - } - totalItems += input.getAmount(); - } - } - - /** - * Adds all the items contained in this instance to the given inventory - * - * @param inventory Inventory to add items to - */ - public void addToInventory(Inventory inventory) { - for (ItemStack is : getItemStackRepresentation()) { - inventory.addItem(is); - } - } - - /** - * Removes the given ItemStack from this map. Only the amount of the given - * ItemStack will be removed, not all of them. If the amount of the given - * itemstack is bigger than the existing ones in this map, not more than the - * amount in this map will be removed - * - * @param input ItemStack to remove - */ - public void removeItemStack(ItemStack input) { - ItemStack is = createMapConformCopy(input); - if (is == null) { - return; - } - Integer value = items.get(is); - if (value != null) { - int newVal = value - input.getAmount(); - if (newVal > 0) { - items.put(is, newVal); - } else { - items.remove(is); - } - } - } - - /** - * Completly removes the given itemstack of this item map, completly independent - * of its amount - * - * @param input ItemStack to remove - */ - public void removeItemStackCompletly(ItemStack input) { - ItemStack is = createMapConformCopy(input); - if (is != null) { - items.remove(is); - } - } - - @Override - public int hashCode() { - int res = 0; - for (Entry entry : items.entrySet()) { - res += entry.hashCode(); - } - return res; - } - - /** - * Adds all the stacks given in the collection to this map - * - * @param stacks Stacks to add - */ - public void addAll(Collection stacks) { - for (ItemStack is : stacks) { - if (is != null) { - addItemStack(is); - } - } - } - - /** - * Merges the given item map into this instance - * - * @param im ItemMap to merge - */ - public void merge(ItemMap im) { - for (Entry entry : im.getEntrySet()) { - addItemAmount(entry.getKey(), entry.getValue()); - } - } - - public void update(Inventory inv) { - items = new HashMap<>(); - totalItems = 0; - for (int i = 0; i < inv.getSize(); i++) { - ItemStack is = inv.getItem(i); - if (is != null) { - addItemStack(is); - } - } - } - - public void addEntrySet(Set> entries) { - for (Entry entry : entries) { - addItemAmount(entry.getKey(), entry.getValue()); - } - } - - /** - * Utility method, which has the amount of items to add as parameter. - * - * @param input ItemStack to sort into the map - * @param amount Amount associated with the given ItemStack - */ - public void addItemAmount(ItemStack input, int amount) { - ItemStack copy = createMapConformCopy(input); - if (copy == null) { - return; - } - copy.setAmount(amount); - addItemStack(copy); - } - - /** - * Gets a submap of this instance which contains all stacks with the same - * material as the given one and their respective amounts - * - * @param m Material to search for - * @return New ItemMap with all ItemStack and their amount whose material - * matches the given one - */ - public ItemMap getStacksByMaterial(Material m) { - ItemMap result = new ItemMap(); - for (ItemStack is : items.keySet()) { - if (is.getType() == m) { - result.addItemAmount(is.clone(), items.get(is)); - } - } - return result; - } - - public ItemMap getStacksByMaterial(ItemStack is) { - return getStacksByMaterial(is.getType()); - } - - /** - * Gets a submap of this instance which contains all stacks with the same - * material and enchants as the given one and their respective amounts - * - * @param m Material to search for - * @param enchants Enchants to search for - * @return New ItemMap with all ItemStack and their amount whose material and - * enchants matches the given one - */ - public ItemMap getStacksByMaterialEnchants(Material m, Map enchants) { - ItemMap result = new ItemMap(); - for (ItemStack is : items.keySet()) { - if (is.getType() == m && is.getItemMeta() != null && is.getItemMeta().getEnchants().equals(enchants)) { - result.addItemAmount(is.clone(), items.get(is)); - } - } - return result; - } - - public ItemMap getStacksByMaterialEnchants(ItemStack is) { - if (is.getItemMeta() != null) { - return getStacksByMaterialEnchants(is.getType(), is.getItemMeta().getEnchants()); - } else { - return getStacksByMaterialEnchants(is.getType(), new HashMap<>()); - } - } - - /** - * Gets a submap of this instance which contains all stacks with the same lore - * as the given and their respective amount - * - * @param lore Lore to search for - * @return New ItemMap with all ItemStacks and their amount whose lore matches - * the given one - */ - public ItemMap getStacksByLore(List lore) { - ItemMap result = new ItemMap(); - for (ItemStack is : items.keySet()) { - if (is.getItemMeta() != null && is.getItemMeta().getLore().equals(lore)) { - result.addItemAmount(is.clone(), items.get(is)); - } - } - return result; - } - - /** - * Gets how many items of the given stack are in this map. Be aware that if a - * stack doesnt equal with the given one, for example because of mismatched NBT - * tags, it wont be included in the result - * - * @param is Exact ItemStack to search for - * @return amount of items like the given stack in this map - */ - public int getAmount(ItemStack is) { - ItemMap matSubMap = getStacksByMaterial(is); - int amount = 0; - for (Entry entry : matSubMap.getEntrySet()) { - ItemStack current = entry.getKey(); - if (is.getItemMeta().equals(current.getItemMeta())) { - amount += entry.getValue(); - } - } - return amount; - } - - /** - * @return How many items are stored in this map total - */ - public int getTotalItemAmount() { - return totalItems; - } - - /** - * @return How many unique items are stored in this map - */ - public int getTotalUniqueItemAmount() { - return items.keySet().size(); - } - - @SuppressWarnings("unchecked") - public Set> getEntrySet() { - return ((HashMap) items.clone()).entrySet(); - } - - /** - * Checks whether an inventory contains exactly what's described in this ItemMap - * - * @param i Inventory to compare - * @return True if the inventory is identical with this instance, false if not - */ - public boolean containedExactlyIn(Inventory i) { - ItemMap invMap = new ItemMap(i); - for (Entry entry : getEntrySet()) { - if (!entry.getValue().equals(invMap.getAmount(entry.getKey()))) { - return false; - } - } - for (ItemStack is : i.getContents()) { - if (is == null) { - continue; - } - if (getStacksByMaterial(is).getTotalUniqueItemAmount() == 0) { - return false; - } - } - return true; - } - - /** - * Checks whether this instance is completly contained in the given inventory, - * which means every stack in this instance is also in the given inventory and - * the amount in the given inventory is either the same or bigger as in this - * instance - * - * @param i inventory to check - * @return true if this instance is completly contained in the given inventory, - * false if not - */ - public boolean isContainedIn(Inventory i) { - ItemMap invMap = new ItemMap(i); - for (Entry entry : getEntrySet()) { - if (entry.getValue() > invMap.getAmount(entry.getKey())) { - return false; - } - } - return true; - } - - @Override - public String toString() { - StringBuilder res = new StringBuilder(); - for (ItemStack is : getItemStackRepresentation()) { - res.append(is.toString() + ";"); - } - return res.toString(); - } - - /** - * Checks how often this ItemMap is contained in the given ItemMap or how often - * this ItemMap could be removed from the given one before creating negative - * stacks - * - * @param i ItemMap to check - * @return How often this map is contained in the given one or Integer.MAX_VALUE - * if this instance is empty - */ - public int getMultiplesContainedIn(Inventory i) { - ItemMap invMap = new ItemMap(i); - int res = Integer.MAX_VALUE; - for (Entry entry : getEntrySet()) { - int pulledAmount = invMap.getAmount(entry.getKey()); - int multiples = pulledAmount / entry.getValue(); - res = Math.min(res, multiples); - } - return res; - } - - /** - * Multiplies the whole content of this instance by the given multiplier - * - * @param multiplier Multiplier to scale the amount of the contained items with - */ - public void multiplyContent(double multiplier) { - totalItems = 0; - for (Entry entry : getEntrySet()) { - items.put(entry.getKey(), (int) (entry.getValue() * multiplier)); - totalItems += (int) (entry.getValue() * multiplier); - } - } - - /** - * Turns this item map into a list of ItemStacks, with amounts that do not - * surpass the maximum allowed stack size for each ItemStack - * - * @return List of stacksize conform ItemStacks - */ - public List getItemStackRepresentation() { - List result = new ArrayList<>(); - for (Entry entry : getEntrySet()) { - ItemStack is = entry.getKey(); - Integer amount = entry.getValue(); - while (amount != 0) { - ItemStack toAdd = is.clone(); - int addAmount = Math.min(amount, is.getMaxStackSize()); - toAdd.setAmount(addAmount); - // log.info("Adding {0} as ItemStack", toAdd.toString()); - result.add(toAdd); - amount -= addAmount; - } - } - return result; - } - - /** - * Clones this map - */ - @Override - public ItemMap clone() { - ItemMap clone = new ItemMap(); - for (Entry entry : getEntrySet()) { - clone.addItemAmount(entry.getKey(), entry.getValue()); - } - return clone; - } - - /** - * Checks whether this instance would completly fit into the given inventory - * - * @param i Inventory to check - * @return True if this ItemMap's item representation would completly fit in the - * inventory, false if not - */ - public boolean fitsIn(Inventory i) { - int size; - if (i instanceof PlayerInventory) { - size = 36; - } else { - size = i.getSize(); - } - ItemMap invCopy = new ItemMap(); - for (ItemStack is : i.getStorageContents()) { - invCopy.addItemStack(is); - } - ItemMap instanceCopy = this.clone(); - instanceCopy.merge(invCopy); - return instanceCopy.getItemStackRepresentation().size() <= size; - } - - /** - * Instead of converting into many stacks of maximum size, this creates a stack - * with an amount of one for each entry and adds the total item amount and stack - * count as lore, which is needed to display larger ItemMaps in inventories - * - * @return UI representation of large ItemMap - */ - public List getLoredItemCountRepresentation() { - Set> entrySet = getEntrySet(); - List items = new ArrayList<>(entrySet.size()); - for (Entry entry : entrySet) { - ItemStack is = entry.getKey().clone(); - ItemUtils.addLore(is, ChatColor.GOLD + "Total item count: " + entry.getValue()); - if (entry.getValue() > entry.getKey().getType().getMaxStackSize()) { - int stacks = entry.getValue() / is.getType().getMaxStackSize(); - int extra = entry.getValue() % is.getType().getMaxStackSize(); - StringBuilder out = new StringBuilder(ChatColor.GOLD.toString()); - if (stacks != 0) { - out.append(stacks + " stack" + (stacks == 1 ? "" : "s")); - } - if (extra != 0) { - out.append(" and " + extra); - out.append(" item" + (extra == 1 ? "" : "s")); - } - ItemUtils.addLore(is, out.toString()); - } - items.add(is); - } - return items; - } - - /** - * Attempts to remove the content of this ItemMap from the given inventory. If - * it fails to find all the required items it will stop and return false - * - * @param i Inventory to remove from - * @return True if everything was successfully removed, false if not - */ - public boolean removeSafelyFrom(Inventory i) { - for (Entry entry : getEntrySet()) { - int amountToRemove = entry.getValue(); - ItemStack is = entry.getKey(); - for (ItemStack inventoryStack : i.getStorageContents()) { - if (inventoryStack == null) { - continue; - } - if (inventoryStack.getType() == is.getType()) { - ItemMap compareMap = new ItemMap(inventoryStack); - int removeAmount = Math.min(amountToRemove, compareMap.getAmount(is)); - if (removeAmount != 0) { - ItemStack cloneStack = inventoryStack.clone(); - cloneStack.setAmount(removeAmount); - if (!i.removeItem(cloneStack).isEmpty()) { - return false; - } else { - amountToRemove -= removeAmount; - if (amountToRemove <= 0) { - break; - } - } - } - } - } - if (amountToRemove > 0) { - if (i instanceof PlayerInventory) { - PlayerInventory pInv = (PlayerInventory) i; - ItemStack offHand = pInv.getItemInOffHand(); - if (offHand == null) { - return false; - } - if (offHand.getType() == is.getType()) { - ItemMap compareMap = new ItemMap(offHand); - int removeAmount = Math.min(amountToRemove, compareMap.getAmount(is)); - int updatedCount = Math.max(0, offHand.getAmount() - removeAmount); - amountToRemove -= removeAmount; - if (updatedCount == 0) { - pInv.setItemInOffHand(null); - } else { - offHand.setAmount(updatedCount); - } - } - - } - if (amountToRemove > 0) { - return false; - } - } - } - return true; - } - - @Override - public boolean equals(Object o) { - if (o instanceof ItemMap) { - ItemMap im = (ItemMap) o; - if (im.getTotalItemAmount() == getTotalItemAmount()) { - return im.getEntrySet().equals(getEntrySet()); - } - } - return false; - } - - /** - * Utility to not mess with stacks directly taken from inventories - * - * @param is Template ItemStack - * @return Cloned ItemStack with its amount set to 1 - */ - private static ItemStack createMapConformCopy(ItemStack is) { - ItemStack copy = is.clone(); - copy.setAmount(1); - net.minecraft.server.v1_16_R3.ItemStack s = CraftItemStack.asNMSCopy(copy); - if (s == null) { - log.info("Attempted to create map conform copy of " + copy.toString() - + ", but couldn't because this item can't be held in inventories since Minecraft 1.8"); - return null; - } - s.setRepairCost(0); - copy = CraftItemStack.asBukkitCopy(s); - return copy; - } - - /** - * Utility to add NBT tags to an item and produce a custom stack size - * - * @param is Template Bukkit ItemStack - * @param amt Output Stack Size - * @param map Java Maps and Lists representing NBT data - * @return Cloned ItemStack with amount set to amt and NBT set to map. - */ - public static ItemStack enrichWithNBT(ItemStack is, int amt, Map map) { - log.fine("Received request to enrich " + is.toString()); - ItemStack copy = is.clone(); - amt = (amt < 1 ? 1 : amt > is.getMaxStackSize() ? is.getMaxStackSize() : amt); - copy.setAmount(amt); - net.minecraft.server.v1_16_R3.ItemStack s = CraftItemStack.asNMSCopy(copy); - if (s == null) { - log.severe("Failed to create enriched copy of " + copy.toString()); - return null; - } - - NBTTagCompound nbt = s.getTag(); - if (nbt == null) { - nbt = new NBTTagCompound(); - } - - TagManager.mapToNBT(nbt, map); - s.setTag(nbt); - copy = CraftItemStack.asBukkitCopy(s); - return copy; - } - - public static NBTTagCompound mapToNBT(NBTTagCompound base, Map map) { - return TagManager.mapToNBT(base, map); - } - - public static NBTTagList listToNBT(NBTTagList base, List list) { - return TagManager.listToNBT(base, list); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/itemHandling/TagManager.java b/src/main/java/vg/civcraft/mc/civmodcore/itemHandling/TagManager.java deleted file mode 100644 index e59a76c9..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/itemHandling/TagManager.java +++ /dev/null @@ -1,317 +0,0 @@ -package vg.civcraft.mc.civmodcore.itemHandling; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.logging.Logger; -import net.minecraft.server.v1_16_R3.NBTBase; -import net.minecraft.server.v1_16_R3.NBTTagByte; -import net.minecraft.server.v1_16_R3.NBTTagByteArray; -import net.minecraft.server.v1_16_R3.NBTTagCompound; -import net.minecraft.server.v1_16_R3.NBTTagDouble; -import net.minecraft.server.v1_16_R3.NBTTagFloat; -import net.minecraft.server.v1_16_R3.NBTTagInt; -import net.minecraft.server.v1_16_R3.NBTTagIntArray; -import net.minecraft.server.v1_16_R3.NBTTagList; -import net.minecraft.server.v1_16_R3.NBTTagLong; -import net.minecraft.server.v1_16_R3.NBTTagShort; -import net.minecraft.server.v1_16_R3.NBTTagString; -import org.bukkit.Bukkit; -import org.bukkit.configuration.MemorySection; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; -import org.bukkit.inventory.ItemStack; - -@Deprecated -public class TagManager { - - private static final Logger log = Bukkit.getLogger(); - - private NBTTagCompound tag; - - public TagManager() { - this.tag = new NBTTagCompound(); - } - - public TagManager(ItemStack is) { - if (is == null) { - throw new IllegalArgumentException("Expected item stack parameter but NULL passed."); - } - - net.minecraft.server.v1_16_R3.ItemStack s = CraftItemStack.asNMSCopy(is); - this.tag = s.getTag(); - - if (this.tag == null) { - this.tag = new NBTTagCompound(); - } - } - - private TagManager(NBTTagCompound tag) { - this.tag = tag; - } - - public String getString(String key) { - return this.tag.getString(key); - } - - public void setString(String key, String value) { - this.tag.setString(key, value); - } - - public int getInt(String key) { - return this.tag.getInt(key); - } - - public void setInt(String key, int value) { - this.tag.setInt(key, value); - } - - public short getShort(String key) { - return this.tag.getShort(key); - } - - public void setShort(String key, short value) { - this.tag.setShort(key, value); - } - - public byte getByte(String key) { - return this.tag.getByte(key); - } - - public void setByte(String key, byte value) { - this.tag.setByte(key, value); - } - - public List getStringList(String key) { - NBTTagList tagList = this.tag.getList(key, 8); - List list = new ArrayList<>(); - - for (int i = 0; i < tagList.size(); i++) { - list.add(tagList.getString(i)); - } - - return list; - } - - public void setStringList(String key, List list) { - NBTTagList tagList = new NBTTagList(); - - for (String s : list) { - tagList.add(NBTTagString.a(s)); - } - - this.tag.set(key, tagList); - } - - public List getIntList(String key) { - NBTTagList tagList = this.tag.getList(key, 3); - List list = new ArrayList<>(); - - for (int i = 0; i < tagList.size(); i++) { - list.add(tagList.e(i)); - } - - return list; - } - - public void setIntList(String key, List list) { - NBTTagList tagList = new NBTTagList(); - - for (Integer i : list) { - tagList.add(NBTTagInt.a(i)); - } - - this.tag.set(key, tagList); - } - - /** - * This wasn't really deprecated so much as they just didn't put in the short accessor to the - * tag list (not sure if they forgot or what, but it used to be missing) - * This basically copies the same implementation as the getIntList for shorts - * Note: You need to decompile or use an IDE to get the function name that is the 'short' accessor - * in this code when a new version is made - * - * So this is the old comment, I am keeping it for posterity in case it is removed again - * Reference the git history to see the old implementation, have fun wit hteh deobfuscation - * - * As of 1.12, the base NBTTagList has no accessor specific for Shorts, so we'll mark it deprecated here. - * Weirdly, the superclass has a method f() that still returns a Short for all number types, but NBTNumber isn't - * visible so ... hack it is. - * - * @param key - * @return - */ - @Deprecated - public List getShortList(String key) { - NBTTagList tagList = this.tag.getList(key, 2); - List list = new ArrayList<>(); - - for (int i = 0; i < tagList.size(); ++i) { - list.add(tagList.d(i)); - } - - return list; - } - - /** - * Deprecating this as well as of 1.12, even though technically it is still supported (nothing prevents the creation) - * however since accessing a short list _is_, so should writing. - * - * @param key - * @param list - */ - @Deprecated - public void setShortList(String key, List list) { - NBTTagList tagList = new NBTTagList(); - - for (Short s : list) { - tagList.add(NBTTagShort.a(s)); - } - - this.tag.set(key, tagList); - } - - public TagManager getCompound(String key) { - return new TagManager(this.tag.getCompound(key)); - } - - public void setCompound(String key, TagManager tag) { - this.tag.set(key, tag.tag); - } - - public ItemStack enrichWithNBT(ItemStack is) { - - net.minecraft.server.v1_16_R3.ItemStack s = CraftItemStack.asNMSCopy(is); - - if (s == null) { - log.severe("Failed to create enriched copy of " + is.toString()); - return null; - } - - s.setTag(this.tag); - - return CraftItemStack.asBukkitCopy(s); - } - - public void setMap(Map map) { - mapToNBT(this.tag, map); - } - - public void setList(String key, List list) { - this.tag.set(key, listToNBT(new NBTTagList(), list)); - } - - @SuppressWarnings("unchecked") - public static NBTTagCompound mapToNBT(NBTTagCompound base, Map map) { - log.fine("Representing map --> NBTTagCompound"); - if (map == null || base == null) { - return base; - } - for (Map.Entry entry : map.entrySet()) { - Object object = entry.getValue(); - if (object instanceof Map) { - log.fine("Adding map at key " + entry.getKey()); - base.set(entry.getKey(), mapToNBT(new NBTTagCompound(), (Map) object)); - } else if (object instanceof MemorySection) { - log.fine("Adding map from MemorySection at key " + entry.getKey()); - base.set(entry.getKey(), mapToNBT(new NBTTagCompound(), ((MemorySection) object).getValues(true))); - } else if (object instanceof List) { - log.fine("Adding list at key " + entry.getKey()); - base.set(entry.getKey(), listToNBT(new NBTTagList(), (List) object)); - } else if (object instanceof String) { - log.fine("Adding String " + object + " at key " + entry.getKey()); - base.setString(entry.getKey(), (String) object); - } else if (object instanceof Double) { - log.fine("Adding Double " + object + " at key " + entry.getKey()); - base.setDouble(entry.getKey(), (Double) object); - } else if (object instanceof Float) { - log.fine("Adding Float " + object + " at key " + entry.getKey()); - base.setFloat(entry.getKey(), (Float) object); - } else if (object instanceof Boolean) { - log.fine("Adding Boolean " + object + " at key " + entry.getKey()); - base.setBoolean(entry.getKey(), (Boolean) object); - } else if (object instanceof Byte) { - log.fine("Adding Byte " + object + " at key " + entry.getKey()); - base.setByte(entry.getKey(), (Byte) object); - } else if (object instanceof Short) { - log.fine("Adding Byte " + object + " at key " + entry.getKey()); - base.setShort(entry.getKey(), (Short) object); - } else if (object instanceof Integer) { - log.fine("Adding Integer " + object + " at key " + entry.getKey()); - base.setInt(entry.getKey(), (Integer) object); - } else if (object instanceof Long) { - log.fine("Adding Long " + object + " at key " + entry.getKey()); - base.setLong(entry.getKey(), (Long) object); - } else if (object instanceof byte[]) { - log.fine("Adding bytearray at key " + entry.getKey()); - base.setByteArray(entry.getKey(), (byte[]) object); - } else if (object instanceof int[]) { - log.fine("Adding intarray at key " + entry.getKey()); - base.setIntArray(entry.getKey(), (int[]) object); - } else if (object instanceof UUID) { - log.fine("Adding UUID " + object + " at key " + entry.getKey()); - base.a(entry.getKey(), (UUID) object); - } else if (object instanceof NBTBase) { - log.fine("Adding nbtobject at key " + entry.getKey()); - base.set(entry.getKey(), (NBTBase) object); - } else { - log.warning("Unrecognized entry in map-->NBT: " + object.toString()); - } - } - return base; - } - - @SuppressWarnings("unchecked") - public static NBTTagList listToNBT(NBTTagList base, List list) { - log.fine("Representing list --> NBTTagList"); - if (list == null || base == null) { - return base; - } - for (Object object : list) { - if (object instanceof Map) { - log.fine("Adding map to list"); - base.add(mapToNBT(new NBTTagCompound(), (Map) object)); - } else if (object instanceof MemorySection) { - log.fine("Adding map from MemorySection to list"); - base.add(mapToNBT(new NBTTagCompound(), ((MemorySection) object).getValues(true))); - } else if (object instanceof List) { - log.fine("Adding list to list"); - base.add(listToNBT(new NBTTagList(), (List) object)); - } else if (object instanceof String) { - log.fine("Adding string " + object + " to list"); - base.add(NBTTagString.a((String) object)); - } else if (object instanceof Double) { - log.fine("Adding double " + object + " to list"); - base.add(NBTTagDouble.a((Double) object)); - } else if (object instanceof Float) { - log.fine("Adding float " + object + " to list"); - base.add(NBTTagFloat.a((Float) object)); - } else if (object instanceof Byte) { - log.fine("Adding byte " + object + " to list"); - base.add(NBTTagByte.a((Byte) object)); - } else if (object instanceof Short) { - log.fine("Adding short " + object + " to list"); - base.add(NBTTagShort.a((Short) object)); - } else if (object instanceof Integer) { - log.fine("Adding integer " + object + " to list"); - base.add(NBTTagInt.a((Integer) object)); - } else if (object instanceof Long) { - log.fine("Adding long " + object + " to list"); - base.add(NBTTagLong.a((Long) object)); - } else if (object instanceof byte[]) { - log.fine("Adding byte array to list"); - base.add(new NBTTagByteArray((byte[]) object)); - } else if (object instanceof int[]) { - log.fine("Adding int array to list"); - base.add(new NBTTagIntArray((int[]) object)); - } else if (object instanceof NBTBase) { - log.fine("Adding nbt object to list"); - base.add((NBTBase) object); - } else { - log.warning("Unrecognized entry in list-->NBT: " + base.toString()); - } - } - return base; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/locations/global/WorldIDManager.java b/src/main/java/vg/civcraft/mc/civmodcore/locations/global/WorldIDManager.java deleted file mode 100644 index 66d8fe45..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/locations/global/WorldIDManager.java +++ /dev/null @@ -1,104 +0,0 @@ -package vg.civcraft.mc.civmodcore.locations.global; - -import java.util.Map; -import java.util.TreeMap; -import java.util.UUID; -import org.bukkit.Bukkit; -import org.bukkit.World; - -public class WorldIDManager { - - private Map uuidToInternalID; - private Map internalIDToUuid; - private CMCWorldDAO dao; - - public WorldIDManager(CMCWorldDAO dao) { - this.dao = dao; - this.uuidToInternalID = new TreeMap<>(); - this.internalIDToUuid = new TreeMap<>(); - if (!setup()) { - throw new IllegalStateException("Failed to initialize CMC world tracking"); - } - } - - /** - * Registers a world for internal use - * - * @param world World to prepare data structures for - * @return Whether successfull or not - */ - public boolean registerWorld(World world) { - if (uuidToInternalID.containsKey(world.getUID())) { - return true; - } - short id = dao.getOrCreateWorldID(world); - if (id == -1) { - // very bad - return false; - } - uuidToInternalID.put(world.getUID(), id); - internalIDToUuid.put(id, world.getUID()); - return true; - } - - /** - * Registers all currently loaded worlds internally - * - * @return Whether all worlds were successfully loaded in or not. Errors here - * would most likely mean a non-working database setup - */ - public boolean setup() { - for (World world : Bukkit.getWorlds()) { - boolean worked = registerWorld(world); - if (!worked) { - return false; - } - } - return true; - } - - /** - * Gets the world object mapped to an internal id - * - * @param id ID to get world for - * @return World if a matching one for the given id exists and the world is - * loaded currently - */ - public World getWorldByInternalID(short id) { - UUID uuid = internalIDToUuid.get(id); - if (uuid == null) { - return null; - } - return Bukkit.getWorld(uuid); - } - - /** - * Retrieves the internal id used for a world based on the worlds name. Should - * only be used to convert legacy data over - * - * @param name Name of the world - * @return Id of the world or -1 if no such world is known - */ - public short getInternalWorldIdByName(String name) { - World world = Bukkit.getWorld(name); - return getInternalWorldId(world); - } - - /** - * Retrieves the internal id used for a world. - * - * @param world World to get ID for - * @return Id of the world or -1 if no such world is known - */ - public short getInternalWorldId(World world) { - if (world == null) { - return -1; - } - Short result = uuidToInternalID.get(world.getUID()); - if (result == null) { - return -1; - } - return result; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/maps/MapColours.java b/src/main/java/vg/civcraft/mc/civmodcore/maps/MapColours.java new file mode 100644 index 00000000..b3a3e27d --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/maps/MapColours.java @@ -0,0 +1,112 @@ +package vg.civcraft.mc.civmodcore.maps; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import net.minecraft.world.level.material.MaterialMapColor; +import org.apache.commons.collections4.CollectionUtils; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; + +/** + * This is a mapped version of NMS class {@link MaterialMapColor} to make setting pixel colours easier. + * + * Read more. + * + * Deobf path: net.minecraft.world.level.material.MaterialColor + */ +public enum MapColours { + + NONE(MaterialMapColor.b), + GRASS(MaterialMapColor.c), + SAND(MaterialMapColor.d), + WOOL(MaterialMapColor.e), // White wool + FIRE(MaterialMapColor.f), + ICE(MaterialMapColor.g), + METAL(MaterialMapColor.h), + PLANT(MaterialMapColor.i), + SNOW(MaterialMapColor.j), + CLAY(MaterialMapColor.k), + DIRT(MaterialMapColor.l), + STONE(MaterialMapColor.m), + WATER(MaterialMapColor.n), + WOOD(MaterialMapColor.o), + QUARTZ(MaterialMapColor.p), + COLOR_ORANGE(MaterialMapColor.q), + COLOR_MAGENTA(MaterialMapColor.r), + COLOR_LIGHT_BLUE(MaterialMapColor.s), + COLOR_YELLOW(MaterialMapColor.t), + COLOR_LIGHT_GREEN(MaterialMapColor.u), + COLOR_PINK(MaterialMapColor.v), + COLOR_GRAY(MaterialMapColor.w), + COLOR_LIGHT_GRAY(MaterialMapColor.x), + COLOR_CYAN(MaterialMapColor.y), + COLOR_PURPLE(MaterialMapColor.z), + COLOR_BLUE(MaterialMapColor.A), + COLOR_BROWN(MaterialMapColor.B), + COLOR_GREEN(MaterialMapColor.C), + COLOR_RED(MaterialMapColor.D), + COLOR_BLACK(MaterialMapColor.E), + GOLD(MaterialMapColor.F), + DIAMOND(MaterialMapColor.G), + LAPIS(MaterialMapColor.H), + EMERALD(MaterialMapColor.I), + PODZOL(MaterialMapColor.J), + NETHER(MaterialMapColor.K), + TERRACOTTA_WHITE(MaterialMapColor.L), + TERRACOTTA_ORANGE(MaterialMapColor.M), + TERRACOTTA_MAGENTA(MaterialMapColor.N), + TERRACOTTA_LIGHT_BLUE(MaterialMapColor.O), + TERRACOTTA_YELLOW(MaterialMapColor.P), + TERRACOTTA_LIGHT_GREEN(MaterialMapColor.Q), + TERRACOTTA_PINK(MaterialMapColor.R), + TERRACOTTA_GRAY(MaterialMapColor.S), + TERRACOTTA_LIGHT_GRAY(MaterialMapColor.T), + TERRACOTTA_CYAN(MaterialMapColor.U), + TERRACOTTA_PURPLE(MaterialMapColor.V), + TERRACOTTA_BLUE(MaterialMapColor.W), + TERRACOTTA_BROWN(MaterialMapColor.X), + TERRACOTTA_GREEN(MaterialMapColor.Y), + TERRACOTTA_RED(MaterialMapColor.Z), + TERRACOTTA_BLACK(MaterialMapColor.aa), + CRIMSON_NYLIUM(MaterialMapColor.ab), + CRIMSON_STEM(MaterialMapColor.ac), + CRIMSON_HYPHAE(MaterialMapColor.ad), + WARPED_NYLIUM(MaterialMapColor.ae), + WARPED_STEM(MaterialMapColor.af), + WARPED_HYPHAE(MaterialMapColor.ag), + WARPED_WART_BLOCK(MaterialMapColor.ah), + DEEPSLATE(MaterialMapColor.ai), + RAW_IRON(MaterialMapColor.aj), + GLOW_LICHEN(MaterialMapColor.ak); + + private final MaterialMapColor nms; + + MapColours(@Nonnull final MaterialMapColor nms) { + this.nms = Objects.requireNonNull(nms); + } + + public MaterialMapColor asNMS() { + return this.nms; + } + + public static void init() { + final Set cmcMapColours = Stream.of(values()) + .map(MapColours::asNMS) + .collect(Collectors.toSet()); + final Set nmsMapColours = Stream.of(MaterialMapColor.a) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + final Collection missingColours = CollectionUtils.disjunction(cmcMapColours, nmsMapColours); + if (!missingColours.isEmpty()) { + final CivLogger logger = CivLogger.getLogger(MapColours.class); + logger.warning("The following map colours are missing: " + missingColours.stream() + /** {@link MaterialMapColor#MaterialMapColor(int, int)} "id" parameter */ + .map(colour -> Integer.toString(colour.am)) + .collect(Collectors.joining(","))); + } + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTDeserializer.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTDeserializer.java new file mode 100644 index 00000000..b563f8e5 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTDeserializer.java @@ -0,0 +1,16 @@ +package vg.civcraft.mc.civmodcore.nbt; + +import javax.annotation.Nonnull; +import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; + +/** + * Function that's returned by {@link NBTSerialization#getDeserializer(Class)} to retrieve a serializable's version of + * {@link NBTSerializable#fromNBT(NBTCompound)}. + */ +@FunctionalInterface +public interface NBTDeserializer { + + @Nonnull + T fromNBT(@Nonnull final NBTCompound nbt); + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTHelper.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTHelper.java new file mode 100644 index 00000000..ce282a89 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTHelper.java @@ -0,0 +1,81 @@ +package vg.civcraft.mc.civmodcore.nbt; + +import java.util.UUID; +import lombok.experimental.ExtensionMethod; +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.nbt.extensions.NBTTagCompoundExtensions; +import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.utilities.UuidUtils; + +@UtilityClass +@ExtensionMethod(NBTTagCompoundExtensions.class) +public final class NBTHelper { + + // ------------------------------------------------------------ + // Location + // ------------------------------------------------------------ + + private static final String LOCATION_WORLD_KEY = "world"; + private static final String LOCATION_X_KEY = "x"; + private static final String LOCATION_Y_KEY = "y"; + private static final String LOCATION_Z_KEY = "z"; + private static final String LOCATION_YAW_KEY = "yaw"; + private static final String LOCATION_PITCH_KEY = "pitch"; + + public static Location locationFromNBT(final NBTCompound nbt) { + if (nbt == null) { + return null; + } + final UUID worldUUID = nbt.getUUID(LOCATION_WORLD_KEY); + return new Location( + UuidUtils.isNullOrIdentity(worldUUID) ? null : Bukkit.getWorld(worldUUID), + nbt.getDouble(LOCATION_X_KEY), + nbt.getDouble(LOCATION_Y_KEY), + nbt.getDouble(LOCATION_Z_KEY), + nbt.getFloat(LOCATION_YAW_KEY), + nbt.getFloat(LOCATION_PITCH_KEY)); + } + + public static NBTCompound locationToNBT(final Location location) { + if (location == null) { + return null; + } + final var nbt = new NBTCompound(); + nbt.setUUID(LOCATION_WORLD_KEY, location.isWorldLoaded() ? location.getWorld().getUID() : UuidUtils.IDENTITY); + nbt.setDouble(LOCATION_X_KEY, location.getX()); + nbt.setDouble(LOCATION_Y_KEY, location.getY()); + nbt.setDouble(LOCATION_Z_KEY, location.getZ()); + if (location.getYaw() != 0) { + nbt.setFloat(LOCATION_YAW_KEY, location.getYaw()); + } + if (location.getPitch() != 0) { + nbt.setFloat(LOCATION_PITCH_KEY, location.getPitch()); + } + return nbt; + } + + // ------------------------------------------------------------ + // ItemStack + // ------------------------------------------------------------ + + public static ItemStack itemStackFromNBT(final NBTCompound nbt) { + if (nbt == null) { + return null; + } + return net.minecraft.world.item.ItemStack.a(nbt).getBukkitStack(); + } + + public static NBTCompound itemStackToNBT(final ItemStack item) { + if (item == null) { + return null; + } + final var nbt = new NBTCompound(); + ItemUtils.getNMSItemStack(item).save(nbt); + return nbt; + } + +} \ No newline at end of file diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializable.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializable.java new file mode 100644 index 00000000..8352ac4c --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializable.java @@ -0,0 +1,32 @@ +package vg.civcraft.mc.civmodcore.nbt; + +import javax.annotation.Nonnull; +import org.apache.commons.lang3.NotImplementedException; +import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; + +public interface NBTSerializable { + + /** + * Serializes this class onto a given NBTCompound. + * + * @param nbt The NBTCompound to serialize into, which should NEVER be null, so feel free to throw an + * {@link NBTSerializationException} if it is. You can generally assume that the NBTCompound is new and + * therefore empty, but you may wish to check that. + */ + void toNBT(@Nonnull final NBTCompound nbt); + + /** + *

    Deserializes a given NBTCompound into a new class instance.

    + * + *

    NOTE: When copying this to your extension class, change the return type to that class.

    + * + * @param nbt The NBTCompound to deserialize from, which should NEVER be null, so feel free to throw an + * {@link NBTSerializationException} if it is. + * @return Returns a new instance of this class. + */ + @Nonnull + public static NBTSerializable fromNBT(@Nonnull final NBTCompound nbt) { + throw new NotImplementedException("Please implement me on your class!"); + } + +} \ No newline at end of file diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerialization.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerialization.java new file mode 100644 index 00000000..c2736c96 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerialization.java @@ -0,0 +1,162 @@ +package vg.civcraft.mc.civmodcore.nbt; + +import com.google.common.base.Preconditions; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import lombok.experimental.ExtensionMethod; +import lombok.experimental.UtilityClass; +import net.minecraft.nbt.NBTCompressedStreamTools; +import net.minecraft.nbt.NBTReadLimiter; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.bukkit.craftbukkit.v1_17_R1.util.CraftNBTTagConfigSerializer; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.pdc.extensions.PersistentDataContainerExtensions; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; + +@UtilityClass +@ExtensionMethod(PersistentDataContainerExtensions.class) +public class NBTSerialization { + + private static final CivLogger LOGGER = CivLogger.getLogger(NBTSerialization.class); + + /** + * Retrieves the NBT data from an item. + * + * @param item The item to retrieve the NBT form. + * @return Returns the item's NBT. + */ + public static NBTCompound fromItem(final ItemStack item) { + if (item == null) { + return null; + } + final var nmsItem = ItemUtils.getNMSItemStack(item); + if (nmsItem == null) { + return null; + } + return new NBTCompound(nmsItem.getOrCreateTag()); + } + + /** + * Generates an NBT compound based on a given persistent data container. + * + * @param container The container to generate an NBT compound from. + * @return Returns a newly generated NBT compound by wrapping the PDC's inner-map. + */ + @Nonnull + public static NBTCompound fromPDC(@Nonnull final PersistentDataContainer container) { + return new NBTCompound(container.getRaw()); + } + + /** + * Processes an item's NBT before setting again. + * + * @param item The item to process. + * @param processor The processor. + * @return Returns the given item with the processed NBT, or null if it could not be successfully processed. + */ + public static ItemStack processItem(final ItemStack item, final Consumer processor) { + Preconditions.checkArgument(ItemUtils.isValidItem(item)); + Preconditions.checkArgument(processor != null); + final var nmsItem = ItemUtils.getNMSItemStack(item); + if (nmsItem == null) { + return null; + } + final var nbt = new NBTCompound(nmsItem.getOrCreateTag()); + try { + processor.accept(nbt); + } + catch (final Throwable exception) { + LOGGER.log(Level.WARNING, "Could not process item NBT!", exception); + return null; + } + return nmsItem.getBukkitStack(); + } + + public static NBTTagCompound fromMap(final Map data) { + return (NBTTagCompound) CraftNBTTagConfigSerializer.deserialize(data); + } + + public static NBTTagList fromList(final List data) { + return (NBTTagList) CraftNBTTagConfigSerializer.deserialize(data); + } + + /** + * Attempts to serialize an NBTCompound into a data array. + * + * @param nbt The NBTCompound to serialize. + * @return Returns a data array representing the given NBTCompound serialized, or otherwise null. + */ + public static byte[] toBytes(final NBTTagCompound nbt) { + if (nbt == null) { + return null; + } + final ByteArrayDataOutput output = ByteStreams.newDataOutput(); + try { + NBTCompressedStreamTools.a(nbt, output); + } + catch (final IOException exception) { + LOGGER.log(Level.WARNING, "Could not serialise NBT to bytes!", exception); + return null; + } + return output.toByteArray(); + } + + /** + * Attempts to deserialize NBT data into an NBTCompound. + * + * @param bytes The NBT data as a byte array. + * @return Returns an NBTCompound if the deserialization was successful, or otherwise null. + */ + public static NBTTagCompound fromBytes(final byte[] bytes) { + if (ArrayUtils.isEmpty(bytes)) { + return null; + } + final ByteArrayDataInput input = ByteStreams.newDataInput(bytes); + try { + return NBTCompressedStreamTools.a(input, NBTReadLimiter.a); + } + catch (final IOException exception) { + LOGGER.log(Level.WARNING, "Could not deserialise NBT from bytes!", exception); + return null; + } + } + + /** + * Dynamically retrieves a serializable's {@link NBTSerializable#fromNBT(NBTCompound) fromNBT} method. + * + * @param The type of the serializable. + * @param clazz The serializable's class. + * @return Returns a deserializer function. + */ + @SuppressWarnings("unchecked") + public static NBTDeserializer getDeserializer(final Class clazz) { + final var method = MethodUtils.getMatchingAccessibleMethod(clazz, "fromNBT", NBTCompound.class); + if (!Objects.equals(clazz, method.getReturnType())) { + throw new IllegalArgumentException("That class hasn't implemented its own fromNBT method.. please fix"); + } + return (nbt) -> { + try { + return (T) method.invoke(null, nbt); + } + catch (final IllegalAccessException | InvocationTargetException exception) { + throw new NBTSerializationException(exception); + } + }; + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerializationException.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializationException.java similarity index 62% rename from src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerializationException.java rename to src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializationException.java index 1ff08b6a..8b4eb90d 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerializationException.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializationException.java @@ -1,11 +1,15 @@ -package vg.civcraft.mc.civmodcore.serialization; +package vg.civcraft.mc.civmodcore.nbt; + +import java.io.Serial; +import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; /** - * Exception to be used within {@link NBTSerializable#serialize(NBTCompound)} and - * {@link NBTSerializable#deserialize(NBTCompound)}. + * Exception that ought to be used within {@link NBTSerializable#toNBT(NBTCompound)} and + * {@link NBTSerializable#fromNBT(NBTCompound)}. */ public class NBTSerializationException extends RuntimeException { + @Serial private static final long serialVersionUID = 606023177729327630L; public NBTSerializationException() { @@ -24,4 +28,4 @@ public NBTSerializationException(Throwable cause) { super(cause); } -} +} \ No newline at end of file diff --git a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTType.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTType.java similarity index 53% rename from src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTType.java rename to src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTType.java index d0aa7e87..c17bfeab 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTType.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTType.java @@ -1,8 +1,23 @@ -package vg.civcraft.mc.civmodcore.serialization; +package vg.civcraft.mc.civmodcore.nbt; + +import lombok.experimental.UtilityClass; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; /** - * NBT Type IDs for usage with {@link NBTCompound#hasKeyOfType(String, int)}. + * NBT Type IDs for usage with: + * + *
      + *
    • {@link NBTTagCompound#hasKeyOfType(String, int)}
    • + *
    • {@link NBTBase#getTypeId()}
    • + *
    • {@link NBTTagList#e()} // list element type
    • + *
    • etc...
    • + *
    + * + * This is a better version of {@link org.bukkit.craftbukkit.v1_17_R1.util.CraftMagicNumbers.NBT} */ +@UtilityClass public final class NBTType { public static final byte END = (byte) 0; @@ -19,4 +34,4 @@ public final class NBTType { public static final byte INT_ARRAY = (byte) 11; public static final byte LONG_ARRAY = (byte) 12; -} +} \ No newline at end of file diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagCompoundExtensions.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagCompoundExtensions.java new file mode 100644 index 00000000..bef0a57f --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagCompoundExtensions.java @@ -0,0 +1,114 @@ +package vg.civcraft.mc.civmodcore.nbt.extensions; + +import java.util.UUID; +import lombok.experimental.ExtensionMethod; +import lombok.experimental.UtilityClass; +import net.minecraft.nbt.NBTTagCompound; +import vg.civcraft.mc.civmodcore.utilities.UuidUtils; + +/** + * Set of extension methods for {@link NBTTagCompound}. Use {@link ExtensionMethod @ExtensionMethod} to take most + * advantage of this. + */ +@UtilityClass +public final class NBTTagCompoundExtensions { + + private static final String UUID_MOST_SUFFIX = "Most"; + private static final String UUID_LEAST_SUFFIX = "Least"; + + /** + * @param self The NBTTagCompound to get the size of. + * @return Returns the number of key-value pairs. + */ + public static int size(final NBTTagCompound self) { + return self.x.size(); + } + + /** + * @param self The NBTTagCompound to clear. + */ + public static void clear(final NBTTagCompound self) { + self.x.clear(); + } + + /** + * @param self The NBTTagCompound to do the adopting. + * @param other The NBTTagCompound to adopt. + */ + public static void adopt(final NBTTagCompound self, + final NBTTagCompound other) { + if (self == other || self.x == other.x) { // Ignore highlighter + return; + } + self.x.clear(); + self.x.putAll(other.x); + } + + /** + * @param self The NBTTagCompound to check if there's a UUID on. + * @param key The key of the UUID. + * @return Returns true if there's a UUID value for that key. + */ + public static boolean hasUUID(final NBTTagCompound self, + final String key) { + return self.b(key); + } + + /** + * @param self The NBTTagCompound to get the UUID from. + * @param key The key of the UUID. + * @return Returns a UUID, defaulted to 00000000-0000-0000-0000-000000000000. + */ + public static UUID getUUID(final NBTTagCompound self, + final String key) { + if (!hasUUID(self, key)) { + return UuidUtils.IDENTITY; + } + return self.a(key); + } + + /** + * @param self The NBTTagCompound to get the UUID from. + * @param key The key of the UUID. + * @param value The UUID value. + */ + public static void setUUID(final NBTTagCompound self, + final String key, + final UUID value) { + setUUID(self, key, value, false); + } + + /** + * @param self The NBTTagCompound to get the UUID from. + * @param key The key of the UUID. + * @param value The UUID value. + * @param useLegacyFormat Whether to use Mojang's legacy least+most format. + */ + public static void setUUID(final NBTTagCompound self, + final String key, + final UUID value, + final boolean useLegacyFormat) { + if (value == null) { + removeUUID(self, key); + return; + } + if (useLegacyFormat) { + self.setLong(key + UUID_MOST_SUFFIX, value.getMostSignificantBits()); + self.setLong(key + UUID_LEAST_SUFFIX, value.getLeastSignificantBits()); + return; + } + self.a(key, value); + } + + /** + * @param self The NBTTagCompound to remove the UUID from. + * @param key The key of the UUID. + */ + public static void removeUUID(final NBTTagCompound self, + final String key) { + self.remove(key); + self.remove(key + UUID_MOST_SUFFIX); + self.remove(key + UUID_LEAST_SUFFIX); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagListExtensions.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagListExtensions.java new file mode 100644 index 00000000..16387a3a --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagListExtensions.java @@ -0,0 +1,623 @@ +package vg.civcraft.mc.civmodcore.nbt.extensions; + +import java.util.UUID; +import lombok.experimental.ExtensionMethod; +import lombok.experimental.UtilityClass; +import net.minecraft.nbt.GameProfileSerializer; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagByte; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagDouble; +import net.minecraft.nbt.NBTTagFloat; +import net.minecraft.nbt.NBTTagInt; +import net.minecraft.nbt.NBTTagIntArray; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagLong; +import net.minecraft.nbt.NBTTagLongArray; +import net.minecraft.nbt.NBTTagShort; +import net.minecraft.nbt.NBTTagString; +import vg.civcraft.mc.civmodcore.nbt.NBTType; +import vg.civcraft.mc.civmodcore.utilities.UuidUtils; + +/** + * Set of extension methods for {@link NBTTagList}. Use {@link ExtensionMethod @ExtensionMethod} to take most advantage + * of this. + */ +@UtilityClass +public final class NBTTagListExtensions { + + /** + * @param self The NBTTagList to get the element type of. + * @return Returns the NBTTagList's element type. Match it against {@link NBTType}. If it matches + * {@link NBTType#END} then it's an empty list. + */ + public static byte getElementType(final NBTTagList self) { + return self.e(); + } + + /** + * Checks whether a given NBT element is appropriate for the given list. + * + * @param self The NBTTagList to check against. + * @param value The value the check the appropriateness for. + * @return Returns true if the type is appropriate for the list. + */ + public static boolean isAppropriateType(final NBTTagList self, + final NBTBase value) { + /** This is a direct copy of {@link NBTTagList#a(NBTBase)} */ + if (value == null || value.getTypeId() == NBTType.END) { + return false; + } + final var elementType = getElementType(self); + return elementType == NBTType.END || elementType == value.getTypeId(); + } + + /** + * @param self The NBTTagList to get the boolean from. + * @param index The index of the boolean. + * @return Returns a boolean, defaulted to false. + */ + public static boolean getBoolean(final NBTTagList self, + final int index) { + return getByte(self, index) != (byte) 0; + } + + /** + * @param self The NBTTagList to set the boolean to. + * @param index The index of the boolean to set. + * @param value The value of the boolean. + */ + public static void setBoolean(final NBTTagList self, + final int index, + final boolean value) { + self.set(index, NBTTagByte.a(value)); + } + + /** + * @param self The NBTTagList to add the boolean to. + * @param index The index of the boolean to add to. + * @param value The value of the boolean. + */ + public static void addBoolean(final NBTTagList self, + final int index, + final boolean value) { + self.add(index, NBTTagByte.a(value)); + } + + /** + * @param self The NBTTagList to add the boolean to. + * @param value The value of the boolean. + */ + public static void addBoolean(final NBTTagList self, + final boolean value) { + addElement(self, NBTTagByte.a(value)); + } + + /** + * @param self The NBTTagList to get the byte from. + * @param index The index of the byte. + * @return Returns a byte, defaulted to 0x00. + */ + public static byte getByte(final NBTTagList self, + final int index) { + if (self.get(index) instanceof NBTTagByte nbtByte) { + return nbtByte.asByte(); + } + return (byte) 0; + } + + /** + * @param self The NBTTagList to set the byte to. + * @param index The index of the byte to set. + * @param value The value of the byte. + */ + public static void setByte(final NBTTagList self, + final int index, + final byte value) { + self.set(index, NBTTagByte.a(value)); + } + + /** + * @param self The NBTTagList to add the byte to. + * @param index The index of the byte to add to. + * @param value The value of the byte. + */ + public static void addByte(final NBTTagList self, + final int index, + final byte value) { + self.add(index, NBTTagByte.a(value)); + } + + /** + * @param self The NBTTagList to add the boolean to. + * @param value The value of the boolean. + */ + public static void addByte(final NBTTagList self, + final byte value) { + addElement(self, NBTTagByte.a(value)); + } + + /** + * @param self The NBTTagList to get the short from. + * @param index The index of the short. + * @return Returns a short, defaulted to 0. + */ + public static short getShort(final NBTTagList self, + final int index) { + return self.d(index); + } + + /** + * @param self The NBTTagList to set the short to. + * @param index The index of the short to set. + * @param value The value of the short. + */ + public static void setShort(final NBTTagList self, + final int index, + final short value) { + self.set(index, NBTTagShort.a(value)); + } + + /** + * @param self The NBTTagList to add the short to. + * @param index The index of the short to add to. + * @param value The value of the short. + */ + public static void addShort(final NBTTagList self, + final int index, + final short value) { + self.add(index, NBTTagShort.a(value)); + } + + /** + * @param self The NBTTagList to add the short to. + * @param value The value of the short. + */ + public static void addShort(final NBTTagList self, + final short value) { + addElement(self, NBTTagShort.a(value)); + } + + /** + * @param self The NBTTagList to get the integer from. + * @param index The index of the integer. + * @return Returns an integer, defaulted to 0. + */ + public static int getInt(final NBTTagList self, + final int index) { + return self.e(index); + } + + /** + * @param self The NBTTagList to set the integer to. + * @param index The index of the integer to set. + * @param value The value of the integer. + */ + public static void setInt(final NBTTagList self, + final int index, + final int value) { + self.set(index, NBTTagInt.a(value)); + } + + /** + * @param self The NBTTagList to add the integer to. + * @param index The index of the integer to add to. + * @param value The value of the integer. + */ + public static void addInt(final NBTTagList self, + final int index, + final int value) { + self.add(index, NBTTagInt.a(value)); + } + + /** + * @param self The NBTTagList to add the integer to. + * @param value The value of the integer. + */ + public static void addInt(final NBTTagList self, + final int value) { + addElement(self, NBTTagInt.a(value)); + } + + /** + * @param self The NBTTagList to get the long from. + * @param index The index of the long. + * @return Returns a long, defaulted to 0L. + */ + public static long getLong(final NBTTagList self, + final int index) { + if (self.get(index) instanceof NBTTagLong nbtLong) { + return nbtLong.asLong(); + } + return 0L; + } + + /** + * @param self The NBTTagList to set the long to. + * @param index The index of the long to set. + * @param value The value of the long. + */ + public static void setLong(final NBTTagList self, + final int index, + final long value) { + self.set(index, NBTTagLong.a(value)); + } + + /** + * @param self The NBTTagList to add the long to. + * @param index The index of the long to add to. + * @param value The value of the long. + */ + public static void addLong(final NBTTagList self, + final int index, + final long value) { + self.add(index, NBTTagLong.a(value)); + } + + /** + * @param self The NBTTagList to add the long to. + * @param value The value of the long. + */ + public static void addLong(final NBTTagList self, + final long value) { + addElement(self, NBTTagLong.a(value)); + } + + /** + * @param self The NBTTagList to get the float from. + * @param index The index of the float. + * @return Returns a float, defaulted to 0.0f. + */ + public static float getFloat(final NBTTagList self, + final int index) { + return self.i(index); + } + + /** + * @param self The NBTTagList to set the float to. + * @param index The index of the float to set. + * @param value The value of the float. + */ + public static void setFloat(final NBTTagList self, + final int index, + final float value) { + self.set(index, NBTTagFloat.a(value)); + } + + /** + * @param self The NBTTagList to add the float to. + * @param index The index of the float to add to. + * @param value The value of the float. + */ + public static void addFloat(final NBTTagList self, + final int index, + final float value) { + self.add(index, NBTTagFloat.a(value)); + } + + /** + * @param self The NBTTagList to add the float to. + * @param value The value of the float. + */ + public static void addFloat(final NBTTagList self, + final float value) { + addElement(self, NBTTagFloat.a(value)); + } + + /** + * @param self The NBTTagList to get the double from. + * @param index The index of the double. + * @return Returns a double, defaulted to 0.0d. + */ + public static double getDouble(final NBTTagList self, + final int index) { + return self.h(index); + } + + /** + * @param self The NBTTagList to set the double to. + * @param index The index of the double to set. + * @param value The value of the double. + */ + public static void setDouble(final NBTTagList self, + final int index, + final double value) { + self.set(index, NBTTagDouble.a(value)); + } + + /** + * @param self The NBTTagList to add the double to. + * @param index The index of the double to add to. + * @param value The value of the double. + */ + public static void addDouble(final NBTTagList self, + final int index, + final double value) { + self.add(index, NBTTagDouble.a(value)); + } + + /** + * @param self The NBTTagList to add the double to. + * @param value The value of the double. + */ + public static void addDouble(final NBTTagList self, + final double value) { + addElement(self, NBTTagDouble.a(value)); + } + + /** + * @param self The NBTTagList to get the UUID from. + * @param index The index of the UUID. + * @return Returns a UUID, defaulted to 00000000-0000-0000-0000-000000000000. + */ + public static UUID getUUID(final NBTTagList self, + final int index) { + if (self.get(index) instanceof NBTTagIntArray nbtIntArray) { + /** Copied from {@link NBTTagCompound#a(String)} */ + return GameProfileSerializer.a(nbtIntArray); + } + return UuidUtils.IDENTITY; + } + + /** + * @param self The NBTTagList to set the UUID to. + * @param index The index of the UUID to set. + * @param value The value of the UUID. + */ + public static void setUUID(final NBTTagList self, + final int index, + final UUID value) { + /** Copied from {@link NBTTagCompound#a(String, UUID)} */ + self.set(index, GameProfileSerializer.a(value)); + } + + /** + * @param self The NBTTagList to add the UUID to. + * @param index The index of the UUID to add to. + * @param value The value of the UUID. + */ + public static void addUUID(final NBTTagList self, + final int index, + final UUID value) { + /** Copied from {@link NBTTagCompound#a(String, UUID)} */ + self.add(index, GameProfileSerializer.a(value)); + } + + /** + * @param self The NBTTagList to add the UUID to. + * @param value The value of the UUID. + */ + public static void addUUID(final NBTTagList self, + final UUID value) { + /** Copied from {@link NBTTagCompound#a(String, UUID)} */ + addElement(self, GameProfileSerializer.a(value)); + } + + /** + * @param self The NBTTagList to get the String from. + * @param index The index of the String. + * @return Returns a String, defaulted to "". + */ + public static String getString(final NBTTagList self, + final int index) { + if (self.get(index) instanceof NBTTagString nbtString) { + return nbtString.asString(); + } + return ""; + } + + /** + * @param self The NBTTagList to set the String to. + * @param index The index of the String to set. + * @param value The value of the String. + */ + public static void setString(final NBTTagList self, + final int index, + final String value) { + self.set(index, NBTTagString.a(value)); + } + + /** + * @param self The NBTTagList to add the String to. + * @param index The index of the String to add to. + * @param value The value of the String. + */ + public static void addString(final NBTTagList self, + final int index, + final String value) { + self.add(index, NBTTagString.a(value)); + } + + /** + * @param self The NBTTagList to add the String to. + * @param value The value of the String. + */ + public static void addString(final NBTTagList self, + final String value) { + addElement(self, NBTTagString.a(value)); + } + + /** + * @param self The NBTTagList to get the compound from. + * @param index The index of the compound. + * @return Returns a compound, defaulted to {}. + */ + public static NBTTagCompound getCompound(final NBTTagList self, + final int index) { + if (self.get(index) instanceof NBTTagCompound nbtCompound) { + return nbtCompound; + } + return new NBTTagCompound(); + } + + /** + * @param self The NBTTagList to set the compound to. + * @param index The index of the compound to set. + * @param value The value of the compound. + */ + public static void setCompound(final NBTTagList self, + final int index, + final NBTTagCompound value) { + self.set(index, value); + } + + /** + * @param self The NBTTagList to add the compound to. + * @param index The index of the compound to add to. + * @param value The value of the compound. + */ + public static void addCompound(final NBTTagList self, + final int index, + final NBTTagCompound value) { + self.add(index, value); + } + + /** + * @param self The NBTTagList to add the compound to. + * @param value The value of the compound. + */ + public static void addCompound(final NBTTagList self, + final NBTTagCompound value) { + addElement(self, value); + } + + /** + * @param self The NBTTagList to get the integer array from. + * @param index The index of the integer array. + * @return Returns an integer array, defaulted to []. + */ + public static int[] getIntArray(final NBTTagList self, + final int index) { + return self.f(index); + } + + /** + * @param self The NBTTagList to set the integer array to. + * @param index The index of the integer array to set. + * @param values The value of the integer array. + */ + public static void setIntArray(final NBTTagList self, + final int index, + final int[] values) { + self.set(index, new NBTTagIntArray(values)); + } + + /** + * @param self The NBTTagList to add the integer array to. + * @param index The index of the integer array to add to. + * @param values The value of the integer array. + */ + public static void addIntArray(final NBTTagList self, + final int index, + final int[] values) { + self.add(index, new NBTTagIntArray(values)); + } + + /** + * @param self The NBTTagList to add the integer array to. + * @param values The value of the integer array. + */ + public static void addIntArray(final NBTTagList self, + final int[] values) { + addElement(self, new NBTTagIntArray(values)); + } + + /** + * @param self The NBTTagList to get the long array from. + * @param index The index of the long array. + * @return Returns an long array, defaulted to []. + */ + public static long[] getLongArray(final NBTTagList self, + final int index) { + return self.g(index); + } + + /** + * @param self The NBTTagList to set the long array to. + * @param index The index of the long array to set. + * @param values The value of the long array. + */ + public static void setLongArray(final NBTTagList self, + final int index, + final long[] values) { + self.set(index, new NBTTagLongArray(values)); + } + + /** + * @param self The NBTTagList to add the long array to. + * @param index The index of the long array to add to. + * @param values The value of the long array. + */ + public static void addLongArray(final NBTTagList self, + final int index, + final long[] values) { + self.add(index, new NBTTagLongArray(values)); + } + + /** + * @param self The NBTTagList to add the long array to. + * @param values The value of the integer array. + */ + public static void addLongArray(final NBTTagList self, + final long[] values) { + addElement(self, new NBTTagLongArray(values)); + } + + /** + * @param self The NBTTagList to get the list from. + * @param index The index of the list. + * @return Returns an list, defaulted to []. + */ + public static NBTTagList getList(final NBTTagList self, + final int index) { + return self.b(index); + } + + /** + * @param self The NBTTagList to set the list to. + * @param index The index of the list to set. + * @param value The value of the list. + */ + public static void setList(final NBTTagList self, + final int index, + final NBTTagList value) { + self.set(index, value); + } + + /** + * @param self The NBTTagList to add the list to. + * @param index The index of the list to add to. + * @param value The value of the list. + */ + public static void addList(final NBTTagList self, + final int index, + final NBTTagList value) { + self.add(index, value); + } + + /** + * @param self The NBTTagList to add the list to. + * @param value The value of the list. + */ + public static void addList(final NBTTagList self, + final NBTTagList value) { + addElement(self, value); + } + + /** + * An alternative for {@link NBTTagList#add(Object)} for that respects type consistency. + * + * @param self The NBTTagList to add the list to. + * @param value The NBT element to add. + */ + public static void addElement(final NBTTagList self, + final NBTBase value) { + if (!isAppropriateType(self, value)) { + throw new UnsupportedOperationException(String.format( + "Trying to add tag of type %d to list of %d", + value.getTypeId(), getElementType(self))); + } + self.add(value); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/storage/BatchedNbtStorage.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/storage/BatchedNbtStorage.java new file mode 100644 index 00000000..b0be579a --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/storage/BatchedNbtStorage.java @@ -0,0 +1,112 @@ +package vg.civcraft.mc.civmodcore.nbt.storage; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import java.io.File; +import java.util.Objects; +import java.util.stream.Stream; +import org.apache.commons.lang3.ArrayUtils; +import org.bukkit.craftbukkit.libs.org.apache.commons.io.FileUtils; +import org.bukkit.craftbukkit.libs.org.apache.commons.io.FilenameUtils; +import org.bukkit.plugin.Plugin; +import vg.civcraft.mc.civmodcore.nbt.NBTSerializable; +import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.utilities.CivLogger; + +public abstract class BatchedNbtStorage { + + protected static final String EXTENSION = "nbt"; + + protected final CivLogger logger; + protected final File storageFolder; + + public BatchedNbtStorage(final Plugin plugin, final String folder) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(folder)); + this.logger = CivLogger.getLogger(plugin.getClass(), getClass()); + this.storageFolder = new File(plugin.getDataFolder(), folder); + } + + /** + * Loads all the ".nbt" files from the storage folder. + * + * @return Returns a parallel stream of all the correct parsed nbt files into their appropriate container. + */ + public Stream loadAll() { + if (!this.storageFolder.isDirectory()) { + return Stream.empty().parallel(); + } + final var files = this.storageFolder.listFiles(); + if (ArrayUtils.isEmpty(files)) { + return Stream.empty().parallel(); + } + assert files != null; + return Stream.of(files).parallel() + .filter(file -> FilenameUtils.isExtension(file.getName(), EXTENSION)) + .map(this::loadFile) + .filter(Objects::nonNull); + } + + /** + * Saves a given stream of elements to their respective files. + * + * @param elements The elements to save. + * @return Returns a parallel stream of all elements that were successfully saved. + */ + public Stream saveSelected(final Stream elements) { + if (elements == null) { + return Stream.empty().parallel(); + } + return elements.parallel() + .filter(Objects::nonNull) + .map(this::saveElement) + .filter(Objects::nonNull); // Remove unsuccessful saves + } + + /** + * Removes all given elements' respective files. + * + * @param elements The elements to remove the files of. + * @return Returns a parallel stream of elements whose files could not be removed. + */ + public Stream removeSelected(final Stream elements) { + if (elements == null) { + return Stream.empty().parallel(); + } + return elements.parallel() + .map(this::removeElement) + .filter(Objects::nonNull); // Remove successful deletions + } + + /** + * This method is called during {@link #loadAll()} and is used to read and parse the data within the given file. You + * should also check the file's name using maybe {@link FilenameUtils#getBaseName(String)} to ensure it's correctly + * formatted. I'd recommend using {@link FileUtils#readFileToByteArray(File)} to read the file, then using + * {@link NBTSerializable#fromNBT(NBTCompound)} to convert that into a usable NBT instance. If for whatever + * reason the file cannot be correctly parsed, the correct course of action is to log the error using + * {@link this#logger} and returning null. + * + * @param file The file to read and parse. + * @return Returns a valid instance of the resulting container, or null if something went wrong. + */ + protected abstract T loadFile(final File file); + + /** + * This method is called during {@link #saveSelected(Stream)} and is used to save particular elements to their + * respective files. You can use I'd recommend you use {@link FileUtils#writeByteArrayToFile(File, byte[])} via + * {@link NBTSerializable#toNBT(NBTCompound)}. If the element was successfully saved, return the element or + * otherwise return null. + * + * @param element The element to save to its respective file. + * @return Returns the element if successfully saved, otherwise null. + */ + protected abstract T saveElement(final T element); + + /** + * This method is called during {@link #removeSelected(Stream)} and is used to delete particular elements' files. + * + * @param element The element to delete the file of. + * @return Returns null if the file was successfully deleted, otherwise return the given element. + */ + protected abstract T removeElement(final T element); + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/nbt/wrappers/NBTCompound.java b/src/main/java/vg/civcraft/mc/civmodcore/nbt/wrappers/NBTCompound.java new file mode 100644 index 00000000..f1106d63 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/nbt/wrappers/NBTCompound.java @@ -0,0 +1,900 @@ +package vg.civcraft.mc.civmodcore.nbt.wrappers; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.experimental.ExtensionMethod; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagDouble; +import net.minecraft.nbt.NBTTagFloat; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagShort; +import net.minecraft.nbt.NBTTagString; +import org.apache.commons.lang3.StringUtils; +import vg.civcraft.mc.civmodcore.nbt.NBTType; +import vg.civcraft.mc.civmodcore.nbt.extensions.NBTTagListExtensions; +import vg.civcraft.mc.civmodcore.utilities.JavaExtensions; +import vg.civcraft.mc.civmodcore.utilities.UuidUtils; + +@ExtensionMethod({JavaExtensions.class, NBTTagListExtensions.class}) +public class NBTCompound extends NBTTagCompound { + + public static final String NULL_STRING = "\u0000"; + private static final String UUID_MOST_SUFFIX = "Most"; + private static final String UUID_LEAST_SUFFIX = "Least"; + private static final String UUID_KEY = "uuid"; + + /** + * Creates a new NBTCompound. + */ + public NBTCompound() { + super(); + } + + /** + * Creates a new NBTCompound based on an existing inner-map. + */ + public NBTCompound(@Nonnull final Map raw) { + super(Objects.requireNonNull(raw)); + } + + /** + * Creates a new NBTCompound by extracting the inner-map of the given NBTTagCompound. + * + * @param tag The NBTTagCompound to extract from. + */ + public NBTCompound(@Nullable final NBTTagCompound tag) { + this(tag.orElseGet(NBTTagCompound::new).x); + } + + /** + * Returns the size of the tag compound. + * + * @return The size of the tag compound. + */ + public int size() { + return this.x.size(); + } + + /** + * Checks if the tag compound is empty. + * + * @return Returns true if the tag compound is empty. + */ + public boolean isEmpty() { + return this.x.isEmpty(); + } + + /** + * Checks if the tag compound contains a particular key. + * + * @param key The key to check. + * @return Returns true if the contains the given key. + */ + @Override + public boolean hasKey(@Nullable final String key) { + return this.x.containsKey(key); + } + + /** + * Checks if the tag compound contains a particular key of a particular type. + * + * @param key The key to check. + * @param type The type to check for. + * @return Returns true if the contains the given key of the given type. + */ + @Override + public boolean hasKeyOfType(@Nonnull final String key, final int type) { + return super.hasKeyOfType(key, type); + } + + /** + * Gets the keys within this compound. + * + * @return Returns the set of keys. + */ + @Nonnull + @Override + public Set getKeys() { + return this.x.keySet(); + } + + /** + * Moves a value from one key to another. + * + * @param fromKey The previous key. + * @param toKey The new key. + */ + public void switchKey(@Nonnull final String fromKey, @Nonnull final String toKey) { + if (StringUtils.equals(fromKey, toKey)) { + return; + } + this.x.computeIfPresent(fromKey, (_key, value) -> { + this.x.put(toKey, value); + return null; + }); + } + + /** + *

    Removes a key and its respective value from the tag compound, if it exists.

    + * + *

    Note: If you're removing a UUID, use {@link NBTCompound#removeUUID(String)} instead.

    + * + * @param key The key to remove. + */ + @Override + public void remove(@Nullable final String key) { + this.x.remove(key); + } + + /** + * Clears all values from the tag compound. + */ + public void clear() { + this.x.clear(); + } + + /** + * Adopts a copy of the NBT data from another compound. + * + * @param nbt The NBT data to copy and adopt. + */ + public void adopt(@Nonnull final NBTCompound nbt) { + Preconditions.checkNotNull(nbt); + this.x.clear(); + this.x.putAll(nbt.x); + } + + /** + * Gets a primitive boolean value from a key. + * + * @param key The key to get the boolean from. + * @return The value of the key, default: FALSE + */ + @Override + public boolean getBoolean(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getBoolean(key); + } + + /** + * Sets a primitive boolean value to a key. + * + * @param key The key to set to the boolean to. + * @param value The boolean to set to the key. + */ + @Override + public void setBoolean(@Nonnull final String key, final boolean value) { + Preconditions.checkNotNull(key); + super.setBoolean(key, value); + } + + /** + * Gets a primitive byte value from a key. + * + * @param key The key to get the byte from. + * @return The value of the key, default: 0 + */ + @Override + public byte getByte(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getByte(key); + } + + /** + * Sets a primitive byte value to a key. + * + * @param key The key to set to the byte to. + * @param value The byte to set to the key. + */ + @Override + public void setByte(@Nonnull final String key, final byte value) { + Preconditions.checkNotNull(key); + super.setByte(key, value); + } + + /** + * Gets a primitive short value from a key. + * + * @param key The key to get the short from. + * @return The value of the key, default: 0 + */ + @Override + public short getShort(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getShort(key); + } + + /** + * Sets a primitive short value to a key. + * + * @param key The key to set to the short to. + * @param value The short to set to the key. + */ + @Override + public void setShort(@Nonnull final String key, final short value) { + Preconditions.checkNotNull(key); + super.setShort(key, value); + } + + /** + * Gets a primitive integer value from a key. + * + * @param key The key to get the integer from. + * @return The value of the key, default: 0 + */ + @Override + public int getInt(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getInt(key); + } + + /** + * Sets a primitive integer value to a key. + * + * @param key The key to set to the integer to. + * @param value The integer to set to the key. + */ + @Override + public void setInt(@Nonnull final String key, final int value) { + Preconditions.checkNotNull(key); + super.setInt(key, value); + } + + /** + * Gets a primitive long value from a key. + * + * @param key The key to get the long from. + * @return The value of the key, default: 0L + */ + @Override + public long getLong(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getLong(key); + } + + /** + * Sets a primitive long value to a key. + * + * @param key The key to set to the long to. + * @param value The long to set to the key. + */ + @Override + public void setLong(@Nonnull final String key, final long value) { + Preconditions.checkNotNull(key); + super.setLong(key, value); + } + + /** + * Gets a primitive float value from a key. + * + * @param key The key to get the float from. + * @return The value of the key, default: 0f + */ + @Override + public float getFloat(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getFloat(key); + } + + /** + * Sets a primitive float value to a key. + * + * @param key The key to set to the float to. + * @param value The float to set to the key. + */ + @Override + public void setFloat(@Nonnull final String key, final float value) { + Preconditions.checkNotNull(key); + super.setFloat(key, value); + } + + /** + * Gets a primitive double value from a key. + * + * @param key The key to get the double from. + * @return The value of the key, default: 0d + */ + @Override + public double getDouble(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getDouble(key); + } + + /** + * Sets a primitive double value to a key. + * + * @param key The key to set to the double to. + * @param value The double to set to the key. + */ + @Override + public void setDouble(@Nonnull final String key, final double value) { + Preconditions.checkNotNull(key); + super.setDouble(key, value); + } + + /** + * Checks whether a UUID exists at the given key. + * + * @param key The key of the UUID. + * @return Returns true if a UUID exists at the given key. + */ + public boolean hasUUID(@Nonnull final String key) { + return super.b(key); + } + + /** + * Gets a UUID value from a key. + * + * @param key The key to get the UUID from. + * @return The value of the key, default: 00000000-0000-0000-0000-000000000000 + */ + public UUID getUUID(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return !super.b(key) ? UuidUtils.IDENTITY : super.a(key); + } + + /** + * Gets a UUID value from a key. + * + * @param key The key to get the UUID from. + * @return The value of the key, default: NULL + */ + @Nullable + public UUID getNullableUUID(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return !super.b(key) ? null : super.a(key); + } + + /** + * Sets a UUID value to a key. + * + * @param key The key to set to the UUID to. + * @param value The UUID to set to the key. + */ + public void setUUID(@Nonnull final String key, @Nullable final UUID value) { + setUUID(key, value, false); + } + + /** + * Sets a UUID value to a key. + * + * @param key The key to set to the UUID to. + * @param value The UUID to set to the key. + * @param useMojangFormat Whether to save as Mojang's least+most, or the updated int array. + */ + public void setUUID(@Nonnull final String key, @Nullable final UUID value, final boolean useMojangFormat) { + Preconditions.checkNotNull(key); + if (value == null) { + removeUUID(key); + } + else { + if (useMojangFormat) { + super.setLong(key + UUID_MOST_SUFFIX, value.getMostSignificantBits()); + super.setLong(key + UUID_LEAST_SUFFIX, value.getLeastSignificantBits()); + } + else { + super.a(key, value); + } + } + } + + /** + * Removes a UUID value, which is necessary because Bukkit stores UUIDs by splitting up the two significant parts + * into their own values. + * + * @param key The key of the UUID to remove. + */ + public void removeUUID(@Nonnull final String key) { + Preconditions.checkNotNull(key); + remove(key); + remove(key + UUID_MOST_SUFFIX); + remove(key + UUID_LEAST_SUFFIX); + } + + /** + * Gets a String value from a key. + * + * @param key The key to get the String from. + * @return The value of the key, default: "" + */ + @Nonnull + @Override + public String getString(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getString(key); + } + + /** + * Gets a String value from a key. + * + * @param key The key to get the String from. + * @return The value of the key, default: NULL + */ + @Nullable + public String getNullableString(@Nonnull final String key) { + Preconditions.checkNotNull(key); + if (!super.hasKeyOfType(key, 8)) { + return null; + } + final String value = super.getString(key); + if (NULL_STRING.equals(value)) { + return null; + } + return value; + } + + /** + * Sets a String value to a key. + * + * @param key The key to set to the String to. + * @param value The String to set to the key. + */ + @Override + public void setString(@Nonnull final String key, @Nullable final String value) { + Preconditions.checkNotNull(key); + if (value == null) { + remove(key); + } + else { + super.setString(key, value); + } + } + + /** + * Gets an NBT compound from a key. + * + * @param key The key to get the NBT compound from. + * @return The value of the key, default: {} + */ + @Nonnull + @Override + public NBTCompound getCompound(@Nonnull final String key) { + final var found = getNullableCompound(key); + return found == null ? new NBTCompound() : found; + } + + /** + * Gets an NBT compound from a key. + * + * @param key The key to get the NBT compound from. + * @return The value of the key, default: NULL + */ + @Nullable + public NBTCompound getNullableCompound(@Nonnull final String key) { + Preconditions.checkNotNull(key); + final var found = this.x.get(key); + if (found instanceof NBTCompound compound) { + return compound; + } + if (found instanceof NBTTagCompound compound) { + return new NBTCompound(compound); + } + return null; + } + + /** + * Sets an NBT compound to a key. + * + * @param key The key to set to the NBT compound to. + * @param value The NBT compound to set to the key. + */ + public void setCompound(@Nonnull final String key, @Nullable final NBTCompound value) { + Preconditions.checkNotNull(key); + if (value == null) { + remove(key); + } + else { + set(key, value); + } + } + + /** + * Gets a Component value from a key. + * + * @param key The key of the Component. + * @return Returns a Component, defaulted to {@link Component#empty()} + */ + public Component getComponent(@Nonnull final String key) { + Preconditions.checkNotNull(key); + if (hasKeyOfType(key, NBTType.STRING)) { + return Component.empty(); + } + else { + return GsonComponentSerializer.gson().deserialize(getString(key)); + } + } + + /** + * Sets a Component value to a key. + * + * @param key The key of the Component to set. + * @param value The Component value to set. + */ + public void setComponent(@Nonnull final String key, @Nonnull final Component value) { + Preconditions.checkNotNull(key); + Preconditions.checkNotNull(value); + setString(key, GsonComponentSerializer.gson().serialize(value)); + } + + // ------------------------------------------------------------ + // Array Functions + // ------------------------------------------------------------ + + /** + * Gets an array of primitive booleans from a key. + * + * @param key The key to of the array. + * @return The values of the key, default: empty array + */ + @Nonnull + public boolean[] getBooleanArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + final byte[] cache = getByteArray(key); + final boolean[] result = new boolean[cache.length]; + for (int i = 0; i < cache.length; i++) { + result[i] = cache[i] != 0; + } + return result; + } + + /** + * Sets an array of primitive booleans to a key. + * + * @param key The key to set to array to. + * @param booleans The booleans to set to the key. + */ + public void setBooleanArray(@Nonnull final String key, @Nullable final boolean[] booleans) { + Preconditions.checkNotNull(key); + if (booleans == null) { + remove(key); + return; + } + final byte[] converted = new byte[booleans.length]; + for (int i = 0; i < booleans.length; i++) { + converted[i] = (byte) (booleans[i] ? 1 : 0); + } + setByteArray(key, converted); + } + + /** + * Gets an array of primitive bytes from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + @Override + public byte[] getByteArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getByteArray(key); + } + + /** + * Sets an array of primitive bytes to a key. + * + * @param key The key to set to the bytes to. + * @param bytes The bytes to set to the key. + */ + @Override + public void setByteArray(@Nonnull final String key, @Nullable final byte[] bytes) { + Preconditions.checkNotNull(key); + if (bytes == null) { + remove(key); + return; + } + super.setByteArray(key, bytes); + } + + /** + * Gets an array of primitive shorts from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + public short[] getShortArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + final NBTTagList list = getList(key, NBTType.SHORT); + final short[] result = new short[list.size()]; + for (int i = 0; i < result.length; i++) { + if (list.get(i) instanceof NBTTagShort nbtShort) { + result[i] = nbtShort.asShort(); + } + } + return result; + } + + /** + * Sets an array of primitive bytes to a key. + * + * @param key The key to set to values to. + * @param shorts The shorts to set to the key. + */ + public void setShortArray(@Nonnull final String key, @Nullable final short[] shorts) { + Preconditions.checkNotNull(key); + if (shorts == null) { + remove(key); + return; + } + final NBTTagList list = new NBTTagList(); + for (final short value : shorts) { + list.add(NBTTagShort.a(value)); + } + set(key, list); + } + + /** + * Gets an array of primitive integers from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + @Override + public int[] getIntArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getIntArray(key); + } + + /** + * Sets an array of primitive integers to a key. + * + * @param key The key to set to values to. + * @param ints The values to set to the key. + */ + @Override + public void setIntArray(@Nonnull final String key, @Nullable final int[] ints) { + Preconditions.checkNotNull(key); + if (ints == null) { + remove(key); + return; + } + super.setIntArray(key, ints); + } + + /** + * Gets an array of primitive longs from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + @Override + public long[] getLongArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + return super.getLongArray(key); + } + + /** + * Sets an array of primitive longs to a key. + * + * @param key The key to set to values to. + * @param longs The values to set to the key. + */ + public void setLongArray(@Nonnull final String key, @Nullable final long[] longs) { + Preconditions.checkNotNull(key); + if (longs == null) { + remove(key); + return; + } + super.a(key, longs); + } + + /** + * Gets an array of primitive floats from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + public float[] getFloatArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + final NBTTagList list = getList(key, NBTType.FLOAT); + final float[] result = new float[list.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = list.i(i); + } + return result; + } + + /** + * Sets an array of primitive floats to a key. + * + * @param key The key to set to values to. + * @param floats The values to set to the key. + */ + public void setFloatArray(@Nonnull final String key, @Nullable final float[] floats) { + Preconditions.checkNotNull(key); + if (floats == null) { + remove(key); + return; + } + final NBTTagList list = new NBTTagList(); + for (final float value : floats) { + list.add(NBTTagFloat.a(value)); + } + set(key, list); + } + + /** + * Gets an array of primitive doubles from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + public double[] getDoubleArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + final NBTTagList list = getList(key, NBTType.DOUBLE); + final double[] result = new double[list.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = list.h(i); + } + return result; + } + + /** + * Sets an array of primitive doubles to a key. + * + * @param key The key to set to values to. + * @param doubles The values to set to the key. + */ + public void setDoubleArray(@Nonnull final String key, @Nullable final double[] doubles) { + Preconditions.checkNotNull(key); + if (doubles == null) { + remove(key); + return; + } + final NBTTagList list = new NBTTagList(); + for (final double value : doubles) { + list.add(NBTTagDouble.a(value)); + } + set(key, list); + } + + /** + * Gets an array of UUIDs from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + public UUID[] getUUIDArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + if (get(key) instanceof NBTTagList list) { + if (list.e() == NBTType.INT_ARRAY) { + final UUID[] result = new UUID[list.size()]; + for (int i = 0, l = list.size(); i < l; i++) { + result[i] = list.getUUID(i); + } + return result; + } + if (list.e() == NBTType.COMPOUND) { + final UUID[] result = new UUID[list.size()]; + for (int i = 0, l = list.size(); i < l; i++) { + result[i] = list.getCompound(i).a(UUID_KEY); + } + return result; + } + } + return new UUID[0]; + } + + /** + * Sets an array of UUIDs to a key. + * + * @param key The key to set to values to. + * @param uuids The values to set to the key. + */ + public void setUUIDArray(@Nonnull final String key, @Nullable final UUID[] uuids) { + Preconditions.checkNotNull(key); + if (uuids == null) { + remove(key); + return; + } + final NBTTagList list = new NBTTagList(); + for (final UUID value : uuids) { + list.addUUID(value); + } + set(key, list); + } + + /** + * Gets an array of Strings from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + public String[] getStringArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + final NBTTagList list = getList(key, NBTType.STRING); + final String[] result = new String[list.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = list.get(i) instanceof NBTTagString nbtString ? nbtString.asString() : ""; + } + return result; + } + + /** + * Sets an array of Strings to a key. + * + * @param key The key to set to values to. + * @param strings The values to set to the key. + */ + public void setStringArray(@Nonnull final String key, @Nullable final String[] strings) { + Preconditions.checkNotNull(key); + if (strings == null) { + remove(key); + return; + } + final NBTTagList list = new NBTTagList(); + List.of(strings).forEach((string) -> list.add(NBTTagString.a(string))); + set(key, list); + } + + /** + * Gets an array of tag compounds from a key. + * + * @param key The key to get the values of. + * @return The values of the key, default: empty array + */ + @Nonnull + public NBTCompound[] getCompoundArray(@Nonnull final String key) { + Preconditions.checkNotNull(key); + final NBTTagList list = getList(key, NBTType.COMPOUND); + final NBTCompound[] result = new NBTCompound[list.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = new NBTCompound(list.getCompound(i)); + } + return result; + } + + /** + * Sets an array of tag compounds to a key. + * + * @param key The key to set to values to. + * @param compounds The values to set to the key. + */ + public void setCompoundArray(@Nonnull final String key, @Nullable final NBTCompound[] compounds) { + Preconditions.checkNotNull(key); + if (compounds == null) { + remove(key); + return; + } + final NBTTagList list = new NBTTagList(); + list.addAll(List.of(compounds)); + set(key, list); + } + + // ------------------------------------------------------------ + // Object Overrides + // ------------------------------------------------------------ + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object instanceof NBTCompound other) { + return Objects.equals(this.x, other.x); + } + return false; + } + + @Nonnull + @Override + public String toString() { + return "NBTCompound" + this.x.toString(); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/particles/ParticleEffect.java b/src/main/java/vg/civcraft/mc/civmodcore/particles/ParticleEffect.java index e012c695..0dc35783 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/particles/ParticleEffect.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/particles/ParticleEffect.java @@ -1,87 +1,63 @@ package vg.civcraft.mc.civmodcore.particles; +import javax.annotation.Nonnull; +import lombok.NonNull; import org.bukkit.Location; import org.bukkit.Particle; -public class ParticleEffect { - - private Particle particle; - - private float offsetX; - - private float offsetY; - - private float offsetZ; - - private float speed; - - private int particleCount; - - public ParticleEffect(Particle particle, float offsetX, float offsetY, float offsetZ, float speed, int particleCount) { - this.particle = particle; - this.offsetX = offsetX; - this.offsetY = offsetY; - this.offsetZ = offsetZ; - this.speed = speed; - this.particleCount = particleCount; - } - - /** - * @return the type of particle used in this effect - */ +/** + * @param particle The type of particle to effect, ie: explosion, heart, lava... etc + * @param offsetX The maximum randomised offset on the X axis. + * @param offsetY The maximum randomised offset on the Y axis. + * @param offsetZ The maximum randomised offset on the Z axis. + * @param speed The speed of the effect. + * @param particleCount The number (density) of the particle effect. + */ +public record ParticleEffect(@NonNull Particle particle, + float offsetX, + float offsetY, + float offsetZ, + float speed, + int particleCount) { + + @Deprecated public Particle getParticle() { - return particle; + return this.particle; } - /** - * @return the amount to be randomly offset by in the X axis - */ + @Deprecated public float getOffsetX() { - return offsetX; + return this.offsetX; } - /** - * @return the amount to be randomly offset by in the Y axis - */ + @Deprecated public float getOffsetY() { - return offsetY; + return this.offsetY; } - /** - * @return the amount to be randomly offset by in the Z axis - */ + @Deprecated public float getOffsetZ() { - return offsetZ; + return this.offsetZ; } - /** - * @return the speed of the particles - */ + @Deprecated public float getSpeed() { - return speed; + return this.speed; } - /** - * @return the amount of particle to display. - */ + @Deprecated public int getParticleCount() { - return particleCount; + return this.particleCount; } /** * Display an effect defined in the config around a reinforcement. * - * @param location - * the location of the reinforcement. + * @param location the location of the reinforcement. */ - public void playEffect(Location location) { - location.getWorld().spawnParticle(particle, location, particleCount, offsetX, offsetY, offsetZ, speed, null); - } - - @Override - public String toString() { - return String.format(" type: %s \n offsetX: %f \n offsetY: %f \n offsetZ: %f \n speed:" - + " " + "%f \n particleCount: %d", particle, offsetX, offsetY, offsetZ, speed, particleCount); + public void playEffect(@Nonnull final Location location) { + location.getWorld().spawnParticle(this.particle, location, this.particleCount, + this.offsetX, this.offsetY, this.offsetZ, this.speed, null); } } diff --git a/src/main/java/vg/civcraft/mc/civmodcore/pdc/AbstractPersistentDataType.java b/src/main/java/vg/civcraft/mc/civmodcore/pdc/AbstractPersistentDataType.java new file mode 100644 index 00000000..0090706b --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/pdc/AbstractPersistentDataType.java @@ -0,0 +1,45 @@ +package vg.civcraft.mc.civmodcore.pdc; + +import java.util.Objects; +import javax.annotation.Nonnull; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataType; + +public abstract class AbstractPersistentDataType implements PersistentDataType { + + protected final Class

    primitiveClass; + protected final Class complexClass; + + public AbstractPersistentDataType(@Nonnull final Class

    primitiveClass, + @Nonnull final Class complexClass) { + this.primitiveClass = Objects.requireNonNull(primitiveClass); + this.complexClass = Objects.requireNonNull(complexClass); + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public final Class

    getPrimitiveType() { + return this.primitiveClass; + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public final Class getComplexType() { + return this.complexClass; + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public abstract P toPrimitive(@Nonnull C instance, + @Nonnull PersistentDataAdapterContext adapter); + + /** {@inheritDoc} */ + @Nonnull + @Override + public abstract C fromPrimitive(@Nonnull P raw, + @Nonnull PersistentDataAdapterContext adapter); + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentDataTypes.java b/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentDataTypes.java new file mode 100644 index 00000000..9f40faa7 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentDataTypes.java @@ -0,0 +1,50 @@ +package vg.civcraft.mc.civmodcore.pdc; + +import javax.annotation.Nonnull; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataType; + +@UtilityClass +public final class PersistentDataTypes { + public static final String DECODER_ERROR = "Was unable to decode that %s! [%s]"; + + /** + * Boolean data type... because believe it or not but PDC doesn't already have this ಠ_ಠ + */ + public static final PersistentDataType BOOLEAN = new AbstractPersistentDataType<>(Byte.class, Boolean.class) { + @Nonnull + @Override + public Byte toPrimitive(@Nonnull final Boolean bool, + @Nonnull final PersistentDataAdapterContext adapter) { + return (byte) (bool ? 1 : 0); + } + @Nonnull + @Override + public Boolean fromPrimitive(@Nonnull final Byte raw, + @Nonnull final PersistentDataAdapterContext adapter) { + return raw != (byte) 0; + } + }; + + /** + * Converts Components to Strings and vice versa. + */ + public static final PersistentDataType COMPONENT = new AbstractPersistentDataType<>(String.class, Component.class) { + @Nonnull + @Override + public String toPrimitive(@Nonnull final Component component, + @Nonnull final PersistentDataAdapterContext adapter) { + return GsonComponentSerializer.gson().serialize(component); + } + @Nonnull + @Override + public Component fromPrimitive(@Nonnull final String raw, + @Nonnull final PersistentDataAdapterContext adapter) { + return GsonComponentSerializer.gson().deserialize(raw); + } + }; + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentEnumDataType.java b/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentEnumDataType.java new file mode 100644 index 00000000..49018231 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentEnumDataType.java @@ -0,0 +1,59 @@ +package vg.civcraft.mc.civmodcore.pdc; + +import java.util.Objects; +import javax.annotation.Nonnull; +import org.apache.commons.lang3.EnumUtils; +import org.bukkit.persistence.PersistentDataAdapterContext; + +/** + * This class enables the easy encoding and decoding of enums. + */ +public class PersistentEnumDataType> extends AbstractPersistentDataType { + + protected final T defaultValue; + protected final boolean useDefault; + + /** + * This constructor is used when you want invalid enum decodings to error. + * + * @param enumClass The class of the enum. + */ + public PersistentEnumDataType(@Nonnull final Class enumClass) { + super(String.class, enumClass); + this.defaultValue = null; + this.useDefault = false; + } + + /** + * This constructor is used when you want invalid enum decodings to fall back to a default value. + * + * @param defaultValue The default value that {@link #fromPrimitive(String, PersistentDataAdapterContext)} will + * return if the raw value is null or invalid. + */ + @SuppressWarnings("unchecked") + public PersistentEnumDataType(@Nonnull final T defaultValue) { + super(String.class, (Class) defaultValue.getClass()); + this.defaultValue = defaultValue; + this.useDefault = true; + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public String toPrimitive(@Nonnull final T instance, + @Nonnull final PersistentDataAdapterContext adapter) { + return instance.name(); + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public T fromPrimitive(@Nonnull final String raw, + @Nonnull final PersistentDataAdapterContext adapter) { + return this.useDefault ? + EnumUtils.getEnum(this.complexClass, raw, this.defaultValue) : + Objects.requireNonNull(EnumUtils.getEnum(this.complexClass, raw), + String.format(PersistentDataTypes.DECODER_ERROR, this.complexClass.getSimpleName(), raw)); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentMapDataType.java b/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentMapDataType.java new file mode 100644 index 00000000..2711565a --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/pdc/PersistentMapDataType.java @@ -0,0 +1,73 @@ +package vg.civcraft.mc.civmodcore.pdc; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nonnull; +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +/** + * This class enables easier encoding and decoding of maps. + */ +public abstract class PersistentMapDataType implements PersistentDataType> { + + private final PersistentDataType keyEncoder; + private final PersistentDataType valueEncoder; + + public PersistentMapDataType(@Nonnull final PersistentDataType keyEncoder, + @Nonnull final PersistentDataType valueEncoder) { + this.keyEncoder = Objects.requireNonNull(keyEncoder); + this.valueEncoder = Objects.requireNonNull(valueEncoder); + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public Class getPrimitiveType() { + return PersistentDataContainer.class; + } + + /** {@inheritDoc} */ + @SuppressWarnings({"unchecked", "rawtypes"}) + @Nonnull + @Override + public Class> getComplexType() { + return (Class>) ((Class) Map.class); + } + + /** + * @param initialSize Initial size of the map. + * @return Returns a new map that's used in {@link #fromPrimitive(PersistentDataContainer, PersistentDataAdapterContext)}. + */ + @Nonnull + protected abstract Map newMap(int initialSize); + + /** {@inheritDoc} */ + @Nonnull + @Override + public PersistentDataContainer toPrimitive(@Nonnull final Map map, + @Nonnull final PersistentDataAdapterContext adapter) { + final var pdc = adapter.newPersistentDataContainer(); + map.forEach((key, value) -> pdc.set( + this.keyEncoder.toPrimitive(key, adapter), + this.valueEncoder, value)); + return pdc; + } + + /** {@inheritDoc} */ + @Nonnull + @Override + public Map fromPrimitive(@Nonnull final PersistentDataContainer pdc, + @Nonnull final PersistentDataAdapterContext adapter) { + final Set keys = pdc.getKeys(); + final Map map = newMap(keys.size()); + keys.forEach((key) -> map.put( + this.keyEncoder.fromPrimitive(key, adapter), + Objects.requireNonNull(pdc.get(key, this.valueEncoder)))); + return map; + } + +} \ No newline at end of file diff --git a/src/main/java/vg/civcraft/mc/civmodcore/pdc/extensions/PersistentDataContainerExtensions.java b/src/main/java/vg/civcraft/mc/civmodcore/pdc/extensions/PersistentDataContainerExtensions.java new file mode 100644 index 00000000..8f7e42ca --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/pdc/extensions/PersistentDataContainerExtensions.java @@ -0,0 +1,101 @@ +package vg.civcraft.mc.civmodcore.pdc.extensions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import lombok.experimental.ExtensionMethod; +import lombok.experimental.UtilityClass; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagList; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.v1_17_R1.persistence.CraftPersistentDataContainer; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import vg.civcraft.mc.civmodcore.nbt.NBTType; + +/** + * Set of extension methods for {@link PersistentDataContainer}. Use {@link ExtensionMethod @ExtensionMethod} to take + * most advantage of this. + */ +@UtilityClass +public class PersistentDataContainerExtensions { + + /** + * @param self The PersistentDataContainer to get the internal NBT of. + * @return Returns the PDC's inner-map. + */ + @Nonnull + public static Map getRaw(@Nonnull final PersistentDataContainer self) { + return ((CraftPersistentDataContainer) self).getRaw(); + } + + /** + * @param self The PersistentDataContainer to get the size of. + * @return Returns the PDC's size. + */ + public static int size(@Nonnull final PersistentDataContainer self) { + return getRaw(self).size(); + } + + /** + * @param self The PersistentDataContainer to check whether the list is on. + * @param key The key of the list. + * @return Returns true if a list is present at that key. + */ + public static boolean hasList(@Nonnull final PersistentDataContainer self, + @Nonnull final NamespacedKey key) { + final var found = getRaw(self).get(key.toString()); + return found != null && found.getTypeId() == NBTType.LIST; + } + + /** + * @param self The PersistentDataContainer to get the list from. + * @param key The key of the list. + * @param type The type of the list elements. + * @param

    The primitive type the list elements type. + * @param The complex type of the list elements type. + */ + public static List getList(@Nonnull final PersistentDataContainer self, + @Nonnull final NamespacedKey key, + @Nonnull final PersistentDataType type) { + final var pdc = (CraftPersistentDataContainer) self; + final var found = pdc.getRaw().get(key.toString()); + if (!(found instanceof NBTTagList tagList)) { + return null; + } + final var typeRegistry = pdc.getDataTagTypeRegistry(); + final var result = new ArrayList(tagList.size()); + for (final NBTBase nbtElement : tagList) { + final P primitiveElement = typeRegistry.extract(type.getPrimitiveType(), nbtElement); + final C complexElement = type.fromPrimitive(primitiveElement, pdc.getAdapterContext()); + result.add(Objects.requireNonNull(complexElement)); + } + return result; + } + + /** + * @param self The PersistentDataContainer to set the list to. + * @param key The key of the list. + * @param type The type of the list elements. + * @param

    The primitive type the list elements type. + * @param The complex type of the list elements type. + * @param list The list to set. + */ + public static void setList(@Nonnull final PersistentDataContainer self, + @Nonnull final NamespacedKey key, + @Nonnull final PersistentDataType type, + @Nonnull final List list) { + final var pdc = (CraftPersistentDataContainer) self; + final var typeRegistry = pdc.getDataTagTypeRegistry(); + final var result = new NBTTagList(); + for (final C complexElement : list) { + final P primitiveElement = type.toPrimitive(complexElement, pdc.getAdapterContext()); + final NBTBase nbtElement = typeRegistry.wrap(type.getPrimitiveType(), primitiveElement); + result.add(Objects.requireNonNull(nbtElement)); + } + pdc.getRaw().put(key.toString(), result); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/bottom/BottomLine.java b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/bottom/BottomLine.java similarity index 97% rename from src/main/java/vg/civcraft/mc/civmodcore/scoreboard/bottom/BottomLine.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/bottom/BottomLine.java index 2f4d0996..fa8e1f2e 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/bottom/BottomLine.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/bottom/BottomLine.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.scoreboard.bottom; +package vg.civcraft.mc.civmodcore.players.scoreboard.bottom; import java.util.Iterator; import java.util.Map; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/bottom/BottomLineAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/bottom/BottomLineAPI.java similarity index 94% rename from src/main/java/vg/civcraft/mc/civmodcore/scoreboard/bottom/BottomLineAPI.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/bottom/BottomLineAPI.java index 8eee8384..dbc4503d 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/bottom/BottomLineAPI.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/bottom/BottomLineAPI.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.scoreboard.bottom; +package vg.civcraft.mc.civmodcore.players.scoreboard.bottom; import java.util.Map; import java.util.Map.Entry; @@ -6,17 +6,16 @@ import java.util.TreeMap; import java.util.TreeSet; import java.util.UUID; +import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import vg.civcraft.mc.civmodcore.CivModCorePlugin; +@UtilityClass public final class BottomLineAPI { - private BottomLineAPI() { - } - private static Set lines = new TreeSet<>(); private static final String SEPARATOR = ChatColor.BOLD + " " + ChatColor.BLACK + "|| " + ChatColor.RESET; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/CivScoreBoard.java b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/CivScoreBoard.java similarity index 98% rename from src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/CivScoreBoard.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/CivScoreBoard.java index 8ea4253e..a7cb764a 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/CivScoreBoard.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/CivScoreBoard.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.scoreboard.side; +package vg.civcraft.mc.civmodcore.players.scoreboard.side; import java.util.Iterator; import java.util.Map; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/ScoreBoardAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/ScoreBoardAPI.java similarity index 94% rename from src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/ScoreBoardAPI.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/ScoreBoardAPI.java index d2e6f35a..0bc07f85 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/ScoreBoardAPI.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/ScoreBoardAPI.java @@ -1,13 +1,15 @@ -package vg.civcraft.mc.civmodcore.scoreboard.side; +package vg.civcraft.mc.civmodcore.players.scoreboard.side; import com.google.common.base.Preconditions; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import java.util.UUID; +import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +@UtilityClass public class ScoreBoardAPI { private static Map openScores = new TreeMap<>(); diff --git a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/ScoreBoardListener.java b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/ScoreBoardListener.java similarity index 85% rename from src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/ScoreBoardListener.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/ScoreBoardListener.java index 84a7a48d..ae5312dd 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/scoreboard/side/ScoreBoardListener.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/scoreboard/side/ScoreBoardListener.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.scoreboard.side; +package vg.civcraft.mc.civmodcore.players.scoreboard.side; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/AltRequestEvent.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/AltRequestEvent.java similarity index 94% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/AltRequestEvent.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/AltRequestEvent.java index 406c2e26..67dc9870 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/AltRequestEvent.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/AltRequestEvent.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings; +package vg.civcraft.mc.civmodcore.players.settings; import com.google.common.base.Preconditions; import java.util.UUID; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/MenuDialog.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/MenuDialog.java similarity index 90% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/MenuDialog.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/MenuDialog.java index 75faf2ed..b51f6322 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/MenuDialog.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/MenuDialog.java @@ -1,11 +1,11 @@ -package vg.civcraft.mc.civmodcore.playersettings; +package vg.civcraft.mc.civmodcore.players.settings; import java.util.Collections; import java.util.List; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import vg.civcraft.mc.civmodcore.chat.dialog.Dialog; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; public class MenuDialog extends Dialog { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/PlayerSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSetting.java similarity index 90% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/PlayerSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSetting.java index 0d2465b5..50d73403 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/PlayerSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSetting.java @@ -1,20 +1,18 @@ -package vg.civcraft.mc.civmodcore.playersettings; +package vg.civcraft.mc.civmodcore.players.settings; import com.google.common.base.Preconditions; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; /** * Contains a value for every players for one setting @@ -35,7 +33,7 @@ public PlayerSetting(JavaPlugin owningPlugin, T defaultValue, String niceName, S String description, boolean canBeChangedByPlayer) { Preconditions.checkNotNull(gui, "GUI ItemStack can not be null."); - values = new TreeMap<>(); + this.values = new ConcurrentHashMap<>(); this.defaultValue = defaultValue; this.owningPlugin = owningPlugin; this.niceName = niceName; @@ -61,13 +59,6 @@ protected void applyInfoToItemStack(ItemStack item, UUID player) { */ public abstract T deserialize(String serial); - Map dumpAllSerialized() { - Map result = new HashMap<>(); - for (Entry entry : values.entrySet()) { - result.put(entry.getKey().toString(), serialize(entry.getValue())); - } - return result; - } /** * @return Textual description shown in the GUI for this setting @@ -134,6 +125,10 @@ public T getValue(UUID player) { public T getValue(Player player) { return getValue(player.getUniqueId()); } + + public boolean hasValue(UUID player) { + return values.containsKey(player); + } /** * @return Can the owning player freely edit this value @@ -200,13 +195,22 @@ void load(String player, String serial) { public void setValue(UUID player, T value) { if (listeners != null) { T oldValue = getValue(player); - for(SettingChangeListener listener: listeners) { + for (SettingChangeListener listener : listeners) { listener.handle(player, this, oldValue, value); } } values.put(player, value); } + /** + * Used for initial setting of a players value when their data is loaded. Does not call any setting change listeners + * @param player UUID of the player whose data is being initialized + * @param value Value to initialize to + */ + public void setValueInternal(UUID player, T value) { + values.put(player, value); + } + /** * Sets the given value for the given player. Null values are only allowed if * the (de-)serialization implementation can properly handle it, which is not diff --git a/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSettingAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSettingAPI.java new file mode 100644 index 00000000..9e05afa1 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSettingAPI.java @@ -0,0 +1,80 @@ +package vg.civcraft.mc.civmodcore.players.settings; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import lombok.experimental.UtilityClass; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuOption; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.impl.AltConsistentSetting; + +/** + * Allows creating settings, which will automatically be available in players + * configuration GUI + * + */ +@UtilityClass +public final class PlayerSettingAPI { + + private static final Map> SETTINGS_BY_IDENTIFIER = new ConcurrentHashMap<>(); + + private static final Map>> SETTINGS_BY_PLUGIN = new ConcurrentHashMap<>(); + + private static final MenuSection MAIN_MENU = new MenuSection("Config", "", null); + + /** + * @return GUI main menu + */ + public static MenuSection getMainMenu() { + return MAIN_MENU; + } + + public static Collection> getAllSettings() { + return Collections.unmodifiableCollection(SETTINGS_BY_IDENTIFIER.values()); + } + + /** + * Gets a setting by its identifier + * + * @param identifier Identifier to get setting for + * @return Setting with the given identifier or null if no such setting exists + */ + public static PlayerSetting getSetting(String identifier) { + return SETTINGS_BY_IDENTIFIER.get(identifier); + } + + /** + * Settings must be registered on every startup to be available. Identifiers + * must be unique globally. + * + * If a setting had values assigned but is not registered on startup its old + * values will be left alone. + * + * @param setting Setting to register + * @param menu Menu in which this value will appear + */ + public static void registerSetting(PlayerSetting setting, MenuSection menu) { + Preconditions.checkArgument(setting != null, "Player setting cannot be null."); + if (setting instanceof AltConsistentSetting) { + if (setting.canBeChangedByPlayer()) { + menu.addItem(new MenuOption(menu, setting)); + } + menu = null; + setting = ((AltConsistentSetting) setting).getWrappedSetting(); + } + List> pluginSettings = SETTINGS_BY_PLUGIN.computeIfAbsent( + setting.getOwningPlugin().getName(), + k -> new ArrayList<>()); + Preconditions.checkArgument(!pluginSettings.contains(setting), + "Cannot register the same player setting twice."); + SETTINGS_BY_IDENTIFIER.put(setting.getIdentifier(), setting); + pluginSettings.add(setting); + if (menu != null && setting.canBeChangedByPlayer()) { + menu.addItem(new MenuOption(menu, setting)); + } + } +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/SettingChangeListener.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/SettingChangeListener.java similarity index 80% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/SettingChangeListener.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/SettingChangeListener.java index 4e3f47a7..65e2665a 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/SettingChangeListener.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/SettingChangeListener.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings; +package vg.civcraft.mc.civmodcore.players.settings; import java.util.UUID; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/SettingTypeManager.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/SettingTypeManager.java similarity index 70% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/SettingTypeManager.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/SettingTypeManager.java index 5013a7cb..ace4d6dd 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/SettingTypeManager.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/SettingTypeManager.java @@ -1,18 +1,20 @@ -package vg.civcraft.mc.civmodcore.playersettings; +package vg.civcraft.mc.civmodcore.players.settings; import java.util.HashMap; import java.util.Map; +import lombok.experimental.UtilityClass; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.playersettings.impl.BooleanSetting; -import vg.civcraft.mc.civmodcore.playersettings.impl.DoubleSetting; -import vg.civcraft.mc.civmodcore.playersettings.impl.FloatSetting; -import vg.civcraft.mc.civmodcore.playersettings.impl.IntegerSetting; -import vg.civcraft.mc.civmodcore.playersettings.impl.LongSetting; -import vg.civcraft.mc.civmodcore.playersettings.impl.StringSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.BooleanSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.DoubleSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.FloatSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.IntegerSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.LongSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.StringSetting; +@UtilityClass public class SettingTypeManager { private static Map, PlayerSetting> settings; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/players/settings/commands/ConfigCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/commands/ConfigCommand.java new file mode 100644 index 00000000..1e08d9cd --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/commands/ConfigCommand.java @@ -0,0 +1,92 @@ +package vg.civcraft.mc.civmodcore.players.settings.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; + +@CommandAlias("config") +public final class ConfigCommand extends BaseCommand { + + private static final String MANAGE_PERMISSION = "cmc.config.manage"; + + @Default + @Description("Allows configuring player specific settings") + public void openConfig(final Player sender) { + PlayerSettingAPI.getMainMenu().showScreen(sender); + } + + @Subcommand("get") + @Description("Lets you read any config setting for any player") + @Syntax("config get ") + @CommandPermission(MANAGE_PERMISSION) + public void readConfigValue(final CommandSender sender, + final String player, + final String setting) { + final UUID playerUUID = INTERNAL_resolvePlayer(player); + if (playerUUID == null) { + sender.sendMessage(ChatColor.RED + "Could not resolve player " + player); + return; + } + final PlayerSetting foundSetting = PlayerSettingAPI.getSetting(setting); + if (foundSetting == null) { + sender.sendMessage(ChatColor.RED + "Could not find setting with identifier " + setting); + return; + } + final String currentValue = foundSetting.getSerializedValueFor(playerUUID); + sender.sendMessage(ChatColor.GREEN + "Value for setting [" + foundSetting.getIdentifier() + "] " + + "for player [" + playerUUID + "] is: " + currentValue); + } + + @Subcommand("set") + @Description("Lets you write any config setting for any player") + @Syntax("config set ") + @CommandPermission(MANAGE_PERMISSION) + public void writeConfigValue(final CommandSender sender, + final String player, + final String setting, + final String value) { + final UUID playerUUID = INTERNAL_resolvePlayer(player); + if (playerUUID == null) { + sender.sendMessage(ChatColor.RED + "Could not resolve player " + player); + return; + } + final PlayerSetting foundSetting = PlayerSettingAPI.getSetting(setting); + if (foundSetting == null) { + sender.sendMessage(ChatColor.RED + "Could not find setting with identifier " + setting); + return; + } + if (!foundSetting.isValidValue(value)) { + sender.sendMessage(ChatColor.RED + "[" + value + "] is not a valid value for this setting"); + return; + } + foundSetting.setValueFromString(playerUUID, value); + sender.sendMessage(ChatColor.GREEN + "Set value for setting [" + foundSetting.getIdentifier() + "] " + + "for player [" + playerUUID + "] to: " + value); + } + + private static UUID INTERNAL_resolvePlayer(final String value) { + try { + return UUID.fromString(value); + } + catch (final IllegalArgumentException e) { + final OfflinePlayer offline = Bukkit.getOfflinePlayerIfCached(value); + if (offline == null) { + return null; + } + return offline.getUniqueId(); + } + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ClickableMenuItem.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/ClickableMenuItem.java similarity index 78% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ClickableMenuItem.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/ClickableMenuItem.java index 420ffd74..affbe61c 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ClickableMenuItem.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/ClickableMenuItem.java @@ -1,8 +1,8 @@ -package vg.civcraft.mc.civmodcore.playersettings.gui; +package vg.civcraft.mc.civmodcore.players.settings.gui; import java.util.function.Function; import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; public class ClickableMenuItem extends MenuItem { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuItem.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuItem.java similarity index 78% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuItem.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuItem.java index 47d55869..be2b35ba 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuItem.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuItem.java @@ -1,7 +1,7 @@ -package vg.civcraft.mc.civmodcore.playersettings.gui; +package vg.civcraft.mc.civmodcore.players.settings.gui; import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; public abstract class MenuItem { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuOption.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuOption.java similarity index 73% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuOption.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuOption.java index 592a9123..1bdc4cef 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuOption.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuOption.java @@ -1,10 +1,10 @@ -package vg.civcraft.mc.civmodcore.playersettings.gui; +package vg.civcraft.mc.civmodcore.players.settings.gui; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.inventorygui.Clickable; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class MenuOption extends MenuItem { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuSection.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuSection.java similarity index 88% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuSection.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuSection.java index 6b698954..05acd9a5 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/MenuSection.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/gui/MenuSection.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.gui; +package vg.civcraft.mc.civmodcore.players.settings.gui; import com.google.common.base.Preconditions; import java.util.ArrayList; @@ -10,12 +10,12 @@ import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.MultiPageView; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.inventorygui.Clickable; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; -import vg.civcraft.mc.civmodcore.inventorygui.MultiPageView; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; public class MenuSection extends MenuItem { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/AltConsistentSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/AltConsistentSetting.java similarity index 89% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/AltConsistentSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/AltConsistentSetting.java index c5140ca1..84d40b39 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/AltConsistentSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/AltConsistentSetting.java @@ -1,9 +1,9 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.util.UUID; import org.bukkit.Bukkit; -import vg.civcraft.mc.civmodcore.playersettings.AltRequestEvent; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.AltRequestEvent; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; /** * @@ -46,7 +46,7 @@ public T getValue(UUID player) { @Override public void setValue(UUID uuid, T value) { - setValue(getMain(uuid), value); + super.setValue(getMain(uuid), value); } private static UUID getMain(UUID account) { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/BooleanSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/BooleanSetting.java similarity index 90% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/BooleanSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/BooleanSetting.java index f9f0e53e..5e0d7ec3 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/BooleanSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/BooleanSetting.java @@ -1,12 +1,12 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.util.UUID; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; public class BooleanSetting extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/BoundedIntegerSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/BoundedIntegerSetting.java similarity index 95% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/BoundedIntegerSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/BoundedIntegerSetting.java index 49a4b528..9b75633e 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/BoundedIntegerSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/BoundedIntegerSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.util.UUID; import org.bukkit.ChatColor; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/CommandReplySetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/CommandReplySetting.java similarity index 97% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/CommandReplySetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/CommandReplySetting.java index f6962add..bb59601b 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/CommandReplySetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/CommandReplySetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.util.Map; import java.util.Map.Entry; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DecimalFormatSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DecimalFormatSetting.java similarity index 91% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DecimalFormatSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DecimalFormatSetting.java index 3918f3c4..e3431c75 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DecimalFormatSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DecimalFormatSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.text.DecimalFormat; import java.util.UUID; @@ -6,7 +6,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class DecimalFormatSetting extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DisplayLocationSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DisplayLocationSetting.java similarity index 91% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DisplayLocationSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DisplayLocationSetting.java index d61d5067..51d1af3a 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DisplayLocationSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DisplayLocationSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.util.Arrays; import java.util.UUID; @@ -8,11 +8,11 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.LClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.MultiPageView; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; -import vg.civcraft.mc.civmodcore.inventorygui.LClickable; -import vg.civcraft.mc.civmodcore.inventorygui.MultiPageView; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; public class DisplayLocationSetting extends LimitedStringSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DoubleSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DoubleSetting.java similarity index 91% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DoubleSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DoubleSetting.java index 84ecb0df..fa15e5e2 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/DoubleSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/DoubleSetting.java @@ -1,9 +1,9 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.text.DecimalFormat; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class DoubleSetting extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/EnumSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/EnumSetting.java new file mode 100644 index 00000000..5ffcc0f4 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/EnumSetting.java @@ -0,0 +1,95 @@ +package vg.civcraft.mc.civmodcore.players.settings.impl; + +import java.util.Comparator; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.lang3.EnumUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.MultiPageView; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; + +public class EnumSetting> extends PlayerSetting { + + private final Class enumClass; + + public EnumSetting(final JavaPlugin owningPlugin, + final T defaultValue, + final String niceName, + final String identifier, + final ItemStack gui, + final String description, + final boolean canBeChangedByPlayer, + final Class enumClass) { + super(owningPlugin, defaultValue, niceName, identifier, gui, description, canBeChangedByPlayer); + this.enumClass = Objects.requireNonNull(enumClass); + } + + @Override + public T deserialize(final String raw) { + return EnumUtils.getEnum(this.enumClass, raw, getDefaultValue()); + } + + @Override + public boolean isValidValue(final String raw) { + return EnumUtils.isValidEnum(this.enumClass, raw); + } + + @Override + public String serialize(final T value) { + if (value == null) { + return null; + } + return value.name(); + } + + @Override + public String toText(final T value) { + if (value == null) { + return ""; + } + return value.name(); + } + + @Override + public void handleMenuClick(final Player player, final MenuSection menu) { + final T currentValue = getValue(player); + + final var view = new MultiPageView(player, + EnumUtils.getEnumList(this.enumClass).stream() + .sorted(Comparator.comparing(Enum::name)) + .map(value -> { + final var item = new ItemStack(value == currentValue ? Material.GREEN_DYE : Material.RED_DYE); + ItemUtils.setDisplayName(item, ChatColor.GOLD + toText(value)); + return new Clickable(item) { + @Override + protected void clicked(final Player ignored) { + setValue(player, value); + handleMenuClick(player, menu); + } + }; + }) + .collect(Collectors.toList()), + getNiceName(), + true); + + final var backButtonItem = new ItemStack(Material.ARROW); + ItemUtils.setDisplayName(backButtonItem, ChatColor.AQUA + "Go back to " + menu.getName()); + view.setMenuSlot(new Clickable(backButtonItem) { + @Override + public void clicked(final Player clicker) { + menu.showScreen(clicker); + } + }, 0); + + view.showScreen(); + } + +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/FloatSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/FloatSetting.java similarity index 91% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/FloatSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/FloatSetting.java index 0b85ee05..37999478 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/FloatSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/FloatSetting.java @@ -1,9 +1,9 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.text.DecimalFormat; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class FloatSetting extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/IntegerSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/IntegerSetting.java similarity index 92% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/IntegerSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/IntegerSetting.java index 6ef08012..9f4a8c8f 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/IntegerSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/IntegerSetting.java @@ -1,10 +1,10 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.util.UUID; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class IntegerSetting extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/JsonSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/JsonSetting.java similarity index 89% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/JsonSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/JsonSetting.java index aad7dab5..5249174d 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/JsonSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/JsonSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -6,7 +6,7 @@ import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class JsonSetting extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/LimitedStringSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/LimitedStringSetting.java similarity index 94% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/LimitedStringSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/LimitedStringSetting.java index 06d48ca1..11679b95 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/LimitedStringSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/LimitedStringSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import java.util.Collection; import java.util.HashSet; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/LongSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/LongSetting.java similarity index 89% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/LongSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/LongSetting.java index f7d936fe..5e1c46bb 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/LongSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/LongSetting.java @@ -1,9 +1,9 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class LongSetting extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/StringSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/StringSetting.java similarity index 66% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/StringSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/StringSetting.java index 177c0d1c..4156048e 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/StringSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/StringSetting.java @@ -1,8 +1,9 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl; +package vg.civcraft.mc.civmodcore.players.settings.impl; +import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; public class StringSetting extends PlayerSetting { @@ -11,6 +12,10 @@ public StringSetting(JavaPlugin plugin, String defaultValue, String name, String super(plugin, defaultValue, name, identifier, gui, description, true); } + public StringSetting(JavaPlugin plugin, String defaultValue, String identifier) { + super(plugin, defaultValue, identifier, identifier, new ItemStack(Material.STONE), "", false); + } + @Override public String deserialize(String serial) { return serial; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/AbstractCollectionSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/AbstractCollectionSetting.java similarity index 93% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/AbstractCollectionSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/AbstractCollectionSetting.java index c2008013..3ee93960 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/AbstractCollectionSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/AbstractCollectionSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl.collection; +package vg.civcraft.mc.civmodcore.players.settings.impl.collection; import java.util.ArrayList; import java.util.Collection; @@ -12,13 +12,13 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import vg.civcraft.mc.civmodcore.chat.dialog.Dialog; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.MultiPageView; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.inventorygui.Clickable; -import vg.civcraft.mc.civmodcore.inventorygui.IClickable; -import vg.civcraft.mc.civmodcore.inventorygui.MultiPageView; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; -import vg.civcraft.mc.civmodcore.playersettings.SettingTypeManager; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSetting; +import vg.civcraft.mc.civmodcore.players.settings.SettingTypeManager; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; public abstract class AbstractCollectionSetting, T> extends PlayerSetting { diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/ListSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/ListSetting.java similarity index 88% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/ListSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/ListSetting.java index 7730c0fd..555d66da 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/ListSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/ListSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl.collection; +package vg.civcraft.mc.civmodcore.players.settings.impl.collection; import java.util.ArrayList; import java.util.List; @@ -11,7 +11,7 @@ public ListSetting(JavaPlugin owningPlugin, List defaultValue, String name, S String description, Class elementClass) { super(owningPlugin, defaultValue, name, identifier, gui, description, elementClass, (c) -> { if (c == null) { - return new ArrayList<>(); + return new ArrayList<>(0); } return new ArrayList<>(c); }); diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/SetSetting.java b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/SetSetting.java similarity index 92% rename from src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/SetSetting.java rename to src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/SetSetting.java index a3c28b07..4e12b3e0 100644 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/impl/collection/SetSetting.java +++ b/src/main/java/vg/civcraft/mc/civmodcore/players/settings/impl/collection/SetSetting.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.civmodcore.playersettings.impl.collection; +package vg.civcraft.mc.civmodcore.players.settings.impl.collection; import java.util.HashSet; import java.util.Set; diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/PlayerSettingAPI.java b/src/main/java/vg/civcraft/mc/civmodcore/playersettings/PlayerSettingAPI.java deleted file mode 100644 index 1a4b0528..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/PlayerSettingAPI.java +++ /dev/null @@ -1,151 +0,0 @@ -package vg.civcraft.mc.civmodcore.playersettings; - -import com.google.common.base.Preconditions; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import vg.civcraft.mc.civmodcore.CivModCorePlugin; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuOption; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; -import vg.civcraft.mc.civmodcore.playersettings.impl.AltConsistentSetting; - -/** - * Allows creating settings, which will automatically be available in players - * configuration GUI - * - */ -public final class PlayerSettingAPI { - - private static final String FILE_NAME = "civ-player-settings.yml"; - - private static final Map> SETTINGS_BY_IDENTIFIER = new HashMap<>(); - - private static final Map>> SETTINGS_BY_PLUGIN = new HashMap<>(); - - private static final MenuSection MAIN_MENU = new MenuSection("Config", "", null); - - private PlayerSettingAPI() { } - - /** - * @return GUI main menu - */ - public static MenuSection getMainMenu() { - return MAIN_MENU; - } - - /** - * Gets a setting by its identifier - * - * @param identifier Identifier to get setting for - * @return Setting with the given identifier or null if no such setting exists - */ - public static PlayerSetting getSetting(String identifier) { - return SETTINGS_BY_IDENTIFIER.get(identifier); - } - - private static void loadValues(PlayerSetting setting) { - File folder = setting.getOwningPlugin().getDataFolder(); - if (!folder.isDirectory()) { - return; - } - File file = new File(folder, FILE_NAME); - if (!file.isFile()) { - return; - } - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - ConfigurationSection section = config.getConfigurationSection(setting.getIdentifier()); - if (section == null) { - return; - } - for (String key : section.getKeys(false)) { - setting.load(key, section.getString(key)); - } - } - - /** - * Settings must be registered on every startup to be available. Identifiers - * must be unique globally. - * - * If a setting had values assigned but is not registered on startup its old - * values will be left alone. - * - * @param setting Setting to register - * @param menu Menu in which this value will appear - */ - public static void registerSetting(PlayerSetting setting, MenuSection menu) { - Preconditions.checkArgument(setting != null, "Player setting cannot be null."); - if (setting instanceof AltConsistentSetting) { - if (setting.canBeChangedByPlayer()) { - menu.addItem(new MenuOption(menu, setting)); - } - menu = null; - setting = ((AltConsistentSetting) setting).getWrappedSetting(); - } - loadValues(setting); - List> pluginSettings = SETTINGS_BY_PLUGIN.computeIfAbsent( - setting.getOwningPlugin().getName(), - k -> new ArrayList<>()); - Preconditions.checkArgument(!pluginSettings.contains(setting), - "Cannot register the same player setting twice."); - SETTINGS_BY_IDENTIFIER.put(setting.getIdentifier(), setting); - pluginSettings.add(setting); - if (menu != null && setting.canBeChangedByPlayer()) { - menu.addItem(new MenuOption(menu, setting)); - } - } - - // TODO: While this deregisteres the settings, those settings then need to be removed from menus - // Maybe menus need a rework? -// public static void deregisterPluginSettings(Plugin plugin) { -// Preconditions.checkArgument(plugin != null); -// Iteration.iterateThenClear(SETTINGS_BY_PLUGIN.get(plugin.getName()), (setting) -> -// SETTINGS_BY_IDENTIFIER.remove(setting.getIdentifier())); -// SETTINGS_BY_PLUGIN.remove(plugin.getName()); -// } - - /** - * Saves all values to their save files - */ - public static void saveAll() { - for (Entry>> pluginEntry : SETTINGS_BY_PLUGIN.entrySet()) { - if (pluginEntry.getValue().isEmpty()) { - continue; - } - File folder = pluginEntry.getValue().get(0).getOwningPlugin().getDataFolder(); - if (!folder.isDirectory()) { - folder.mkdirs(); - } - File file = new File(folder, FILE_NAME); - YamlConfiguration config; - if (file.isFile()) { - config = YamlConfiguration.loadConfiguration(file); - } - else { - config = new YamlConfiguration(); - } - for (PlayerSetting setting : pluginEntry.getValue()) { - ConfigurationSection section; - if (config.isConfigurationSection(setting.getIdentifier())) { - section = config.getConfigurationSection(setting.getIdentifier()); - } else { - section = config.createSection(setting.getIdentifier()); - } - for (Entry entry : setting.dumpAllSerialized().entrySet()) { - section.set(entry.getKey(), entry.getValue()); - } - } - try { - config.save(file); - } catch (IOException e) { - CivModCorePlugin.getInstance().severe("Failed to save settings", e); - } - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigCommand.java deleted file mode 100644 index f563ee93..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package vg.civcraft.mc.civmodcore.playersettings.gui; - -import co.aikar.commands.annotation.CommandAlias; -import co.aikar.commands.annotation.CommandPermission; -import co.aikar.commands.annotation.Default; -import co.aikar.commands.annotation.Description; -import co.aikar.commands.annotation.Subcommand; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.command.AikarCommand; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; - -@CommandAlias("config") -public class ConfigCommand extends AikarCommand { - - @Default - @Description("Allows configuring player specific settings") - public void execute(Player player) { - PlayerSettingAPI.getMainMenu().showScreen(player); - } - - @Subcommand("save") - @Description("Save all settings to the file.") - @CommandPermission("cmc.config.save") - public void save(CommandSender sender) { - PlayerSettingAPI.saveAll(); - sender.sendMessage(ChatColor.GREEN + "/config has been saved."); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigGetAnyCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigGetAnyCommand.java deleted file mode 100644 index 77060070..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigGetAnyCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package vg.civcraft.mc.civmodcore.playersettings.gui; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import vg.civcraft.mc.civmodcore.command.CivCommand; -import vg.civcraft.mc.civmodcore.command.StandaloneCommand; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; - -@CivCommand(id = "configgetany") -public class ConfigGetAnyCommand extends StandaloneCommand { - - @Override - public boolean execute(CommandSender sender, String[] args) { - if (args.length < 2) { - return false; - } - UUID uuid = ConfigSetAnyCommand.resolveName(args [0]); - if (uuid == null) { - sender.sendMessage(ChatColor.RED + "Could not resolve player " + args[0]); - return false; - } - String settingName = args [1]; - PlayerSetting setting = PlayerSettingAPI.getSetting(settingName); - if (setting == null) { - sender.sendMessage(ChatColor.RED + "Could not find setting with identifier " + args[1]); - return true; - } - String value = setting.getSerializedValueFor(uuid); - sender.sendMessage(ChatColor.GREEN + "Value for setting " + setting.getIdentifier() + " for player " + uuid + " is " + value); - return true; - } - - @Override - public List tabComplete(CommandSender sender, String[] args) { - return Collections.emptyList(); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigSetAnyCommand.java b/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigSetAnyCommand.java deleted file mode 100644 index 8957ef4f..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/playersettings/gui/ConfigSetAnyCommand.java +++ /dev/null @@ -1,64 +0,0 @@ -package vg.civcraft.mc.civmodcore.playersettings.gui; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import org.apache.commons.lang3.StringUtils; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.OfflinePlayer; -import org.bukkit.command.CommandSender; -import vg.civcraft.mc.civmodcore.command.CivCommand; -import vg.civcraft.mc.civmodcore.command.StandaloneCommand; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSetting; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; - -@CivCommand(id = "configsetany") -public class ConfigSetAnyCommand extends StandaloneCommand { - - @Override - public boolean execute(CommandSender sender, String[] args) { - if (args.length < 3) { - return false; - } - UUID uuid = resolveName(args [0]); - if (uuid == null) { - sender.sendMessage(ChatColor.RED + "Could not resolve player " + args[0]); - return false; - } - String settingName = args [1]; - PlayerSetting setting = PlayerSettingAPI.getSetting(settingName); - if (setting == null) { - sender.sendMessage(ChatColor.RED + "Could not find setting with identifier " + args[1]); - return true; - } - String value = StringUtils.join(Arrays.copyOfRange(args, 2, args.length), " "); - if (!setting.isValidValue(value)) { - sender.sendMessage(ChatColor.RED + value + " is not a valid value for this setting"); - return true; - } - setting.setValueFromString(uuid, value); - sender.sendMessage(ChatColor.GREEN + "Set value for setting " + setting.getIdentifier() + " for player " + uuid + " to " + value); - return true; - } - - public static UUID resolveName(String name) { - try { - return UUID.fromString(name); - } - catch (IllegalArgumentException e) { - OfflinePlayer offline = Bukkit.getOfflinePlayerIfCached(name); - if (offline == null) { - return null; - } - return offline.getUniqueId(); - } - } - - @Override - public List tabComplete(CommandSender sender, String[] args) { - return Collections.emptyList(); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCache.java b/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCache.java deleted file mode 100644 index 1949b3fd..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCache.java +++ /dev/null @@ -1,20 +0,0 @@ -package vg.civcraft.mc.civmodcore.serialization; - -/** - * Class designed to encode and decode directly on NBT rather than use cache fields. - */ -public abstract class NBTCache implements NBTSerializable { - - protected final NBTCompound nbt = new NBTCompound(); - - @Override - public void serialize(NBTCompound other) { - other.adopt(this.nbt); - } - - @Override - public void deserialize(NBTCompound other) { - this.nbt.adopt(other); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCompound.java b/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCompound.java deleted file mode 100644 index 5015d8ec..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCompound.java +++ /dev/null @@ -1,997 +0,0 @@ -package vg.civcraft.mc.civmodcore.serialization; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.io.ByteArrayDataInput; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; -import java.io.IOException; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; -import net.minecraft.server.v1_16_R3.NBTBase; -import net.minecraft.server.v1_16_R3.NBTCompressedStreamTools; -import net.minecraft.server.v1_16_R3.NBTReadLimiter; -import net.minecraft.server.v1_16_R3.NBTTagCompound; -import net.minecraft.server.v1_16_R3.NBTTagDouble; -import net.minecraft.server.v1_16_R3.NBTTagFloat; -import net.minecraft.server.v1_16_R3.NBTTagList; -import net.minecraft.server.v1_16_R3.NBTTagLong; -import net.minecraft.server.v1_16_R3.NBTTagShort; -import net.minecraft.server.v1_16_R3.NBTTagString; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; -import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.util.NullUtils; -import vg.civcraft.mc.civmodcore.util.Validation; - -/** - * Wrapper class for NBTTagCompounds to make NBT serialization and deserialization as robust as possible. Intended to - * replace {@link vg.civcraft.mc.civmodcore.itemHandling.TagManager TagManager} though the .mapToNBT and .listToNBT - * APIs will not be re-implemented here as it's better to have a finer control of how data is written and read. - */ -public class NBTCompound implements Cloneable, Validation { - - public static final String NULL_STRING = "\u0000"; - - private static final String INTERNAL_MAP_KEY = "map"; - - private static final String UUID_MOST_SUFFIX = "Most"; - - private static final String UUID_LEAST_SUFFIX = "Least"; - - private static final String UUID_KEY = "uuid"; - - private NBTTagCompound tag; - - /** - * Creates a new NBTCompound, generating a new NBTTagCompound. - */ - public NBTCompound() { - this(new NBTTagCompound()); - } - - /** - * Creates a new NBTCompound by wrapping an existing NBTTagCompound. - * - * @param tag The NBTTagCompound to wrap. - */ - public NBTCompound(NBTTagCompound tag) { - this.tag = tag == null ? new NBTTagCompound() : tag; - } - - /** - * Creates a new NBTCompound by wrapping and serialising an NBTSerializable object. - * - * @param The type of the given NBTSerializable. - * @param object The NBTSerializable to wrap and serialize. - */ - public NBTCompound(T object) { - this(); - Preconditions.checkArgument(object != null); - object.serialize(this); - } - - @Override - public boolean isValid() { - if (this.tag == null) { - return false; - } - return true; - } - - /** - * Returns the size of the tag compound. - * - * @return The size of the tag compound. - */ - public int size() { - return this.tag.e(); - } - - /** - * Checks if the tag compound is empty. - * - * @return Returns true if the tag compound is empty. - */ - public boolean isEmpty() { - return this.tag.isEmpty(); - } - - /** - * Checks if the tag compound contains a particular key. - * - * @param key The key to check. - * @return Returns true if the contains the given key. - */ - public boolean hasKey(String key) { - return this.tag.hasKey(key); - } - - /** - * Checks if the tag compound contains a particular key of a particular type. - * - * @param key The key to check. - * @param type The type to check for. - * @return Returns true if the contains the given key of the given type. - */ - public boolean hasKeyOfType(String key, int type) { - return this.tag.hasKeyOfType(key, type); - } - - /** - * Gets the keys within this compound. - * - * @return Returns the set of keys. - */ - public Set getKeys() { - return this.tag.getKeys(); - } - - /** - *

    Removes a key and its respective value from the tag compound, if it exists.

    - * - *

    Note: If you're removing a UUID, use {@link NBTCompound#removeUUID(String)} instead.

    - * - * @param key The key to remove. - */ - public void remove(String key) { - this.tag.remove(key); - } - - /** - * Clears all values from the tag compound. - */ - @SuppressWarnings("unchecked") - public void clear() { - try { - ((Map) FieldUtils.readField(this.tag, INTERNAL_MAP_KEY, true)).clear(); - } - catch (IllegalAccessException ignored) { - this.tag.getKeys().forEach(this.tag::remove); - } - } - - /** - * Returns the underlying NBTTagCompound. - * - * @return The wrapped NBTTagCompound. - */ - public NBTTagCompound getRAW() { - return this.tag; - } - - /** - * Adopts a copy of the NBT data from another compound. - * - * @param nbt The NBT data to copy and adopt. - */ - public void adopt(NBTCompound nbt) { - Preconditions.checkArgument(Validation.checkValidity(nbt)); - this.tag = nbt.tag.clone(); - } - - /** - * Gets a primitive boolean value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: FALSE - */ - public boolean getBoolean(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getBoolean(key); - } - - /** - * Sets a primitive boolean value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setBoolean(String key, boolean value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.setBoolean(key, value); - } - - /** - * Gets a primitive byte value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: 0x0 - */ - public byte getByte(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getByte(key); - } - - /** - * Sets a primitive byte value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setByte(String key, byte value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.setByte(key, value); - } - - /** - * Gets a primitive short value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: 0 - */ - public short getShort(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getShort(key); - } - - /** - * Sets a primitive short value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setShort(String key, short value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.setShort(key, value); - } - - /** - * Gets a primitive integer value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: 0 - */ - public int getInteger(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getInt(key); - } - - /** - * Sets a primitive integer value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setInteger(String key, int value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.setInt(key, value); - } - - /** - * Gets a primitive long value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: 0L - */ - public long getLong(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getLong(key); - } - - /** - * Sets a primitive long value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setLong(String key, long value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.setLong(key, value); - } - - /** - * Gets a primitive float value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: 0.0f - */ - public float getFloat(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getFloat(key); - } - - /** - * Sets a primitive float value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setFloat(String key, float value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.setFloat(key, value); - } - - /** - * Gets a primitive double value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: 0.0d - */ - public double getDouble(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getDouble(key); - } - - /** - * Sets a primitive double value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setDouble(String key, double value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.setDouble(key, value); - } - - /** - * Gets a UUID value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: NULL - */ - public UUID getUUID(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (!this.tag.b(key)) { - return null; - } - return this.tag.a(key); - } - - /** - * Sets a UUID value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setUUID(String key, UUID value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (value == null) { - removeUUID(key); - } - else { - this.tag.a(key, value); - } - } - - /** - * Removes a UUID value, which is necessary because Bukkit stores UUIDs by splitting up the two significant parts - * into their own values. - * - * @param key The key of the UUID to remove. - */ - public void removeUUID(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - this.tag.remove(key); - this.tag.remove(key + UUID_MOST_SUFFIX); - this.tag.remove(key + UUID_LEAST_SUFFIX); - } - - /** - * Gets a UUID value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: NULL - */ - public String getString(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (!this.tag.hasKeyOfType(key, 8)) { - return null; - } - String value = this.tag.getString(key); - if (NULL_STRING.equals(value)) { - return null; - } - return value; - } - - /** - * Sets a String value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setString(String key, String value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (value == null) { - this.tag.remove(key); - } - else { - this.tag.setString(key, value); - } - } - - /** - * Gets a tag compound value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: NULL - */ - public NBTCompound getCompound(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (!this.tag.hasKeyOfType(key, 10)) { - return null; - } - return new NBTCompound(this.tag.getCompound(key)); - } - - /** - * Sets a tag compound value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setCompound(String key, NBTCompound value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (value == null) { - this.tag.remove(key); - } - else { - this.tag.set(key, value.tag); - } - } - - // ------------------------------------------------------------ - // Array Functions - // ------------------------------------------------------------ - - /** - * Gets an array of primitive booleans from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public boolean[] getBooleanArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - byte[] cache = this.tag.getByteArray(key); - boolean[] result = new boolean[cache.length]; - for (int i = 0; i < cache.length; i++) { - result[i] = cache[i] != 0; - } - return result; - } - - /** - * Sets an array of primitive booleans to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setBooleanArray(String key, boolean[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - byte[] cache = new byte[values.length]; - for (int i = 0; i < values.length; i++) { - cache[i] = (byte) (values[i] ? 0x1 : 0x0); - } - this.tag.setByteArray(key, cache); - } - } - - /** - * Gets an array of primitive bytes from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public byte[] getByteArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getByteArray(key); - } - - /** - * Sets an array of primitive bytes to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setByteArray(String key, byte[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - this.tag.setByteArray(key, values); - } - } - - /** - * Gets an array of primitive shorts from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public short[] getShortArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - NBTTagList list = this.tag.getList(key, 2); - short[] result = new short[list.size()]; - for (int i = 0; i < result.length; i++) { - NBTBase base = list.get(i); - if (base.getTypeId() != 2) { - result[i] = 0; - } - else if (!(base instanceof NBTTagShort)) { - result[i] = 0; - } - else { - result[i] = ((NBTTagShort) base).asShort(); - } - } - return result; - } - - /** - * Sets an array of primitive bytes to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setShortArray(String key, short[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - NBTTagList list = new NBTTagList(); - for (short value : values) { - list.add(NBTTagShort.a(value)); - } - this.tag.set(key, list); - } - } - - /** - * Gets an array of primitive integers from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public int[] getIntegerArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - return this.tag.getIntArray(key); - } - - /** - * Sets an array of primitive integers to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setIntegerArray(String key, int[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - this.tag.setIntArray(key, values); - } - } - - /** - * Gets an array of primitive longs from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public long[] getLongArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (this.tag.hasKeyOfType(key, 12)) { - return this.tag.getLongArray(key); - } - NBTTagList list = this.tag.getList(key, 4); - long[] result = new long[list.size()]; - for (int i = 0; i < result.length; i++) { - NBTBase base = list.get(i); - if (base.getTypeId() != 4) { - result[i] = 0; - } - else if (!(base instanceof NBTTagLong)) { - result[i] = 0; - } - else { - result[i] = ((NBTTagLong) base).asLong(); - } - } - return result; - } - - /** - * Sets an array of primitive longs to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setLongArray(String key, long[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - this.tag.a(key, values); - } - } - - /** - * Gets an array of primitive floats from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public float[] getFloatArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - NBTTagList list = this.tag.getList(key, 5); - float[] result = new float[list.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = list.i(i); - } - return result; - } - - /** - * Sets an array of primitive floats to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setFloatArray(String key, float[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - NBTTagList list = new NBTTagList(); - for (float value : values) { - list.add(NBTTagFloat.a(value)); - } - this.tag.set(key, list); - } - } - - /** - * Gets an array of primitive doubles from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public double[] getDoubleArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - NBTTagList list = this.tag.getList(key, 6); - double[] result = new double[list.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = list.h(i); - } - return result; - } - - /** - * Sets an array of primitive doubles to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setDoubleArray(String key, double[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - NBTTagList list = new NBTTagList(); - for (double value : values) { - list.add(NBTTagDouble.a(value)); - } - this.tag.set(key, list); - } - } - - /** - * Gets an array of UUIDs from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public UUID[] getUUIDArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - NBTCompound[] found = getCompoundArray(key); - UUID[] result = new UUID[found.length]; - for (int i = 0; i < found.length; i++) { - result[i] = found[i].getUUID(UUID_KEY); - } - return result; - } - - /** - * Sets an array of UUIDs to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setUUIDArray(String key, UUID[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - NBTTagList list = new NBTTagList(); - for (UUID value : values) { - NBTCompound nbt = new NBTCompound(); - if (value != null) { - nbt.setUUID(UUID_KEY, value); - } - list.add(nbt.tag); - } - this.tag.set(key, list); - } - } - - /** - * Gets an array of Strings from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public String[] getStringArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - NBTTagList list = this.tag.getList(key, 8); - String[] result = new String[list.size()]; - for (int i = 0; i < result.length; i++) { - NBTBase base = list.get(i); - if (base.getTypeId() != 8) { - result[i] = ""; - } - else if (!(base instanceof NBTTagString)) { - result[i] = ""; - } - else { - result[i] = base.asString(); - if (result[i].equals(NULL_STRING)) { - result[i] = null; - } - } - } - return result; - } - - /** - * Sets an array of Strings to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setStringArray(String key, String[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - NBTTagList list = new NBTTagList(); - for (String value : values) { - if (value == null) { - list.add(NBTTagString.a(NULL_STRING)); - } - else { - list.add(NBTTagString.a(value)); - } - } - this.tag.set(key, list); - } - } - - /** - * Gets an array of tag compounds from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - public NBTCompound[] getCompoundArray(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - NBTTagList list = this.tag.getList(key, 10); - NBTCompound[] result = new NBTCompound[list.size()]; - for (int i = 0; i < result.length; i++) { - NBTTagCompound base = list.getCompound(i); - if (base.getTypeId() != 10) { - result[i] = new NBTCompound(); - } - else { - result[i] = new NBTCompound(base); - } - } - return result; - } - - /** - * Sets an array of tag compounds to a key. - * - * @param key The key to set to values to. - * @param values The values to set to the key. - */ - public void setCompoundArray(String key, NBTCompound[] values) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (values == null) { - this.tag.remove(key); - } - else { - NBTTagList list = new NBTTagList(); - for (NBTCompound value : values) { - list.add(value.tag); - } - this.tag.set(key, list); - } - } - - /** - * Gets a list value from a key. - * - * @param key The key to get the value of. - * @return The value of the key, default: empty list - */ - public NBTCompoundList getSerializableList(String key) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (!this.tag.hasKeyOfType(key, 9)) { - return new NBTCompoundList<>(); - } - return NBTCompoundList.deserialize(this.tag.getList(key, 10)); - } - - /** - * Sets a list value to a key. - * - * @param key The key to set to value to. - * @param value The value to set to the key. - */ - public void setSerializableList(String key, NBTCompoundList value) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(key)); - if (value == null) { - this.tag.remove(key); - } - else { - this.tag.set(key, value.serialize()); - } - } - - // ------------------------------------------------------------ - // NBT Base Functions - // ------------------------------------------------------------ - - @Override - public NBTCompound clone() { - NBTCompound clone; - try { - clone = (NBTCompound) super.clone(); - } - catch (CloneNotSupportedException ignored) { - clone = new NBTCompound(); - } - clone.tag = this.tag.clone(); - return clone; - } - - @Override - public int hashCode() { - return this.tag.hashCode(); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (!(other instanceof NBTCompound)) { - return false; - } - return NullUtils.equalsNotNull(this.tag, ((NBTCompound) other).tag); - } - - @Override - public String toString() { - return "NBTCompound" + this.tag.toString(); - } - - // ------------------------------------------------------------ - // NBT Utilities (Maybe should go into their own class?) - // ------------------------------------------------------------ - - /** - * Retrieves the NBT data from an item. - * - * @param item The item to retrieve the NBT form. - * @return Returns the item's NBT. - */ - public static NBTCompound fromItem(ItemStack item) { - if (item == null) { - return null; - } - net.minecraft.server.v1_16_R3.ItemStack craftItem = CraftItemStack.asNMSCopy(item); - if (craftItem == null) { - return null; - } - return new NBTCompound(craftItem.getTag()); - } - - /** - * Sets an NBT compound to an item. You must use the returned item, instead of the item you pass in. - * - * @param item The item to set the NBT to. - * @param nbt The NBT to set to the item. - * @return The item with the saved NBT. - * - * @deprecated Use {@link NBTCompound#processItem(ItemStack, Consumer)} instead. - */ - @Deprecated - public static ItemStack toItem(ItemStack item, NBTCompound nbt) { - return processItem(item, (current) -> current.adopt(nbt)); - } - - /** - * Processes an item's NBT before setting again. - * - * @param item The item to process. - * @param processor The processor. - * @return Returns the given item with the processed NBT, or null if it could not be successfully processed. - */ - public static ItemStack processItem(ItemStack item, Consumer processor) { - Preconditions.checkArgument(ItemUtils.isValidItem(item)); - Preconditions.checkArgument(processor != null); - net.minecraft.server.v1_16_R3.ItemStack craftItem = CraftItemStack.asNMSCopy(item); - if (craftItem == null) { - return null; - } - NBTCompound nbt = new NBTCompound(craftItem.getTag()); - try { - processor.accept(nbt); - } - catch (Exception exception) { - return null; - } - craftItem.setTag(nbt.tag); - return CraftItemStack.asBukkitCopy(craftItem); - } - - /** - * Attempts to deserialize NBT data into an NBTCompound. - * - * @param bytes The NBT data as a byte array. - * @return Returns an NBTCompound if the deserialization was successful, or otherwise null. - */ - public static NBTCompound fromBytes(byte[] bytes) { - if (ArrayUtils.isEmpty(bytes)) { - return null; - } - ByteArrayDataInput input = ByteStreams.newDataInput(bytes); - NBTCompound nbt = new NBTCompound(); - try { - nbt.tag = NBTCompressedStreamTools.a(input, NBTReadLimiter.a); - } - catch (IOException exception) { - return null; - } - return nbt; - } - - /** - * Attempts to serialize an NBTCompound into a data array. - * - * @param nbt The NBTCompound to serialize. - * @return Returns a data array representing the given NBTCompound serialized, or otherwise null. - */ - public static byte[] toBytes(NBTCompound nbt) { - if (nbt == null) { - return null; - } - ByteArrayDataOutput output = ByteStreams.newDataOutput(); - try { - NBTCompressedStreamTools.a(nbt.tag, output); - } - catch (IOException exception) { - return null; - } - return output.toByteArray(); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCompoundList.java b/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCompoundList.java deleted file mode 100644 index 83fcdee8..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTCompoundList.java +++ /dev/null @@ -1,79 +0,0 @@ -package vg.civcraft.mc.civmodcore.serialization; - -import static vg.civcraft.mc.civmodcore.util.NullCoalescing.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import net.minecraft.server.v1_16_R3.NBTBase; -import net.minecraft.server.v1_16_R3.NBTTagCompound; -import net.minecraft.server.v1_16_R3.NBTTagList; -import org.apache.commons.lang3.reflect.FieldUtils; -import vg.civcraft.mc.civmodcore.util.Validation; - -/** - *

    Represents a list of nbt class serializable elements of the same type.

    - * - *

    Read More:

    - *
      - *
    • {@link NBTSerialization}
    • - *
    • {@link NBTSerialization#serialize(NBTSerializable)}
    • - *
    • {@link NBTSerialization#deserialize(NBTCompound)}
    • - *
    - */ -@Deprecated -public class NBTCompoundList extends ArrayList { - - /** - * Serializes each element into an {@link NBTTagList}. - * - * @return Returns a populated {@link NBTTagList}. - */ - public NBTTagList serialize() { - NBTTagList list = new NBTTagList(); - List inner = getInnerList(list); - stream().filter(Objects::nonNull) - .map(NBTSerialization::serialize) - .filter(Validation::checkValidity) - .map(NBTCompound::getRAW) - .forEachOrdered(inner::add); - return list; - } - - /** - * Converts a {@link NBTTagList} into an NBTCompoundList, deserializing each element to - * the given generic type. - * - * @param The generic type each element should be cast to. - * @param list The {@link NBTTagList} to convert into NBTCompoundList. - * @return Returns a new NBTCompoundList. - */ - @SuppressWarnings("unchecked") - public static NBTCompoundList deserialize(NBTTagList list) { - NBTCompoundList wrapper = new NBTCompoundList<>(); - if (list == null) { - return wrapper; - } - if (list.d_() != 10) { - return wrapper; - } - getInnerList(list).stream() - .map(nbt -> chain(() -> new NBTCompound((NBTTagCompound) nbt))) - .map(nbt -> chain(() -> (T) NBTSerialization.deserialize(nbt))) - .filter(Objects::nonNull) - .forEachOrdered(wrapper::add); - return wrapper; - } - - @SuppressWarnings("unchecked") - private static List getInnerList(NBTTagList list) { - try { - return (List) FieldUtils.readField(list, "list", true); - } - catch (Exception exception) { - throw new NBTSerializationException( - "Could not encode NBTCompound list to NBTTagList: could not access inner list of NBTTagList.", - exception); - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerializable.java b/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerializable.java deleted file mode 100644 index 6a080dc3..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerializable.java +++ /dev/null @@ -1,31 +0,0 @@ -package vg.civcraft.mc.civmodcore.serialization; - -/** - * Interface that grants a class access to {@link NBTSerialization NBTSerialization's} process of converting a class to - * and from an NBTCompound. - */ -public interface NBTSerializable { - - /** - * Serializes a class into an NBTCompound. - * - * @param nbt The NBTCompound to serialize into, which should NEVER be null, so feel free to throw an - * {@link NBTSerializationException} if it is. You can generally assume that the nbt compound is new and - * therefore empty, but you may wish to check or manually {@link NBTCompound#clear() empty it}, though the - * latter may cause other issues. - * - * @throws NBTSerializationException This is thrown if the implementation has a fatal error serializing. - */ - void serialize(NBTCompound nbt); - - /** - * Deserializes a class into an NBTCompound. - * - * @param nbt The NBTCompound to deserialize from, which should NEVER be null, so feel free to throw an - * {@link NBTSerializationException} if it is. - * - * @throws NBTSerializationException This is thrown if the implementation has a fatal error deserializing. - */ - void deserialize(NBTCompound nbt); - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerialization.java b/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerialization.java deleted file mode 100644 index 98aa67fd..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/serialization/NBTSerialization.java +++ /dev/null @@ -1,173 +0,0 @@ -package vg.civcraft.mc.civmodcore.serialization; - -import com.google.common.base.Strings; -import java.lang.reflect.Constructor; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import vg.civcraft.mc.civmodcore.util.Validation; - -public final class NBTSerialization { - - private static final String NBT_CLASS_PATH_KEY = "=="; - - private static final Logger LOGGER = LoggerFactory.getLogger(NBTSerialization.class.getSimpleName()); - - private static final Map> REGISTERED_CLASSES = new HashMap<>(); - - /** - * Registers an {@link NBTSerializable} class and any aliases it may have. - * - * @param The type of the serializable. - * @param clazz The class of the NBTSerializable class. - * @param aliases The alias class name of the class used for when the class has migrated and legacy support is - * required. - * @throws IllegalArgumentException Throws if 1) the NBTSerializable class is null, 2) the class does not have a - * public zero argument constructor, 3) are not final, or 4) is already registered. (Aliases that are already - * registered will print a warning in lieu of throwing.) - */ - public static void registerNBTSerializable(Class clazz, String... aliases) { - String cannotRegisterError = "Cannot register NBTSerializable: "; - if (clazz == null) { - throw new IllegalArgumentException(cannotRegisterError + "the given class is null."); - } - if (clazz.isAnonymousClass()) { - throw new IllegalArgumentException(cannotRegisterError + "the given class is anonymous."); - } - boolean hasValidConstructor = false; - for (Constructor constructor : clazz.getDeclaredConstructors()) { - if (!Modifier.isPublic(constructor.getModifiers())) { - continue; - } - if (constructor.getParameterCount() > 0) { - continue; - } - hasValidConstructor = true; - break; - } - if (!hasValidConstructor) { - throw new IllegalArgumentException(cannotRegisterError + "the given class has no default constructor."); - } - if (REGISTERED_CLASSES.containsKey(clazz.getName())) { - throw new IllegalArgumentException(cannotRegisterError + "the given class is already registered."); - } - LOGGER.info("NBTSerializable[" + clazz.getName() + "] registered."); - REGISTERED_CLASSES.put(clazz.getName(), clazz); - if (aliases != null) { - String errorMessage = "Could not register the alias for NBTSerializable[" + clazz.getName() + "] as: "; - for (String alias : aliases) { - if (Strings.isNullOrEmpty(alias)) { - LOGGER.warn(errorMessage + "the alias is null or empty."); - continue; - } - if (alias.equals(clazz.getName())) { - LOGGER.warn(errorMessage + "the alias matches the class name."); - continue; - } - if (REGISTERED_CLASSES.containsKey(alias)) { - LOGGER.warn(errorMessage + "the alias is already registered."); - continue; - } - LOGGER.info("NBTSerializable[" + clazz.getName() + "] alias [" + alias + "] registered."); - REGISTERED_CLASSES.putIfAbsent(alias, clazz); - } - } - } - - /** - * Unregisters an {@link NBTSerializable} class and all its paths. - * - * @param The type of the serializable. - * @param clazz The NBTSerializable class to unregister. - * @throws IllegalArgumentException Throws if the given class to unregister is null. - */ - public static void unregisterNBTSerializable(Class clazz) { - if (clazz == null) { - throw new IllegalArgumentException("Cannot unregister NBTSerializable: " + - "the given class is null."); - } - LOGGER.info("NBTSerializable[" + clazz.getName() + "] unregistered."); - REGISTERED_CLASSES.values().remove(clazz); - } - - /** - * Returns the matching registered serializable class. - * - * @param clazz The class path. - * @return Returns the serializable class, or null. - */ - public static Class getSerializableClass(final String clazz) { - if (Strings.isNullOrEmpty(clazz)) { - return null; - } - return REGISTERED_CLASSES.get(clazz); - } - - /** - * Unregisters all {@link NBTSerializable} classes. - */ - public static void clearAllRegistrations() { - LOGGER.info("All NBTSerialization registered classes have been cleared."); - REGISTERED_CLASSES.clear(); - } - - /** - * Serializes an {@link NBTSerializable} class into an {@link NBTCompound} for the purpose of being able to - * deserialize it later. For this purpose, {@link NBTSerializable#serialize(NBTCompound) NBTSerializable's - * serialize} method should not be called directly. - * - * @param serializable The {@link NBTSerializable} class to serialize. - * @return Returns an {@link NBTCompound} with the serialized data, or null if the given object was null, or if an - * error occurred during serialization. - */ - public static NBTCompound serialize(NBTSerializable serializable) { - if (serializable == null) { - return null; - } - NBTCompound nbt = new NBTCompound(); - try { - serializable.serialize(nbt); - } - catch (Exception exception) { - throw new NBTSerializationException( - "NBTSerializable[" + serializable.getClass().getName() + "] could not be serialized.", exception); - } - nbt.setString(NBT_CLASS_PATH_KEY, serializable.getClass().getName()); - return nbt; - } - - /** - * Deserializes an {@link NBTCompound} into a registered {@link NBTSerializable} class, if it exists. - * - * @param nbt The NBT data that's used to populate the new instance of the class. - * @return Returns a populated instance of a class, or null. It will return null if 1) the given NBTCompound is null - * or empty, 2) the class identifier cannot be found, 3) the class cannot be matched. - */ - public static NBTSerializable deserialize(NBTCompound nbt) { - if (!Validation.checkValidity(nbt)) { - return null; - } - String path = nbt.getString(NBT_CLASS_PATH_KEY); - if (Strings.isNullOrEmpty(path)) { - return null; - } - Class clazz = REGISTERED_CLASSES.get(path); - if (clazz == null) { - return null; - } - try { - NBTSerializable instance = clazz.getConstructor().newInstance(); - NBTCompound clone = nbt.clone(); - clone.remove(NBT_CLASS_PATH_KEY); - instance.deserialize(clone); - return instance; - } - catch (Exception exception) { - throw new NBTSerializationException( - "NBTSerializable[" + clazz.getName() + "] could not be deserialized.", exception); - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/Chainer.java b/src/main/java/vg/civcraft/mc/civmodcore/util/Chainer.java deleted file mode 100644 index b7dfeb0f..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/Chainer.java +++ /dev/null @@ -1,86 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import java.util.function.Function; -import java.util.function.Supplier; -import org.apache.commons.lang3.Validate; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nullable; - -/** - *

    Utility class that allows for something roughly within the range of Javascript's optional chaining.

    - * - *

    Read more about that - * here.

    - * - * @author Protonull - */ -public final class Chainer { - - private final T value; - - private Chainer(final T value) { - this.value = value; - } - - /** - * @return Returns the current value of the chainer. - */ - @Nullable - public T get() { - return this.value; - } - - /** - * @param fallback The given fallback should the current value of the chainer be null. - * @return Returns the current value of the chainer, or the given fallback if null. - */ - @Contract("null -> _; !null -> !null") - public T getOrDefault(final T fallback) { - if (this.value == null) { - return fallback; - } - return this.value; - } - - /** - * @param generator Generator that creates a new value should the current value of the chainer be null. - * @return Returns the current value of the chainer, or the result of the generator if null. - */ - @Contract("null -> fail") - public T getOrGenerate(final Supplier generator) { - Validate.notNull(generator, "Chainer parser cannot be null!"); - if (this.value == null) { - return generator.get(); - } - return this.value; - } - - /** - * Continues the chain. - * - * @param The return type of the given parser. - * @param parser The chainer parser, which can safely assume that the passed in object is not-null. - * @return Returns a new chainer instance wrapping the result of the parser. - */ - @Contract("null -> fail; !null -> new") - public Chainer then(final Function parser) { - Validate.notNull(parser, "Chainer parser cannot be null!"); - if (this.value == null) { - return new Chainer<>(null); - } - return new Chainer<>(parser.apply(this.value)); - } - - /** - * Creates a new chainer from a given object. - * - * @param The type of the given object. - * @param value The object to start chaining from. - * @return Returns a new chainer instance. - */ - @Contract("_ -> new") - public static Chainer from(final T value) { - return new Chainer<>(value); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/DependencyGlue.java b/src/main/java/vg/civcraft/mc/civmodcore/util/DependencyGlue.java deleted file mode 100644 index 923c26e8..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/DependencyGlue.java +++ /dev/null @@ -1,142 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import java.util.logging.Logger; -import org.apache.commons.lang3.StringUtils; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.PluginEnableEvent; -import org.bukkit.event.server.ServerLoadEvent; -import org.bukkit.plugin.Plugin; - -/** - * Class designed to make creating glue classes easier, particularly with soft dependencies where your plugins may use - * code from those plugins if they're present. - * - * You'll need to register the glues you create with your plugins .registerListener() method. - */ -public abstract class DependencyGlue implements Listener { - - private final String pluginName; - - private Plugin plugin; - - private Logger logger; - - /** - *

    You must provide the name of the plugin you wish to glue.

    - * - *

    Note: The name check is case-insensitive, but it's recommended for the name to be an exact match, if only for - * readability and ease of find and replace.

    - * - * @param pluginName The name of the plugin you wish to glue. - */ - protected DependencyGlue(String pluginName) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(pluginName)); - this.pluginName = pluginName; - onServerLoad(null); - } - - /** - * Gets the plugin name that this glue will check against. - * - * @return Returns the glue's plugin name. - */ - public String getPluginName() { - return this.pluginName; - } - - /** - * @return Returns the current plugin instance for this glue. - */ - public Plugin plugin() { - return this.plugin; - } - - /** - * @return Returns the current logger instance for this glue. - */ - public Logger logger() { - return this.logger; - } - - /** - * @return Returns true if this glue's plugin is currently enabled, which is updated live. - */ - public boolean isEnabled() { - if (this.plugin == null) { - return false; - } - if (!this.plugin.isEnabled()) { - return false; - } - return true; - } - - /** - *

    Determines whether this glue is safe to use.

    - * - *

    This should be kept for internal use because if the glue plugin is enabled but not safe to use, the - * APIs should themselves account for that.

    - * - * @return Returns true if the glue is deemed safe to use. - */ - public boolean isSafeToUse() { - if (!isEnabled()) { - return false; - } - return true; - } - - /** - * This is called when the glue's plugin is enabled. Use this as a setup. - */ - protected void onGlueEnabled() { - this.logger.info("Gluing " + this.pluginName); - } - - /** - * This is called when the glue's plugin is disabled. Use this as a destructor. - */ - protected void onGlueDisabled() { - this.logger.info("Releasing " + this.pluginName); - } - - @EventHandler - public final void onServerLoad(ServerLoadEvent event) { - for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { - if (plugin == null || !plugin.isEnabled()) { - continue; - } - if (!StringUtils.equalsIgnoreCase(plugin.getName(), this.pluginName)) { - continue; - } - this.plugin = plugin; - this.logger = plugin.getLogger(); - onGlueEnabled(); - break; - } - } - - @EventHandler - public final void onPluginEnable(PluginEnableEvent event) { - if (StringUtils.equalsIgnoreCase(event.getPlugin().getName(), this.pluginName)) { - this.plugin = event.getPlugin(); - this.logger = this.plugin.getLogger(); - onGlueEnabled(); - } - } - - @EventHandler - public final void onPluginDisable(PluginDisableEvent event) { - if (StringUtils.equalsIgnoreCase(event.getPlugin().getName(), this.pluginName)) { - onGlueDisabled(); - this.plugin = null; - this.logger = null; - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/EnumUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/util/EnumUtils.java deleted file mode 100644 index 14ba1f6e..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/EnumUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import com.google.common.base.Strings; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.reflect.MethodUtils; - -/** - * Class of enum utilities. - * - * @deprecated Use {@link org.apache.commons.lang3.EnumUtils} instead. - */ -@Deprecated -public class EnumUtils { - - /** - * Retrieves a enum element from an enum class if the given slug matches. - * - * @param The enum type. - * @param clazz The enum class. - * @param slug The slug of the intended enum element. - * @param caseInsensitive Set to true if you want to check to not care about case sensitivity. - * @return Returns the matched enum element, or null. - * - * @deprecated Use {@link org.apache.commons.lang3.EnumUtils#getEnum(Class, String)} or - * {@link org.apache.commons.lang3.EnumUtils#getEnumIgnoreCase(Class, String)} instead. - */ - @Deprecated - @SuppressWarnings("unchecked") - public static > T fromSlug(Class clazz, String slug, boolean caseInsensitive) { - if (clazz == null || Strings.isNullOrEmpty(slug)) { - return null; - } - T[] values = null; - try { - values = (T[]) MethodUtils.invokeExactStaticMethod(clazz, "values", null); - } - catch (Exception ignored) { } - if (ArrayUtils.isEmpty(values)) { - return null; - } - for (T value : values) { - if (caseInsensitive) { - if (TextUtil.stringEqualsIgnoreCase(value.name(), slug)) { - return value; - } - } - else { - if (TextUtil.stringEquals(value.name(), slug)) { - return value; - } - } - } - return null; - } - - /** - * Null safe way of getting an enum elements' name. - * - * @param element The enum element to get the name of. - * @return Return an element's name or null. - * - * @deprecated Use {@link Chainer} instead. - */ - @Deprecated - public static String getSlug(Enum element) { - if (element == null) { - return null; - } - return element.name(); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/Guard.java b/src/main/java/vg/civcraft/mc/civmodcore/util/Guard.java deleted file mode 100644 index da9b0cd0..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/Guard.java +++ /dev/null @@ -1,43 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import org.apache.commons.lang.NullArgumentException; - -/** - * Class for checking arguments and throwing null argument exceptions. - * - * @deprecated Just use {@link com.google.common.base.Preconditions Preconditions}. - */ -@Deprecated -public final class Guard { - - @Deprecated - public static void ArgumentNotNull(Object argument, String parameterName) { - if (parameterName == null) { - throw new NullArgumentException("parameterName"); - } - if (argument == null) { - throw new NullArgumentException(parameterName); - } - } - - @Deprecated - public static void ArgumentNotNullOrEmpty(String argument, String parameterName) { - if (parameterName == null) { - throw new NullArgumentException("parameterName"); - } - if (argument == null) { - throw new NullArgumentException(parameterName); - } - if (argument.equals("")) { - throw new RuntimeException(parameterName + " can't be empty."); - } - } - - @Deprecated - public static void ArgumentNotEquals(Object argument, String parameterName, Object other, String otherName) { - if (argument.equals(other)) { - throw new RuntimeException(parameterName + " can't be equal to " + otherName); - } - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/Iteration.java b/src/main/java/vg/civcraft/mc/civmodcore/util/Iteration.java deleted file mode 100644 index fa1c5ee2..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/Iteration.java +++ /dev/null @@ -1,350 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.Supplier; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.ArrayUtils; - -/** - * @deprecated Use {@link MoreArrayUtils}, {@link MoreCollectionUtils}, and {@link MoreMapUtils} instead. - */ -@Deprecated -public final class Iteration { - - @FunctionalInterface - public interface ElementAndBoolConsumer { - void accept(T former, boolean latter); - } - - /** - *

    DO NOT USE THIS!

    - * - *

    This method was originally created for developer ease of use, but instead has become a hot bed for - * hidden bugs. The reason for that being the varargs. If you pass in an object that you expect to be - * handled by {@link #isNullOrEmpty(Collection)} but don't realise the object doesn't actually inherit - * from {@link Collection}, like a {@link Map}, then it falls back to this version of the method, giving - * the illusion that it's doing what you intended. But instead of checking whether the object you gave - * is null or empty, it's instead only checking if the method itself has any parameters.

    - * - * @deprecated Use {@link ArrayUtils#isEmpty(Object[])} instead. - */ - @Deprecated - @SafeVarargs - public static boolean isNullOrEmpty(T... array) { - return ArrayUtils.isEmpty(array); - } - - /** - *

    Determines whether a collection is null or empty.

    - * - *

    Note: This will not check the elements within the collection. It only checks if the collection itself exists - * and has elements. If for example the collection has 100 null elements, this function would still return true.

    - * - * @param The type of collection. - * @param collection The collection to check. - * @return Returns true if the collection exists and at least one item. - * - * @deprecated Use {@link CollectionUtils#isEmpty(Collection)} instead. - */ - @Deprecated - public static boolean isNullOrEmpty(Collection collection) { - return collection == null || collection.isEmpty(); - } - - /** - * Returns the first matching item in the parameters, which is particularly useful when you need to match Materials - * without necessarily needing to create a new {@link vg.civcraft.mc.civmodcore.api.MaterialAPI MaterialAPI}. - * - * @param The type of the object to match. - * @param base The object to match. - * @param values An array of items to match against. - * @return Returns true if the base is found within the values. - * - * @deprecated Use {@link org.apache.commons.lang3.ArrayUtils#contains(Object[], Object)} instead. - */ - @Deprecated - @SafeVarargs - public static boolean contains(T base, T... values) { - if (ArrayUtils.isEmpty(values)) { - return false; - } - for (T value : values) { - if (Objects.equals(base, value)) { - return true; - } - } - return false; - } - - /** - * Iterates through a collection before clearing it completely. Useful for wiping out data on plugin disable. - * - * @param The generic type of the collection. - * @param collection The collection to iterate and clear. - * @param processor The iteration processor which will be called for each item in the collection. - * - * @deprecated Use {@link Collection#forEach(Consumer)} and {@link Collection#clear()} instead. - */ - @Deprecated - public static void iterateThenClear(Collection collection, Consumer processor) { - if (isNullOrEmpty(collection) || processor == null) { - return; - } - for (T element : collection) { - processor.accept(element); - } - collection.clear(); - } - - /** - * Iterates through a collection, whereby each element has knowledge of whether it's the last element. - * - * @param The generic type of the collection. - * @param collection The collection to iterate. - * @param processor The iteration processor which will be called for each item in the collection. - * - * @deprecated Use {@link Collection#iterator()} instead. - */ - @Deprecated - public static void iterateHasNext(Collection collection, ElementAndBoolConsumer processor) { - if (isNullOrEmpty(collection) || processor == null) { - return; - } - Iterator iterator = collection.iterator(); - while (iterator.hasNext()) { - processor.accept(iterator.next(), iterator.hasNext()); - } - } - - /** - * Fills an array with a particular value. - * - * @param The type of the array. - * @param array The array to fill. - * @param value The value to fill the array with. - * @return Returns the given array with the filled values. - * - * @deprecated Use {@link MoreArrayUtils#fill(Object[], Object)} instead. - */ - @Deprecated - public static T[] fill(T[] array, T value) { - if (ArrayUtils.isEmpty(array)) { - return array; - } - Arrays.fill(array, value); - return array; - } - - /** @deprecated Use {@link MoreArrayUtils#anyMatch(Object[], Predicate)} instead. */ - @Deprecated - public static boolean some(T[] array, Predicate predicate) { - return anyMatch(array, predicate); - } - - /** - *

    Tests whether there is at least one element in the given array that passes the criteria of the given - * predicate.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

    - * - * @param The type of the array elements. - * @param array The array to iterate. - * @param predicate The element tester. - * @return Returns true if at least one element passes the predicate test. Or false if the array fails the - * {@link ArrayUtils#isEmpty(Object[]) isNullOrEmpty()} test, or true if the give predicate is null. - * - * @deprecated Use {@link MoreArrayUtils#anyMatch(Object[], Predicate)} instead. - */ - @Deprecated - public static boolean anyMatch(T[] array, Predicate predicate) { - if (ArrayUtils.isEmpty(array)) { - return false; - } - if (predicate == null) { - return true; - } - for (T element : array) { - if (predicate.test(element)) { - return true; - } - } - return false; - } - - /** @deprecated Use {@link MoreArrayUtils#allMatch(Object[], Predicate)} instead. */ - @Deprecated - public static boolean every(T[] array, Predicate predicate) { - return allMatch(array, predicate); - } - - /** - *

    Tests whether every element in an array passes the criteria of the given predicate.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every

    - * - * @param The type of the array elements. - * @param array The array to iterate. - * @param predicate The element tester. - * @return Returns true if no element fails the predicate test, or if the array fails the - * {@link ArrayUtils#isEmpty(Object[]) isNullOrEmpty()} test, or if the give predicate is null. - * - * @deprecated Use {@link MoreArrayUtils#allMatch(Object[], Predicate)} instead. - */ - @Deprecated - public static boolean allMatch(T[] array, Predicate predicate) { - if (ArrayUtils.isEmpty(array)) { - return true; - } - if (predicate == null) { - return true; - } - for (T element : array) { - if (!predicate.test(element)) { - return false; - } - } - return true; - } - - /** - * Determines whether a Map Entry is valid in that it exists and so does the key and value. - * - * @param entry The map entry itself. - * @return Returns true if the entry is considered valid. - * - * @deprecated Use {@link MoreMapUtils#validEntry(Map.Entry)} instead. - */ - @Deprecated - public static boolean validEntry(Map.Entry entry) { - if (entry == null) { - return false; - } - if (entry.getKey() == null) { - return false; - } - if (entry.getValue() == null) { - return false; - } - return true; - } - - /** - * Creates a new collection with a given set of predefined elements, if any are given. - * - * @param The type of the elements to store in the collection. - * @param constructor The constructor for the collection. - * @param elements The elements to add to the collection. - * @return Returns a new collection, or null if no constructor was given, or the constructor didn't produce a new - * collection. - * - * @deprecated Use {@link CollectionUtils#addAll(Collection, Object[])} instead. - */ - @Deprecated - @SafeVarargs - public static > K collect(Supplier constructor, T... elements) { - if (constructor == null) { - return null; - } - K collection = constructor.get(); - if (collection == null) { - return null; - } - if (ArrayUtils.isEmpty(elements)) { - return collection; - } - addAll(collection, elements); - return collection; - } - - /** - * Use this to add arbitrary amounts of things to a collection that doesn't already exist as a collection, - * which means you can avoid the dreaded {@code Arrays.asList(thing, thing2);}, thereby creating a new list - * just to immediately discard it in the next operation execution. - * - * @param The type of the elements of the collection and to add. - * @param collection The collection to add the values to. - * @param values The values to add to the collection. - * - * @deprecated Use {@link CollectionUtils#addAll(Collection, Object[])} instead. - */ - @Deprecated - @SafeVarargs - public static void addAll(Collection collection, T... values) { - if (collection == null || ArrayUtils.isEmpty(values)) { - return; - } - for (T value : values) { - // Do not let this be simplified. There's no reason to create a new ArrayList - // as it would be immediately discarded and that's... bad - collection.add(value); - } - } - - /** - * Removes the element at the end of the given list. - * - * @param The type of the list's elements. - * @param list The list to remove the last element from. - * @return Returns the element removed. - * - * @deprecated Use {@link MoreCollectionUtils#removeLastElement(List)} instead. - */ - @Deprecated - public static T removeLastElement(List list) { - if (isNullOrEmpty(list)) { - return null; - } - return list.remove(list.size() - 1); - } - - /** - * Retrieves a random element from an array of elements. - * - * @param The type of element. - * @param array The array to retrieve a value from. - * @return Returns a random element, or null. - * - * @deprecated Use {@link MoreArrayUtils#randomElement(Object[])} instead. - */ - @Deprecated - @SafeVarargs - public static T randomElement(final T... array) { - if (ArrayUtils.isEmpty(array)) { - return null; - } - if (array.length == 1) { - return array[0]; - } - return array[ThreadLocalRandom.current().nextInt(array.length)]; - } - - /** - * Retrieves a random element from an list of elements. - * - * @param The type of element. - * @param list The list to retrieve a value from. - * @return Returns a random element, or null. - * - * @deprecated Use {@link MoreCollectionUtils#randomElement(List)} instead. - */ - @Deprecated - public static T randomElement(final List list) { - if (isNullOrEmpty(list)) { - return null; - } - final int size = list.size(); - if (size == 1) { - return list.get(0); - } - return list.get(ThreadLocalRandom.current().nextInt(size)); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/MoreArrayUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/util/MoreArrayUtils.java deleted file mode 100644 index f76a9f7d..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/MoreArrayUtils.java +++ /dev/null @@ -1,140 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import java.util.Arrays; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Function; -import java.util.function.Predicate; -import org.apache.commons.lang3.ArrayUtils; - -/** - * Utility class that fills in the gaps of {@link ArrayUtils}. - * - * @author Protonull - */ -public final class MoreArrayUtils { - - /** - * Fills an array with a particular value. - * - * @param The type of the array. - * @param array The array to fill. - * @param value The value to fill the array with. - * @return Returns the given array with the filled values. - */ - public static T[] fill(final T[] array, final T value) { - if (ArrayUtils.isEmpty(array)) { - return array; - } - Arrays.fill(array, value); - return array; - } - - /** - *

    Tests whether there is at least one element in the given array that passes the criteria of the given - * predicate.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

    - * - * @param The type of the array's elements. - * @param array The array to iterate. - * @param predicate The element tester. - * @return Returns true if at least one element passes the predicate test. Or false if the array fails the - * {@link ArrayUtils#isEmpty(Object[]) isNullOrEmpty()} test, or true if the give predicate is null. - */ - public static boolean anyMatch(final T[] array, final Predicate predicate) { - if (ArrayUtils.isEmpty(array)) { - return false; - } - if (predicate == null) { - return true; - } - for (final T element : array) { - if (predicate.test(element)) { - return true; - } - } - return false; - } - - /** - *

    Tests whether every element in an array passes the criteria of the given predicate.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every

    - * - * @param The type of the array's elements. - * @param array The array to iterate. - * @param predicate The element tester. - * @return Returns true if no element fails the predicate test, or if the array fails the - * {@link ArrayUtils#isEmpty(Object[]) isNullOrEmpty()} test, or if the give predicate is null. - */ - public static boolean allMatch(final T[] array, final Predicate predicate) { - if (ArrayUtils.isEmpty(array)) { - return true; - } - if (predicate == null) { - return true; - } - for (final T element : array) { - if (!predicate.test(element)) { - return false; - } - } - return true; - } - - /** - * Retrieves a random element from an array of elements. - * - * @param The type of element. - * @param array The array to retrieve a value from. - * @return Returns a random element, or null. - */ - @SafeVarargs - public static T randomElement(final T... array) { - if (ArrayUtils.isEmpty(array)) { - return null; - } - if (array.length == 1) { - return array[0]; - } - return array[ThreadLocalRandom.current().nextInt(array.length)]; - } - - /** - * Computes elements, allowing them to be changed to something of the same type. - * - * @param The type of element. - * @param array The array to compute the elements of. - * @param mapper The compute function itself. - */ - public static void computeElements(final T[] array, final Function mapper) { - if (ArrayUtils.isEmpty(array) || mapper == null) { - return; - } - for (int i = 0, l = array.length; i < l; i++) { - array[i] = mapper.apply(array[i]); - } - } - - /** - * Calculates the number of elements that fulfill a given condition. - * - * @param The type of element. - * @param array The array to match the elements of. - * @param matcher The matcher function itself. - * @return Returns the number of elements that match. - */ - public static int numberOfMatches(final T[] array, final Predicate matcher) { - if (ArrayUtils.isEmpty(array) || matcher == null) { - return 0; - } - int counter = 0; - for (final T element : array) { - if (matcher.test(element)) { - counter++; - } - } - return counter; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/MoreCollectionUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/util/MoreCollectionUtils.java deleted file mode 100644 index 093065f5..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/MoreCollectionUtils.java +++ /dev/null @@ -1,143 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Predicate; -import java.util.function.Supplier; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.ArrayUtils; - -/** - * Utility class that fills in the gaps of {@link CollectionUtils}. - * - * @author Protonull - */ -public final class MoreCollectionUtils { - - /** - * Creates a new collection with a given set of predefined elements, if any are given. - * - * @param The type of the elements to store in the collection. - * @param constructor The constructor for the collection. - * @param elements The elements to add to the collection. - * @return Returns a new collection, or null if no constructor was given, or the constructor didn't produce a new - * collection. - */ - @SafeVarargs - public static > K collect(final Supplier constructor, final T... elements) { - final K collection = Chainer.from(constructor).then(Supplier::get).get(); - if (collection == null) { - return null; - } - CollectionUtils.addAll(collection, elements); - return collection; - } - - /** - *

    Tests whether there is at least one element in the given collection that passes the criteria of the given - * predicate.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

    - * - * @param The type of the collection's elements. - * @param collection The collection to iterate. - * @param predicate The element tester. - * @return Returns true if at least one element passes the predicate test. Or false if the array fails the - * {@link ArrayUtils#isEmpty(Object[]) isNullOrEmpty()} test, or true if the give predicate is null. - */ - public static boolean anyMatch(final Collection collection, final Predicate predicate) { - if (CollectionUtils.isEmpty(collection)) { - return false; - } - if (predicate == null) { - return true; - } - for (final T element : collection) { - if (predicate.test(element)) { - return true; - } - } - return false; - } - - /** - *

    Tests whether every element in an collection passes the criteria of the given predicate.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every

    - * - * @param The type of the collection's elements. - * @param collection The collection to iterate. - * @param predicate The element tester. - * @return Returns true if no element fails the predicate test, or if the array fails the - * {@link ArrayUtils#isEmpty(Object[]) isNullOrEmpty()} test, or if the give predicate is null. - */ - public static boolean allMatch(final Collection collection, final Predicate predicate) { - if (CollectionUtils.isEmpty(collection)) { - return true; - } - if (predicate == null) { - return true; - } - for (final T element : collection) { - if (!predicate.test(element)) { - return false; - } - } - return true; - } - - /** - * Removes the element at the end of the given list. - * - * @param The type of the list's elements. - * @param list The list to remove the last element from. - * @return Returns the element removed. - */ - public static T removeLastElement(final List list) { - if (CollectionUtils.isEmpty(list)) { - return null; - } - return list.remove(list.size() - 1); - } - - /** - * Retrieves a random element from an list of elements. - * - * @param The type of element. - * @param list The list to retrieve a value from. - * @return Returns a random element, or null. - */ - public static T randomElement(final List list) { - if (CollectionUtils.isEmpty(list)) { - return null; - } - final int size = list.size(); - if (size == 1) { - return list.get(0); - } - return list.get(ThreadLocalRandom.current().nextInt(size)); - } - - /** - * Calculates the number of elements that fulfill a given condition. - * - * @param The type of element. - * @param collection The collection to match the elements of. - * @param matcher The matcher function itself. - * @return Returns the number of elements that match. - */ - public static int numberOfMatches(final Collection collection, final Predicate matcher) { - if (CollectionUtils.isEmpty(collection) || matcher == null) { - return 0; - } - int counter = 0; - for (final T element : collection) { - if (matcher.test(element)) { - counter++; - } - } - return counter; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/MoreMapUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/util/MoreMapUtils.java deleted file mode 100644 index c0b68b9f..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/MoreMapUtils.java +++ /dev/null @@ -1,148 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import com.google.common.collect.BiMap; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; -import java.util.function.Function; -import org.apache.commons.collections4.MapUtils; -import org.apache.commons.lang3.ArrayUtils; - -/** - * Utility class that fills in the gaps of {@link MapUtils}. - * - * @author Protonull - */ -public final class MoreMapUtils { - - /** - * Determines whether a Map Entry is valid in that it exists and so does the key and value. - * - * @param entry The map entry itself. - * @return Returns true if the entry is considered valid. - */ - public static boolean validEntry(final Map.Entry entry) { - if (entry == null) { - return false; - } - if (entry.getKey() == null) { - return false; - } - if (entry.getValue() == null) { - return false; - } - return true; - } - - /** - * Retrieves a key from a map based on a given value. If two or more keys share a value, - * the key that's returned is the first that matches during standard iteration. - * - * @param The key type. - * @param The value type. - * @param map The map to retrieve the key from. - * @param value The value to based the search on. - * @return Returns the key, or null. - */ - public static K getKeyFromValue(final Map map, final V value) { - if (MapUtils.isEmpty(map)) { - return null; - } - if (map instanceof BiMap) { - return ((BiMap) map).inverse().get(value); - } - for (final Map.Entry entry : map.entrySet()) { - if (Objects.equals(value, entry.getValue())) { - return entry.getKey(); - } - } - return null; - } - - /** - * Attempts to retrieve a value from a given map from a range of keys. - * - * @param The key type of the map. - * @param The value type of the map. - * @param The desired return type. - * @param map The map to retrieve the value from. - * @param fallback The value that should be returned if none of the keys return a [valid] value. - * @param keys The keys to check. - * @return Returns a value, either from the keys or the fallback, both of which may be null. - */ - @SafeVarargs - public static R attemptGet(final Map map, final R fallback, final K... keys) { - return attemptGet(map, null, fallback, keys); - } - - /** - * Attempts to retrieve a value from a given map from a range of keys. - * - * @param The key type of the map. - * @param The value type of the map. - * @param The desired return type. - * @param map The map to retrieve the value from. - * @param parser The function to process the value from the map. Null will use a default parser. - * @param fallback The value that should be returned if none of the keys return a [valid] value. - * @param keys The keys to check. - * @return Returns a value, either from the keys or the fallback, both of which may be null. - */ - @SafeVarargs - @SuppressWarnings("unchecked") - public static R attemptGet(final Map map, - final Function parser, - final R fallback, - final K... keys) { - if (parser == null) { - // Default parser (basic cast) - return attemptGet(map, (V v) -> (R) v, fallback, keys); - } - if (MapUtils.isEmpty(map) || ArrayUtils.isEmpty(keys)) { - return fallback; - } - for (final K key : keys) { - if (!map.containsKey(key)) { - continue; - } - try { - return parser.apply(map.get(key)); - } - // Yeeeaaaah, I know this is a catch all exception and that's bad, but this really could be anything since - // the parser function could be anything.. it could be a class cast, a null reference, number format... - // But since this is a value parser and not an arbitrary code executor, nothing complication will be run, - // so any exception cab be interpreted as a bad or unexpected value. - catch (Exception ignored) { - return fallback; - } - } - return fallback; - } - - /** - * Sets all the given keys a particular value. - * - * @param The type of the map's key. - * @param The type of the map's values. - * @param map The map to set to. - * @param value The value to set. - * @param keys The keys to set the value for. - */ - @SafeVarargs - public static void setMultipleKeys(final Map map, final V value, final K... keys) { - if (MapUtils.isEmpty(map) || ArrayUtils.isEmpty(keys)) { - return; - } - for (final K key : keys) { - map.put(key, value); - } - } - - /** - * @param The type of the map's values. - * @return Returns a new TreeMap with a String keys that are NOT case sensitive. - */ - public static TreeMap newStringKeyMap() { - return new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/NullCoalescing.java b/src/main/java/vg/civcraft/mc/civmodcore/util/NullCoalescing.java deleted file mode 100644 index e6093d9f..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/NullCoalescing.java +++ /dev/null @@ -1,185 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import com.google.common.base.Objects; -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** - * @deprecated Use {@link NullUtils} instead. - */ -@Deprecated -public final class NullCoalescing { - - @FunctionalInterface - public interface NullChecker { - T get() throws Exception; - } - - /** - *

    Returns the first non-null given parameter, if any are given.

    - * - *

    Emulates: - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator

    - * - * @param Any non-primitive type. - * @param items The list of parameters to find a non-null value from. - * @return Returns the first non-null value found, or null. - * - * @deprecated Use {@link NullUtils#firstNonNull(Object[])} instead. - */ - @Deprecated - @SafeVarargs - public static T coalesce(T... items) { - for (T item : items) { - if (item != null) { - return item; - } - } - return null; - } - - /** - *

    Allows developers to chain statements that might otherwise require a ton of null checking.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining

    - * - * @param Any non-primitive type. - * @param statement Function that throws an exception to call the chained statement within. - * @return Returns the result of the chained statement, or null if the chain failed. - * - * @deprecated Use {@link Chainer} instead. - */ - @Deprecated - public static T chain(NullChecker statement) { - return chain(statement, null); - } - - /** - *

    Allows developers to chain statements that might otherwise require a ton of null checking.

    - * - *

    Emulates: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining and - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator

    - * - * @param Any non-primitive type. - * @param statement Function that throws an exception to call the chained statement within. - * @param fallback The value that will be fallen back upon if something goes wrong. - * @return Returns the result of the chained statement, or the fallback if the chain failed. - * - * @deprecated Use {@link Chainer} instead. - */ - @Deprecated - public static T chain(NullChecker statement, T fallback) { - if (statement == null) { - return fallback; - } - try { - return statement.get(); - } - catch (Exception ignored) { - return fallback; - } - } - - /** - * Runs a handler only if the given value is not null. - * - * @param The type of the given parameter. - * @param value The given parameter. - * @param handler The handler to run if the given parameter exists. - * - * @deprecated Just use an if statement. - */ - @Deprecated - public static void exists(T value, Consumer handler) { - if (value != null && handler != null) { - handler.accept(value); - } - } - - /** - * Executes a function to supply a value should that value not already exist. - * - * @param The type of the value. - * @param value The given value. - * @param handler The supplier that will be run should the given value be null. - * @return Returns the given value or the result of the handler. - * - * @deprecated Just use an if statement. - */ - @Deprecated - public static T notExists(T value, Supplier handler) { - if (value == null && handler != null) { - value = handler.get(); - } - return value; - } - - /** - * Checks whether a value can be cast to a particular type. - * - * @param The type to cast to. - * @param clazz The class of the type. - * @param value The value to attempt to cast. - * @return Returns the value cast to the given type, nor null. - * - * @deprecated Use {@link MoreClassUtils#castOrNull(Class, Object)} instead. - */ - @Deprecated - @SuppressWarnings("unchecked") - public static T castOrNull(Class clazz, Object value) { - if (clazz == null || value == null) { - return null; - } - if (clazz.isAssignableFrom(value.getClass())) { - return (T) value; - } - return null; - } - - /** - * Determines if two objects objects are equal. - * - * @param former The former object. - * @param latter The latter object. - * @return Returns true if the values equal each other. - * - * @deprecated Use {@link Objects#equal(Object, Object)} instead. - */ - @Deprecated - public static boolean equals(Object former, Object latter) { - if (former == latter) { - return true; - } - if (former != null && former.equals(latter)) { - return true; - } - if (latter != null && latter.equals(former)) { - return true; - } - return false; - } - - /** - * Determines if two objects objects are equal, except that null values are disallowed. - * - * @param former The former object. - * @param latter The latter object. - * @return Returns true only if both objects are not null and pass an equals test. - * - * @deprecated Use {@link NullUtils#equalsNotNull(Object, Object)} instead. - */ - @Deprecated - public static boolean equalsNotNull(Object former, Object latter) { - if (former == null || latter == null) { - return false; - } - if (former.equals(latter)) { - return true; - } - if (latter.equals(former)) { - return true; - } - return false; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/NullUtils.java b/src/main/java/vg/civcraft/mc/civmodcore/util/NullUtils.java deleted file mode 100644 index c6118fad..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/NullUtils.java +++ /dev/null @@ -1,57 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import javax.annotation.Nonnull; - -/** - * @author Protonull - */ -public final class NullUtils { - - /** - *

    Returns the first non-null given parameter, if any are given.

    - * - *

    Emulates: - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator

    - * - * @param Any non-primitive type. - * @param items The list of parameters to find a non-null value from. - * @return Returns the first non-null value found, or null. - */ - @SafeVarargs - public static T firstNonNull(final T... items) { - for (final T item : items) { - if (item != null) { - return item; - } - } - return null; - } - - /** - * Determines if two objects objects are equal, except that null values are disallowed. - * - * @param former The former object. - * @param latter The latter object. - * @return Returns true only if both objects are not null and pass an equals test. - */ - public static boolean equalsNotNull(final Object former, final Object latter) { - if (former == null || latter == null) { - return false; - } - return former.equals(latter); - } - - /** - * This is for when you're absolutely sure that the given object isn't null, but for some reason your IDE can't - * tell and is screaming at you to fix the null reference. - * - * @param The type of the given object. - * @param object The object that isn't null. - * @return Returns the object highlighted as non-null. - */ - @Nonnull - public static T isNotNull(final T object) { - return object; - } - -} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/SkinCache.java b/src/main/java/vg/civcraft/mc/civmodcore/util/SkinCache.java new file mode 100644 index 00000000..1bc49b52 --- /dev/null +++ b/src/main/java/vg/civcraft/mc/civmodcore/util/SkinCache.java @@ -0,0 +1,194 @@ +package vg.civcraft.mc.civmodcore.util; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + +/** + * @author caucow ( https://github.com/caucow ) + */ +// Maybe some day this class can be made more generic and accept an ExecutorService in a constructor or something. +// For now it works. Ree. +public class SkinCache { + + private final Plugin plugin; + // TODO: If an easier way to limit the number of threads spawned by Bukkit's scheduler exists, use it. + // This is dirty. It feels bad. + private final ExecutorService executor; + private final BukkitTask watchdog; + private Thread watchdogThread; + /** + * Caching of PlayerProfiles, should only be accessed directly through + * getIfPresent or other non-blocking means. Use futureCache to trigger + * loads. + */ + private LoadingCache skinCache; + /** + * Implementation for "notifying" code waiting on a skin to be loaded. + * Futures returned are processed on this HeadCache's ExecutorService (for + * now unless a better way to limit Bukkit scheduler thread spawning arises) + */ + private LoadingCache> futureCache; + + /** + * Creates a new SkinCache and initializes its ExecutorService and watchdog bukkit task. + * + * Always call {@link #shutdown()} before discarding this object (ex if replacing a SkinCache or disabling a plugin) + * @param plugin Plugin owning the cache, used to schedule Bukkit sync tasks. + * @param downloadThreads Number of threads to use to handle async loading. + */ + public SkinCache(Plugin plugin, int downloadThreads) { + this.plugin = plugin; + + this.skinCache = CacheBuilder.newBuilder() + .expireAfterWrite(6, TimeUnit.HOURS) + .build(new CacheLoader() { + @Override + public SkinData load(UUID uuid) throws Exception { + PlayerProfile profile = Bukkit.createProfile(uuid); + profile.complete(true); + if (profile.hasTextures()) { + return new SkinData(profile); + } + throw new SkinLoadException("Could not complete() PlayerProfile for " + uuid + " (rate limited or profile doesn't exist)"); + } + }); + this.futureCache = CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(new CacheLoader>() { + @Override + public CompletableFuture load(UUID uuid) throws Exception { + return CompletableFuture.supplyAsync(() -> { + try { + return skinCache.get(uuid); + } catch (Exception ex) { + // This was causing "issues" with nms querying + // the skin server. Just going to log the + // exception now I guess + // return new SkinData(Bukkit.createProfile(uuid)); + if (ex instanceof SkinLoadException) { + plugin.getLogger().log(Level.WARNING, "Exception loading skin: " + ex.getMessage()); + } else { + plugin.getLogger().log(Level.WARNING, "Exception loading skin", ex); + } + // also complete exceptionally to cancel later + // CompletionStages + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } else { + throw new RuntimeException("Skin could not be loaded", ex); + } + } + }, executor); + } + }); + + this.executor = Executors.newFixedThreadPool(downloadThreads); + // because I don't trust plugins to clean up on disable so might as well + // have something screeching in the logs. not that many plugins try to + // be reloadable + this.watchdog = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + watchdogThread = Thread.currentThread(); + do { + try { + SkinCache.this.executor.awaitTermination(60, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + shutdown(); + return; + } + if (!plugin.isEnabled()) { + break; + } + } while (!SkinCache.this.executor.isShutdown()); + if (SkinCache.this.executor.isShutdown()) { + return; + } + try { + SkinCache.this.executor.awaitTermination(60, TimeUnit.SECONDS); + if (!SkinCache.this.executor.isShutdown()) { + plugin.getLogger().log(Level.WARNING, "Plugin did not shutdown() HeadCache while disabling!"); + } + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + shutdown(); + }); + } + + /** + * Shut down the ExecutorService and watchdog task, and cancel any remaining async loads. + */ + public void shutdown() { + watchdog.cancel(); + executor.shutdownNow(); + if (watchdogThread != null) { + watchdogThread.interrupt(); + } + } + + /** + * @param playerId UUID of player to get head for + * @param placeholderSupplier If there is not already a skin available for the given player, use this supplier to + * generate a placeholder item while the skin for the skull item is loaded. + * @param notifyAvailable If a skin must be downloaded or retrieved from the server's usercache, this will be used + * as a callback when the skin has been loaded and a skull item created. + * @return A bare player skull item, or a placeholder if the skin for the provided player ID is not loaded yet. + */ + public ItemStack getHeadItem(final UUID playerId, Supplier placeholderSupplier, final Consumer notifyAvailable) { + SkinData skin = skinCache.getIfPresent(playerId); + if (skin != null) { + return createHeadItem(skin); + } + CompletableFuture metaFuture = futureCache.getUnchecked(playerId); + metaFuture.thenAccept((asyncMeta) -> { + if (plugin.isEnabled()) { + ItemStack headItem = createHeadItem(asyncMeta); + Bukkit.getScheduler().runTask(plugin, () -> notifyAvailable.accept(headItem)); + } + }); + return placeholderSupplier.get(); + } + + private ItemStack createHeadItem(SkinData skin) { + ItemStack item = new ItemStack(Material.PLAYER_HEAD); + item.setItemMeta(skin.headMeta.clone()); + return item; + } + + private static class SkinData { + + public final PlayerProfile profile; + public final SkullMeta headMeta; + + public SkinData(PlayerProfile profile) { + this.profile = profile; + this.headMeta = (SkullMeta) Bukkit.getItemFactory().getItemMeta(Material.PLAYER_HEAD); + headMeta.setPlayerProfile(profile); + } + + } + + public static class SkinLoadException extends RuntimeException { + public SkinLoadException(String message) { + super(message); + } + } +} diff --git a/src/main/java/vg/civcraft/mc/civmodcore/util/TextUtil.java b/src/main/java/vg/civcraft/mc/civmodcore/util/TextUtil.java deleted file mode 100644 index 714ab4d8..00000000 --- a/src/main/java/vg/civcraft/mc/civmodcore/util/TextUtil.java +++ /dev/null @@ -1,461 +0,0 @@ -package vg.civcraft.mc.civmodcore.util; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.chat.ChatUtils; - -public class TextUtil { - - public static String formatDuration(long time) { - return formatDuration(time, TimeUnit.MILLISECONDS); - } - - public static String formatDuration(long time, TimeUnit unit) { - long totalSeconds = TimeUnit.SECONDS.convert(time, unit); - long seconds = totalSeconds % 60; - long totalMinutes = totalSeconds / 60; - long minutes = totalMinutes % 60; - long totalHours = totalMinutes / 60; - StringBuilder sb = new StringBuilder(); - if (totalHours > 0) { - sb.append(totalHours); - sb.append(" h "); - } - if (minutes > 0) { - sb.append(minutes); - sb.append(" min "); - } - if (seconds > 0) { - sb.append(seconds); - sb.append(" sec"); - } - return sb.toString().trim(); - } - - // -------------------------------------------- // - // Top-level parsing functions. - // -------------------------------------------- // - - public static String parse(String str, Object... args) { - return String.format(parse(str), args); - } - - public static String parse(String str) { - return parseColor(str); - } - - // -------------------------------------------- // - // Color parsing - // -------------------------------------------- // - - /** - * @deprecated Use {@link ChatUtils#parseColor(String)} instead. - */ - @Deprecated - public static String parseColor(String string) { - string = parseColorAmp(string); - string = parseColorAcc(string); - string = parseColorTags(string); - return string; - } - - /** - * @deprecated Use {@link ChatUtils#parseColorAmp(String)} instead. - */ - @Deprecated - public static String parseColorAmp(String string) { - string = string.replace("&&", "&"); - string = string.replaceAll("&([a-zA-Z0-9])", "§$1"); - return string; - } - - /** - * @deprecated Use {@link ChatUtils#parseColorAcc(String)} instead. - */ - @Deprecated - public static String parseColorAcc(String string) { - return string. - replace("`0", ChatColor.BLACK.toString()). - replace("`1", ChatColor.DARK_BLUE.toString()). - replace("`2", ChatColor.DARK_GREEN.toString()). - replace("`3", ChatColor.DARK_AQUA.toString()). - replace("`4", ChatColor.DARK_RED.toString()). - replace("`5", ChatColor.DARK_PURPLE.toString()). - replace("`6", ChatColor.GOLD.toString()). - replace("`7", ChatColor.GRAY.toString()). - replace("`8", ChatColor.DARK_GRAY.toString()). - replace("`9", ChatColor.BLUE.toString()). - replace("`A", ChatColor.GREEN.toString()). - replace("`a", ChatColor.GREEN.toString()). - replace("`B", ChatColor.AQUA.toString()). - replace("`b", ChatColor.AQUA.toString()). - replace("`C", ChatColor.RED.toString()). - replace("`c", ChatColor.RED.toString()). - replace("`D", ChatColor.LIGHT_PURPLE.toString()). - replace("`d", ChatColor.LIGHT_PURPLE.toString()). - replace("`E", ChatColor.YELLOW.toString()). - replace("`e", ChatColor.YELLOW.toString()). - replace("`F", ChatColor.WHITE.toString()). - replace("`f", ChatColor.WHITE.toString()). - replace("`L", ChatColor.BOLD.toString()). - replace("`l", ChatColor.BOLD.toString()). - replace("`M", ChatColor.STRIKETHROUGH.toString()). - replace("`m", ChatColor.STRIKETHROUGH.toString()). - replace("`N", ChatColor.UNDERLINE.toString()). - replace("`n", ChatColor.UNDERLINE.toString()). - replace("`O", ChatColor.ITALIC.toString()). - replace("`o", ChatColor.ITALIC.toString()). - replace("`R", ChatColor.RESET.toString()). - replace("`r", ChatColor.RESET.toString()); - } - - /** - * @deprecated Use {@link ChatUtils#parseColorTags(String)} instead. - */ - @Deprecated - public static String parseColorTags(String string) { - return string. - replace("", ChatColor.BLACK.toString()). - replace("", ChatColor.DARK_BLUE.toString()). - replace("", ChatColor.DARK_GREEN.toString()). - replace("", ChatColor.DARK_AQUA.toString()). - replace("", ChatColor.DARK_RED.toString()). - replace("", ChatColor.DARK_PURPLE.toString()). - replace("", ChatColor.GOLD.toString()). - replace("", ChatColor.GRAY.toString()). // This has to be lgray because gray is already claimed. - replace("", ChatColor.DARK_GRAY.toString()). - replace("", ChatColor.BLUE.toString()). - replace("", ChatColor.GREEN.toString()). - replace("", ChatColor.AQUA.toString()). - replace("", ChatColor.RED.toString()). - replace("", ChatColor.LIGHT_PURPLE.toString()). - replace("", ChatColor.YELLOW.toString()). - replace("", ChatColor.WHITE.toString()). - replace("", ChatColor.STRIKETHROUGH.toString()). - replace("", ChatColor.UNDERLINE.toString()). - replace("