From 650f47a7c6e1e49118b9d3c7513d81d2cddccfd7 Mon Sep 17 00:00:00 2001 From: StarWishsama Date: Tue, 8 Aug 2023 23:45:02 +0800 Subject: [PATCH] feat(research): economy unlock research --- pom.xml | 14 + .../slimefun4/api/researches/Research.java | 30 +- .../slimefun4/core/SlimefunRegistry.java | 51 +- .../guide/SlimefunGuideImplementation.java | 21 +- .../core/guide/SlimefunGuideUnlockMode.java | 93 +++ .../guide/SlimefunGuideUnlockProvider.java | 11 + .../integrations/IntegrationsManager.java | 622 +++++++++--------- .../integrations/VaultIntegration.java | 39 ++ src/main/resources/config.yml | 1 + src/main/resources/languages/en/messages.yml | 7 +- 10 files changed, 526 insertions(+), 363 deletions(-) create mode 100644 src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockMode.java create mode 100644 src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockProvider.java create mode 100644 src/main/java/io/github/thebusybiscuit/slimefun4/integrations/VaultIntegration.java diff --git a/pom.xml b/pom.xml index 8e4149c4e6..49f59cd175 100644 --- a/pom.xml +++ b/pom.xml @@ -513,6 +513,20 @@ + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + + + * + * + + + commons-lang diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/api/researches/Research.java b/src/main/java/io/github/thebusybiscuit/slimefun4/api/researches/Research.java index 6a4ee6b528..1528ff86e2 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/api/researches/Research.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/api/researches/Research.java @@ -1,15 +1,22 @@ package io.github.thebusybiscuit.slimefun4.api.researches; +import io.github.thebusybiscuit.slimefun4.api.events.PlayerPreResearchEvent; +import io.github.thebusybiscuit.slimefun4.api.events.ResearchUnlockEvent; +import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; +import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; +import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; +import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuideImplementation; +import io.github.thebusybiscuit.slimefun4.core.services.localization.Language; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import io.github.thebusybiscuit.slimefun4.implementation.setup.ResearchSetup; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; - import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; - import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -19,16 +26,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import io.github.thebusybiscuit.slimefun4.api.events.PlayerPreResearchEvent; -import io.github.thebusybiscuit.slimefun4.api.events.ResearchUnlockEvent; -import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; -import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; -import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; -import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuideImplementation; -import io.github.thebusybiscuit.slimefun4.core.services.localization.Language; -import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; -import io.github.thebusybiscuit.slimefun4.implementation.setup.ResearchSetup; - /** * Represents a research, which is bound to one * {@link SlimefunItem} or more and requires XP levels to unlock said item(s). @@ -228,7 +225,8 @@ public void unlockFromGuide(SlimefunGuideImplementation guide, Player player, Pl if (this.canUnlock(player)) { guide.unlockItem(player, sfItem, pl -> guide.openItemGroup(profile, itemGroup, page)); } else { - Slimefun.getLocalization().sendMessage(player, "messages.not-enough-xp", true); + var tokenName = Slimefun.getRegistry().getSlimefunGuideUnlockMode().getTokenName(); + Slimefun.getLocalization().sendMessage(player, "messages.not-enough-token", true, s -> s.replace("%token_name%", tokenName)); } } } @@ -248,8 +246,10 @@ public boolean canUnlock(@Nonnull Player p) { return true; } - boolean creativeResearch = p.getGameMode() == GameMode.CREATIVE && Slimefun.getRegistry().isFreeCreativeResearchingEnabled(); - return creativeResearch || p.getLevel() >= cost; + var creativeResearch = p.getGameMode() == GameMode.CREATIVE && Slimefun.getRegistry().isFreeCreativeResearchingEnabled(); + var unlockProvider = Slimefun.getRegistry().getSlimefunGuideUnlockMode().getUnlockProvider(); + + return creativeResearch || unlockProvider.canUnlock(this, p); } /** diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/SlimefunRegistry.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/SlimefunRegistry.java index bdafdc4293..cb25c1ea49 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/SlimefunRegistry.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/SlimefunRegistry.java @@ -1,5 +1,21 @@ package io.github.thebusybiscuit.slimefun4.core; +import io.github.bakedlibs.dough.collections.KeyMap; +import io.github.bakedlibs.dough.config.Config; +import io.github.thebusybiscuit.slimefun4.api.geo.GEOResource; +import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; +import io.github.thebusybiscuit.slimefun4.api.items.ItemHandler; +import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; +import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; +import io.github.thebusybiscuit.slimefun4.api.researches.Research; +import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuide; +import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuideImplementation; +import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuideMode; +import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuideUnlockMode; +import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlock; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import io.github.thebusybiscuit.slimefun4.implementation.guide.CheatSheetSlimefunGuide; +import io.github.thebusybiscuit.slimefun4.implementation.guide.SurvivalSlimefunGuide; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; @@ -11,9 +27,11 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; - import javax.annotation.Nonnull; - +import me.mrCookieSlime.Slimefun.api.BlockInfoConfig; +import me.mrCookieSlime.Slimefun.api.BlockStorage; +import me.mrCookieSlime.Slimefun.api.inventory.BlockMenuPreset; +import me.mrCookieSlime.Slimefun.api.inventory.UniversalBlockMenu; import org.apache.commons.lang.Validate; import org.bukkit.NamespacedKey; import org.bukkit.Server; @@ -23,27 +41,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import io.github.bakedlibs.dough.collections.KeyMap; -import io.github.bakedlibs.dough.config.Config; -import io.github.thebusybiscuit.slimefun4.api.geo.GEOResource; -import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; -import io.github.thebusybiscuit.slimefun4.api.items.ItemHandler; -import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; -import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; -import io.github.thebusybiscuit.slimefun4.api.researches.Research; -import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuide; -import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuideImplementation; -import io.github.thebusybiscuit.slimefun4.core.guide.SlimefunGuideMode; -import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlock; -import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; -import io.github.thebusybiscuit.slimefun4.implementation.guide.CheatSheetSlimefunGuide; -import io.github.thebusybiscuit.slimefun4.implementation.guide.SurvivalSlimefunGuide; - -import me.mrCookieSlime.Slimefun.api.BlockInfoConfig; -import me.mrCookieSlime.Slimefun.api.BlockStorage; -import me.mrCookieSlime.Slimefun.api.inventory.BlockMenuPreset; -import me.mrCookieSlime.Slimefun.api.inventory.UniversalBlockMenu; - /** * This class houses a lot of instances of {@link Map} and {@link List} that hold * various mappings and collections related to {@link SlimefunItem}. @@ -72,6 +69,7 @@ public final class SlimefunRegistry { private boolean disableLearningAnimation; private boolean logDuplicateBlockEntries; private boolean talismanActionBarMessages; + private SlimefunGuideUnlockMode guideUnlockMode; private final Set tickers = new HashSet<>(); private final Set radioactive = new HashSet<>(); @@ -111,6 +109,8 @@ public void load(@Nonnull Slimefun plugin, @Nonnull Config cfg) { freeCreativeResearches = cfg.getBoolean("researches.free-in-creative-mode"); researchFireworks = cfg.getBoolean("researches.enable-fireworks"); disableLearningAnimation = cfg.getBoolean("researches.disable-learning-animation"); + guideUnlockMode = SlimefunGuideUnlockMode.check(cfg.getString("researches.unlock-research-mode")); + logDuplicateBlockEntries = cfg.getBoolean("options.log-duplicate-block-entries"); talismanActionBarMessages = cfg.getBoolean("talismans.use-actionbar"); } @@ -260,6 +260,11 @@ public SlimefunGuideImplementation getSlimefunGuide(@Nonnull SlimefunGuideMode m return guide; } + @Nonnull + public SlimefunGuideUnlockMode getSlimefunGuideUnlockMode() { + return guideUnlockMode; + } + /** * This returns a {@link Map} connecting the {@link EntityType} with a {@link Set} * of {@link ItemStack ItemStacks} which would be dropped when an {@link Entity} of that type was killed. diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideImplementation.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideImplementation.java index d035715ed7..3a415c8cb2 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideImplementation.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideImplementation.java @@ -1,21 +1,17 @@ package io.github.thebusybiscuit.slimefun4.core.guide; -import java.util.function.Consumer; - -import javax.annotation.Nonnull; -import javax.annotation.ParametersAreNonnullByDefault; - -import org.bukkit.GameMode; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; -import io.github.thebusybiscuit.slimefun4.api.researches.Research; import io.github.thebusybiscuit.slimefun4.core.guide.options.SlimefunGuideSettings; import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; import io.github.thebusybiscuit.slimefun4.implementation.guide.SurvivalSlimefunGuide; +import java.util.function.Consumer; +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; /** * This interface is used for the different implementations that add behaviour @@ -65,12 +61,13 @@ public interface SlimefunGuideImplementation { @ParametersAreNonnullByDefault default void unlockItem(Player p, SlimefunItem sfitem, Consumer callback) { - Research research = sfitem.getResearch(); + var research = sfitem.getResearch(); + var unlockProvider = Slimefun.getRegistry().getSlimefunGuideUnlockMode().getUnlockProvider(); if (p.getGameMode() == GameMode.CREATIVE && Slimefun.getRegistry().isFreeCreativeResearchingEnabled()) { research.unlock(p, true, callback); } else { - p.setLevel(p.getLevel() - research.getCost()); + unlockProvider.processPayment(research, p); boolean skipLearningAnimation = Slimefun.getRegistry().isLearningAnimationDisabled() || !SlimefunGuideSettings.hasLearningAnimationEnabled(p); research.unlock(p, skipLearningAnimation, callback); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockMode.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockMode.java new file mode 100644 index 0000000000..c0bc64265f --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockMode.java @@ -0,0 +1,93 @@ +package io.github.thebusybiscuit.slimefun4.core.guide; + +import io.github.thebusybiscuit.slimefun4.api.researches.Research; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import io.github.thebusybiscuit.slimefun4.integrations.VaultIntegration; +import java.util.Locale; +import javax.annotation.Nonnull; +import org.bukkit.entity.Player; + +/** + * This enum holds the different unlock research modes a {@link SlimefunGuide} can have. + * Each constant corresponds to a research unlock mode and a unlock provider + * + * @author StarWishsama + * @see SlimefunGuide + * @see SlimefunGuideImplementation + * @see SlimefunGuideUnlockProvider + */ +public enum SlimefunGuideUnlockMode { + /** + * Unlock research by withdrawing player's experience level. + */ + EXPERIENCE(new SlimefunGuideUnlockProvider() { + @Override + public boolean canUnlock(@Nonnull Research research, @Nonnull Player p) { + return p.getLevel() >= research.getCost(); + } + + @Override + public void processPayment(@Nonnull Research research, @Nonnull Player p) { + p.setLevel(p.getLevel() - research.getCost()); + } + }), + + /** + * Unlock research by withdrawing player's balance. + */ + ECONOMY(new SlimefunGuideUnlockProvider() { + @Override + public boolean canUnlock(@Nonnull Research research, @Nonnull Player p) { + return VaultIntegration.getPlayerBalance(p) >= research.getCost(); + } + + @Override + public void processPayment(@Nonnull Research research, @Nonnull Player p) { + VaultIntegration.withdrawPlayer(p, research.getCost()); + } + }); + + /** + * Research unlock provider + *

+ * Process player can unlock research and process research payment. + * + * @see SlimefunGuideUnlockProvider + */ + @Nonnull + private final SlimefunGuideUnlockProvider unlockProvider; + + SlimefunGuideUnlockMode(@Nonnull SlimefunGuideUnlockProvider unlockProvider) { + this.unlockProvider = unlockProvider; + } + + /** + * Convert string to certain {@link SlimefunGuideUnlockMode}. + * If string is invalid will fall back to default one (player level) + * + * @param s text to validate + * @return {@link SlimefunGuideUnlockMode} + */ + public static SlimefunGuideUnlockMode check(String s) { + if (s == null) { + return SlimefunGuideUnlockMode.EXPERIENCE; + } + + for (SlimefunGuideUnlockMode value : SlimefunGuideUnlockMode.values()) { + if (value.toString().equalsIgnoreCase(s)) { + return value; + } + } + + return SlimefunGuideUnlockMode.EXPERIENCE; + } + + @Nonnull + public SlimefunGuideUnlockProvider getUnlockProvider() { + return unlockProvider; + } + + public String getTokenName() { + return Slimefun.getLocalization().getMessage("guide.unlock-mode-" + toString().toLowerCase(Locale.ROOT)); + } +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockProvider.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockProvider.java new file mode 100644 index 0000000000..5e24d8a667 --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/guide/SlimefunGuideUnlockProvider.java @@ -0,0 +1,11 @@ +package io.github.thebusybiscuit.slimefun4.core.guide; + +import io.github.thebusybiscuit.slimefun4.api.researches.Research; +import javax.annotation.Nonnull; +import org.bukkit.entity.Player; + +public interface SlimefunGuideUnlockProvider { + boolean canUnlock(@Nonnull Research research, @Nonnull Player p); + + void processPayment(@Nonnull Research research, @Nonnull Player p); +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java b/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java index 579e5bfb69..d9e580ccb5 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java @@ -1,312 +1,310 @@ -package io.github.thebusybiscuit.slimefun4.integrations; - -import java.util.function.Consumer; -import java.util.logging.Level; - -import javax.annotation.Nonnull; -import javax.annotation.ParametersAreNonnullByDefault; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.block.Block; -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; - -import com.gmail.nossr50.events.fake.FakeBlockBreakEvent; -import com.gmail.nossr50.util.skills.SkillUtils; - -import io.github.bakedlibs.dough.protection.ProtectionManager; -import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon; -import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; -import io.github.thebusybiscuit.slimefun4.implementation.items.electric.machines.enchanting.AutoDisenchanter; - -import dev.lone.itemsadder.api.ItemsAdder; - -/** - * This Service holds all interactions and hooks with third-party {@link Plugin Plugins} - * that are not necessarily a dependency or a {@link SlimefunAddon}. - * - * Integration with these plugins happens inside Slimefun itself. - * - * @author TheBusyBiscuit - * - * @see Slimefun - * - */ -public class IntegrationsManager { - - /** - * This is our instance of {@link Slimefun}. - */ - protected final Slimefun plugin; - - /** - * Our {@link ProtectionManager} instance. - */ - private ProtectionManager protectionManager; - - /** - * This boolean determines whether {@link #start()} was run. - */ - private boolean isEnabled = false; - - // Soft dependencies - private boolean isPlaceholderAPIInstalled = false; - private boolean isWorldEditInstalled = false; - private boolean isMcMMOInstalled = false; - private boolean isClearLagInstalled = false; - private boolean isItemsAdderInstalled = false; - private boolean isOrebfuscatorInstalled = false; - - /** - * This initializes the {@link IntegrationsManager} - * - * @param plugin - * Our instance of {@link Slimefun} - */ - public IntegrationsManager(@Nonnull Slimefun plugin) { - this.plugin = plugin; - } - - /** - * This method returns whether the {@link IntegrationsManager} was enabled yet. - * - * @return Whether this {@link IntegrationsManager} has been enabled already. - */ - public boolean isEnabled() { - return isEnabled; - } - - /** - * This method initializes all third party integrations. - */ - public final void start() { - if (isEnabled) { - // Prevent double-registration - throw new UnsupportedOperationException("All integrations have already been loaded."); - } else { - isEnabled = true; - } - - // Load any soft dependencies - onServerLoad(); - - // Load any integrations which aren't dependencies (loadBefore) - plugin.getServer().getScheduler().runTask(plugin, this::onServerStart); - } - - /** - * This method is called when the {@link Server} loaded its {@link Plugin Plugins}. - * We can safely assume that any {@link Plugin} which is a soft dependency of Slimefun - * to be enabled at this point. - */ - private void onServerLoad() { - // PlaceholderAPI hook to provide playerholders from Slimefun. - load("PlaceholderAPI", integration -> { - new PlaceholderAPIIntegration(plugin).register(); - isPlaceholderAPIInstalled = true; - }); - - // WorldEdit Hook to clear Slimefun Data upon //set 0 //cut or any other equivalent - load("WorldEdit", integration -> { - new WorldEditIntegration().register(); - isWorldEditInstalled = true; - }); - - // mcMMO Integration - load("mcMMO", integration -> { - new McMMOIntegration(plugin).register(); - isMcMMOInstalled = true; - }); - - // ClearLag integration (to prevent display items from getting deleted) - load("ClearLag", integration -> { - new ClearLagIntegration(plugin).register(); - isClearLagInstalled = true; - }); - - // ItemsAdder Integration (custom blocks) - load("ItemsAdder", integration -> isItemsAdderInstalled = true); - } - - /** - * This method is called when the {@link Server} has finished loading all its {@link Plugin Plugins}. - */ - private void onServerStart() { - try { - // Load Protection plugin integrations - protectionManager = new ProtectionManager(plugin); - } catch (Exception | LinkageError x) { - Slimefun.logger().log(Level.WARNING, x, () -> "Failed to load Protection plugin integrations for Slimefun v" + Slimefun.getVersion()); - } - - // Orebfuscator Integration - load("Orebfuscator", integration -> { - new OrebfuscatorIntegration(plugin).register(); - isOrebfuscatorInstalled = true; - }); - } - - /** - * This method logs a {@link Throwable} that was caused by a {@link Plugin} - * we integrate into. - * Calling this method will probably log the error and provide the version of this {@link Plugin} - * for error analysis. - * - * @param name - * The name of the {@link Plugin} - * @param throwable - * The {@link Throwable} to throw - */ - @ParametersAreNonnullByDefault - protected void logError(String name, Throwable throwable) { - Plugin externalPlugin = Bukkit.getPluginManager().getPlugin(name); - - if (externalPlugin != null) { - String version = externalPlugin.getDescription().getVersion(); - Slimefun.logger().log(Level.WARNING, "Is {0} v{1} up to date?", new Object[] { name, version }); - Slimefun.logger().log(Level.SEVERE, throwable, () -> "An unknown error was detected while interacting with \"" + name + " v" + version + "\""); - } else { - Slimefun.logger().log(Level.SEVERE, throwable, () -> "An unknown error was detected while interacting with the plugin \"" + name + "\""); - } - } - - /** - * This method loads an integration with a {@link Plugin} of the specified name. - * If that {@link Plugin} is installed and enabled, the provided callback will be run. - * - * @param pluginName - * The name of this {@link Plugin} - * @param consumer - * The callback to run if that {@link Plugin} is installed and enabled - */ - private void load(@Nonnull String pluginName, @Nonnull Consumer consumer) { - Plugin integration = plugin.getServer().getPluginManager().getPlugin(pluginName); - - if (integration != null && integration.isEnabled()) { - String version = integration.getDescription().getVersion(); - Slimefun.logger().log(Level.INFO, "Hooked into Plugin: {0} v{1}", new Object[] { pluginName, version }); - - try { - // Run our callback - consumer.accept(integration); - } catch (Exception | LinkageError x) { - Slimefun.logger().log(Level.WARNING, "Maybe consider updating {0} or Slimefun?", pluginName); - Slimefun.logger().log(Level.WARNING, x, () -> "Failed to hook into " + pluginName + " v" + version); - } - } - } - - /** - * This returns out instance of the {@link ProtectionManager}. - * This bridge is used to hook into any third-party protection {@link Plugin}. - * - * @return Our instanceof of the {@link ProtectionManager} - */ - public @Nonnull ProtectionManager getProtectionManager() { - return protectionManager; - } - - /** - * This checks if one of our third party integrations faked an {@link Event}. - * Faked {@link Event Events} should be ignored in our logic. - * - * @param event - * The {@link Event} to test - * - * @return Whether this is a fake event - */ - public boolean isEventFaked(@Nonnull Event event) { - // This can be changed to "FakeEvent" in a later version - return isMcMMOInstalled && event instanceof FakeBlockBreakEvent; - } - - /** - * This checks if one of our third party integrations has placed a custom - * {@link Block} at this {@link Location}. - * - * @param block - * The {@link Block} to check - * - * @return Whether a different custom {@link Block} exists at that {@link Location} - */ - @SuppressWarnings("deprecation") - public boolean isCustomBlock(@Nonnull Block block) { - if (isItemsAdderInstalled) { - try { - return ItemsAdder.isCustomBlock(block); - } catch (Exception | LinkageError x) { - logError("ItemsAdder", x); - } - } - - return false; - } - - /** - * This checks if one of our third party integrations defines a given - * {@link ItemStack} as custom. - * - * @param item - * The {@link ItemStack} to check - * - * @return Whether this {@link ItemStack} is a custom item - */ - @SuppressWarnings("deprecation") - public boolean isCustomItem(@Nonnull ItemStack item) { - if (isItemsAdderInstalled) { - try { - return ItemsAdder.isCustomItem(item); - } catch (Exception | LinkageError x) { - logError("ItemsAdder", x); - } - } - - return false; - } - - /** - * This method removes any temporary enchantments from the given {@link ItemStack}. - * Some plugins apply enchantments for a short amount of time and remove it later. - * We don't want these items to be exploited using an {@link AutoDisenchanter} for example, - * so we want to be able to strip those temporary enchantments in advance. - * - * @param item - * The {@link ItemStack} - */ - public void removeTemporaryEnchantments(@Nonnull ItemStack item) { - if (isMcMMOInstalled) { - try { - SkillUtils.removeAbilityBuff(item); - } catch (Exception | LinkageError x) { - logError("mcMMO", x); - } - } - } - - public boolean isPlaceholderAPIInstalled() { - return isPlaceholderAPIInstalled; - } - - public boolean isWorldEditInstalled() { - return isWorldEditInstalled; - } - - public boolean isMcMMOInstalled() { - return isMcMMOInstalled; - } - - public boolean isClearLagInstalled() { - return isClearLagInstalled; - } - - public boolean isItemsAdderInstalled() { - return isItemsAdderInstalled; - } - - public boolean isOrebfuscatorInstalled() { - return isOrebfuscatorInstalled; - } -} +package io.github.thebusybiscuit.slimefun4.integrations; + +import com.gmail.nossr50.events.fake.FakeBlockBreakEvent; +import com.gmail.nossr50.util.skills.SkillUtils; +import dev.lone.itemsadder.api.ItemsAdder; +import io.github.bakedlibs.dough.protection.ProtectionManager; +import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import io.github.thebusybiscuit.slimefun4.implementation.items.electric.machines.enchanting.AutoDisenchanter; +import java.util.function.Consumer; +import java.util.logging.Level; +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; + +/** + * This Service holds all interactions and hooks with third-party {@link Plugin Plugins} + * that are not necessarily a dependency or a {@link SlimefunAddon}. + * + * Integration with these plugins happens inside Slimefun itself. + * + * @author TheBusyBiscuit + * + * @see Slimefun + * + */ +public class IntegrationsManager { + + /** + * This is our instance of {@link Slimefun}. + */ + protected final Slimefun plugin; + + /** + * Our {@link ProtectionManager} instance. + */ + private ProtectionManager protectionManager; + + /** + * This boolean determines whether {@link #start()} was run. + */ + private boolean isEnabled = false; + + // Soft dependencies + private boolean isPlaceholderAPIInstalled = false; + private boolean isWorldEditInstalled = false; + private boolean isMcMMOInstalled = false; + private boolean isClearLagInstalled = false; + private boolean isItemsAdderInstalled = false; + private boolean isOrebfuscatorInstalled = false; + + /** + * This initializes the {@link IntegrationsManager} + * + * @param plugin + * Our instance of {@link Slimefun} + */ + public IntegrationsManager(@Nonnull Slimefun plugin) { + this.plugin = plugin; + } + + /** + * This method returns whether the {@link IntegrationsManager} was enabled yet. + * + * @return Whether this {@link IntegrationsManager} has been enabled already. + */ + public boolean isEnabled() { + return isEnabled; + } + + /** + * This method initializes all third party integrations. + */ + public final void start() { + if (isEnabled) { + // Prevent double-registration + throw new UnsupportedOperationException("All integrations have already been loaded."); + } else { + isEnabled = true; + } + + // Load any soft dependencies + onServerLoad(); + + // Load any integrations which aren't dependencies (loadBefore) + plugin.getServer().getScheduler().runTask(plugin, this::onServerStart); + } + + /** + * This method is called when the {@link Server} loaded its {@link Plugin Plugins}. + * We can safely assume that any {@link Plugin} which is a soft dependency of Slimefun + * to be enabled at this point. + */ + private void onServerLoad() { + // PlaceholderAPI hook to provide playerholders from Slimefun. + load("PlaceholderAPI", integration -> { + new PlaceholderAPIIntegration(plugin).register(); + isPlaceholderAPIInstalled = true; + }); + + // WorldEdit Hook to clear Slimefun Data upon //set 0 //cut or any other equivalent + load("WorldEdit", integration -> { + new WorldEditIntegration().register(); + isWorldEditInstalled = true; + }); + + // mcMMO Integration + load("mcMMO", integration -> { + new McMMOIntegration(plugin).register(); + isMcMMOInstalled = true; + }); + + // ClearLag integration (to prevent display items from getting deleted) + load("ClearLag", integration -> { + new ClearLagIntegration(plugin).register(); + isClearLagInstalled = true; + }); + + // ItemsAdder Integration (custom blocks) + load("ItemsAdder", integration -> isItemsAdderInstalled = true); + + // Vault Integration (Economy research unlock) + load("Vault", VaultIntegration::register); + } + + /** + * This method is called when the {@link Server} has finished loading all its {@link Plugin Plugins}. + */ + private void onServerStart() { + try { + // Load Protection plugin integrations + protectionManager = new ProtectionManager(plugin); + } catch (Exception | LinkageError x) { + Slimefun.logger().log(Level.WARNING, x, () -> "Failed to load Protection plugin integrations for Slimefun v" + Slimefun.getVersion()); + } + + // Orebfuscator Integration + load("Orebfuscator", integration -> { + new OrebfuscatorIntegration(plugin).register(); + isOrebfuscatorInstalled = true; + }); + } + + /** + * This method logs a {@link Throwable} that was caused by a {@link Plugin} + * we integrate into. + * Calling this method will probably log the error and provide the version of this {@link Plugin} + * for error analysis. + * + * @param name + * The name of the {@link Plugin} + * @param throwable + * The {@link Throwable} to throw + */ + @ParametersAreNonnullByDefault + protected void logError(String name, Throwable throwable) { + Plugin externalPlugin = Bukkit.getPluginManager().getPlugin(name); + + if (externalPlugin != null) { + String version = externalPlugin.getDescription().getVersion(); + Slimefun.logger().log(Level.WARNING, "Is {0} v{1} up to date?", new Object[] { name, version }); + Slimefun.logger().log(Level.SEVERE, throwable, () -> "An unknown error was detected while interacting with \"" + name + " v" + version + "\""); + } else { + Slimefun.logger().log(Level.SEVERE, throwable, () -> "An unknown error was detected while interacting with the plugin \"" + name + "\""); + } + } + + /** + * This method loads an integration with a {@link Plugin} of the specified name. + * If that {@link Plugin} is installed and enabled, the provided callback will be run. + * + * @param pluginName + * The name of this {@link Plugin} + * @param consumer + * The callback to run if that {@link Plugin} is installed and enabled + */ + private void load(@Nonnull String pluginName, @Nonnull Consumer consumer) { + Plugin integration = plugin.getServer().getPluginManager().getPlugin(pluginName); + + if (integration != null && integration.isEnabled()) { + String version = integration.getDescription().getVersion(); + Slimefun.logger().log(Level.INFO, "Hooked into Plugin: {0} v{1}", new Object[] { pluginName, version }); + + try { + // Run our callback + consumer.accept(integration); + } catch (Exception | LinkageError x) { + Slimefun.logger().log(Level.WARNING, "Maybe consider updating {0} or Slimefun?", pluginName); + Slimefun.logger().log(Level.WARNING, x, () -> "Failed to hook into " + pluginName + " v" + version); + } + } + } + + /** + * This returns out instance of the {@link ProtectionManager}. + * This bridge is used to hook into any third-party protection {@link Plugin}. + * + * @return Our instanceof of the {@link ProtectionManager} + */ + public @Nonnull ProtectionManager getProtectionManager() { + return protectionManager; + } + + /** + * This checks if one of our third party integrations faked an {@link Event}. + * Faked {@link Event Events} should be ignored in our logic. + * + * @param event + * The {@link Event} to test + * + * @return Whether this is a fake event + */ + public boolean isEventFaked(@Nonnull Event event) { + // This can be changed to "FakeEvent" in a later version + return isMcMMOInstalled && event instanceof FakeBlockBreakEvent; + } + + /** + * This checks if one of our third party integrations has placed a custom + * {@link Block} at this {@link Location}. + * + * @param block + * The {@link Block} to check + * + * @return Whether a different custom {@link Block} exists at that {@link Location} + */ + @SuppressWarnings("deprecation") + public boolean isCustomBlock(@Nonnull Block block) { + if (isItemsAdderInstalled) { + try { + return ItemsAdder.isCustomBlock(block); + } catch (Exception | LinkageError x) { + logError("ItemsAdder", x); + } + } + + return false; + } + + /** + * This checks if one of our third party integrations defines a given + * {@link ItemStack} as custom. + * + * @param item + * The {@link ItemStack} to check + * + * @return Whether this {@link ItemStack} is a custom item + */ + @SuppressWarnings("deprecation") + public boolean isCustomItem(@Nonnull ItemStack item) { + if (isItemsAdderInstalled) { + try { + return ItemsAdder.isCustomItem(item); + } catch (Exception | LinkageError x) { + logError("ItemsAdder", x); + } + } + + return false; + } + + /** + * This method removes any temporary enchantments from the given {@link ItemStack}. + * Some plugins apply enchantments for a short amount of time and remove it later. + * We don't want these items to be exploited using an {@link AutoDisenchanter} for example, + * so we want to be able to strip those temporary enchantments in advance. + * + * @param item + * The {@link ItemStack} + */ + public void removeTemporaryEnchantments(@Nonnull ItemStack item) { + if (isMcMMOInstalled) { + try { + SkillUtils.removeAbilityBuff(item); + } catch (Exception | LinkageError x) { + logError("mcMMO", x); + } + } + } + + public boolean isPlaceholderAPIInstalled() { + return isPlaceholderAPIInstalled; + } + + public boolean isWorldEditInstalled() { + return isWorldEditInstalled; + } + + public boolean isMcMMOInstalled() { + return isMcMMOInstalled; + } + + public boolean isClearLagInstalled() { + return isClearLagInstalled; + } + + public boolean isItemsAdderInstalled() { + return isItemsAdderInstalled; + } + + public boolean isOrebfuscatorInstalled() { + return isOrebfuscatorInstalled; + } +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/VaultIntegration.java b/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/VaultIntegration.java new file mode 100644 index 0000000000..55623fa71e --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/VaultIntegration.java @@ -0,0 +1,39 @@ +package io.github.thebusybiscuit.slimefun4.integrations; + +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import java.util.Objects; +import javax.annotation.Nonnull; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.OfflinePlayer; +import org.bukkit.plugin.Plugin; + +public class VaultIntegration { + private static Economy econ; + + static void register(@Nonnull Plugin plugin) { + var rsp = plugin.getServer().getServicesManager().getRegistration(Economy.class); + if (rsp != null) { + econ = rsp.getProvider(); + } else { + throw new RuntimeException("Unable to hook into vault"); + } + } + + public static double getPlayerBalance(OfflinePlayer p) { + Objects.requireNonNull(p, "Player cannot be null!"); + Objects.requireNonNull(econ, "Vault instance cannot be null!"); + + return econ.getBalance(p); + } + + public static void withdrawPlayer(OfflinePlayer p, double withdraw) { + Objects.requireNonNull(p, "Player cannot be null!"); + Objects.requireNonNull(econ, "Vault instance cannot be null!"); + + econ.withdrawPlayer(p, withdraw); + } + + public static boolean isEnabled() { + return econ != null && Slimefun.getRegistry().isResearchingEnabled(); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f906fd3754..6c1211d8d1 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -31,6 +31,7 @@ researches: free-in-creative-mode: true enable-fireworks: true disable-learning-animation: false + unlock-research-mode: EXPERIENCE URID: info-delay: 3000 diff --git a/src/main/resources/languages/en/messages.yml b/src/main/resources/languages/en/messages.yml index f9c32a8115..1b465596e1 100644 --- a/src/main/resources/languages/en/messages.yml +++ b/src/main/resources/languages/en/messages.yml @@ -145,12 +145,17 @@ guide: resourcepack: '&cResourcepack Artist' translator: '&9Translator' + unlock-mode: + experience: "XP" + economy: "money" + + actionbar: radiation: '&6Radiation Exposure Level: &c%level%&7/&e100' messages: not-researched: '&4You do not have enough knowledge to understand this. &cYou will need to unlock &f"%item%&f"' - not-enough-xp: '&4You do not have enough XP to unlock this' + not-enough-token: '&4You do not have enough %token_name% to unlock this' unlocked: '&bYou have unlocked &7"%research%"' only-players: '&4This command is only for Players' unknown-player: '&4Unknown Player: &c%player%'