diff --git a/build.gradle.kts b/build.gradle.kts index 33549936..b202b4d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,7 @@ allprojects { version = "1.6.8" repositories { + mavenLocal() mavenCentral() maven { name = "papermc-repo" @@ -49,6 +50,9 @@ allprojects { maven { name = "nightexpress-releases" url = uri("https://repo.nightexpressdev.com/releases") + content { + includeGroupByRegex("su\\.nightexpress.*") + } } maven { name = "iridiumdevelopment" diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 025052b9..169ed8fb 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -30,6 +30,8 @@ dependencies { compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") compileOnly("su.nightexpress.excellenteconomy:ExcellentEconomy:2.8.0") compileOnly("su.nightexpress.nightcore:main:2.15.3") + compileOnly("su.nightexpress.excellentshop:api:5.1.1") + compileOnly("su.nightexpress.excellentshop:Core:5.1.1") compileOnly("com.github.Gypopo:EconomyShopGUI-API:1.10.0") compileOnly("world.bentobox:bentobox:3.16.2") compileOnly("dev.aurelium:auraskills-api-bukkit:2.3.12") diff --git a/core/src/main/java/github/nighter/smartspawner/hooks/economy/shops/ShopIntegrationManager.java b/core/src/main/java/github/nighter/smartspawner/hooks/economy/shops/ShopIntegrationManager.java index 8fb2079a..a9dd843e 100644 --- a/core/src/main/java/github/nighter/smartspawner/hooks/economy/shops/ShopIntegrationManager.java +++ b/core/src/main/java/github/nighter/smartspawner/hooks/economy/shops/ShopIntegrationManager.java @@ -6,6 +6,7 @@ import github.nighter.smartspawner.hooks.economy.shops.providers.economyshopgui.ESGUICompatibilityHandler; import github.nighter.smartspawner.hooks.economy.shops.providers.shopguiplus.ShopGuiPlusProvider; import github.nighter.smartspawner.hooks.economy.shops.providers.shopguiplus.SpawnerHook; +import github.nighter.smartspawner.hooks.economy.shops.providers.excellentshop.ExcellentShopProvider; import github.nighter.smartspawner.hooks.economy.shops.providers.zshop.ZShopProvider; import lombok.RequiredArgsConstructor; import org.bukkit.Material; @@ -81,6 +82,10 @@ private void detectAndRegisterActiveProviders() { } // registerProviderIfAvailable("ZShop", () -> new ZShopProvider(plugin)); + + if (isPluginAvailable("ExcellentShop")) { + registerProviderIfAvailable("ExcellentShop", () -> new ExcellentShopProvider(plugin)); + } } private boolean tryRegisterSpecificProvider(String providerName) { @@ -125,6 +130,12 @@ private boolean tryRegisterSpecificProvider(String providerName) { return !availableProviders.isEmpty(); } break; + case "excellentshop": + if (isPluginAvailable("ExcellentShop")) { + registerProviderIfAvailable("ExcellentShop", () -> new ExcellentShopProvider(plugin)); + return !availableProviders.isEmpty(); + } + break; } } catch (Exception e) { plugin.debug("Failed to load specific provider " + providerName + ": " + e.getMessage()); diff --git a/core/src/main/java/github/nighter/smartspawner/hooks/economy/shops/providers/excellentshop/ExcellentShopProvider.java b/core/src/main/java/github/nighter/smartspawner/hooks/economy/shops/providers/excellentshop/ExcellentShopProvider.java new file mode 100644 index 00000000..e043a692 --- /dev/null +++ b/core/src/main/java/github/nighter/smartspawner/hooks/economy/shops/providers/excellentshop/ExcellentShopProvider.java @@ -0,0 +1,136 @@ +package github.nighter.smartspawner.hooks.economy.shops.providers.excellentshop; + +import github.nighter.smartspawner.SmartSpawner; +import github.nighter.smartspawner.hooks.economy.shops.providers.ShopProvider; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServerLoadEvent; +import org.bukkit.plugin.Plugin; +import su.nightexpress.excellentshop.ShopAPI; +import su.nightexpress.excellentshop.api.product.ContentType; +import su.nightexpress.excellentshop.virtualshop.VirtualShopModule; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +@RequiredArgsConstructor +public class ExcellentShopProvider implements ShopProvider, Listener { + + private final SmartSpawner plugin; + + // ConcurrentHashMap for Folia thread safety + private final Map priceCache = new ConcurrentHashMap<>(); + private final AtomicBoolean cacheBuilt = new AtomicBoolean(false); + + @Override + public String getPluginName() { + return "ExcellentShop"; + } + + @Override + public boolean isAvailable() { + try { + Plugin shopPlugin = Bukkit.getPluginManager().getPlugin("ExcellentShop"); + if (shopPlugin == null) { + plugin.getLogger().info("[ExcellentShop] Plugin not found on server."); + return false; + } + if (!shopPlugin.isEnabled()) { + plugin.getLogger().info("[ExcellentShop] Plugin found but not enabled."); + return false; + } + + Class.forName("su.nightexpress.excellentshop.ShopAPI"); + Class.forName("su.nightexpress.excellentshop.virtualshop.VirtualShopModule"); + + // Register ServerLoadEvent listener to build cache after full startup + Bukkit.getPluginManager().registerEvents(this, plugin); + + plugin.getLogger().info("[ExcellentShop] Integration ready. Price cache will build after server finishes loading."); + return true; + } catch (ClassNotFoundException | NoClassDefFoundError e) { + plugin.getLogger().warning("[ExcellentShop] API classes not found: " + e.getMessage()); + } catch (Exception e) { + plugin.getLogger().warning("[ExcellentShop] Error initializing: " + e.getMessage()); + } + return false; + } + + /** + * Build the cache after the server has fully started so ExcellentShop's + * async data load is complete and all prices are populated. + */ + @EventHandler + public void onServerLoad(ServerLoadEvent event) { + buildCache(); + } + + @Override + public double getSellPrice(Material material) { + if (material == null || material.isAir()) return 0.0; + + try { + // If cache hasn't been built yet (e.g. first call before ServerLoadEvent), build it now + if (!cacheBuilt.get()) buildCache(); + return priceCache.getOrDefault(material, 0.0); + } catch (Exception e) { + plugin.debug("Error getting sell price for " + material + " from ExcellentShop: " + e.getMessage()); + return 0.0; + } + } + + /** + * Scans all virtual shop products and caches the best (highest) sell price + * per Material. Only considers sellable, item-type products. + */ + private void buildCache() { + if (!cacheBuilt.compareAndSet(false, true)) return; // Only build once + + priceCache.clear(); + + try { + VirtualShopModule virtualShop = ShopAPI.getVirtualShop(); + if (virtualShop == null) { + plugin.getLogger().warning("[ExcellentShop] VirtualShop module not available."); + cacheBuilt.set(false); // Allow retry + return; + } + + virtualShop.getShops().forEach(shop -> + shop.getProductMap().values().forEach(product -> { + if (!product.isSellable()) return; + if (product.getContent().type() != ContentType.ITEM) return; + + Material material = product.getPreview().getType(); + if (material.isAir()) return; + + double sellPrice = product.getSellPrice(); + if (sellPrice < 0) return; // -1 = disabled; 0 is valid + + // Keep the highest sell price if the same material is in multiple shops + priceCache.merge(material, sellPrice, Math::max); + }) + ); + + plugin.getLogger().info("[ExcellentShop] Price cache built: " + priceCache.size() + " materials indexed."); + if (priceCache.isEmpty()) { + plugin.getLogger().warning("[ExcellentShop] Cache is empty — check that your virtual shops have sellable products."); + } + } catch (Exception e) { + plugin.getLogger().warning("[ExcellentShop] Failed to build price cache: " + e.getMessage()); + cacheBuilt.set(false); // Allow retry on next call + } + } + + /** + * Invalidates the price cache — call after shop prices change. + */ + public void invalidateCache() { + priceCache.clear(); + cacheBuilt.set(false); + } +} diff --git a/core/src/main/resources/paper-plugin.yml b/core/src/main/resources/paper-plugin.yml index d776cfe9..3a352647 100644 --- a/core/src/main/resources/paper-plugin.yml +++ b/core/src/main/resources/paper-plugin.yml @@ -42,6 +42,10 @@ dependencies: load: BEFORE required: false join-classpath: true + ExcellentShop: + load: BEFORE + required: false + join-classpath: true # Economy Plugins ExcellentEconomy: diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index 287a9c8b..80802dfb 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -14,6 +14,7 @@ softdepend: - EconomyShopGUI-Premium - ShopGUIPlus - zShop + - ExcellentShop # Utility/Economy Plugins - ExcellentEconomy